相关文章
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驱动分析
文章目录
参考资料
- 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_BUS
temp = 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));
#endif
if (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]);
else
result = 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驱动程序。