根据Linux驱动的分离与分层的思想,Linux内核将 IIC 驱动分成 IIC 总线驱动和 IIC 设备驱动
IIC 总线驱动就是 SOC 的 IIC 控制器驱动,也叫做 IIC 适配器驱动
IIC 设备驱动就是针对具体的 IIC 设备而编写的驱动
1 IIC 总线驱动
I2C 总线驱动重点是 I2C 适配器(也就是 SOC 的 I2C 接口控制器)驱动,这里要用到两个重要的数据结构: i2c_adapter 和 i2c_algorithm, Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter,结构体定义如下:
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};
i2c_algorithm 类型的指针变量 algo,对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。 i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。i2c_algorithm 结构体定义如下:
struct i2c_algorithm {
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
};
master_xfer 就是 IIC 适配器的传输函数,可以通过此函数来完成与 IIC 设备之间的通信。smbus_xfer 就是 SMBUS 总线的传输函数。
I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,然后设置 i2c_algorithm 中的 master_xfer 函数。完成以后通过 i2c_add_numbered_adapter或 i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapte
2 IIC 设备驱动
I2C 设备驱动重点关注两个数据结构: i2c_client 和 i2c_driver,根据总线、设备和驱动模型,I2C 总线上一小节已经讲了。还剩下设备和驱动, i2c_client 就是描述设备信息的, i2c_driver 描述驱动内容。
不需要我们自己创建i2c_client,我们一般在设备树里面添加具体的 I2C 芯片,系统在解析设备树的时候就会知道有这个I2C设备,然后会创建对应的i2c_client, i2c_client 结构体定义如下:
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
i2c 设备驱动框架,i2c_driver 初始化与注册,需要 I2C设备驱动编写人员编写的,IIC 驱动程序就是初始化 i2c_driver,然后向系统注册。注册使用 i2c_register_driver、i2c_add_driver,如果注销i2c_driver 使用 i2c_del_driver。 i2c_driver 结构体定义如下:
struct i2c_driver {
unsigned int class;
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*probe)(struct i2c_client *, const struct i2c_device_id *);------(1)
int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *);
void (*alert)(struct i2c_client *, unsigned int data);
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver; -----------------(2)
const struct i2c_device_id *id_table;
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
当 I2C 设备和驱动匹配成功以后(1)处的 probe 函数就会执行,
(2)处的device_driver 驱动结构体,如果使用设备树的话,需要设置 device_driver 的 of_match_table 成员变量,也就是驱动的兼容(compatible)属性。
i2c_driver 的注册示例代码如下:
/* i2c 驱动的 probe 函数 */
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
/* 函数具体程序 */
return 0;
}
/* i2c 驱动的 remove 函数 */
static int xxx_remove(struct i2c_client *client)
{
/* 函数具体程序 */
return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
{"xxx", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx" },
{ /* Sentinel */ }
};
/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match,
},
.id_table = xxx_id,
};
/* 驱动入口函数 */
static int __init xxx_init(void)
{
int ret = 0;
ret = i2c_add_driver(&xxx_driver);
return ret;
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
i2c_del_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
3 设备与驱动匹配的过程
设备和驱动的匹配过程也是由 I2C 总线完成的, I2C 总线的数据结构为 i2c_bus_type,定义如下:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
其中,i2c_device_match 函数完成的就是设备与驱动匹配的过程,函数内容如下:
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
其中,of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C
设备和驱动匹配。
4 以 ap3216c 为例构建一个 Linux 下 IIC 的驱动框架
- 修改设备树
首先肯定是要修改 IO, AP3216C 用到了 I2C1 接口, I.MX6U-ALPHA 开发板上的 I2C1 接口使用到了 UART4_TXD 和 UART4_RXD,因此肯定要在设备树里面设置这两个 IO。将 UART4_TXD 和 UART4_RXD 这两个 IO 分别复用为 I2C1_SCL 和 I2C1_SDA,电气属性都设置为 0x4001b8b0
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
接着在 i2c1 节点追加 ap3216c 子节点,@后面的“1e”是 ap3216c 的器件地址
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
ap3216c@1e {
compatible = "alientek,ap3216c";
reg = <0x1e>;
};
};
接着我要开始编写驱动程序了
- 首先我们构建一个设备驱动框架
(1)驱动入口与出口,入口注册,出口注销
static int __init ap3216c_init(void)
{
int ret = 0;
ret = i2c_add_driver(&ap3216c_driver);
return ret;
}
static void __exit ap3216c_exit(void)
{
i2c_del_driver(&ap3216c_driver);
}
module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jimmy");
(2)添加一个结构体
/* ap3216c_driver 结构体变量, i2c_driver 类型 */
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.name = "ap3216c",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(ap3216c_of_match),
},
.id_table = ap3216c_id,
};
其中 of_match_table 和 id_table 函数是设备和驱动之间的匹配函数,分别对应两种不同的匹配方式,即有设备树和没有设备树的时候,我们需要定义两个匹配表结构体。同时还要定义一个 probe 函数和一个 remove 函数, probe 函数的内容就是我们字符设备驱动那一套,remove 函数的内容主要对 probe 函数中的资源申请做一个释放。
/* 传统的匹配表 */
static struct i2c_device_id ap3216c_id[] = {
{"alientek,ap3216c", 0},
{}
};
/* 设备树的匹配表 */
static struct of_device_id ap3216c_of_match[] = {
{.compatible = "alientek,ap3216c"},
{}
};
- 接着我们构建一个字符设备驱动模型
定义一个ap3216c设备结构体、设备操作集并创建相应的函数,在 probe 函数中我们主要构建了字符设备的框架,首先创建设备号,如果没有定义设备号我们直接申请设备号,如果没有定义我们通过函数向内核申请设备号。接着初始化 cdev 并添加一个 cdev,接着创建类和设备。
/* ap3216c 设备结构体
private_data成员变量用于存放ap3216c对应的i2c_client */
struct ap3216c_dev {
struct cdev cdev; // 字符设备
dev_t devid; // 设备号
int major; // 主设备号
int minor; // 次设备号
struct class *class; // 类
struct device *device; // 设备
void *private_data; //私有数据
unsigned short ir, als, ps; // 获取的传感器的数据
};
struct ap3216c_dev ap3216cdev;
static const struct file_operations ap3216c_fops = {
.owner = THIS_MODULE,
.open = ap3216c_open,
.read = ap3216c_read,
.release = ap3216c_release,
};
/* ap3216c_probe 函数,当 I2C 设备和驱动匹配成功以后此函数就会执行,和
platform 驱动框架一样。此函数前面都是标准的字符设备注册代码,最后面会
将此函数的第一个参数client传递给ap3216cdev的private_data 成员变量。*/
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = 0;
/* 搭建字符设备驱动框架 */
/*1、创建设备号 */
ap3216cdev.major = 0;
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); /* 获取分配号的主设备号 */
ap3216cdev.minor = MINOR(ap3216cdev.devid); /* 获取分配号的次设备号 */
}
if(ret < 0)
{
printk("ap3216cdev chrdev_region error!\r\n");
ret = -EINVAL;
goto fail_devid;
}
printk("major = %d, monor = % d\r\n", ap3216cdev.major, ap3216cdev.minor);
/* 2、初始化cdev */
ap3216cdev.cdev.owner = THIS_MODULE;
cdev_init(&ap3216cdev.cdev, &ap3216c_fops);
/* 3、添加一个cdev */
ret = cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);
if(ret < 0){
ret = -EINVAL;
goto fail_cdev;
}
/* 4、创建类 */
ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
if (IS_ERR(ap3216cdev.class)) {
ret = PTR_ERR(ap3216cdev.class);
goto fail_class;
}
/* 5、创建设备 */
ap3216cdev.device = device_create(ap3216cdev.class, NULL,
ap3216cdev.devid, NULL, AP3216C_NAME);
if (IS_ERR(ap3216cdev.device)) {
ret = PTR_ERR(ap3216cdev.device);
goto fail_device;
}
ap3216cdev.private_data = client;
return 0;
fail_device:
class_destroy(ap3216cdev.class);
fail_class:
cdev_del(&ap3216cdev.cdev);
fail_cdev:
unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);
fail_devid:
return ret;
}
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;
}
- 最后我们初始化 AP3216C
/* 读取AP3216c的N个寄存器值 */
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
struct i2c_msg msg[2];
struct i2c_client * client = (struct i2c_client *)dev->private_data;
/* iic 协议中读操作第一阶段是主机向从机写一个地址(要读的寄存器地址),第二阶段发送一个读信号开始读数据*/
/* msg[0]发送要读取的首地址 */
msg[0].addr = client->addr; // 从机地址,也就是ap3216c地址
msg[0].flags = 0; // 表示为要发送的数据
msg[0].buf = ® // 要发送的数据也就是寄存器地址(iic协议第一阶段要发送的是
// 要读的寄存器地址)
msg[0].len = 1; // 要发送的寄存器地址长度为1
/* msg[1]读取数据 */
msg[1].addr = client->addr; // 从机地址,也就是ap3216c地址
msg[1].flags = I2C_M_RD; // 表示读数据
msg[1].buf = val; // 接收到的从机发送的数据
msg[1].len = len; // 要读取的寄存器地址长度
return i2c_transfer(client->adapter, msg, 2);
}
/* 向AP3216c写N个寄存器的的数据 */
static int ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 buff[256];
struct i2c_msg msg;
struct i2c_client * client = (struct i2c_client *)dev->private_data;
/* 构建要发送的数据,也就是寄存器首地址+寄存器数据 */
buff[0] = reg;
memcpy(&buff[1], buf, len);
/* iic 协议中写操作是主机向从机写一个地址(要写的寄存器地址),接着就是要写的数据 */
msg.addr = client->addr; // 从机地址,也就是ap3216c地址
msg.flags = 0; // 表示为要发送的数据
msg.buf = buff; // 要发送的数据也就是寄存器的地址+实际要发送的数据
msg.len = len +1; // 要发送的寄存器地址长度(1)+实际数据长度
return i2c_transfer(client->adapter, &msg, 1);
}
/* 读取ap3216c一个寄存器的值*/
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
u8 data = 0;
ap3216c_read_regs(dev, reg, &data, 1);
return data;
}
/* 向ap3216c一个寄存器写数据*/
static unsigned char ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
ap3216c_write_regs(dev, reg, &buf, 1);
return 0;
}
/* 读取AP3216C的数据 */
void ap3216c_readdata(struct ap3216c_dev *dev)
{
unsigned char buf[6];
unsigned char i;
/* 循环读取所有传感器数据 */
for(i = 0; i < 6; i++)
{
buf[i] = ap3216c_read_reg(&ap3216cdev, AP3216C_IRDATALOW + i);
}
if(buf[0] & 0X80) /* IR_OF位为1,则数据无效 */
dev->ir = 0;
else /* 读取IR传感器的数据 */
dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03);
dev->als = ((unsigned short)buf[3] << 8) | buf[2]; /* 读取ALS传感器的数据 */
if(buf[4] & 0x40) /* IR_OF位为1,则数据无效*/
dev->ps = 0;
else /* 读取PS传感器的数据 */
dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F);
}
static int ap3216c_open(struct inode *node, struct file *filp)
{
unsigned char value = 0;
filp->private_data = &ap3216cdev;
printk("open\r\n");
/* 初始化ap3216c */
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X04); /* 复位AP3216C */
mdelay(50);
ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03); /* 开启ALS、PS+IR */
value = ap3216c_read_reg(&ap3216cdev, AP3216C_SYSTEMCONG); /* 读取刚刚写进去的0X03 */
printk("AP3216C_SYSTEMCONG = %#x\r\n", value);
return 0;
}
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt,
loff_t *offset)
{
short data[3];
int err = 0;
struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
/* 向应用返回ap3216c的原始数据 */
ap3216c_readdata(dev);
data[0] = dev->ir;
data[1] = dev->als;
data[2] = dev->ps;
err = copy_to_user(buf, data, sizeof(data));
if(err < 0) {
printk("error copying\r\n");
}
return 0;
}
首先我们在 open 函数中初始化 ap3216c
ap3216c_read_regs 函数实现多字节读取
ap3216c_write_regs 函数实现连续多字节写操作
ap3216c_write_reg 函数用于向 AP3216C 的指定寄存器写入数据,用于一个寄存器的数据写操作
ap3216c_read_reg 函数用于读取 AP3216C 的指定寄存器数据,用于一个寄存器的数据读取
ap3216c_readdata 函数用于读取 AP3216C 的数据,读取原始数据,包括 ALS,PS 和 IR
在 read 函数中通过 copy_to_user 函数将读取到的数据发送到用户空间
这里我们重要说一个函数那就是 i2c_transfer
前面我们说到 IIC 设备驱动首先要做的就是初始化 i2c_driver 并向 Linux 内核注册。当设备和驱动匹配以后 i2c_driver 里面的 probe 函数就会执行, probe 函数里面所做的就是字符设备驱动那一套了。 一般需要在 probe 函数里面初始化 I2C 设备,要初始化 I2C 设备就必须能够对 I2C 设备寄存器进行读写操作,这里就要用到 i2c_transfer 函数了。i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数。函数原型如下:
int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num)
/* adap: 所使用的 I2C 适配器, i2c_client 会保存其对应的 i2c_adapter。
msgs: I2C 要发送的一个或多个消息。
num: 消息数量,也就是 msgs 的数量。*/