spi 驱动一:spi基本结构和spidev文件系统
作者: 李云鹏(qqliyunpeng@sina.cn)
版本号: 20170124
更新时间: <-->
原创时间: <2017-01-24>
版权: 本文采用以下协议进行授权,自由转载 - 非商用 - 非衍生 - 保持署名 | Creative Commons BY-NC-ND 3.0,转载请注明作者及出处.
1. spi 简介:
spi 中包含时钟线(clk)、MOSI(主设备发送,从设备接收)、MISO(主设备接收,从设备发送)、片选(CS),有四种工作模式(下边有介绍),此篇文章中介绍的是半双工的spi,他一般应用在和传感器的数据交互。
driver/spi 文件下的文件:
spi-bitbang.c 和 spi-bitbang-txrx.h 是 通用的用IO口模拟spi
spidev.c:devfs形式的spi驱动,此部分内核文档说明了,是半双工形式,即同一时间 MISO MOSI 只有一个在运行。又叫3线制
spi-coldfire-qspi.c:
coldfire是Freescale公司在M68K基础上开发的微处理器芯片。
QSPI是Queued SPI的简写,是Motorola公司推出的SPI接口的扩展,比SPI应用更加广泛。使用该接口,用户可以一次性传输包含多达16个8位或16位数据的传输队列。一旦传输启动,直到传输结束,都不需要CPU干预,极大的提高了传输效率。该协议在ColdFire系列MCU得到广泛应用。
spi-davinci.c:
davinci是Ti推出的一类芯片,名字叫达芬奇处理器,达芬奇技术是一种数字图像、视频、语音、音频信号处理的新平台,一经推出,就受到热烈欢迎,以其为基础的应用开发层出不穷。
spi-bufferfly.c:
是AVR的bufferfly平台,它长的是下边的样子,至于它的具体的性能,看这里
spi-dw.c、spi-dw-mid.c、spi-dw-mmio.c、spi-dw-pci.c、pxa2xx_spi.c:
dw是designware的缩写,是美国新思科技科技公司(synopsys)的被SoC/ASIC设计者最钟爱的设计IP库和验证IP库。它包括一个独立于工艺的、经验证的、可综合的虚拟微架构的元件集合,包括逻辑、算术、存储和专用元件系列,超过140个模块。
spi-oc-tiny.c:
oc是opencores的缩写,opencores是开源硬件ip核的社区,它里边提供了设计好的fpag/asic的ip核,这里的这个文件就是针对他里边的spi驱动器设计的驱动。
2. 关键的结构:
在spi.h中定义了spi相关的结构体,首先是spi_device:
/* master 侧的从设备的代理 */
struct spi_device {
struct device dev;
struct spi_master *master;
u32 max_speed_hz;
u8 chip_select;
u8 mode;
#define SPI_CPHA 0x01 /* 时钟相位 */
#define SPI_CPOL 0x02 /* 时钟极性 */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* 芯片片选是不是高有效? */
#define SPI_LSB_FIRST 0x08 /* 先传输最低有效位 */
#define SPI_3WIRE 0x10 /* SI/SO 信号共享 */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 就一个设备,不用片选 */
#define SPI_READY 0x80 /* slave pulls low to pause */
u8 bits_per_word;
int irq;
void *controller_state;
void *controller_data;
char modalias[SPI_NAME_SIZE];
};
对mode的定义根据的是s3c2440芯片手册上的有关SPI TRANSFER FORMAT部分:
CPOL = 0,表示的是时钟线空闲电平时低电平
CPOL = 1,表示的是时钟线空闲电平时高电平
CPHA = 0,在sck的第一个跳变沿开始采样,从图中可以看到的是上升沿
CPHA = 1,在sck的第二个跳变沿开始采样,从图中可以看到的是下降沿
SPI_MODE_0:
SPI_MODE_1:
SPI_MODE_2:
SPI_MODE_3:
然后来看看spi_master:spi作为master的控制器
struct spi_master {
struct device dev;
struct list_head list;
s16 bus_num; // 如果是负数,则是动态分配,整数指定用哪个spi控制器/驱动(从0开始)
u16 num_chipselect; // 片选信号的个数,也就是从设备的数量,从设别就事0~num_chipselect,可以有没有连接的从设备
u16 dma_alignment; // 是不是要求DMA的buffer的对齐
u16 mode_bits; // 控制器启动的模式位,被控制器驱动解析
/* 驱动器的其他设置 */
u16 flags;
#define SPI_MASTER_HALF_DUPLEX BIT(0) /* 不能全双工 */
#define SPI_MASTER_NO_RX BIT(1) /* 不能读buffer */
#define SPI_MASTER_NO_TX BIT(2) /* 不能写buffer */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
int (*setup)(struct spi_device *spi); // 时钟和spi模式的设置函数
int (*transfer)(struct spi_device *spi, // 用于将message加入到message队列中,此函数一般不需要用户自己设计,保持null即可
struct spi_message *mesg);
void (*cleanup)(struct spi_device *spi);
bool queued;
struct kthread_worker kworker; // kthread_worker,看相应的章节
struct task_struct *kworker_task; // kthread_worker中使用的任务,看相应的章节
struct kthread_work pump_messages; // kthread_work,看相应的章节
spinlock_t queue_lock;
struct list_head queue; // spi_message队列头,结构看下边的图
struct spi_message *cur_msg; // 正在处理的spi_message
bool busy;
bool running;
bool rt;
int (*prepare_transfer_hardware)(struct spi_master *master); // 准备传输前硬件初始化
int (*transfer_one_message)(struct spi_master *master, // 传输的硬件层的实现
struct spi_message *mesg);
int (*unprepare_transfer_hardware)(struct spi_master *master); // 当没有message传输,驱动将调用这个函数来释放掉相关硬件
};
再看看spi_message:
struct spi_message {
struct list_head transfers; // 链表的节点,构成的关系看后边的图
struct spi_device *spi;
unsigned is_dma_mapped:1; // 是不是使用dma模式
/* 传输完成后调用的函数相关部分 */
void (*complete)(void *context); // 调用的函数首地址
void *context; // 调用的函数中传入的参数
unsigned actual_length; // 所有传输成功的段中总的字节数
int status; // 0,成功,其他的赋值是errno值
struct list_head queue; // 链表节点,构成的关系看后边的图
void *state; // 被拥有这个message的驱动使用
};
还有 spi_transfer:
struct spi_transfer {
const void *tx_buf; // 发送的缓冲区
void *rx_buf; // 接收的缓冲区
unsigned len; // 缓冲区中可以存放的字节数
dma_addr_t tx_dma; // 如果设置了 spi_message.is_dma_mapped 被设置了,这个变量时tx_buf的DMA地址
dma_addr_t rx_dma;
unsigned cs_change:1; // 如果设置了,则此次传输完成后将会翻转片选
u8 bits_per_word; // 此次传输一个word含多少位,如果值是0,则会用默认值
u16 delay_usecs; // 在此次传输完成到改变片选之前的延时,单位ms,之后开始下一次transfer,或者此次spi_message结束
u32 speed_hz; // 设置一个速度,0会用默认值
struct list_head transfer_list; // 看后边的图,链表的节点
};
这几个结构再应用中的关系如下图:
3. spidev.c 文件分析:
在文件的开头声明了一个32位的bitmap(bitmap相关知识,跳转到这里)和一个链表的头:
#define SPIDEV_MAJOR 153 /* assigned */
#define N_SPI_MINORS 32 /* ... up to 256 */
static DECLARE_BITMAP(minors, N_SPI_MINORS); /* 声明了一个含32位的bitmap,用于做为spi设备的索引 */
static LIST_HEAD(device_list); /* 声明一个链表的头 */
static DEFINE_MUTEX(device_list_lock); /* 实现对链表原子操作的互斥锁 */
然后,我们从 module_init(spidev_init); 开始
static int __init spidev_init(void)
{
int status;
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops); /* 注册主设备号是 153 的字符设备,同时注册上对字符设备的操作的函数 */
spidev_class = class_create(THIS_MODULE, "spidev"); /* 创建一个名字是 spidev 的类,为mdev/udev在 /dev 下创建节点做准备 */
status = spi_register_driver(&spidev_spi_driver); /* 注册 驱动程序 */
return status;
}
module_init(spidev_init);
static void __exit spidev_exit(void)
{
spi_unregister_driver(&spidev_spi_driver);
class_destroy(spidev_class);
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
}
module_exit(spidev_exit);
【1】上边的程序中省略了错误检查和处理部分
【2】spi devfs 驱动的主设备号固定是 153
再来看看 spi_driver 结构体
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "spidev",
.owner = THIS_MODULE,
},
.probe = spidev_probe,
.remove = __devexit_p(spidev_remove),
};
spi_driver 结构体是啥样子,和 platform_driver 驱动有什么相似和不同
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
int (*suspend)(struct spi_device *spi, pm_message_t mesg);
int (*resume)(struct spi_device *spi);
struct device_driver driver;
};
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
static int __devinit spidev_probe(struct spi_device *spi)
{
struct spidev_data *spidev;
int status;
unsigned long minor;
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); /* 申请并初始化一个 spidev_data 的区域 */
spidev->spi = spi; /* 将spidev_data结构体中的spi指针指向匹配好的spi设备 */
spin_lock_init(&spidev->spi_lock);
mutex_init(&spidev->buf_lock);
INIT_LIST_HEAD(&spidev->device_entry);
/* If we can allocate a minor number, hook up this device.
* Reusing minors is fine so long as udev or mdev is working.
*/
mutex_lock(&device_list_lock);/* 得到互斥锁 */
minor = find_first_zero_bit(minors, N_SPI_MINORS); /* 在0-31中找到一个没有被使用的作为次设备号 */
if (minor < N_SPI_MINORS) {
struct device *dev;
spidev->devt = MKDEV(SPIDEV_MAJOR, minor); /* 赋值spidev中的设备号 */
dev = device_create(spidev_class, &spi->dev, spidev->devt, /* 创建设备节点,名字是spidevA.B,A是设备结构体中的master.bus_num,B是设备中的chipselect */
spidev, "spidev%d.%d", /* A是设备结构体中的master.bus_num,B是设备中的chipselect */
spi->master->bus_num, spi->chip_select);
status = IS_ERR(dev) ? PTR_ERR(dev) : 0;
} else {
dev_dbg(&spi->dev, "no minor number available!\n");
status = -ENODEV;
}
if (status == 0) {
set_bit(minor, minors); /* 将使用了0-31中使用了作为次设备号的位置1 */
list_add(&spidev->device_entry, &device_list); /* 将组装好的spidev_data结构体添加到链表中 */
}
mutex_unlock(&device_list_lock); /* 解锁 */
if (status == 0)
spi_set_drvdata(spi, spidev); /* 将spi.dev.p指向这个组装好的spi_data结构体,方便整个文件中其他函数的使用 */
else
kfree(spidev);
return status;
}
【1】spidev_data 结构体
struct spidev_data {
dev_t devt; /* 设备号 */
spinlock_t spi_lock; /* 自旋锁 */
struct spi_device *spi; /* 指向spi设备结构体的指针 */
struct list_head device_entry; /* 链表的节点入口 */
struct mutex buf_lock; /* 互斥锁 */
unsigned users;
u8 *buffer;
};
删除函数 remove:spidev_remove
static int __devexit spidev_remove(struct spi_device *spi)
{
struct spidev_data *spidev = spi_get_drvdata(spi); /* 得到设备结构体 */
/* make sure ops on existing fds can abort cleanly */
spin_lock_irq(&spidev->spi_lock); /* 获得自旋锁 */
spidev->spi = NULL;
spi_set_drvdata(spi, NULL);
spin_unlock_irq(&spidev->spi_lock); /* 释放自旋锁 */
/* prevent new opens */
mutex_lock(&device_list_lock);
list_del(&spidev->device_entry); /* 从链表中删除设备节点 */
device_destroy(spidev_class, spidev->devt); /* 删类 */
clear_bit(MINOR(spidev->devt), minors); /* 去除bitmap中的次设备号相应的位 */
if (spidev->users == 0) /* 这里保证驱动程序没有被使用 */
kfree(spidev);
mutex_unlock(&device_list_lock);
return 0;
}
上边是设备节点的创建,接下来,我们看看对设备节点的操作函数:
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
.write = spidev_write,
.read = spidev_read,
.unlocked_ioctl = spidev_ioctl,
.compat_ioctl = spidev_compat_ioctl,
.open = spidev_open,
.release = spidev_release,
.llseek = no_llseek,
};
字符设备打开和退出函数:spidev_open、spidev_release
static int spidev_open(struct inode *inode, struct file *filp)
{
struct spidev_data *spidev;
int status = -ENXIO;
mutex_lock(&device_list_lock);
list_for_each_entry(spidev, &device_list, device_entry) {
if (spidev->devt == inode->i_rdev) { /* 遍历链表,看有没有相同设备号的设备,有的话 status = 0,并将spidev指向相应位置 */
status = 0;
break;
}
}
if (status == 0) { /* 设备号有效 */
if (!spidev->buffer) {
spidev->buffer = kmalloc(bufsiz, GFP_KERNEL); /* 第一次打开,buffer指向一个预留出一个4096大小的空间 */
if (!spidev->buffer) {
dev_dbg(&spidev->spi->dev, "open/ENOMEM\n");
status = -ENOMEM;
}
}
if (status == 0) {
spidev->users++; /* 用户数目加1 */
filp->private_data = spidev; /* 将spi设备结构体地址放到filp的private_data中,方便读写的时候引用 */
nonseekable_open(inode, filp); /* 【1】 */
}
} else
pr_debug("spidev: nothing for minor %d\n", iminor(inode));
mutex_unlock(&device_list_lock);
return status;
}
static int spidev_release(struct inode *inode, struct file *filp)
{
struct spidev_data *spidev;
int status = 0;
mutex_lock(&device_list_lock);
spidev = filp->private_data; /* 得到spidev_data结构体 */
filp->private_data = NULL; /* 清指针 */
/* last close? */
spidev->users--; /* 将spidev中的用户数减1 */
if (!spidev->users) { /* 如果只有一个用户程序访问 */
int dofree;
kfree(spidev->buffer); /* 显式的释放在open的时候分配的内存空间 */
spidev->buffer = NULL;
/* ... after we unbound from the underlying device? */
spin_lock_irq(&spidev->spi_lock);
dofree = (spidev->spi == NULL); /* 判断此时是不是spi设备没有了 */
spin_unlock_irq(&spidev->spi_lock);
if (dofree)
kfree(spidev); /* 当spi设备此时也不在了,直接释放掉spidev_data */
}
mutex_unlock(&device_list_lock);
return status;
}
【1】nonseekable_open(inode, filp); 展开如下:
int nonseekable_open(struct inode *inode, struct file *filp)
{
filp->f_mode &= ~(FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
return 0;
}
/* file is seekable */
#define FMODE_LSEEK ((__force fmode_t)0x4)
/* file can be accessed using pread */
#define FMODE_PREAD ((__force fmode_t)0x8)
/* file can be accessed using pwrite */
#define FMODE_PWRITE ((__force fmode_t)0x10)
再来看看文件操作结构体file_operations中的.write,spidev_write函数:
static ssize_t
spidev_write(struct file *filp, const char __user *buf,
size_t count, loff_t *f_pos)
{
struct spidev_data *spidev;
ssize_t status = 0;
unsigned long missing;
/* chipselect only toggles at start or end of operation */
if (count > bufsiz) /* 如果写的数据个数大于4096,则返回错误 */
return -EMSGSIZE;
spidev = filp->private_data; /* 得到spidev_data结构体数据地址 */
mutex_lock(&spidev->buf_lock);
missing = copy_from_user(spidev->buffer, buf, count); /* 将用户数据拷贝到spidev中的buffer指向的区域 */
if (missing == 0) { /* 返回0,拷贝成功 */
status = spidev_sync_write(spidev, count); // 将buffer中的内容发送出去【1】
} else
status = -EFAULT;
mutex_unlock(&spidev->buf_lock);
return status;
}
【1】spidev_sync_write函数实现如下:
static inline ssize_t
spidev_sync_write(struct spidev_data *spidev, size_t len) /* sync 同步,这个函数是同步写 */
{
struct spi_transfer t = {
.tx_buf = spidev->buffer, /* 将发送的数据指针指向spidev中的buffer */
.len = len,
};
struct spi_message m;
spi_message_init(&m); /* 清空m空间,初始化m中的transfers链表头 */
spi_message_add_tail(&t, &m); /* 将t中的transfer_list节点添加到m中的transfer链表的尾部 */
return spidev_sync(spidev, &m); // 此函数看下边函数实现
}
static inline void spi_message_init(struct spi_message *m)
{
memset(m, 0, sizeof *m);
INIT_LIST_HEAD(&m->transfers);
}
static inline void
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
list_add_tail(&t->transfer_list, &m->transfers);
}
static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
DECLARE_COMPLETION_ONSTACK(done); /* 声明一个完成量 */
int status;
message->complete = spidev_complete; /* 关联上传输完成后的回调函数,回调函数中只做了唤醒一个等待的执行单元这一个操作 */
message->context = &done; /* 完成量作为 sidev_complete 函数的参数 */
spin_lock_irq(&spidev->spi_lock);
if (spidev->spi == NULL)
status = -ESHUTDOWN;
else
status = spi_async(spidev->spi, message); /* 传输message,通过调用spi_async->__spi_async->master->transfer完成数据的传输,*/
/*这个函数里边直接调用了__spi_async 函数,如【1】 */
spin_unlock_irq(&spidev->spi_lock);
if (status == 0) {
wait_for_completion(&done); /* 阻塞的等待完成量的完成 */
status = message->status;
if (status == 0)
status = message->actual_length;
}
return status; /* 返回实际发送的长度 */
}
【1】__spi_async的实现:
static int __spi_async(struct spi_device *spi, struct spi_message *message)
{
struct spi_master *master = spi->master;
/* 做一些保证工作 */
message->spi = spi;
message->status = -EINPROGRESS;
return master->transfer(spi, message); /* 根据spi中的一些参数将message加入到队列中 */
}
#这里是错误的开始
以下是错误的分析,以下是在spidev驱动中本来应该是可以这样的,但是,应该是早期驱动的不完善,分析到最后,发现有几个函数的默认函数是没有提供的,在查看v3.13 版本的内核后发现慢慢的在添加默认函数。
master->transfer 的关联的工作在 spi_register_master(此函数依然在spi.c文件中)函数中:
int spi_register_master(struct spi_master *master)
{
...
/* If we're using a queued driver, start the queue */
if (master->transfer) // 如果控制器中指定了transfer,打印信息
dev_info(dev, "master is unqueued, this is deprecated\n");
else {
status = spi_master_initialize_queue(master);
if (status) {
device_unregister(&master->dev);
goto done;
}
}
...
}
接下来我们来分析分析 spi_master_initialize_queue 函数:
static int spi_master_initialize_queue(struct spi_master *master)
{
int ret;
master->queued = true; // 使能队列
master->transfer = spi_queued_transfer; // 关联发生在这里
ret = spi_init_queue(master); // spi初始化一个队列
if (ret) {
goto err_init_queue;
}
ret = spi_start_queue(master); // spi开始一个队列的传输
if (ret) {
goto err_start_queue;
}
return 0;
err_start_queue:
err_init_queue:
spi_destroy_queue(master); // 遇到错误,在退出前需要销毁队列
return ret;
}
static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
{
struct spi_master *master = spi->master;
unsigned long flags;
msg->actual_length = 0;
msg->status = -EINPROGRESS;
list_add_tail(&msg->queue, &master->queue); // 将msg的链表的入口挂接到master的queue队列的后边
if (master->running && !master->busy) // master处于运行态,但是不忙的情况
queue_kthread_work(&master->kworker, &master->pump_messages); // 开始 master 的kthread_work挂到kthread_worder中的work_list下,
// 并且处理 kthread_work
spin_unlock_irqrestore(&master->queue_lock, flags);
return 0;
}
static int spi_init_queue(struct spi_master *master)
{
struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 }; // 这个值是最大内核线程优先级
INIT_LIST_HEAD(&master->queue); // 初始化master中的queue链表头
spin_lock_init(&master->queue_lock);
master->running = false;
master->busy = false;
init_kthread_worker(&master->kworker); // 初始化master中的kthread_worker
master->kworker_task = kthread_run(kthread_worker_fn, // 创建内核线程,执行kthread_worker的work_list下挂载的kthread_work中的func
&master->kworker,
dev_name(&master->dev));
...
init_kthread_work(&master->pump_messages, spi_pump_messages); // 初始化kthread_work,并将kthread_work中的func指向spi_pump_message
// spi_pump_messages 是实际的对硬件的操作
if (master->rt) { // 如果设置了 bool 行的rt为true,则将会把master中的kworker_task的任务优先级设置为最高 (MAX_RT_PRIO - 1)
dev_info(&master->dev,
"will run message pump with realtime priority\n");
sched_setscheduler(master->kworker_task, SCHED_FIFO,¶m);
}
return 0;
}
static int spi_start_queue(struct spi_master *master)
{
unsigned long flags;
spin_lock_irqsave(&master->queue_lock, flags);
if (master->running || master->busy) {
spin_unlock_irqrestore(&master->queue_lock, flags);
return -EBUSY;
}
master->running = true;
master->cur_msg = NULL;
spin_unlock_irqrestore(&master->queue_lock, flags);
queue_kthread_work(&master->kworker, &master->pump_messages); // 将 kthread_work 挂接到 kthread_worder 中的work_list下,此时kthread_worker
// 处于就绪态,只要是挂接了 kthread_work,就会执行kthread_work中的函数
return 0;
}
static int spi_destroy_queue(struct spi_master *master)
{
int ret;
ret = spi_stop_queue(master); // 尝试的将master中的running赋值成false,当然要查看是不是正在运行,如果一直运行,超过5s也会将running赋值成false
if (ret) {
dev_err(&master->dev, "problem destroying queue\n");
return ret;
}
flush_kthread_worker(&master->kworker); // 等待kthread_worker中work_list下挂载的所有work都执行完
kthread_stop(master->kworker_task); // 停止内核线程
return 0;
}
【1】kthread_worker和kthread_work部分请查看
http://blog.csdn.net/qqliyunpeng/article/details/53931350
kthread_work中的函数 spi_pump_messages函数分析:
/*-------------------------------------------------------------------------*/
/**
* spi_pump_messages - kthread work 中执行spi的message队列的函数
* @work: 指向包含在master结构体中的kthread_work结构体
*
* 这个函数的功能是检查在队列中是不是有spi message,如果有,则调用驱动初始化硬件
* 并且开始传输每个message
*
*/
static void spi_pump_messages(struct kthread_work *work)
{
struct spi_master *master =
container_of(work, struct spi_master, pump_messages); // 得到包含pump_messages的spi_master结构体
unsigned long flags;
bool was_busy = false;
int ret;
/* Lock queue and check for queue work */
spin_lock_irqsave(&master->queue_lock, flags);
if (list_empty(&master->queue) || !master->running) { // 如果master中的queue下挂着spi_message,并且master没有运行
if (master->busy) {
ret = master->unprepare_transfer_hardware(master); // 如果master处于busy状态,则释放硬件
if (ret) {
spin_unlock_irqrestore(&master->queue_lock, flags);
dev_err(&master->dev,
"failed to unprepare transfer hardware\n");
return;
}
}
master->busy = false;
spin_unlock_irqrestore(&master->queue_lock, flags);
return;
}
if (master->cur_msg) { // 确保没有正在传输的message
spin_unlock_irqrestore(&master->queue_lock, flags);
return;
}
master->cur_msg =
list_entry(master->queue.next, struct spi_message, queue); // 找到存储在master中queue下的第一个message
list_del_init(&master->cur_msg->queue); // 删除挂接到queue下队列中的第一个message
if (master->busy)
was_busy = true; // 表示不是master中queue下的第一个message
else
master->busy = true;
spin_unlock_irqrestore(&master->queue_lock, flags);
if (!was_busy) { // 如果是master queue下的第一个message
ret = master->prepare_transfer_hardware(master); // 初始化传输前的硬件
if (ret) {
dev_err(&master->dev,
"failed to prepare transfer hardware\n");
return;
}
}
ret = master->transfer_one_message(master, master->cur_msg); // 传输master中queue下的第一个message
if (ret) {
dev_err(&master->dev,
"failed to transfer one message from queue\n");
return;
}
}
我们再来分析分析master下的跟硬件相关设置的几个函数:.prepare_transfer_hardware函数、transfer_one_message函数、unprepare_transfer_hardware函数:
到这里,发现,驱动中竟然没有这几个默认函数的实现,至此,驱动的分析认为是错误的。但是到了后期的版本,v3.13之后,默认函数添加上了,在这里先不分析了。
#这里是错误的结束
master->transfer 的关联工作在 spi-s3c24xx.c 中的s3c24xx_spi_probe函数中的spi_bitbang_start 中,我们来看看spi_bitbang_start函数:
/**
* spi_bitbang_start - 开始一个 polled/bitbanging SPI master 驱动
* @bitbang: driver 句柄
*
* 调用者应该已经初始化了结构体的所有部分为0,并且提供了片选的回调函数和I/O循环
* 如果master已经有一个transfer方法,最后一步应该调用 spi_bitbang_transfer
*
* 对于 i/o loops, 提供回调每个word(对于bitbanging或者对于硬件,相当于基本的移位寄存器)
* 或者是每个spi_transfer(这样能更好的利用硬件如fifos或者DMA).
*
* 使用 per-word I/O loops 应该使用 spi_bitbang_setup,spi_bitbang_cleanup 和
* spi_bitbang_setup_transfer去处理spi master 的方法. 这些方法是默认的如果bitbang->txrx_bufs
* 没有指定
*
* 这个函数注册spi_master, 这个函数将会处理请求在一个专有任务中,在大多数情况下
* 保持 IRQ 的非阻塞状态.如果想停止执行请求,调用call spi_bitbang_stop().
*/
int spi_bitbang_start(struct spi_bitbang *bitbang)
{
int status;
INIT_WORK(&bitbang->work, bitbang_work);
spin_lock_init(&bitbang->lock);
INIT_LIST_HEAD(&bitbang->queue);
if (!bitbang->master->mode_bits) // 在s3c_24xx_spi_probe 中有赋值,不为空
bitbang->master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;
if (!bitbang->master->transfer) // 在这里,master的transfer还没指定,是NULL,所以执行下边语句
bitbang->master->transfer = spi_bitbang_transfer; // 这里就是 master->transfer的赋值的地方了
if (!bitbang->txrx_bufs) { // 在s3c24xx_spi_probe 中有赋值,不为空
...
} else if (!bitbang->master->setup) // 不为空
return -EINVAL;
/* this task is the only thing to touch the SPI bits */
bitbang->busy = 0;
bitbang->workqueue = create_singlethread_workqueue( // 创建一个workqueue_struct空间,并且返回它的地址
dev_name(bitbang->master->dev.parent));
/* driver may get busy before register() returns, especially
* if someone registered boardinfo for devices
*/
status = spi_register_master(bitbang->master);
return status;
}
【1】此部分函数中移除了错误处理的部分,只保留了主要的过程和步骤
我们来看看此时的spi_register_master函数:
int spi_register_master(struct spi_master *master)
{
struct device *dev = master->dev.parent;
struct boardinfo *bi;
int status = -ENODEV;
/* 注册设备(device),之后用户空间将会能够看到它.
*/
status = device_add(&master->dev);
if (master->transfer) // 在这里,我们上边刚刚添加了关联,因此,此处不是NULL,打印信息
dev_info(dev, "master is unqueued, this is deprecated\n");
else { // 此处不执行
status = spi_master_initialize_queue(master);
...
}
list_add_tail(&master->list, &spi_master_list); // master 挂接到 spi_master_list 链表头下
list_for_each_entry(bi, &board_list, list)
spi_match_master_to_boardinfo(master, &bi->board_info); // 比较控制器和设备,如果有了相同的bus_num,则创建一个新设备(device)
mutex_unlock(&board_lock);
/* 从设备树上注册设备,我们这里没有采用设备树,因此,此函数是空,进去直接返回,,, */
of_register_spi_devices(master);
done:
return status;
}
platfor的设备在下边例子中改动的arch/arm/mach-s3c24xx/mach-mini244o.c 文件中的
platform的驱动在 spi-s3c24xx.c 中mini2440_devices[] 这个platform_device 结构的数组中的&s3c_device_spi1 这句,这个结构是在arch/arm/plat-samsung/devs.c 文件中已经定义好的。另外要说明的一点是在 arch/arm/mach-s3c24xx/mach-mini2440.c 这个文件中还将 s3c_device_spi1 进行了扩展,将struct s3c2410_spi_info 结构类型的数据放到了s3c_device_spi1.dev.platform_data中(此处的意思是将地址给了它):
static struct resource s3c_spi1_resource[] = {
[0] = DEFINE_RES_MEM(S3C24XX_PA_SPI1, SZ_32), // 第一个参数是spi寄存器的基地址,第二个参数是地址所占用的位数
[1] = DEFINE_RES_IRQ(IRQ_SPI1), // spi1对应的中断
};
struct platform_device s3c_device_spi1 = {
.name = "s3c2410-spi", // 跟 spi-s3c24xx.c 中进行匹配的关键
.id = 1,
.num_resources = ARRAY_SIZE(s3c_spi1_resource),
.resource = s3c_spi1_resource,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
接下来我们就来具体的分析分析.probe函数:
static int __devinit s3c24xx_spi_probe(struct platform_device *pdev)
{
struct s3c2410_spi_info *pdata;
struct s3c24xx_spi *hw;
struct spi_master *master;
struct resource *res;
int err = 0;
master = spi_alloc_master(&pdev->dev, sizeof(struct s3c24xx_spi)); // 分配了一个master的空间
hw = spi_master_get_devdata(master); // 相当于hw = master->dev->p->driver_data,对hw的操作实际上就是对master->dev->p->driver_data的操作
memset(hw, 0, sizeof(struct s3c24xx_spi)); // 将从driver_data首地址开始之后分配s3c24xx_spi结构体大小的空间
hw->master = spi_master_get(master); // 相当于 hw->master = master
hw->pdata = pdata = pdev->dev.platform_data; // 得到从平台文件中来的platform_device的私有数据
hw->dev = &pdev->dev;
platform_set_drvdata(pdev, hw); // pdev->dev->p->driver_data = hw
init_completion(&hw->done);
/* initialise fiq handler */
s3c24xx_spi_initfiq(hw); // 初始化了fiq
/* setup the master state. */
/* the spi->mode bits understood by this driver: */
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; // 模式选择,片选高有效
master->num_chipselect = hw->pdata->num_cs;
master->bus_num = pdata->bus_num;
/* setup the state for the bitbang driver */
hw->bitbang.master = hw->master;
hw->bitbang.setup_transfer = s3c24xx_spi_setupxfer;
hw->bitbang.chipselect = s3c24xx_spi_chipsel;
hw->bitbang.txrx_bufs = s3c24xx_spi_txrx;
hw->master->setup = s3c24xx_spi_setup;
hw->master->cleanup = s3c24xx_spi_cleanup;
dev_dbg(hw->dev, "bitbang at %p\n", &hw->bitbang);
/* find and map our resources */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
hw->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
hw->regs = ioremap(res->start, resource_size(res));
hw->irq = platform_get_irq(pdev, 0);
err = request_irq(hw->irq, s3c24xx_spi_irq, 0, pdev->name, hw); //申请中断,发生中断进s3c24xx_spi_irq,此函数中处理接收的数据
hw->clk = clk_get(&pdev->dev, "spi");
/* setup any gpio we can */
if (!pdata->set_cs) { // 我们的mach-mini2440文件中没有提供这个函数
if (pdata->pin_cs < 0) {
dev_err(&pdev->dev, "No chipselect pin\n");
goto err_register;
}
err = gpio_request(pdata->pin_cs, dev_name(&pdev->dev)); // 验证一下cs的gpio的有效性
hw->set_cs = s3c24xx_spi_gpiocs; // 操作片选的函数关联上
gpio_direction_output(pdata->pin_cs, 1); // 1表示通过判断gpio_desc中的flag来设置输入输出
} else
hw->set_cs = pdata->set_cs; // 如果提供了,用用户的
s3c24xx_spi_initialsetup(hw); // 设置spi控制器的初始状态
/* register our spi controller */
err = spi_bitbang_start(&hw->bitbang); // 开始以个spi_master 驱动,上边有函数分析
return 0;
}
我们来看看建立传输阶段:
/**
* spi_bitbang_transfer - 默认传给发送队列
*/
int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m)
{
struct spi_bitbang *bitbang;
unsigned long flags;
int status = 0;
m->actual_length = 0;
m->status = -EINPROGRESS;
bitbang = spi_master_get_devdata(spi->master); // 得到数据
if (!spi->max_speed_hz)
status = -ENETDOWN;
else {
list_add_tail(&m->queue, &bitbang->queue);
queue_work(bitbang->workqueue, &bitbang->work); // 提交给工作队列,开始实际的传输工作
}
return status;
}
实际的传输是s3c24xx_spi_txrx:
static int s3c24xx_spi_txrx(struct spi_device *spi, struct spi_transfer *t)
{
struct s3c24xx_spi *hw = to_hw(spi);
hw->tx = t->tx_buf;
hw->rx = t->rx_buf;
hw->len = t->len;
hw->count = 0;
init_completion(&hw->done);
hw->fiq_inuse = 0;
if (s3c24xx_spi_usefiq(hw) && t->len >= 3)
s3c24xx_spi_tryfiq(hw);
/* send the first byte */
writeb(hw_txbyte(hw, 0), hw->regs + S3C2410_SPTDAT); // 向发送寄存器中写入发送数组中的第一个( hw->tx ? hw->tx[0] : 0;)
// 我们只管开始第一个,其他的是有工作队列会自动的发送其他的
wait_for_completion(&hw->done);
return hw->count;
}
至此,我们先分析到这里,虽然分析完了,可实际上还是有部分不是分析的很清楚,但是感觉到这里,对于一般的spi应用来说是足够了,另外考虑到对spi的分析用的时间比较长,暂时就先到这里。
对spi整体分层是一个linux中设计的精髓,但是,自己还是没能整理出一张好的分层图,这就留到以后的学习和实践中去吧。
4. 程序验证:
对spidev的使用在jz2440开发板上的使用需要先添加平台设备,经过看原理图,发现,控制器0上的引脚并没有引出来。
diff --git a/arch/arm/mach-s3c24xx/mach-mini2440.c b/arch/arm/mach-s3c24xx/mach-mini2440.c
index fa4dc4d..8b16989 100644
--- a/arch/arm/mach-s3c24xx/mach-mini2440.c
+++ b/arch/arm/mach-s3c24xx/mach-mini2440.c
@@ -62,6 +62,9 @@
#include "common.h"
+#include <linux/spi/spi.h> // 在头文件处添加两个头文件
+#include <linux/spi/s3c24xx.h>
+
#define MACH_MINI2440_DM9K_BASE (S3C2410_CS4 + 0x300)
static struct map_desc mini2440_iodesc[] __initdata = {
@@ -505,10 +508,12 @@ static struct platform_device mini2440_audio = {
/*
* I2C devices
*/
+#if 0 // 此部分是为了消除警告
static struct at24_platform_data at24c08 = {
.byte_len = SZ_8K / 8,
.page_size = 16,
};
+#endif
static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
#if 0
@@ -519,6 +524,44 @@ static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
#endif
};
+/* // 在i2c下边添加一下的spi部分
+ * spi devices
+ */
+#if 0
+static struct spi_board_info s3c2410_spi0_board[]={
+ [0] = {
+ .modalias = "spidev",
+ .bus_num = 0, // spi controller0
+ .chip_select = 0, // 在控制器下的第一个设备,这个选项必须是从0开始依次递增
+ /*.irq = IRQ_EINT9,*/ // 接收数据时用irq通知
+ .max_speed_hz = 500 * 1000,
+ },
+};
+
+static struct s3c2410_spi_info s3c2410_spi0_platdata={
+ .pin_cs = S3C2410_GPG(2), // 片选引脚
+ .num_cs = 1, // 几个slave设备
+ .bus_num = 1, // 哪个控制器,从0开始,此处可能有问题,未验证
+};
+#endif
+
+static struct spi_board_info s3c2410_spi1_board[] =
+{
+ [0] = {
+ .modalias = "spidev",
+ .bus_num = 1, // spi controller1
+ .chip_select = 0,
+ /*.irq = IRQ_EINT2,*/ // 此处应该是EINT11(GPG3),此处没有验证
+ .max_speed_hz = 500 * 1000,
+ }
+};
+
+static struct s3c2410_spi_info s3c2410_spi1_platdata = {
+ .pin_cs = S3C2410_GPG(3), // GPG3引脚做片选引脚
+ .num_cs = 1,
+ .bus_num = 1, // 控制器1
+};
+
static struct platform_device uda1340_codec = {
.name = "uda134x-codec",
.id = -1,
@@ -528,6 +571,7 @@ static struct platform_device *mini2440_devices[] __initdata = {
&s3c_device_ohci,
&s3c_device_wdt,
&s3c_device_i2c0,
+ &s3c_device_spi1, // 此处看下边解释 【1】
&s3c_device_rtc,
&s3c_device_usbgadget,
&mini2440_device_eth,
@@ -693,10 +737,13 @@ static void __init mini2440_init(void)
s3c24xx_mci_set_platdata(&mini2440_mmc_cfg);
s3c_nand_set_platdata(&mini2440_nand_info);
s3c_i2c0_set_platdata(NULL);
+ s3c_device_spi1.dev.platform_data = &s3c2410_spi1_platdata;
i2c_register_board_info(0, mini2440_i2c_devs,
ARRAY_SIZE(mini2440_i2c_devs));
+ spi_register_board_info(s3c2410_spi1_board,ARRAY_SIZE(s3c2410_spi1_board));
+
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
if (features.count) /* the optional features */
【1】 s3c_device_spi1 结构:
/* SPI */
#ifdef CONFIG_PLAT_S3C24XX
static struct resource s3c_spi0_resource[] = {
[0] = DEFINE_RES_MEM(S3C24XX_PA_SPI, SZ_32), // (开始地址,大小)
[1] = DEFINE_RES_IRQ(IRQ_SPI0), // spi的中断0
};
struct platform_device s3c_device_spi0 = {
.name = "s3c2410-spi",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_spi0_resource), // 资源个数
.resource = s3c_spi0_resource, // 资源首地址
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
static struct resource s3c_spi1_resource[] = {
[0] = DEFINE_RES_MEM(S3C24XX_PA_SPI1, SZ_32),
[1] = DEFINE_RES_IRQ(IRQ_SPI1),
};
struct platform_device s3c_device_spi1 = {
.name = "s3c2410-spi",
.id = 1,
.num_resources = ARRAY_SIZE(s3c_spi1_resource),
.resource = s3c_spi1_resource,
.dev = {
.dma_mask = &samsung_device_dma_mask,
.coherent_dma_mask = DMA_BIT_MASK(32),
}
};
#endif /* CONFIG_PLAT_S3C24XX */
至此,平台文件算是改动好了,重新编译内核:
make uImage
将生成的uImage拷贝到tftp的下载目录下:
cp arch/arm/boot/uImage /tftpboot/
重新搭建环境,启动开发板,我们就能在/dev下看到spidev1.0 节点了。
测试程序我们选用内核自带的测试程序:Documentation/spi/spidev_test.c
arm-none-linux-gnueabi-gcc spidev_test.c -o spidev_test -march=armv4t
拷贝生成的spidev_test 到nfs共享目录下的根文件下:
cp spidev_test /source/fs_mini/
由于是半双工,我们可以将 MOSI 和 MISO 用一根线短路。
启动开发板,在开发板上可以看到如下图中内容:
更多推荐
所有评论(0)