I2C
分为I2C控制器驱动, 由原厂进行编写, 还有具体的设备驱动, 由我们自己编写
之前使用的platform是虚拟出来的总线, I2C可以自己作为一条总线, 重点就是I2C是适配器
总线驱动
重要的数据结构:i2c_adapter和i2c_algorithm,定义在include/linux/i2c.h
文件中
驱动是i2c-imx.c文件, 使用platform从设备树获取设备
定义一个物理的控制器
或者说I2C适配器驱动的主要工作就是初始化i2c_adapter结构体变量,然后设置i2c_algorithm中的master_xfer函数, 这个函数用来进行I2C读写操作, 还实现了一个functionality函数, 用于返回可以提供的功能
int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter*adap)
注册结构体, 这个结构体中的i2c_algorithm包含了IIC控制器访问的接口函数, 需要实现
void i2c_del_adapter(struct i2c_adapter * adap)
删除
设备驱动
- i2c_client结构体
定义在include/linux/i2c.h
文件中
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;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
};
描述具体的设备, flgs: 标志, addr芯片的地址, 7位放在低位, adapter: 使用的控制器
不需要自己创建i2c_client, 通过设备树来告诉
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
mag3110@0e {
compatible = "fsl,mag3110";
reg = <0x0e>;
position = <2>;
};
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
};
};
一般在这里添加具体的芯片, 系统在解析的时候就会知道然后创建对应的i2c_client
- i2c_driver结构体
需要驱动编写人员进行编写,
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
有参数probe和remove函数, 具体就是初始化这个然后注册
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
注册函数
另外i2c_add_driver也常常用于注册i2c_driver,i2c_add_driver是一个宏
#define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE,driver)
void i2c_del_driver(struct i2c_driver *driver)
删除
没有使用设备树
需要在BSP里面使用i2c_board_info结构体来描述一个具体的I2C设备
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct fwnode_handle *fwnode;
int irq;
};
使用设备树
i2c1: i2c@021a0000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a0000 0x4000>;
interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C1>;
status = "disabled";
};
&i2c1 {
clock-frequency = <100000>; //总线时钟频率
pinctrl-names = "default"; //两个引脚
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay"; //打开控制器
mag3110@0e {
compatible = "fsl,mag3110"; //名字, 自己起
reg = <0x0e>; //具体的地址
position = <2>; //方向,可选
};
fxls8471@1e {
compatible = "fsl,fxls8471";
reg = <0x1e>;
position = <0>;
interrupt-parent = <&gpio5>;
interrupts = <0 8>;
};
};
添加设备信息, 挂在到哪一个设备树下面就使用哪一个, 创建重点是compatible属性和reg属性的设置
实际编写
- 修改设备树, 添加相关的信息, 添加的地址属性是7位的地址
- 挂载i2c_driver结构体i2c_add_driver, i2c_del_driver
- 初始化这个结构体
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id);
static int ap3216c_remove(struct i2c_client *client);
//设备树匹配表
static struct of_device_id ap3216c_of_match[] = {
{.compatible = "jiao,ap3216c"},
{/* sentinel */},
};
//传统匹配表
static struct i2c_device_id ap3216c_id[] = {
{"jiao,ap3216c123",0},
{}
};
static struct i2c_driver ap3216c_driver = {
.probe = ap3216c_probe,
.remove = ap3216c_remove,
.driver = {
.name = "ap3216c",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(), //设备树匹配方式
},
.id_table = ap3216c_id, //老版本的匹配方式
};
两种方法一同使用的时候才可以正常使用, id_table必须存在, 但可以不匹配, 原因是probe的判断条件是只有两个都存在的时候才会进行
- 使用字符设备
- 通过IIC控制器中的函数进行发送接收数据
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
第一个参数, adap就是设备对应的适配器, 就是传入probe函数的第一个结构体的参数之一
struct i2c_msg { __u16 addr; /* 从机地址*/ __u16 flags; //标志, 就是下面的, 0表示发送 #define I2C_M_TEN 0x0010 /* this is a ten bit chip address */ #define I2C_M_RD 0x0001 /* read data, from slave to master */ #define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */ #define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */ #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */ __u16 len; /* 消息的长度 */ __u8 *buf; /* 消息 */ };
不同的传输的数据
struct i2c_client *client = *dev->private_data; struct i2c_msg msg[2]; //发送要读取的寄存器 msg[0].addr = client->addr; msg[0].flags = 0; msg[0].buf = ® //发送的数据,就是寄存器首地址 msg[0].len = 1; //接受地址 msg[1].addr = client->addr; msg[1].flags = I2C_M_RD; msg[1].buf = val; //接收 msg[1].len = len; i2c_transfer(client->adapter, msg, 2);
根据协议, 首先发送寄存器首地址, 之后进行读取数据
struct i2c_client *client = dev->private_data; u8 b[256]; b[0] = reg; //根据协议构建发送的数据 memcpy(&b[1], buf, len); struct i2c_msg msg; msg.addr = client->addr; msg.flags = 0; msg.buf = b; //发送的数据 msg.len = 1+len; return return i2c_transfer(client->adapter, msg, 1);
也可以使用函数
i2c_smbus_read_byte_data
读取一个