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驱动分析


参考资料

  • Linux内核真正的I2C控制器驱动程序
    • IMX6ULL: Linux-4.9.88\drivers\i2c\busses\i2c-imx.c
  • 芯片手册
    • IMXX6ULL:IMX6ULLRM.pdf
      • Chapter 31: I2C Controller (I2C)

一、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控制器内部结构

从结构图可以看出,它里面包含了:

  1. 频率寄存器
  2. 控制寄存器
  3. 状态寄存器
  4. 数据寄存器:发送和接收都经过这个寄存器
  5. 地址寄存器:如果作为从设备,可以给它设置一个地址
  6. 移位寄存器

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/16eb33333e364b85aefc89f4ca0147b2.png

三、I2C控制器操作方法

I2C控制器的操作步骤如下:

  • 使能时钟,设置时钟
  • 发送数据:
    • 把数据写入tx_register,等待中断发生
    • 中断发生后,判断状态:是否发生错误、是否得到回应信号(ACK)
    • 把下一个数据写入tx_register,等待中断:如此循环
  • 接收数据:
    • 设置controller_register,进入接收模式,启动接收,等待中断发生
    • 中断发生后,判断状态,读取rx_register得到数据
    • 如此循环

四、代码分析

4.1 程序模型

万能驱动模型:平台总线-设备-驱动模型。platform_device使用设备树来定义。platform_driverplatform_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驱动程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值