4.4 编写I2C slave driver所需使用的API
1. 前言
本文从I2C consumer的角度,介绍怎么在linux中,利用I2C framework提供的接口,编写I2C slave device的驱动程序。
2. 两种设备形态
嵌入式系统中,I2C总线上连接的slave device,有两种形态,如下:
形态1,CPU和设备之间的所有数据交互,都是通过I2C总线进行,没有其它方式,如PMIC、Audio codec等。
形态2,I2C只是CPU和设备之间进行数据交互的一种,例如HDMI,图像以及音频数据通过TDMS接口传输,EDID等信息的交互通过I2C总线(在HDMI协议中称作DDC接口)。
这两种设备形态决定了设备在设备模型中的位置:
形态1比较简单,以PMIC为例,可以把它看作I2C bus上的一个设备;
形态2就复杂了,以TV为例,它一部分功能可看作I2C bus上的一个设备,另一部分是却是platform bus(HDMI Controller)上的一个设备,它的设备驱动要怎么写?一般是以其主要功能为准,TV的主要功能明显是音视频传输,因此应该当做一个platform设备。
2.1 形态1
形态1,pmic的DTS node是i2c1的一个child node,I2C core负责该设备的创建和注册,以及和其driver的probe等操作:
/* arch/arm/boot/dts/imx6dl-riotboard.dts */
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
…
pmic: pf0100@08 {
compatible = "fsl,pfuze100";
…
};
...
};
2.2 形态2
形态2,hdmi的DTS node位于根目录,作为platform device存在,它的DDC功能所使用的i2c2,是以一个变量的形式引用的:
/* arch/arm/boot/dts/imx6dl-riotboard.dts */
&hdmi {
ddc-i2c-bus = <&i2c2>;
status = "okay";
};
这两种不同的DTS描述,决定了最终的I2C slave device driver有不同的编写方式,具体请参考后面章节的描述。
3. 驱动编写步骤
针对第2节所描述的两种不同的设备形态,有两种驱动编写方法。
3.1 形态1
1)根据硬件的连接方式,确定该设备所从属的I2C controller(在I2C framework中称作I2C adapter),例如第2节例子中的i2c1。
2)在I2C adapter的DTS node中,添加该设备的DTS描述,其格式和正常的platform device一致。
3)DTS描述中的compatible关键字用于设备和驱动的probe,如“compatible = "fsl,pfuze100";”,其它字段根据实际情况自行添加。
4)编写该设备的驱动程序,完成如下内容(具体可参考drivers/regulator/pfuze100-regulator.c):
a)定义一个struct i2c_driver类型的变量,并调用module_i2c_driver接口将其注册到I2C core中。
b)该变量包含应包含一个DTS中的“compatible ”字段相同的of_match_table,以及一个probe接口。
5)由“文章I2C provider”的描述可知,I2C framework core会在每一个I2C adapter注册时,为它下面所有的slave device创建struct i2c_client结构,并匹配对应的struct i2c_driver变量,调用driver的probe接口。
6)i2c_driver的probe接口的输入参数是struct i2c_client类型的指针(代表I2C slave device),以struct i2c_client指针为参数,可以调用i2c_master_send/i2c_master_recv接口进行简单的I2C传输,同时,也可以通过该指针获得所属的I2C adapter指针,然后通过i2c_transfer接口,进行更为复杂的read、write操作(可参考“drivers/base/regmap/regmap-i2c.c”中的regmap_i2c_read接口)。
3.2 形态2
1)根据硬件的连接方式,确定该设备所从属的I2C controller(在I2C framework中称作I2C adapter),例如第2节例子中的i2c2。
2)将该设备(如HDMI)当做一个platform device,并按照platform device的通用方法,提供DTS描述、编写platform driver,可参考第2节中描述的hdmi的例子。
3)DTS描述中,使用一个变量,指向其I2C adapter的DTS节点,例如:“ddc-i2c-bus = <&i2c2>; ”。
4)在platform driver的probe函数中,以“ddc-i2c-bus ”参数,调用of_parse_phandle接口,获取I2C adapter的device node(即i2c的device node),然后调用of_find_i2c_adapter_by_node获取相应的I2C adapter指针,如下:
/* drivers/gpu/drm/panel/panel-simple.c */
ddc = of_parse_phandle(dev->of_node, "ddc-i2c-bus", 0);
if (ddc) {
panel->ddc = of_find_i2c_adapter_by_node(ddc);
of_node_put(ddc);
if (!panel->ddc) {
err = -EPROBE_DEFER;
goto free_backlight;
}
}
5)获得struct i2c_adapter指针后,即可通过i2c_transfer接口,即可进行read、write操作。
4. 关键数据结构和API介绍
4.1 i2c client
由前文可知,I2C framework使用struct i2c_client抽象I2C slave device(对应设备模型中的struct device),具体如下:
/**
* struct i2c_client - represent an I2C slave device
* @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address;
* I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking
* @addr: Address used on the I2C bus connected to the parent adapter.
* @name: Indicates the type of the device, usually a chip name that's
* generic enough to hide second-sourcing and compatible revisions.
* @adapter: manages the bus segment hosting this I2C device
* @dev: Driver model device node for the slave.
* @irq: indicates the IRQ generated by this device (if any)
* @detected: member of an i2c_driver.clients list or i2c-core's
* userspace_devices list
* @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter
* calls it to pass on slave events to the slave driver.
*
* An i2c_client identifies a single device (i.e. chip) connected to an
* i2c bus. The behaviour exposed to Linux is defined by the driver
* managing the device.
*/
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
};
1)flags,指示该I2C slave device一些特性,包括:
I2C_CLIENT_PEC,indicates it uses SMBus Packet Error Checking;
I2C_CLIENT_TEN,indicates the device uses a ten bit chip address;
I2C_CLIENT_WAKE,该设备具备wakeup的能力。
2)addr,该设备的7-bit的slave地址。
3)adapter,该设备所在的I2C controller。
4)irq,irq number(如果有的话)。
通常情况下,struct i2c_client变量是由I2C core在register adapter的时候,解析adapter的child node自行创建的(具体可参考“文章 I2C provider”),该数据结构中的有些信息,可通过DTS配置,包括:
xxx:xxx@08 {
reg = <0x08>; /* 对应struct i2c_client中的‘addr’*/
interrupts = <16 8>; /* 对应struct i2c_client中的‘irq’*/
wakeup-source; /* 对应flags中的I2C_CLIENT_WAKE */
};
4.2 I2C adapter
I2C数据传输(read or write),需要以struct i2c_adapter为操作对象,具体可参考“文章 I2C provider”。
4.3 i2c msg
I2C数据传输的单位,具体可参考“文章I2C provider”。
4.4 编写I2C slave driver所需使用的API
/* include/linux/i2c.h */
/* must call put_device() when done with returned i2c_client device */
extern struct i2c_client *of_find_i2c_device_by_node(struct device_node *node);
/* must call put_device() when done with returned i2c_adapter device */
extern struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node *node);
通过DTS节点获取相应的client或者adapter指针,3.2中的例子已经说明了of_find_i2c_adapter_by_node函数的使用场景。
/* include/linux/i2c.h */
/*
* The master routines are the ones normally used to transmit data to devices
* on a bus (or read from them). Apart from two basic transfer functions to
* transmit one message at a time, a more complex version can be used to
* transmit an arbitrary number of messages without interruption.
* @count must be be less than 64k since msg.len is u16.
*/
extern int i2c_master_send(const struct i2c_client *client, const char *buf,
int count);
extern int i2c_master_recv(const struct i2c_client *client, char *buf,
int count);
/* Transfer num messages.
*/
extern int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
/* Unlocked flavor */
extern int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
i2c数据传输有关的接口有两类:
一类是以i2c client为参数,进行简单的数据收发,包括i2c_master_send/i2c_master_recv。该方法只可以通过标准方式,发送或者接收一定数量的数据。
另一类是以i2c adapter和i2c msg为参数,可以更为灵活的read或者write数据,包括i2c_transfer。使用该方法可以以struct i2c_msg为参数,一次读取、或者写入、或者读取加写入,一定数量的数据。