linux下如何进行SPI驱动,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1. SPI总线1.1. SPI总线概述SPI,是英语Serial Peripheral &nb
linux下如何进行SPI驱动,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
1. SPI总线
1.1. SPI总线概述
SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。SPI总线的构成及信号类型如图1-1所示:
MOSI – 主设备数据输出,从设备数据输入 对应MOSI master output slave input
MISO – 主设备数据输入,从设备数据输出 对应MISO master input slave output
CLK – 时钟信号,由主设备产生
nCS – 从设备使能信号,由主设备控制
图1-1 SPI总线模型
1.2. SPI总线时序
SPI接口在Master控制下产生的从设备使能信号和时钟信号,两个双向移位寄存器按位传输进行数据交换,传输数据高位在前(MSB first),低位在后。如下图所示,在CLK的下降沿上数据改变,上升沿一位数据被存入移位寄存器。
图1-2 spi传输时序图
在一个SPI时钟周期内,会完成如下操作:(1)Master通过MOSI线发送1位数据,同时Slave通过MOSI线读取这1位数据;(2)Slave通过MISO线发送1位数据,同时Master通过MISO线读取这1位数据。Master和Slave各有一个移位寄存器,如图1-3所示,而且这两个移位寄存器连接成环状。依照CLK的变化,数据以MSB first的方式依次移出Master寄存器和Slave寄存器,并且依次移入Slave寄存器和Master寄存器。当寄存器中的内容全部移出时,相当于完成了两个寄存器内容的交换。
1.3. SPI总线传输模式
SPI总线传输一共有4种模式,这4种模式分别由时钟极性(CPOL,Clock Polarity)和时钟相位(CPHA,Clock Phase)来定义,其中CPOL参数规定了SCK时钟信号空闲状态的电平,CPHA规定了数据是在SCK时钟的上升沿被采样还是下降沿被采样。这四种模式的时序图如下图1-4所示:
模式0:CPOL= 0,CPHA=0。CLK串行时钟线空闲是为低电平,数据在SCK时钟的上升沿被采样,数据在CLK时钟的下降沿切换
模式1:CPOL= 0,CPHA=1。CLK串行时钟线空闲是为低电平,数据在SCK时钟的下降沿被采样,数据在CLK时钟的上升沿切换
模式2:CPOL= 1,CPHA=0。CLK串行时钟线空闲是为高电平,数据在SCK时钟的下降沿被采样,数据在CLK时钟的上升沿切换
模式3:CPOL= 1,CPHA=1。CLK串行时钟线空闲是为高电平,数据在SCK时钟的上升沿被采样,数据在CLK时钟的下降沿切换 其中比较常用的模式是模式0和模式3。为了更清晰的描述SPI总线的时序,下面展现了模式0下的SPI时序图1-5:
图1-5 mode0下的SPI时序图
1.4. SPI总线的优缺点
(1) 在点对点的通信中,SPI接口不需要进行寻址操作,且为全双工通信,显得简单高效。(2) SPI接口没有指定的流控制,没有应答机制确认是否接收到数据。
2. Linux SPI 框架
2.1. 软件架构
Linux系统对spi设备具有很好的支持,linux系统下的spi驱动程序从逻辑上可以分为3个部分:
鸿蒙官方战略合作共建——HarmonyOS技术社区
spi核心(SPI Core):SPI Core是Linux内核用来维护和管理spi的核心部分,SPI Core提供操作接口函数,允许一个spi master,spi driver和spi device初始化时在SPI Core中进行注册,以及退出时进行注销。
spi控制器驱动(SPI Master Driver):SPI Master针对不同类型的spi控制器硬件,实现spi总线的硬件访问操作。SPI Master通过接口函数向SPI Core注册一个控制器。
spi设备驱动(SPI Device Driver):SPI Driver是对应于spi设备端的驱动程序,通过接口函数向SPI Core进行注册,SPI Driver的作用是将spi设备挂接到spi总线上;Linux的软件架构图如图2-1所示:
图2-1 spi软件架构图
2.2. 初始化及退出流程
2.2.1. 注册spi控制器
注册spi控制器到内核分为两个阶段:第一个阶段,使用spi_alloc_master,分配一个spi_master的空间,具体流程如图2-2所示:
第二阶段,使用spi_reGISter_master将第一阶段分配的spi_master注册到内核中,具体流程如2-3所示:
2.2.2. 注销spi控制器
spi控制器注销的流程如图2-4所示:
2.3. 关键数据结构
2.3.1. spi_device
struct spi_device { struct device dev; u32 max_speed_hz; u8 chip_select; u8 mode; #define SPI_CPHA 0x01 #define SPI_CPOL 0x02 #define SPI_MODE_0 (0|0) #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 #define SPI_LOOP 0x20 #define SPI_NO_CS 0x40 #define SPI_READY 0x80 u8 bits_per_Word; int irq; void *controller_state; void *controller_data; char modalias[SPI_NAME_SIZE]; int cs_gpio; };
spi_device代表一个外围spi设备,由master controller driver注册完成后扫描BSP中注册设备产生的设备链表并向spi_bus注册产生。在内核中,每个spi_device代表一个物理的spi设备。
2.3.2. 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); int (*suspend)(struct spi_device *spi, pm_message_t mesg); int (*resume)(struct spi_device *spi); struct device_driver driver; };
spi_driver代表一个SPI protocol drivers,即外设驱动
2.3.3. struct spi_master
struct spi_master { struct device dev; struct list_head list; s16 bus_num; u16 num_chipselect; u16 dma_alignment; u16 mode_bits; u32 bits_per_word_mask; u16 flags; #define SPI_MASTER_NO_RX BIT(1) #define SPI_MASTER_NO_TX BIT(2) spinlock_t bus_lock_spinlock; struct mutex bus_lock_mutex; bool bus_lock_flag; int (*setup)(struct spi_device *spi); int (*transfer)(struct spi_device *spi, struct spi_message *mesg); void (*cleanup)(struct spi_device *spi); bool queued; struct kthread_worker kworker; struct task_struct *kworker_task; struct kthread_work pump_messages; spinlock_t queue_lock; struct list_head queue; struct spi_message *cur_msg; 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); int *cs_gpiOS; };
spi_master代表一个spi控制器。
2.3.4. struct spi_message 和spi_transfer
要完成和SPI设备的数据传输工作,我们还需要另外两个数据结构:spi_message和spi_transfer。
spi_message包含了一个的spi_transfer结构序列,一旦控制器接收了一个spi_message,其中的spi_transfer应该按顺序被发送,并且不能被其它spi_message打断,所以我们认为spi_message就是一次SPI数据交换的原子操作。下面我们看看这两个数据结构的定义:
struct spi_message :
struct spi_message { struct list_head transfers; struct spi_device *spi; unsigned is_dma_mapped:1; void (*complete)(void *context); void *context; unsigned actual_length; int status; struct list_head queue; void *state; };
链表字段queue用于把该结构挂在代表控制器的spi_master结构的queue字段上,控制器上可以同时被加入多个spi_message进行排队。另一个链表字段transfers则用于链接挂在本message下的spi_tranfer结构。complete回调函数则会在该message下的所有spi_transfer都被传输完成时被调用,以便通知协议驱动处理接收到的数据以及准备下一批需要发送的数据。我们再来看看spi_transfer结构:spi_transfer
struct spi_transfer { const void *tx_buf; void *rx_buf; unsigned len; dma_addr_t tx_dma; dma_addr_t rx_dma; unsigned cs_change:1; u8 bits_per_word; u16 delay_usecs; u32 speed_hz; #ifdef CONFIG_SPI_LOMBO struct lombo_spi_operate_para *esop; #endif struct list_head transfer_list; };
首先,transfer_list链表字段用于把该transfer挂在一个spi_message结构中,tx_buf和rx_buf提供了非dma模式下的数据缓冲区地址,len则是需要传输数据的长度,tx_dma和rx_dma则给出了dma模式下的缓冲区地址。原则来讲,spi_transfer才是传输的最小单位,之所以又引进了spi_message进行打包,我觉得原因是:有时候希望往spi设备的多个不连续的地址(或寄存器)一次性写入,如果没有spi_message进行把这样的多个spi_transfer打包,因为通常真正的数据传送工作是在另一个内核线程(工作队列)中完成的,不打包的后果就是会造成更多的进程切换,效率降低,延迟增加,尤其对于多个不连续地址的小规模数据传送而言就更为明显。
2.3.5. spi_board_info
struct spi_board_info { char modalias[SPI_NAME_SIZE]; const void *platfORM_data; void *controller_data; int irq; u32 max_speed_hz; u16 bus_num; u16 chip_select; u8 mode; };
2.4. 数据传输流程
整体的数据传输流程大致上是这样的:
鸿蒙官方战略合作共建——HarmonyOS技术社区
定义一个spi_message结构;
用spi_message_init函数初始化spi_message;
定义一个或数个spi_transfer结构,初始化并为数据准备缓冲区并赋值给spi_transfer相应的字段(tx_buf,rx_buf等);
通过spi_message_init函数把这些spi_transfer挂在spi_message结构下;
如果使用同步方式,调用spi_sync(),如果使用异步方式,调用spi_async();(我调试外设时,只使用过spi_sync
传输示意图如图2-5所示:
2.4.1. 数据准备
2.4.1.1. spi_message_init
static inline void spi_message_init(struct spi_message *m) { memset(m, 0, sizeof *m); INIT_LIST_HEAD(&m->transfers); }
初始化spi_message:清空message,初始化transfers链表头。
2.4.1.2. spi_message_add_tail
static inline void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m) { list_add_tail(&t->transfer_list, &m->transfers); }
将spi_transfer加入到spi_message的链表尾部。
2.4.2. 数据传输
SPI数据传输可以有两种方式:同步方式和异步方式。所谓同步方式是指数据传输的发起者必须等待本次传输的结束,期间不能做其它事情,用代码来解释就是,调用传输的函数后,直到数据传输完成,函数才会返回。而异步方式则正好相反,数据传输的发起者无需等待传输的结束,数据传输期间还可以做其它事情,用代码来解释就是,调用传输的函数后,函数会立刻返回而不用等待数据传输完成,我们只需设置一个回调函数,传输完成后,该回调函数会被调用以通知发起者数据传送已经完成。同步方式简单易用,很适合处理那些少量数据的单次传输。但是对于数据量大、次数多的传输来说,异步方式就显得更加合适。对于SPI控制器来说,要支持异步方式必须要考虑以下两种状况:
对于同一个数据传输的发起者,既然异步方式无需等待数据传输完成即可返回,返回后,该发起者可以立刻又发起一个message,而这时上一个message还没有处理完。
对于另外一个不同的发起者来说,也有可能同时发起一次message传输请求 首先分析spi_sync()接口的实现流程,如图2-6:
其次分析spi_async_locked接口的实现流程,如图2-7所示:
spi_queued_transfer接口的实现流程如图3-8所示:
spi_pump_messages函数的处理流程如图3-9所示:
图中transfer_one_message是spi控制器驱动要实现的,主要功能是处理spi_message中的每个spi_transfer。
2.5. 关键函数解析
2.5.1. spi_alloc_master
原型:
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
功能:分配一个spi_master结构体指针。
参数:dev:spi控制器device指针 size :分配的driver-private data大小
返回值 :成功,返回spi_master指针;否则返回NULL
2.5.2. spi_register_master
原型:
int spi_register_master(struct spi_master *master)
功能 注册spi控制器驱动到内核。
参数 master:spi_master指针
返回值 成功,返回0;否则返回错误码
2.5.3. spi_unregister_master
原型:
void spi_unregister_master(struct spi_master *master)
功能 注销spi控制器驱动。
参数 master:spi_master指针
返回值 无
3. Demo
#include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/i2c.h> #include <linux/spi/spi.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_gpio.h> #include <linux/platform_device.h> #include <asm/Mach/map.h> #include <asm/uaccess.h> #include <asm/io.h> #include "icm20608reg.h" #define ICM20608_CNT 1 #define ICM20608_NAME "icm20608" struct icm20608_dev { dev_t devid; struct cdev cdev; struct class *class; struct device *device; struct device_node *nd; int major; void *private_data; int cs_gpio; signed int gyro_x_adc; signed int gyro_y_adc; signed int gyro_z_adc; signed int accel_x_adc; signed int accel_y_adc; signed int accel_z_adc; signed int temp_adc; }; static struct icm20608_dev icm20608dev; static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len) { int ret; unsigned char txdata[len]; struct spi_message m; struct spi_transfer *t; struct spi_device *spi = (struct spi_device *)dev->private_data; gpio_set_value(dev->cs_gpio, 0); t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); txdata[0] = reg | 0x80; t->tx_buf = txdata; t->len = 1; spi_message_init(&m); spi_message_add_tail(t, &m); ret = spi_sync(spi, &m); txdata[0] = 0xff; t->rx_buf = buf; t->len = len; spi_message_init(&m); spi_message_add_tail(t, &m); ret = spi_sync(spi, &m); kfree(t); gpio_set_value(dev->cs_gpio, 1); return ret; } static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len) { int ret; unsigned char txdata[len]; struct spi_message m; struct spi_transfer *t; struct spi_device *spi = (struct spi_device *)dev->private_data; t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); gpio_set_value(dev->cs_gpio, 0); txdata[0] = reg & ~0x80; t->tx_buf = txdata; t->len = 1; spi_message_init(&m); spi_message_add_tail(t, &m); ret = spi_sync(spi, &m); t->tx_buf = buf; t->len = len; spi_message_init(&m); spi_message_add_tail(t, &m); ret = spi_sync(spi, &m); kfree(t); gpio_set_value(dev->cs_gpio, 1); return ret; } static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg) { u8 data = 0; icm20608_read_regs(dev, reg, &data, 1); return data; } static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value) { u8 buf = value; icm20608_write_regs(dev, reg, &buf, 1); } void icm20608_readdata(struct icm20608_dev *dev) { unsigned char data[14]; icm20608_read_regs(dev, ICM20_ACCEL_XOUT_H, data, 14); dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]); dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]); dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]); dev->temp_adc = (signed short)((data[6] << 8) | data[7]); dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]); dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]); dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]); } static int icm20608_open(struct inode *inode, struct file *filp) { filp->private_data = &icm20608dev; return 0; } static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off) { signed int data[7]; long err = 0; struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data; icm20608_readdata(dev); data[0] = dev->gyro_x_adc; data[1] = dev->gyro_y_adc; data[2] = dev->gyro_z_adc; data[3] = dev->accel_x_adc; data[4] = dev->accel_y_adc; data[5] = dev->accel_z_adc; data[6] = dev->temp_adc; err = copy_to_user(buf, data, sizeof(data)); return 0; } static int icm20608_release(struct inode *inode, struct file *filp) { return 0; } static const struct file_operations icm20608_ops = { .owner = THIS_MODULE, .open = icm20608_open, .read = icm20608_read, .release = icm20608_release, }; void icm20608_reginit(void) { u8 value = 0; icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80); mdelay(50); icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01); mdelay(50); value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I); printk("ICM20608 ID = %#X\r\n", value); icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00); } static int icm20608_probe(struct spi_device *spi) { int ret = 0; if (icm20608dev.major) { icm20608dev.devid = MKDEV(icm20608dev.major, 0); register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME); } else { alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME); icm20608dev.major = MAJOR(icm20608dev.devid); } cdev_init(&icm20608dev.cdev, &icm20608_ops); cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT); icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME); if (IS_ERR(icm20608dev.class)) { return PTR_ERR(icm20608dev.class); } icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME); if (IS_ERR(icm20608dev.device)) { return PTR_ERR(icm20608dev.device); } icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000"); if(icm20608dev.nd == NULL) { printk("ecspi3 node not find!\r\n"); return -EINVAL; } icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0); if(icm20608dev.cs_gpio < 0) { printk("can't get cs-gpio"); return -EINVAL; } ret = gpio_direction_output(icm20608dev.cs_gpio, 1); if(ret < 0) { printk("can't set gpio!\r\n"); } spi->mode = SPI_MODE_0; spi_setup(spi); icm20608dev.private_data = spi; icm20608_reginit(); return 0; } static int icm20608_remove(struct spi_device *spi) { cdev_del(&icm20608dev.cdev); unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT); device_destroy(icm20608dev.class, icm20608dev.devid); class_destroy(icm20608dev.class); return 0; } static const struct spi_device_id icm20608_id[] = { {"alientek,icm20608", 0}, {} }; static const struct of_device_id icm20608_of_match[] = { { .compatible = "alientek,icm20608" }, { } }; static struct spi_driver icm20608_driver = { .probe = icm20608_probe, .remove = icm20608_remove, .driver = { .owner = THIS_MODULE, .name = "icm20608", .of_match_table = icm20608_of_match, }, .id_table = icm20608_id, }; static int __init icm20608_init(void) { return spi_register_driver(&icm20608_driver); } static void __exit icm20608_exit(void) { spi_unregister_driver(&icm20608_driver); } module_init(icm20608_init); module_exit(icm20608_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR(yikoulinux");
关于Linux下如何进行SPI驱动问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注编程网操作系统频道了解更多相关知识。
--结束END--
本文标题: Linux下如何进行SPI驱动
本文链接: https://lsjlt.com/news/280671.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-03-01
2024-03-01
2024-03-01
2024-03-01
2024-03-01
2024-02-29
2024-02-29
2024-02-29
2024-02-29
2024-02-29
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0