Linux 驱动开发(三)SPI
2021/11/21 7:10:45
本文主要是介绍Linux 驱动开发(三)SPI,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
spi 驱动框架和 iic 驱动框架类似,都分为主机控制器驱动和设备驱动。
1 SPI 主机驱动
SOC 的spi外设驱动是半导体产商写好的,SPI 主机驱动器采用了 platfom 驱动框架。我们可以从内核中文件中找到 spi_imx_driver 结构体:
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, };
当控制器的设备和驱动匹配以后,spi_imx_probe 函数就会执行,spi_imx_probe 函数会从设备树中读取相应的节点属性值,申请并初始化 spi_master,最后调用 spi_bitbang_start 函数(spi_bitbang_start 会调用 spi_register_master 函数)向 Linux 内核注册
spi_master。spi 控制器驱动的核心就是 spi_master 结构体。
struct spi_master { struct device dev; struct list_head list; s16 bus_num; u16 num_chipselect, dma_alignment, mode_bits; u32 bits_per_word_mask; #define SPI_BPW_MASK(bits) BIT((bits) - 1) #define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1)) #define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1)) u32 min_speed_hz, max_speed_hz; u16 flags; #define SPI_MASTER_HALF_DUPLEX BIT(0) /* can't do full duplex */ #define SPI_MASTER_NO_RX BIT(1) /* can't do buffer read */ #define SPI_MASTER_NO_TX BIT(2) /* can't do buffer write */ #define SPI_MASTER_MUST_RX BIT(3) /* requires rx */ #define SPI_MASTER_MUST_TX BIT(4) /* requires tx */ 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 (*can_dma)(struct spi_master *master, struct spi_device *spi,struct spi_transfer *xfer); 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 idling,busy,running,rt,auto_runtime_pm, cur_msg_prepared,cur_msg_mapped; struct completion xfer_completion; size_t max_dma_len; 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 (*prepare_message)(struct spi_master *master,struct spi_message *message); int (*unprepare_message)(struct spi_master *master,struct spi_message *message); void (*set_cs)(struct spi_device *spi, bool enable); int (*transfer_one)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *transfer); void (*handle_err)(struct spi_master *master, struct spi_message *message); int *cs_gpios; struct dma_chan *dma_tx; struct dma_chan *dma_rx; void *dummy_rx; void *dummy_tx; };
这里的内容有很多,我们重点关注几个函数
int (*transfer)(struct spi_device *spi, struct spi_message *mesg);
transfer 函数,和 i2c_algorithm 中的 master_xfer 函数一样,控制器数据传输函数。
int (*transfer_one_message)(struct spi_master *master,struct spi_message *mesg);
transfer_one_message 函数,也用于 SPI 数据发送,用于发送一个 spi_message,SPI 的数据会打包成 spi_message,然后以队列方式发送出去。
也就是 SPI 主机端最终会通过 transfer 函数与 SPI 设备进行通信,因此对于 SPI 主机控制器的驱动编写者而言 transfer 函数是需要实现的,因为不同的 SOC 其 SPI 控制器不同,寄存器都不一样。
SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。
2 SPI 设备驱动
spi设备驱动的核心内容就是 spi_driver 结构体,它和 i2c_driver、 platform_driver 基本一样,内容包括 probe、remove函数等,当 spi 设备和驱动匹配成功以后 probe 函数就会执行。
spi_driver 初始化完成以后需要向 Linux 内核注册, spi_driver 注册函数为
spi_register_driver,注销 SPI 设备驱动以后也需要注销掉前面注册的 spi_driver,使用 spi_unregister_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; };
一个基础的 spi_driver 驱动框架
/* probe function */ static int xxx_probe(struct spi_driver *spi) { /* specific content */ return 0; } /* remove function */ static void xxx_remove(struct spi_driver *spi) { /* specific content */ return 0; } /* traditional match table */ static const struct spi_driver_id xxx_id[] = { {"xxx",0}, {} }; /* device_tree match table */ static const struct spi_driver_id xxx_of_match[] = { {.compatible = "xxx"}, {} }; /* spi_driver structure */ static struct spi_driver xxx_driver = { .probe = xx_probe, .remove = xxx_remove, .driver = { .owner = THIS_MODULE, .name = "xxx", .of_match_table = xxx_of_match, }, .id_table = xxx_id, }; /* module entrance */ static int __init xxx_init(void) { return spi_register_driver(&xxx_driver); } /* module exit */ static void __exit xxx_exit(void) { spi_unregister_driver(&xxx_driver); } module_init(xx_init); module_exit(xxx_exit);
3 spi 设备和驱动匹配过程
SPI 设备和驱动的匹配过程是由 SPI 总线来完成的,SPI 总线为 spi_bus_type
struct bus_type spi_bus_type = { .name = "spi", .dev_groups = spi_dev_groups, .match = spi_match_device, .uevent = spi_uevent, };
可以看出, SPI 设备和驱动的匹配函数为 spi_match_device,函数内容如下:
static int spi_match_device(struct device *dev, struct device_driver *drv) { const struct spi_device *spi = to_spi_device(dev); const struct spi_driver *sdrv = to_spi_driver(drv); /* Attempt an OF style match */ if (of_driver_match_device(dev, drv)) return 1; /* Then try ACPI */ if (acpi_driver_match_device(dev, drv)) return 1; if (sdrv->id_table) return !!spi_match_id(sdrv->id_table, spi); return strcmp(spi->modalias, drv->name) == 0; }
of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 SPI 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 SPI 设备和驱动匹配。spi_match_id 函数用于传统的、无设备树的 SPI 设备和驱动匹配过程。比较 SPI设备名字和 spi_device_id 的 name 字段是否相等,相等的话就说明 SPI 设备和驱动匹配。
4 spi 数据收发流程
spi 的收发关键是两个结构体,spi_transfer 和 spi_message
struct spi_transfer { const void *tx_buf; void *rx_buf; unsigned len; dma_addr_t tx_dma; dma_addr_t rx_dma; struct sg_table tx_sg; struct sg_table rx_sg; unsigned cs_change:1; unsigned tx_nbits:3; unsigned rx_nbits:3; #define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */ #define SPI_NBITS_DUAL 0x02 /* 2bits transfer */ #define SPI_NBITS_QUAD 0x04 /* 4bits transfer */ u8 bits_per_word; u16 delay_usecs; u32 speed_hz; struct list_head transfer_list; };
tx_buf 保存着要发送的数据, rx_buf 用于保存接收到的数据, len 是要进行传输的数据长度
truct spi_message { struct list_head transfers; struct spi_device *spi; unsigned is_dma_mapped:1; void (*complete)(void *context); void *context; unsigned frame_length; unsigned actual_length; int status; struct list_head queue; void *state; };
在使用spi_message之前需要对其进行初始化, spi_message 初始化函数为spi_message_init
spi_message 初始化完成以后需要将 spi_transfer 添加到 spi_message 队列中,要用到 spi_message_add_tail
函数
spi_message 准备好以后既可以进行数据传输了,数据传输分为同步传输和异步传输,同步传输会阻塞的等待 SPI 数据传输完成,同步传输函数为 spi_sync
异步传输不会阻塞的等到 SPI 数据传输完成,异步传输需要设置 spi_message 中的 complete 成员变量, complete 是一个回调函数,当 SPI 异步传输完成以后此函数就会被调用。 SPI 异步传输函数为 ```spi_async``
5 以 icm20608 为例构建一个 Linux 下 SPI 驱动框架
- 修改设备树
在 iomuxc 节点中添加一个新的子节点来描述 ICM20608 所使用的 SPI 引脚,子节点名字为 pinctrl_ecspi3,内容如下:
pinctrl_ecspi3: icm20608grp { fsl,pins = < MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0 /* CS */ MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1 /* SCLK */ MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1 /* MISO */ MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1 /* MOSI */ >; };
接着在 ecspi3 节点追加 icm20608 子节点
&ecspi3 { fsl,spi-num-chipselects = <1>; cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi3>; status = "okay"; spidev0: icm20608@0 { /* @0中的0表示icm20608连接在ECSPI3的第0个通道上 */ compatible = "alientek,icm20608"; spi-max-frequency = <8000000>; reg = <0>; }; };
接着开始编写驱动程序
- 创建一个 icm20608 设备结构体
/* icm20608 device struct*/ struct icm20608_dev { int major; int minor; dev_t devid; struct cdev cdev; struct device *device; struct class *class; void *private_data; struct device_node *nd; int cs_gpio; /* gpio num of cs */ signed int gyro_x_adc; /* initial value of gyro_x */ signed int gyro_y_adc; /* initial value of gyro_y */ signed int gyro_z_adc; /* initial value of gyro_z */ signed int accel_x_adc; /* initial value of accel_x */ signed int accel_y_adc; /* initial value of accel_y */ signed int accel_z_adc; /* initial value of accel_z*/ signed int temp_adc; /* initial tempture value */ }; struct icm20608_dev icm20608dev;
- icm20608 的 spi_driver 注册与注销
/* no device tree match*/ struct spi_device_id icm20608_id [] = { {"alientek,icm20608", 0}, {} }; /* device-tree match*/ static const struct of_device_id icm20608_of_match [] = { {.compatible = "alientek,icm20608"}, {} }; /* spi driver*/ static struct spi_driver icm20608_driver = { .probe = icm20608_probe, .remove = icm20608_remove, .driver = { .name = "icm20608", .owner = THIS_MODULE, .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("jimmy");
- 由于匹配成功后开始执行 probe 函数。接着编写 probe 和 remove 函数
/* probe function */ static int icm20608_probe(struct spi_device *spi) { int ret =0; /* create character device agriculture */ /* device ID */ icm20608dev.major = 0; if(icm20608dev.major){ /* given major*/ icm20608dev.devid = MKDEV(icm20608dev.major, 0); register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME); } else { /* without major*/ ret = alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME); icm20608dev.major = MAJOR(icm20608dev.devid); icm20608dev.minor = MINOR(icm20608dev.devid); } if(ret < 0){ printk("icm20608 chrdev_region error!!!\r\n"); goto fail_devid; } printk("major=%d, minor=%d\r\n",icm20608dev.major, icm20608dev.minor); /* create cdev */ icm20608dev.cdev.owner = THIS_MODULE; cdev_init(&icm20608dev.cdev, &icm20608_fops); ret = cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT); if(ret < 0){ printk("icm20608 cdev_add error!!\r\n"); goto fail_cdev; } /* create device node */ icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME); if(IS_ERR(icm20608dev.class)){ ret = PTR_ERR(icm20608dev.class); printk("icm20608 class_create error!!\r\n"); goto fail_class; } icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME); if(IS_ERR(icm20608dev.device)){ ret = PTR_ERR(icm20608dev.device); printk("icm20608 device_create error!!\r\n"); goto fail_device; } /* require shipselect pin */ icm20608dev.nd = of_get_parent(spi->dev.of_node); icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs_gpio", 0); if(icm20608dev.cs_gpio < 0){ printk("cannot get cs_gpio!\r\n"); goto fail_gpio; } /* request gpio */ ret = gpio_request(icm20608dev.cs_gpio, "cs"); if(ret < 0){ printk("gpio requset fail!\r\n"); goto fail_gpio; } /* set the output, high level, invalid */ ret = gpio_direction_output(icm20608dev.cs_gpio, 1); if(ret < 0){ printk("unable to set output!\r\n"); goto fail_set_output; } /* initialize spi_device */ spi->mode = SPI_MODE_0; /* MODE0, CPOL=0, CPHA=0 */ spi_setup(spi); /* set private data */ icm20608dev.private_data = spi; /* initialize icm20608 */ icm20608_initialize(&icm20608dev); return 0; fail_set_output: gpio_free(icm20608dev.cs_gpio); fail_gpio: device_destroy(icm20608dev.class, icm20608dev.devid); fail_device: class_destroy(icm20608dev.class); fail_class: cdev_del(&icm20608dev.cdev); fail_cdev: unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT); fail_devid: return ret; } /* remove function */ 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); gpio_free(icm20608dev.cs_gpio); return 0; }
- 字符设备操作集
static int icm20608_open(struct inode *inode, struct file *filp) { filp->private_data = &icm20608dev; return 0; } static int icm20608_release(struct inode *inode, struct file *filp){ return 0; } static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offset) { 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)); if(err < 0){ printk("copy to user error!!\r\n"); return err; } return 0; } /* device operations struct */ static struct file_operations icm20608_fops = { .owner = THIS_MODULE, .read = icm20608_read, .open = icm20608_open, .release = icm20608_release, };
- icm20608 初始化以及读写寄存器函数编写
/* icm20608 initial function */ void icm20608_initialize(struct icm20608_dev *dev) { u8 value = 0; icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x80); /* reset, sleep mode */ mdelay(50); icm20608_write_onereg(dev, ICM20_PWR_MGMT_1, 0x01); /* close sleep mode, select clock automatically */ mdelay(50); value = icm20608_read_onereg(dev, ICM20_WHO_AM_I); printk("ICM20608 ID = %#x\r\n", value); value = icm20608_read_onereg(dev, ICM20_PWR_MGMT_1); printk("ICM20_PWR_MGMT_1 = %#x\r\n", value); icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */ icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */ icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */ icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */ icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz */ 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); /* 关闭FIFO */ }
/* read data of icm20608 */ 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]); }
/* icm20608 read a register */ static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg) { u8 data = 0; icm20608_read_regs(dev, reg, &data, 1); return data; } /* icm20608 write a register */ static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value) { u8 buf = value; icm20608_write_regs(dev, reg, &buf, 1); } /* spi write register function */ static int icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, int len) { int ret = 0; unsigned char txdata[len]; struct spi_message msg; struct spi_transfer *t; struct spi_device *spi = (struct spi_device *)dev->private_data; /* shipselect set low, select icm20608 */ gpio_set_value(dev->cs_gpio, 0); /* create spi_transfer*/ t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* step 1: send address of register */ txdata[0] = reg & ~ 0x80; // when write data, bit 7 need to be cleared t->tx_buf = txdata; // data needs to be sent t->len = 1; // len is one byte spi_message_init(&msg); // initialize spi_message spi_message_add_tail(t, &msg); // add spi_trnasfer to spi_message ret = spi_sync(spi, &msg); // set send method as sync /* step2: send data needs to be written */ t->tx_buf = buf; // data needs to be written t->len = len; spi_message_init(&msg); spi_message_add_tail(t, &msg); ret = spi_sync(spi, &msg); kfree(t); /* shipselect set high */ gpio_set_value(icm20608dev.cs_gpio, 1); return ret; } /* spi read register function */ static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len) { int ret = 0; unsigned char txdata[len]; struct spi_message msg; struct spi_transfer *t; struct spi_device *spi = (struct spi_device *)dev->private_data; /* shipselect set low, select icm20608 */ gpio_set_value(icm20608dev.cs_gpio, 0); /* create spi_transfer*/ t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* step 1: send address of register */ txdata[0] = reg | 0x80; // if you use icm20608 by spi, set the highest bit of reg's add t->tx_buf = txdata; // data needs to be sent t->len = 1; // len is one byte spi_message_init(&msg); // initialize spi_message spi_message_add_tail(t, &msg); // add spi_trnasfer to spi_message ret = spi_sync(spi, &msg); // set send method as sync /* step2: read data */ txdata[0] = 0xff; // random number, no meaning t->tx_buf = txdata; t->len = len; spi_message_init(&msg); spi_message_add_tail(t, &msg); ret = spi_sync(spi, &msg); kfree(t); /* shipselect set high */ gpio_set_value(icm20608dev.cs_gpio, 1); return ret; }
我们可以看到读写寄存器的操作是很繁琐的,有没有高级的API函数呢显然是有得,上面的两个函数我们可以使用内核中的其他函数替换。
/* spi read register function using linux kernel function */ static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len) { u8 data = 0; struct spi_device *spi = (struct spi_device *)dev->private_data; gpio_set_value(dev->cs_gpio, 0); data = reg | 0x80; spi_write(spi, &data, 1); /* send the address of reg you want to read */ spi_read(spi, buf, len); /* read data */ gpio_set_value(dev->cs_gpio, 1); return data; } /* spi write register function using linux kernel function */ static void icm20608_write_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len) { u8 data = 0; struct spi_device *spi = (struct spi_device *)dev->private_data; gpio_set_value(dev->cs_gpio, 0); data = reg & ~ 0x80; spi_write(spi, &data, 1); /* send the address of reg you want to write*/ spi_read(spi, buf, len); /* data you want to write */ gpio_set_value(dev->cs_gpio, 1); }
这篇关于Linux 驱动开发(三)SPI的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-12如何创建可引导的 ESXi USB 安装介质 (macOS, Linux, Windows)
- 2024-11-08linux的 vi编辑器中搜索关键字有哪些常用的命令和技巧?-icode9专业技术文章分享
- 2024-11-08在 Linux 的 vi 或 vim 编辑器中什么命令可以直接跳到文件的结尾?-icode9专业技术文章分享
- 2024-10-22原生鸿蒙操作系统HarmonyOS NEXT(HarmonyOS 5)正式发布
- 2024-10-18操作系统入门教程:新手必看的基本操作指南
- 2024-10-18初学者必看:操作系统入门全攻略
- 2024-10-17操作系统入门教程:轻松掌握操作系统基础知识
- 2024-09-11Linux部署Scrapy学习:入门级指南
- 2024-09-11Linux部署Scrapy:入门级指南
- 2024-08-21【Linux】分区向左扩容的方法