系列文章目录
本系列使用的开发板为正点原子阿尔法IMX6ULL开发板,及根据正点原子所的提供教程学习同系列笔记已放置链接在上面。
文章目录
步骤:
- 修改设备树
- 定义i2c_driver结构体,然后在of_device_id设置compatible值匹配设备树
- 注册i2c_add_driver(放在入口函数)
- 编写probe函数函数,设备匹配成功会自动运行
- 初始化I2C器件(放在open函数)
- 编写对应器件的读/写
以ap3216c为例:
一、修改设备树
1. 添加I2C引脚
打开 imx6ull-alientek-emmc.dts,在pinctrl_i2c:i2clgrp中已经定义好了两个引脚,这是NXP写的,直接用就可以了
2. 在i2c节点追加子节点
找到&i2c1这个节点,内容如下:
&i2c1 {
clock-frequency = <100000>;//i2c的频率为100KHz
pinctrl-names = "default";//
pinctrl-0 = <&pinctrl_i2c1>;//指定i2c所使用的IO
status = "okay";//
};
追加内容后为:
&i2c1 {
clock-frequency = <100000>;//i2c的频率为100KHz
pinctrl-names = "default";//
pinctrl-0 = <&pinctrl_i2c1>;//指定i2c所使用的IO
status = "okay";//
ap3216c@1e {//1e为器件地址
compatible = "alientek,ap3216c";//匹配值
reg = <0x1e>;//器件地址
};
};
追加成功后会在/sys/bus/i2c/devices目录下看到x-001e的子目录。
二、I2C设备驱动
关于I2C总线(控制器或适配器)驱动一般是由半导体厂商完成,所以不多关注。
需要重点关注的两个数据结构i2c_client 和 i2c_driver
2.1 i2c_client结构体
i2c_client 结构体定义在 include/linux/i2c.h 文件中,内容如下:
struct i2c_client {
unsigned short flags; /* 标志 */
unsigned short addr; /* 芯片地址,7 位,存在低 7 位*/
......
char name[I2C_NAME_SIZE]; /* 名字 */
struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
struct device dev; /* 设备结构体 */
int irq; /* 中断 */
struct list_head detected;
......
};
一个设备对应一个i2c_client,每检测到一个I2C设备就会给这个I2C设备分配一个i2c_client。
2.2 i2c_driver 结构体
类似 platform_driver,是需要重点处理的内容,其定义在 include/linux/i2c.h 文件中,包含了驱动与设备的匹配和兼容属性(compatible),匹配过程由I2C核心来完成。
i2c_driver 结构体构建完成以后需要使用 int i2c_register_driver向Linux内核注册这个i2c_driver, int i2c_register_driver函数原型如下:
int i2c_register_driver(struct module *owner,
struct i2c_driver *driver)
owner:一般为 THIS_MODULE。
driver:要注册的 i2c_driver。
返回值:0,成功;负值,失败。
也可以使用:
#define i2c_add_driver(driver) \
i2c_register_driver(THIS_MODULE, driver)
注销需要使用函数i2c_del_driver 函数,此函数原型如下:
void i2c_del_driver(struct i2c_driver *driver)
driver:要注销的 i2c_driver。
返回值:无。
2.3 i2c_driver注册示例
/* i2c 驱动的 probe 函数 */
static int xxx_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
/* 函数具体程序 */
eturn 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);
三、数据的收发
对I2C设备寄存器进行读写操作,要用到i2c_transfer 函数了。i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数,对于 I.MX6U 而言就是i2c_imx_xfer 这个函数。i2c_transfer 函数原型如下:
int i2c_transfer(struct i2c_adapter *adap,
struct i2c_msg *msgs,
int num)
adap:所使用的 I2C 适配器,i2c_client 会保存其对应的 i2c_adapter。,就是i2c_client里面的struct i2c_adapter *adapter
msgs:I2C 要发送的一个或多个消息。
num:消息数量,也就是 msgs 的数量。
返回值:负值,失败,其他非负值,发送的 msgs 数量。
需要重点关注的是msgs参数,这是一个i2c_msg 类型的指针参数,是需要传递的消息,其定义在include/uapi/linux/i2c.h 文件中,结构体内容如下:
struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags; /* 标志 */
#define I2C_M_TEN 0x0010
#define I2C_M_RD 0x0001
#define I2C_M_STOP 0x8000
#define I2C_M_NOSTART 0x4000
#define I2C_M_REV_DIR_ADDR 0x2000
#define I2C_M_IGNORE_NAK 0x1000
#define I2C_M_NO_RD_ACK 0x0800
#define I2C_M_RECV_LEN 0x0400
__u16 len; /* 消息(本 msg)长度 */
__u8 *buf; /* 消息数据 */
};
使用 i2c_transfer 函数发送数据之前要先构建好 i2c_msg,使用 i2c_transfer 进行 I2C 数据收发的示例代码如下:
/* 设备结构体 */
struct xxx_dev {
......
void *private_data; /* 私有数据,一般会设置为 i2c_client */
};
/*
* @description : 读取 I2C 设备多个寄存器数据
* @param – dev : I2C 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
static int xxx_read_regs(struct xxx_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; /* I2C 器件地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */
/* msg[1],第二条读消息,读取寄存器数据 */
msg[1].addr = client->addr; /* I2C 器件地址 */
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 {
ret = -EREMOTEIO;
}
return ret;
}
/*
* @description : 向 I2C 设备多个寄存器写入数据
* @param – dev : 要写入的设备结构体
* @param – reg : 要写入的寄存器首地址
* @param – buf : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/
static s32 xxx_write_regs(struct xxx_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; /* I2C 器件地址 */
msg.flags = 0; /* 标记为写数据 */
msg.buf = b; /* 要发送的数据缓冲区 */
msg.len = len + 1; /* 要发送的数据长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
另外还有两个API函数分别用于I2C数据的收发操作,这两个函数最终都会调用i2c_transfer。
- i2c_master_send
int i2c_master_send(const struct i2c_client *client,
const char *buf,
int count)
client:I2C 设备对应的 i2c_client。
buf:要发送的数据。
count:要发送的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。
- i2c_master_recv
int i2c_master_recv(const struct i2c_client *client,
char *buf,
int count)
client:I2C 设备对应的 i2c_client。
buf:要接收的数据。
count:要接收的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无符号 16 位)类型的数据。
返回值:负值,失败,其他非负值,发送的字节数。