1520 字
8 分钟
Linux SPI驱动通用框架和编写细节
设备树
- 追加节点:
需要确保UART2节点为
disabled,因IO占用
/* Note: to enable ecspi3, uart2 node in imx6ul-14x14-evk.dtsi must be disabled */&ecspi3 { fsl,spi-num-chipselects = <1>; cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>; pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi3>; status = "okay";
spidev: icm20608@0 { compatible = "alientek,icm20608"; spi-max-frequency = <8000000>; reg = <0>; };};驱动
- 创建of_match_table,匹配设备树
static const struct of_device_id icm20608_of_match[] = { { .compatible = "alientek,icm20608" }, { /* Sential */ }};- 创建
spi_driver
static struct spi_driver icm20608_driver = { .driver = { .owner = THIS_MODULE, .name = "icm20608", .of_match_table = icm20608_of_match, }, .probe = icm20608_probe, .remove = icm20608_remove,};- 填充
probe和remove:
static int icm20608_probe(struct spi_device *spi){ printk("SPI driver and DTS match OK\r\n");
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); } printk("device reg ok, major = %d\r\n", icm20608dev.major);
icm20608dev.cdev.owner = THIS_MODULE; cdev_init(&icm20608dev.cdev, &icm20608_fops); cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
icm20608dev.class = class_create(ICM20608_NAME);
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
/* SPI初始化 */ spi->mode = SPI_MODE_0; /* MODE0:CPOL = 0, CPHA = 0 */ spi_setup(spi); icm20608dev.private_data = spi; /* 保存spi_driver至结构体 */
icm20608_reginit(); return 0;}static void 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);}- 完善接收和发送函数
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len){ int ret = -1; struct spi_device *spi = (struct spi_device *)dev->private_data;
/* tx buffer */ unsigned char *txdata = kmalloc(1, GFP_KERNEL); if (!txdata) return -ENOMEM; txdata[0] = reg | 0x80;
/* rx buffer */ /* 待接收数据长度为len,但SPI为全双工,第一次向从机发送准备读取消息时从机返回空数据,所以需要加一 */ unsigned char *rxdata = kmalloc(len + 1, GFP_KERNEL);
struct spi_transfer t = { .tx_buf = txdata, .rx_buf = rxdata, .len = len + 1, };
struct spi_message m; spi_message_init(&m); spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m); if (ret){ return ret; }
memcpy(buf, &rxdata[1], len);
return 0;}
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len){ int ret = -1; struct spi_device *spi = (struct spi_device *)dev->private_data;
unsigned char *txdata = kmalloc(len + 1, GFP_KERNEL); txdata[0] = reg & 0x7F; memcpy(&txdata[1], buf, len);
struct spi_transfer t = { .tx_buf = txdata, .len = len + 1, };
struct spi_message m;
spi_message_init(&m); spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m); if (ret){ return ret; } return 0;}注意:读取寄存器消息时不能将两条消息分开发送,因为ICM20608要求在读取数据时连续发送数据,即发送读取命令后立即接收从机返回的消息,CS片选信号在这期间始终拉低。如果分开两条信息发送,比如:
struct spi_transfer t[2] = { { .tx_buf = txdata, .len = 1, }, { .rx_buf = rxdata, .len = len, }, };
...
spi_message_add_tail(&t[0], &m); spi_message_add_tail(&t[1], &m);这么做会导致SPI在发送t[0]和t[1]时在两条消息中间短暂拉高CS,导致SPI丢失数据。
完整驱动
#include <linux/types.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/libata.h> /* 新版kernel不再支持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; /* 陀螺仪X轴原始值 */ signed int gyro_y_adc; /* 陀螺仪Y轴原始值 */ signed int gyro_z_adc; /* 陀螺仪Z轴原始值 */ signed int accel_x_adc; /* 加速度计X轴原始值 */ signed int accel_y_adc; /* 加速度计Y轴原始值 */ signed int accel_z_adc; /* 加速度计Z轴原始值 */ signed int temp_adc; /* 温度原始值 */};
struct icm20608_dev icm20608dev;
static int icm20608_open(struct inode *inode, struct file *filp);static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt);static int icm20608_release(struct inode *inode, struct file *filp);static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len);static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len);static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg);static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value);void icm20608_readdata(struct icm20608_dev *dev);void icm20608_reginit(void);static int icm20608_probe(struct spi_device *spi);static void icm20608_remove(struct spi_device *spi);
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 *offt){ signed int data[7]; long err = 0; struct icm20608_dev *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 int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len){ int ret = -1; struct spi_device *spi = (struct spi_device *)dev->private_data;
/* tx buffer */ unsigned char *txdata = kmalloc(1, GFP_KERNEL); if (!txdata) return -ENOMEM; txdata[0] = reg | 0x80;
/* rx buffer */ /* 待接收数据长度为len,但SPI为全双工,第一次向从机发送准备读取消息时从机返回空数据,所以需要加一 */ unsigned char *rxdata = kmalloc(len + 1, GFP_KERNEL);
struct spi_transfer t = { .tx_buf = txdata, .rx_buf = rxdata, .len = len + 1, };
struct spi_message m; spi_message_init(&m); spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m); if (ret){ return ret; }
memcpy(buf, &rxdata[1], len);
return 0;}
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len){ int ret = -1; struct spi_device *spi = (struct spi_device *)dev->private_data;
unsigned char *txdata = kmalloc(len + 1, GFP_KERNEL); txdata[0] = reg & 0x7F; memcpy(&txdata[1], buf, len);
struct spi_transfer t = { .tx_buf = txdata, .len = len + 1, };
struct spi_message m;
spi_message_init(&m); spi_message_add_tail(&t, &m);
ret = spi_sync(spi, &m); if (ret){ return ret; } return 0;}
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] = { 0 }; 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]);}
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); /* 陀螺仪±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 */}
static const struct of_device_id icm20608_of_match[] = { { .compatible = "alientek,icm20608" }, { /* Sential */ }};
static struct file_operations icm20608_fops = { .owner = THIS_MODULE, .open = icm20608_open, .read = icm20608_read, .release = icm20608_release,};
static int icm20608_probe(struct spi_device *spi){ printk("SPI driver and DTS match OK\r\n");
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); } printk("device reg ok, major = %d\r\n", icm20608dev.major);
icm20608dev.cdev.owner = THIS_MODULE; cdev_init(&icm20608dev.cdev, &icm20608_fops); cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
icm20608dev.class = class_create(ICM20608_NAME);
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
spi->mode = SPI_MODE_0; /* MODE0:CPOL = 0, CPHA = 0 */ spi_setup(spi); icm20608dev.private_data = spi;
icm20608_reginit(); return 0;}
static void 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);}
static struct spi_driver icm20608_driver = { .driver = { .owner = THIS_MODULE, .name = "icm20608", .of_match_table = icm20608_of_match, }, .probe = icm20608_probe, .remove = icm20608_remove,};
module_spi_driver(icm20608_driver);
MODULE_LICENSE("GPL");MODULE_AUTHOR("aki"); Linux SPI驱动通用框架和编写细节
https://arkmostima.pages.dev/posts/linuxspi/