i2c adapter——I2C适配器
在i2c总线结构中,i2c adapter对应着真实的物理设备i2c适配器。
i2c-imx.c是I2C适配器的平台总线驱动,文件中包含了对I2C适配器注册和操作的函数,
第一步,将adapter驱动注册到platform总线上——i2c_adap_imx_init调用platform_driver_register,将i2x_imx_driver注册到platform_bus_type,即平台总线上。
//i2c-imx.c
static int __init i2c_adap_imx_init(void)
{
return platform_driver_register(&i2c_imx_driver);
}
第二步,将i2c设备注册到platform总线上——在设备树中,SOC内部的I2C 适配器设备有相关代码,在解析设备树后,会注册到platform总线上。
//imx7ull.dtsi
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";
};
i2c2: i2c@021a4000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
reg = <0x021a4000 0x4000>;
interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_I2C2>;
status = "disabled";
};
第三步,i2c_imx平台总线驱动调用probe函数。
i2c_add_numbered_adapter用来静态声明一个i2c adapter,通常用在SOC或系统主板上的i2c 适配器,其总线号必须静态指定。反之则会调用i2c_add_adapter来注册一个i2c adapter。
//i2c-imx.c
static int i2x_imc_probe(struct platform_device *pdev*)
{
...
ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
...
}
i2c_add_numbered_adapter调用i2c_register_adapter向i2c总线上注册一个adapter。
static int i2c_register_adapter(struct i2c_adapter *adap*)
{
...
//指定了adapter的成员struct device dev的名字为i2c-%d
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
//指定要注册到i2c设备总线
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
...
}
对于上一段代码有两点需要注意:
- i2c_bus_type是在i2c-core中的全局变量,类型为struct bus_type,它就代表了i2c设备总线。
- 至此,在设备总线i2c_bus_type上面就成功注册了SOC自带的i2c adapter。这时我们在文件系统中还看不到任何的设备节点文件。
小结:
i2c-imx平台总线驱动将SOC自带的I2C适配器注册到平台总线上,与设备树提供的设备匹配上后,将i2c适配器注册到了i2c总线上。这时,i2c总线上仅仅有i2c设备,而没有i2c驱动。
下面介绍i2c驱动。
I2C适配器的通用驱动——i2c-dev
i2c-dev驱动如何创建i2c adapter设备的
i2c-dev.c 是i2c设备驱动文件,使用字符设备的接口。
static int __init i2c_dev_init(void)
{
...
/* Keep track of adapters which will be added or removed later */
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
...
/* Bind to already existing adapters right away */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
...
}
i2c_dev_init函数使用两种方法来保证i2c适配器设备可以被正确创建:
第一种方法:bus_register_notifier,通过注册通知时间,当有i2c设备从总线上添加或删除时,就会通知总线的相关回调函数。
第二种方法:i2c_for_each_dev调用bus_for_each_dev,遍历i2c_bus_type,即i2c总线上的所有i2c适配器,调用回调函数i2cdev_attach_adapter。
i2cdev_attach_adapter函数以字符设备驱动的方法创建相关的i2c适配器设备,代码如下:
static int i2cdev_attach_adapter(struct device *dev, void*dummy)
{
...
cdev_init(&i2c_dev->cdev, &i2cdev_fops);
...
res = cdev_add(&i2c_dev->cdev, MKDEV(I2C_MAJOR, adap->nr), 1);
...
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr), NULL,
"i2c-%d", adap->nr);
...
}
小结:
至此,i2c-dev作为i2c设备驱动创建了设备节点文件,注册了对i2c-dev设备的操作方法,即i2cdev_fops。
在设备中生成了设备节点
[root@imx6ull:/]# ls /sys/class/i2c-dev/
i2c-0 i2c-1
如何通过i2c-dev驱动来操作i2c设备?
在i2cdev_fops中注册了设备操作的回调函数,可以参考字符设备驱动模型。代码如下:
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
要注意到的是i2c-dev驱动对应的设备是i2c适配器,以i2cdev_read为例
i2cdev_read调用i2c_master_recv,i2c_master_recv又会调用i2c_transfer函数,i2c_transfer最终调用adap->algo->master_xfer。
master_xfer是i2c_adapter成员i2c_algorithm的成员,它表示了i2c适配器传输数据的方式。代码如下:
static struct i2c_algorithm i2c_imx_algo = {
.master_xfer = i2c_imx_xfer,
.functionality = i2c_imx_func,
};
其中的master_xfer在文件中有相关实现,这里不再列出,而functionality则代表设备支持的i2c通讯的物理信号上支持的一些特点。
i2c-tool工具就是使用了i2c-dev的ioctl接口实现信息的发送