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);
}