I2C驱动(十二) -- 主控芯片的i2c_adapter驱动分析
相关文章
I2C驱动(一) – I2C协议
I2C驱动(二) – SMBus协议
I2C驱动(三) – 驱动中的几个重要结构
I2C驱动(四) – I2C-Tools介绍
I2C驱动(五) – 通用驱动i2c-dev.c分析
I2C驱动(六) – I2C驱动程序模型
I2C驱动(七) – 编写I2C设备驱动之i2c_driver
I2C驱动(八) – 编写I2C设备驱动之i2c_client
I2C驱动(九) – i2c_adapter控制器驱动框架编写
I2C驱动(十) – i2c_adapter控制器驱动完善与上机实验
I2C驱动(十一) – gpio模拟的i2c总线驱动i2c-gpio.c分析
I2C驱动(十二) – 主控芯片的i2c_adapter驱动分析
文章目录
- 相关文章
- 参考资料
- 一、I2C-GPIO的缺点
- 二、I2C控制器内部结构
- 2.1 通用简化结构
- 2.2 IMX6ULL的I2C控制器内部结构
- 三、I2C控制器操作方法
- 四、代码分析
- 4.1 程序模型
- 4.2 设备树
- 4.3 驱动程序分析
- 五、总结
参考资料
- Linux内核真正的I2C控制器驱动程序
- IMX6ULL:
Linux-4.9.88\drivers\i2c\busses\i2c-imx.c
- IMX6ULL:
- 芯片手册
- IMXX6ULL:
IMX6ULLRM.pdf
Chapter 31: I2C Controller (I2C)
- IMXX6ULL:
一、I2C-GPIO的缺点
I2C驱动(十一) – gpio模拟的i2c总线驱动i2c-gpio.c分析中介绍了gpio模拟的i2c总线驱动程序。假设它是100kHz的频率,则传输一位数据的时间是10微秒,那么传输一个字节数据再加上回应信号、起始和终止信号,总时间就大于90微秒,这90微秒内CPU就完全被占住。如果传输数据量很大的话,效率就很低。
二、I2C控制器内部结构
2.1 通用简化结构
i2c控制器内部通常会包含有以下寄存器:
control_register
:设置控制参数,例如频率等信息;clock_module
:时钟模块,提供时钟;status_register
:状态寄存器;int_register
:中断寄存器;shift_register
:移位寄存器;tx_register
:发送寄存器;rx_register
:接收寄存器;
假设要发送一个字节数据,只要把数据放入发送寄存器,i2c控制器就会自动通过移位寄存器一位一位的往外传输,这期间就可以休眠,不会占用CPU,当传输结束后,会发出一个中断信号,告诉CPU数据传输完成。
如果是接收一个字节数据,外面的数据会经过移位寄存器一位一位的存放到接收寄存器,完成后会发出一个中断信号,这时候就可以去读接收寄存器中的数据。
2.2 IMX6ULL的I2C控制器内部结构
从结构图可以看出,它里面包含了:
- 频率寄存器
- 控制寄存器
- 状态寄存器
- 数据寄存器:发送和接收都经过这个寄存器
- 地址寄存器:如果作为从设备,可以给它设置一个地址
- 移位寄存器
三、I2C控制器操作方法
I2C控制器的操作步骤如下:
- 使能时钟,设置时钟
- 发送数据:
- 把数据写入
tx_register
,等待中断发生 - 中断发生后,判断状态:是否发生错误、是否得到回应信号(ACK)
- 把下一个数据写入
tx_register
,等待中断:如此循环
- 把数据写入
- 接收数据:
- 设置
controller_register
,进入接收模式,启动接收,等待中断发生 - 中断发生后,判断状态,读取
rx_register
得到数据 - 如此循环
- 设置
四、代码分析
4.1 程序模型
万能驱动模型:平台总线-设备-驱动模型。platform_device
使用设备树来定义。platform_driver
和platform_device
匹配成功后调用probe
函数,在probe
函数中分配,设置,注册i2c_adapter
结构体。
4.2 设备树
#address-cells
和#size-cells
:用来指定它下面挂接的设备的地址表示方式;compatible
:和驱动程序进行比较;reg
:控制器的寄存器地址和大小;interrupts
:指定中断clock
:时钟status
:节点状态
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"; // 在包含它的文件中改为"okay"
};
4.3 驱动程序分析
先看入口函数:它里面注册了一个platform_driver
结构体
static int __init i2c_adap_imx_init(void)
{/* 注册一个platform_driver */return platform_driver_register(&i2c_imx_driver);
}
platform_driver
结构体中包含了of_match_table
和probe
函数,和设备树匹配成功后probe
函数就会被调用。
static struct platform_driver i2c_imx_driver = {.probe = i2c_imx_probe,.remove = i2c_imx_remove,.driver = {.name = DRIVER_NAME,.pm = I2C_IMX_PM_OPS,.of_match_table = i2c_imx_dt_ids,},.id_table = imx_i2c_devtype,
};
probe
函数,里面做了一些设备树的解析和硬件设置,核心是i2c_adapter
中的algo
成员的设置。
static int i2c_imx_probe(struct platform_device *pdev)
{
...i2c_imx->adapter.algo = &i2c_imx_algo; //核心
...
}
i2c_imx_algo
中的 master_xfer
成员实现了寄存器的操作。
static struct i2c_algorithm i2c_imx_algo = {.master_xfer = i2c_imx_xfer,.functionality = i2c_imx_func,
};
i2c_imx_xfer
核心函数,需要结合芯片手册去分析。
static int i2c_imx_xfer(struct i2c_adapter *adapter,struct i2c_msg *msgs, int num)
{unsigned int i, temp;int result;bool is_lastmsg = false;bool enable_runtime_pm = false;struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);if (!pm_runtime_enabled(i2c_imx->adapter.dev.parent)) {pm_runtime_enable(i2c_imx->adapter.dev.parent);enable_runtime_pm = true;}result = pm_runtime_get_sync(i2c_imx->adapter.dev.parent);if (result < 0)goto out;/* Start I2C transfer */result = i2c_imx_start(i2c_imx);if (result) {if (i2c_imx->adapter.bus_recovery_info) {i2c_recover_bus(&i2c_imx->adapter);result = i2c_imx_start(i2c_imx);}}if (result)goto fail0;/* read/write data */for (i = 0; i < num; i++) {if (i == num - 1)is_lastmsg = true;if (i) {dev_dbg(&i2c_imx->adapter.dev,"<%s> repeated start\n", __func__);temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);temp |= I2CR_RSTA;imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);result = i2c_imx_bus_busy(i2c_imx, 1);if (result)goto fail0;}dev_dbg(&i2c_imx->adapter.dev,"<%s> transfer message: %d\n", __func__, i);/* write/read data */
#ifdef CONFIG_I2C_DEBUG_BUStemp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);dev_dbg(&i2c_imx->adapter.dev,"<%s> CONTROL: IEN=%d, IIEN=%d, MSTA=%d, MTX=%d, TXAK=%d, RSTA=%d\n",__func__,(temp & I2CR_IEN ? 1 : 0), (temp & I2CR_IIEN ? 1 : 0),(temp & I2CR_MSTA ? 1 : 0), (temp & I2CR_MTX ? 1 : 0),(temp & I2CR_TXAK ? 1 : 0), (temp & I2CR_RSTA ? 1 : 0));temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2SR);dev_dbg(&i2c_imx->adapter.dev,"<%s> STATUS: ICF=%d, IAAS=%d, IBB=%d, IAL=%d, SRW=%d, IIF=%d, RXAK=%d\n",__func__,(temp & I2SR_ICF ? 1 : 0), (temp & I2SR_IAAS ? 1 : 0),(temp & I2SR_IBB ? 1 : 0), (temp & I2SR_IAL ? 1 : 0),(temp & I2SR_SRW ? 1 : 0), (temp & I2SR_IIF ? 1 : 0),(temp & I2SR_RXAK ? 1 : 0));
#endifif (msgs[i].flags & I2C_M_RD)result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);else {if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)result = i2c_imx_dma_write(i2c_imx, &msgs[i]);elseresult = i2c_imx_write(i2c_imx, &msgs[i]);}if (result)goto fail0;}fail0:/* Stop I2C transfer */i2c_imx_stop(i2c_imx);pm_runtime_mark_last_busy(i2c_imx->adapter.dev.parent);pm_runtime_put_autosuspend(i2c_imx->adapter.dev.parent);out:if (enable_runtime_pm)pm_runtime_disable(i2c_imx->adapter.dev.parent);dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n", __func__,(result < 0) ? "error" : "success msg",(result < 0) ? result : num);return (result < 0) ? result : num;
}
五、总结
本文介绍了实际主控芯片的i2c_adapter驱动程序。