总结
和i2c框架图类似 spi控制器也会当作spi设备 挂在spi总线上面
-
SPI核心层
提供SPI控制器驱动和设备驱动的注册方法、注销方法、SPI通信硬件无关接口 -
SPI主机驱动
主要包含SPI硬件体系结构中适配器(spi控制器)的控制,用于产生SPI 读写时序
主要数据结构:spi_master(spi_controller) -
SPI设备驱动
通过SPI主机驱动与CPU交换数据
主要数据结构:spi_device和spi_driver
和iic不一样的地方 因为速度高了很多提供了同步和异步的传输方式
异步的时候不是自己发送数据 把数据给内核线程 线程帮忙发送(前面讲的内核流水线工人)
同时和iic一样有万能驱动 开机自行注册 使用万能驱动 让app操作构造的spi控制器节点 直接操作每一个spi控制器
建议看iic子系统再来理解这个 spi写的有点冲忙
spi总线的注册
spi总线定义
struct bus_type spi_bus_type = { //设定了spi设备和驱动匹配规则
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
spi总线的注册
drivers/spi/spi.c 开机自动运行
static int __init spi_init(void)
status = bus_register(&spi_bus_type); //新增总线 sys/bus/spi
status = class_register(&spi_master_class); //新增设备类 sys/class/spi_master
spi控制器注册
设备树节点注册
spi的控制器的设备树节点 用第三个spi控制器举例
ecspi3: ecspi@2010000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
reg = <0x2010000 0x4000>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ECSPI3>,
<&clks IMX6UL_CLK_ECSPI3>;
clock-names = "ipg", "per";
dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
dma-names = "rx", "tx";
status = "disabled";
};
核心层(spi控制器)的主要结构体 spi_master
#define spi_master
struct device dev; //继承device结构体,linux设备驱动模型
...
struct list_head list; //链表节点,链接spi_controller
s16 bus_num; //spi控制器编号,通过这个对不同控制器识别
u16 num_chipselect; //片选信号数量
...
struct spi_message *cur_msg;//注意这个结构体类型,表明正在处理的消息队列
...
int (*setup)(struct spi_device *spi);//初始化spi设备使用
int (*transfer)(struct spi_device *spi, //传输spi消息,因为是异步的,其实就是把消息加入到消息队列queue
struct spi_message *mesg);
void (*cleanup)(struct spi_device *spi);
struct kthread_worker kworker;//内核流水线工人
struct task_struct *kworker_task;//指向一个线程.线程配合工人工作
struct kthread_work pump_messages;//工人的具体工作,负责发送queue消息
struct list_head queue; //挂载一系列的spi消息,消息会给工人处理
struct spi_message *cur_msg;
...
int (*transfer_one)(struct spi_controller *ctlr, struct spi_device *spi,struct spi_transfer *transfer);
int (*prepare_transfer_hardware)(struct spi_controller *ctlr);
int (*transfer_one_message)(struct spi_controller *ctlr,struct spi_message *mesg);
void (*set_cs)(struct spi_device *spi, bool enable);
...
int *cs_gpios; //负责记录具体片选信号
}
//初始化完控制器后 需要注册到内核
int spi_register_master(struct spi_master *master)
spi_register_controller(struct spi_controller *ctlr)
struct device *dev = ctlr->dev.parent;//拿到结构体的dev的父指针
struct boardinfo *bi;
int status = -ENODEV;
int id, first_dynamic;
dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);//给dev设置名字,
status = device_add(&ctlr->dev);//加入linux设备驱动模型
if (ctlr->transfer) { //判断transfer的成员变量是否在
spi_controller_initialize_queue(ctlr);
ctlr->transfer = spi_queued_transfer;
ctlr->transfer_one_message = spi_transfer_one_message;
/* 具体工作&ctlr->pump_messages ,对于这个工作的处理函数spi_pump_messages,被kthread_worker_fn线程进行调用*/
ret = spi_init_queue(ctlr); //初始化内核线程工人,初始化内核具体工作
kthread_init_worker(&ctlr->kworker);//初始化一个内核流水线工人
ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker,"%s", dev_name(&ctlr->dev));
//为流水线工人创造一个线程
kthread_init_work(&ctlr->pump_messages, spi_pump_messages);//初始化一个具体的工作,这个工作的具体函数就是spi_pump_messages
spi_pump_messages函数实现
void spi_pump_messages(struct kthread_work *work)
struct spi_controller *ctlr = container_of(work, struct spi_controller, pump_messages); //根据pump_messages获取spi_controller
__spi_pump_messages(ctlr, true);
list_first_entry(&ctlr->queue, struct spi_message, queue);//通过queue找到要传输的message
list_del_init(&ctlr->cur_msg->queue);//删除列表上的第一个message结构体,所以queue链接到第二个message结构体了
if (ctlr->prepare_message)
ret = ctlr->prepare_message(ctlr, ctlr->cur_msg);
ret = ctlr->transfer_one_message(ctlr, ctlr->cur_msg);
ret = spi_start_queue(ctlr);//启动内核具体工作
kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages); //把内核具体工作交给流水线工人
spi控制器驱动
static struct platform_driver spi_imx_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = spi_imx_dt_ids,
.pm = IMX_SPI_PM,
},
.id_table = spi_imx_devtype,
.probe = spi_imx_probe,
.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver); //这边的init有点奇怪 但还是调用了module_init
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)
module_init(__driver##_init); \
//获取设备树节点信息,初始化spi时钟,
static int spi_imx_probe(struct platform_device *pdev)
struct spi_master *master;
master = spi_alloc_master(&pdev->dev,sizeof(struct spi_imx_data) + sizeof(int) * num_cs);
spi_imx = spi_master_get_devdata(master);
ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs); //找到片选信号的数量
master->num_chipselect = num_cs;
ret = spi_bitbang_start(&spi_imx->bitbang);
ret = spi_register_master(spi_master_get(master)); //把spi主控制器注册到linux系统
spi_device设备
struct spi_device {
struct device dev;
struct spi_controller *controller; //表示这个设备在哪个spi控制器
struct spi_controller *master; /* compatibility layer */
u32 max_speed_hz; //通讯最大频率
u8 chip_select;
u8 bits_per_word; //8位还是16位通讯
u16 mode; //spi的四大通讯模式
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#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 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */
#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */
#define SPI_RX_DUAL 0x400 /* receive with 2 wires */
#define SPI_RX_QUAD 0x800 /* receive with 4 wires */
...
char modalias[SPI_NAME_SIZE]; //记录spi设备名字,因为要和driver配对
...
}
spi 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);
struct device_driver driver; 继承driver
};
probe里面大概做了
int spi_register_driver(struct spi_driver *sdrv) 注册一个spi驱动
同步异步函数分析
spi_controller->queue->spi_message
通过queue把一系列的 spi_message链接
spi_controller->queue->spi_message->transfer
把一系列的transfer给链接起来 一个transfer就是一个传输基本单位 所以也是message管理通讯消息
spi_sync函数 同步数据传输 sync总会有地方阻塞 async异步和下面的操作类似 但是不会阻塞
spi_sync(struct spi_device *spi, struct spi_message *message)
ret = __spi_sync(spi, message);
status = __spi_validate(spi, message);//判断各种参数正常
message->complete = spi_complete;
message->context = &done;
message->spi = spi;
status = __spi_queued_transfer(spi, message, false);
if (!ctlr->running) { //判断spi控制器是否被占用
spin_unlock_irqrestore(&ctlr->queue_lock, flags);
return -ESHUTDOWN;
}
list_add_tail(&msg->queue, &ctlr->queue); //把message消息的queue挂载在控制器的queue的末尾
kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);//把内核具体工作交给流水线工人
#define spi_register_master(_ctlr) spi_register_controller(_ctlr)
int spi_register_controller(struct spi_controller *ctlr)
struct device *dev = ctlr->dev.parent; //拿到控制器的dev属性的父节点
dev_set_name(&ctlr->dev, "spi%u", ctlr->bus_num);//给控制器的device属性设置名字
status = device_add(&ctlr->dev); //创建/sys/下面的目录
if(ctlr->transfer_one || ctlr->transfer_one_message)
spi_controller_initialize_queue(ctlr);
ctlr->transfer = spi_queued_transfer;
ret = spi_init_queue(ctlr); //初始化一个内核流水线工人和具体工作
kthread_init_worker(&ctlr->kworker); //初始化工人
ctlr->kworker_task = kthread_run(kthread_worker_fn, &ctlr->kworker, //初始化工人的线程
"%s", dev_name(&ctlr->dev));
kthread_init_work(&ctlr->pump_messages, spi_pump_messages);//初始化具体工作
spi_start_queue() //启动工人的具体工作
kthread_queue_work(&ctlr->kworker, &ctlr->pump_messages);
万能spi驱动分析
drivers/spi/spidev.c
开机后自动加载spidev驱动模块
设备树增加节点 引用spi3控制器
&ecspi3{ //引用spi3控制器的节点
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>; //初始化spi3控制器的相关引脚
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
spidev@0 { //spi的设备节点
compatible = "spidev";
spi-max-frequency = <20000000>;
reg = <0>;
};
};
驱动实现 drivers/spi/spidev.c
开机后自动装载
module_init(spidev_init);
int __init spidev_init(void)
register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);//对哈希表1申请设备号????还有呢
spidev_class = class_create(THIS_MODULE, "spidev"); 创建class类
spi_register_driver(&spidev_spi_driver);//在spi总线上新增驱动
spidev_spi_driver.probe = spidev_probe
//spi_dev结构体
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 *tx_buffer;//发送buffer
u8 *rx_buffer;//接受buffer
u32 speed_hz;//速率
};
//看看probe函数 本质上创建一个字符设备 因为init中已经在哈希表1和哈希表二登记了主次设备号和file_operation结构体
//下面就不用注册file_operation结构体了 只用新注册一个设备
int spidev_probe(struct spi_device *spi) //这里传进来的值也就是上面设备树节点spidev@0
struct spidev_data *spidev;
spidev = kzalloc(sizeof(*spidev), GFP_KERNEL); //给spi_dev分配内存
minor = find_first_zero_bit(minors, N_SPI_MINORS);//分配次设备号
spidev->devt = MKDEV(SPIDEV_MAJOR, minor); //合成总设备号
dev = device_create(spidev_class, &spi->dev, spidev->devt, //在class下面创建设备类
spidev, "spidev%d.%d",
spi->master->bus_num, spi->chip_select);//spi控制器编号 和spi片选编号 方便从文件名知道这个文件搞啥
list_add(&spidev->device_entry, &device_list);//以后想找到spidev直接遍历device_list链表就很方便
spidev->speed_hz = spi->max_speed_hz;//初始化传输速率
spi_set_drvdata(spi, spidev);//把spidev属性记录到 spi_driver->data下面
//file_operation里面其他的函数
.open = spidev_open, //差不多为tx rxbuff分配内存
static int spidev_open(struct inode *inode, struct file *filp)
list_for_each_entry(spidev, &device_list, device_entry) //遍历全局device_list链表,找到spi_dev
if (spidev->devt == inode->i_rdev)//根据主次设备号 找到了想要的spi_dev
status = 0;
if (!spidev->tx_buffer) //看看有没有tx_buff
spidev->tx_buffer = kmalloc(bufsiz, GFP_KERNEL);
if (!spidev->rx_buffer)
spidev->rx_buffer = kmalloc(bufsiz, GFP_KERNEL);
filp->private_data = spidev;//把spi_dev放入文件指针的私有数据里面
.read = spidev_read,
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
spidev = filp->private_data; //把spi_dev拿出来
status = spidev_sync_read(spidev, count);
struct spi_transfer t = { //创建一个spi传输结构体
.rx_buf = spidev->rx_buffer,
.len = len,
.speed_hz = spidev->speed_hz,
};
struct spi_message m;
spi_message_init(&m); //初始化一个spi_message
spi_message_add_tail(&t, &m);//把spi_transfer加入spi_message中进行管理
return spidev_sync(spidev, &m);//数据发送
spi_async(spidev->spi, message);//之前的数据异步发送
missing = copy_to_user(buf, spidev->rx_buffer, status);//收到数据回给应用层
.unlocked_ioctl = spidev_ioctl, 也就是ioctrl 这个表示32位的ioctrl
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
实验 使用spi3控制器 进行自行收发
继续对上面的 驱动进行描述 应用层可以使用驱动生成的 spi控制器设备节点 进行操作spi控制器收发
应用层在ioctrl默认选择发送一个 spi_ioc_transfer 结构体
struct spi_ioc_transfer {
__u64 tx_buf; //spi发送缓冲区
__u64 rx_buf; //spi接受缓冲区
__u32 len; //数据长度
__u32 speed_hz;
__u16 delay_usecs;
__u8 bits_per_word;
__u8 cs_change;
__u8 tx_nbits;
__u8 rx_nbits;
__u16 pad;
}
驱动中的ioctrl
.unlocked_ioctl = spidev_ioct
switch (cmd) {
case SPI_IOC_RD_MODE:
xxx
case SPI_IOC_RD_LSB_FIRST::
xxxx
default:
ioc = spidev_get_ioc_message(cmd, //传过来的arg可能不止一个 spi_ioc_transfer,可能是个结构体数组
(struct spi_ioc_transfer __user *)arg, &n_ioc);//n_ioc记录传过来的ico个数
tmp = _IOC_SIZE(cmd);//获得总数据的大小
*n_ioc = tmp / sizeof(struct spi_ioc_transfer);//得出有多少个ioc结构体传入
retval = spidev_message(spidev, ioc, n_ioc); //调用一系列spi核心函数 ,构造msg和trans进行数据收发
struct spi_message msg;
struct spi_transfer *k_xfers;
spi_message_init(&msg);
if (copy_from_user(tx_buf, (const u8 __user *) //拷贝用户空间的数据未接下来发送准备
(uintptr_t) u_tmp->tx_buf,
spi_message_add_tail(k_tmp, &msg);
status = spidev_sync(spidev, &msg);//数据同步发送
if (__copy_to_user((u8 __user *)
(uintptr_t) u_tmp->rx_buf, rx_buf, //读到的数据返回用户空间
xxx...
开始实验
设备树节点的修改
iomux节点增加
pinctrl_ecspi3:ecspi3grp {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__ECSPI3_SS0 0x1a090
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x11090
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x11090
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x11090
>;
};
引用适配器做出修改 增加节点
&ecspi3{ //spi3控制器支持多组片选引脚
fsl,spi-num-chipselects = <1>; //这里修改这个spi3控制器支持多少个片选引脚
cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; //每个片选引脚使用到的具体引脚
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
spidev@0 {
compatible = "spidev";
spi-max-frequency = <20000000>;
reg = <0>; //指定这个spi设备使用哪个片选引脚
};
};