spi 驱动一:spi基本结构和spidev文件系统

spi 驱动一:spi基本结构和spidev文件系统

qqliyunpeng 2017-01-24 18:20:46 12926 收藏 2
展开
作者: 李云鹏([email protected])

版本号: 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驱动器设计的驱动。

  1. 关键的结构:
    在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; // 看后边的图,链表的节点

};

这几个结构再应用中的关系如下图:

  1. 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;
};

言归正传,在和设备匹配上的时候调用匹配函数 probe 函数:spidev_probe
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;
}

还有一个问题没有解决的是,在什么时候执行了s3c24xx_spi_probe函数呢?是在platform的设备和驱动相匹配了之后执行的:
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中设计的精髓,但是,自己还是没能整理出一张好的分层图,这就留到以后的学习和实践中去吧。

  1. 程序验证:
    对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 // 在头文件处添加两个头文件
+#include
+
#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 用一根线短路。

启动开发板,在开发板上可以看到如下图中内容:

————————————————
版权声明:本文为CSDN博主「qqliyunpeng」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qqliyunpeng/article/details/53326226

你可能感兴趣的:(spi 驱动一:spi基本结构和spidev文件系统)