正点原子I.MX6U驱动例程速通(五)
前情说明:本文为正点原子I.MX6U 驱动开发篇代码总结,摘录了部分例程中主要的代码框架,完整代码及教程请参考正点原子官方。
本文对应《正点原子I.MX6U嵌入式Linux驱动开发指南》中的以下章节:
21_iic22_spi
为减少篇幅,部分框架重复内容使用...表示。
注意:iic和spi是重要且常用的两个总线,建议查看详细开发文档进行学习,本文只涉及核心代码框架。
21_iic
I2C总线驱动重点是I2C适配器驱动,这里要用到两个重要的数据结构:i2c_adapter和i2c_algorithm。Linux内核将Soc的I2C适配器抽象成i2c_adapter,i2c_algorithm就是I2C适配器与设备进行通信的方法。I2C设备驱动重点关注两个数据结构:i2c_client和i2c_driver,i2c_client就是描述设备信息的,i2c_driver描述驱动内容,类似于platform_driver。
1.设备结构体
struct ap3216c_dev { dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */struct device_node *nd; /* 设备节点 */ int major; /* 主设备号 */ void *private_data; /* 私有数据 */ unsigned short ir, als, ps; /* 三个光传感器数据 */};
2.读取多个寄存器数据
i2c读写API:i2c_transfer
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len){ int ret;struct i2c_msg msg[2];struct i2c_client *client = (struct i2c_client *)dev->private_data; /* msg[0]为发送要读取的首地址 */ msg[0].addr = client->addr; /* ap3216c地址 */ msg[0].flags = 0; /* 标记为发送数据 */ msg[0].buf = ® /* 读取的首地址 */ msg[0].len = 1; /* reg长度*/ /* msg[1]读取数据 */ msg[1].addr = client->addr; /* ap3216c地址 */ msg[1].flags = I2C_M_RD; /* 标记为读取数据*/ msg[1].buf = val; /* 读取数据缓冲区 */ msg[1].len = len; /* 要读取的数据长度*/ ret = i2c_transfer(client->adapter, msg, 2); if(ret == 2) { ret = 0; } else { printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len); ret = -EREMOTEIO; } return ret;}
3.多个寄存器写入数据
i2c读写API:i2c_transfer
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len){ u8 b[256];struct i2c_msg msg;struct i2c_client *client = (struct i2c_client *)dev->private_data; b[0] = reg; /* 寄存器首地址 */ memcpy(&b[1],buf,len); /* 将要写入的数据拷贝到数组b里面 */ msg.addr = client->addr; /* ap3216c地址 */ msg.flags = 0; /* 标记为写数据 */ msg.buf = b; /* 要写入的数据缓冲区 */ msg.len = len + 1; /* 要写入的数据长度 */ return i2c_transfer(client->adapter, &msg, 1);}
4.传统匹配方式ID列表
static conststruct i2c_device_id ap3216c_id[] = { {"alientek,ap3216c", 0}, {}};
5.设备树匹配列表
static conststruct of_device_id ap3216c_of_match[] = { { .compatible = "alientek,ap3216c" }, { /* Sentinel */ }};
6.i2c驱动结构体
staticstruct i2c_driver ap3216c_driver = { .probe = ap3216c_probe, .remove = ap3216c_remove, .driver = { .owner = THIS_MODULE, .name = "ap3216c", .of_match_table = ap3216c_of_match, }, .id_table = ap3216c_id,};
7.probe函数
i2c驱动的probe函数,当驱动与设备匹配以后此函数就会执行
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id){ /* 1、构建设备号 */ if (ap3216cdev.major) { ap3216cdev.devid = MKDEV(ap3216cdev.major, 0); register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME); } else { alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME); ap3216cdev.major = MAJOR(ap3216cdev.devid); } /* 2、注册设备 */ cdev_init(&ap3216cdev.cdev, &ap3216c_ops); cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT); /* 3、创建类 */ ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME); if (IS_ERR(ap3216cdev.class)) { return PTR_ERR(ap3216cdev.class); } /* 4、创建设备 */ ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME); if (IS_ERR(ap3216cdev.device)) { return PTR_ERR(ap3216cdev.device); } ap3216cdev.private_data = client; return 0;}
8.remove函数
i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
static int ap3216c_remove(struct i2c_client *client){ /* 删除设备 */ cdev_del(&ap3216cdev.cdev); unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT); /* 注销掉类和设备 */ device_destroy(ap3216cdev.class, ap3216cdev.devid); class_destroy(ap3216cdev.class); return 0;}
9.入口函数
static int __init ap3216c_init(void){ int ret = 0; ret = i2c_add_driver(&ap3216c_driver); return ret;}
10.出口函数
static void __exit ap3216c_exit(void){ i2c_del_driver(&ap3216c_driver);}
22_spi
Linux内核使用spi_master表示SPI主机驱动,SPI主机驱动的核心就是申请spi_master,然后初始化spi_master,最后向 Linux 内核注册spi_master。Linux内核使用spi_driver结构体来表示SPI设备驱动,SPI设备和驱动的匹配过程是由SPI总线来完成的,这点和platform、I2C等驱动一样,SPI总线为spi_bus_type。
1.设备结构体
struct icm20608_dev { dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */struct device_node *nd; /* 设备节点 */ int major; /* 主设备号 */ void *private_data; /* 私有数据 */ 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; /* 温度原始值 */};
2.读取多个寄存器数据
调用spi_message_init(&m);初始化spi_message,随后调用spi_message_add_tail(t, &m);将spi_transfer添加到spi_message队列,最后调用spi_sync(spi, &m);实现同步传输
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len){ int ret = -1; unsigned char txdata[1]; unsigned char * rxdata;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); /* 申请内存 */ if(!t) { return -ENOMEM; } rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL); /* 申请内存 */ if(!rxdata) { goto out1; } /* 一共发送len+1个字节的数据,第一个字节为 寄存器首地址,一共要读取len个字节长度的数据,*/ txdata[0] = reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */ t->tx_buf = txdata; /* 要发送的数据 */ t->rx_buf = rxdata; /* 要读取的数据 */ t->len = len+1; /* t->len=发送的长度+读取的长度 */ spi_message_init(&m); /* 初始化spi_message */ spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ ret = spi_sync(spi, &m); /* 同步发送 */ if(ret) { goto out2; } memcpy(buf , rxdata+1, len); /* 只需要读取的数据 */out2: kfree(rxdata); /* 释放内存 */out1: kfree(t); /* 释放内存 */ return ret;}
3.多个寄存器写入数据
调用spi_message_init(&m);初始化spi_message,随后调用spi_message_add_tail(t, &m);将spi_transfer添加到spi_message队列,最后调用spi_sync(spi, &m);实现同步传输
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len){ int ret = -1; unsigned char *txdata;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); /* 申请内存 */ if(!t) { return -ENOMEM; } txdata = kzalloc(sizeof(char)+len, GFP_KERNEL); if(!txdata) { goto out1; } /* 一共发送len+1个字节的数据,第一个字节为 寄存器首地址,len为要写入的寄存器的集合,*/ *txdata = reg & ~0x80; /* 写数据的时候首寄存器地址bit8要清零 */ memcpy(txdata+1, buf, len); /* 把len个寄存器拷贝到txdata里,等待发送 */ t->tx_buf = txdata; /* 要发送的数据 */ t->len = len+1; /* t->len=发送的长度+读取的长度 */ spi_message_init(&m); /* 初始化spi_message */ spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */ ret = spi_sync(spi, &m); /* 同步发送 */ if(ret) { goto out2; }out2: kfree(txdata); /* 释放内存 */out1: kfree(t); /* 释放内存 */ return ret;}
4.probe函数
spi驱动的probe函数,当驱动与设备匹配以后此函数就会执行
static int icm20608_probe(struct spi_device *spi){ /* 1、构建设备号 */ 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); } /* 2、注册设备 */ cdev_init(&icm20608dev.cdev, &icm20608_ops); cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT); /* 3、创建类 */ icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME); if (IS_ERR(icm20608dev.class)) { return PTR_ERR(icm20608dev.class); } /* 4、创建设备 */ icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME); if (IS_ERR(icm20608dev.device)) { return PTR_ERR(icm20608dev.device); } /*初始化spi_device */ spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/ spi_setup(spi); icm20608dev.private_data = spi; /* 设置私有数据 */ /* 初始化ICM20608内部寄存器 */ icm20608_reginit(); return 0;}
5.remove函数
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;}
6.传统匹配方式ID列表
static conststruct spi_device_id icm20608_id[] = { {"alientek,icm20608", 0}, {}};
7.设备树匹配列表
static conststruct of_device_id icm20608_of_match[] = { { .compatible = "alientek,icm20608" }, { /* Sentinel */ }};
8.SPI驱动结构体
staticstruct 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,};
9.入口函数
static int __init icm20608_init(void){ return spi_register_driver(&icm20608_driver);}
10.出口函数
static void __exit icm20608_exit(void){ spi_unregister_driver(&icm20608_driver);}