当前位置: 首页 > news >正文

I2C驱动(十一) -- gpio模拟的i2c总线驱动i2c-gpio.c分析

相关文章

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控制器驱动完善与上机实验

文章目录

  • 相关文章
  • 参考资料
  • 一、平台-总线-设备驱动模型
  • 二、设备树分析
  • 三、驱动程序分析
    • 3.1 i2c-gpio驱动层次
    • 3.2 `bit_xfer`传输函数分析
  • 四、怎么使用i2c-gpio
  • 五、总结


参考资料

  • i2c_spec.pdf
  • Linux文档
    • Linux-4.9.88\Documentation\devicetree\bindings\i2c\i2c-gpio.txt
  • Linux驱动源码
    • Linux-4.9.88\drivers\i2c\busses\i2c-gpio.c

一、平台-总线-设备驱动模型

i2c-gpio.c也是基于万能框架:平台-总线-设备模型来写的。platform_device部分来自设备树,platform_driver就是i2c-gpio.c驱动。下面分析两边的代码。
在这里插入图片描述

二、设备树分析

设备树节点如下:

i2c_gpio: i2c-gpio {
		compatible = "i2c-gpio";
		#address-cells = <1>;
		#size-cells = <0>;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_i2c_gpio>;
		gpios = <
			&gpio5 1 GPIO_ACTIVE_HIGH /* SDA */
			&gpio5 0 GPIO_ACTIVE_HIGH /* SCL */
		>;
		i2c-gpio,delay-us = <5>;	/* ~100 kHz */
		status = "okay";

		ds1339: rtc@68 {
			compatible = "dallas,ds1339";
			reg = <0x68>;
			status = "disabled";
		};
	};
  • compatible 属性用于和i2c-gpio.c程序中.of_match_table结构中的compatible 进行比较。
  • #address-cells #size-cells 属性用来指定 reg属性的地址和大小用多少个32位数据表示。reg属性在子节点中用了表示i2c设备地址。
  • pinctrl-namespinctrl-0属性表示使用pinctrl将引脚配置成gpio模式。
  • gpios 属性用于指定gpio引脚
  • i2c-gpio,delay-us属性表示时钟频率
  • status 属性节点使能状态
  • ds1339: rtc@68表示的是这个i2c总线下挂的设备,地址是0x68。

三、驱动程序分析

3.1 i2c-gpio驱动层次

从入口函数开始,入口函数注册了一个platform_driver结构。

tatic int __init i2c_gpio_init(void)
{
...
	ret = platform_driver_register(&i2c_gpio_driver);
...
}

platform_driver结构包含了of_match_table数组,probe函数。

static struct platform_driver i2c_gpio_driver = {
	.driver		= {
		.name	= "i2c-gpio",
		.of_match_table	= of_match_ptr(i2c_gpio_dt_ids), //和设备树比较
	},
	.probe		= i2c_gpio_probe,  //匹配成功调用
	.remove		= i2c_gpio_remove, //做一些和probe相反的工作
};

of_match_table数组中的compatible 和设备树匹配成功,调用probe函数。

static const struct of_device_id i2c_gpio_dt_ids[] = {
	{ .compatible = "i2c-gpio", }, //与设备树的compatible 比较
	{ /* sentinel */ }
};

来看probe函数:

  • of_i2c_gpio_get_pins从设备树获取gpio引脚。
  • adap = &priv->adap;分配的i2c_adapter
  • of_i2c_gpio_get_props从设备树获取属性值,用来设置硬件参数和i2c_adapter结构。
  • i2c_bit_add_numbered_bus注册i2c_adapter,这个是重点,这里面会有算法部分设置,下面继续分析。
static int i2c_gpio_probe(struct platform_device *pdev)
{
...
	unsigned int sda_pin, scl_pin; //sda 和 scl引脚
	int ret;

	/* First get the GPIO pins; if it fails, we'll defer the probe. */
	if (pdev->dev.of_node) {
	    /* 从设备树中获取        sda 和 scl */
		ret = of_i2c_gpio_get_pins(pdev->dev.of_node,
					   &sda_pin, &scl_pin);
		if (ret)
			return ret;
	} else {
		if (!dev_get_platdata(&pdev->dev))
			return -ENXIO;
		pdata = dev_get_platdata(&pdev->dev);
		sda_pin = pdata->sda_pin;
		scl_pin = pdata->scl_pin;
	}
    /*devm_gpio_request 可以自动处理清理工作 */
	ret = devm_gpio_request(&pdev->dev, sda_pin, "sda");
	if (ret) {
		if (ret == -EINVAL)
			ret = -EPROBE_DEFER;	/* Try again later */
		return ret;
	}
	ret = devm_gpio_request(&pdev->dev, scl_pin, "scl");
	if (ret) {
		if (ret == -EINVAL)
			ret = -EPROBE_DEFER;	/* Try again later */
		return ret;
	}

	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;
	/* 分配了i2c_adapter */	
	adap = &priv->adap;
	bit_data = &priv->bit_data;
	pdata = &priv->pdata;

	if (pdev->dev.of_node) {
		pdata->sda_pin = sda_pin;
		pdata->scl_pin = scl_pin;
		/*从设备树获取属性*/
		of_i2c_gpio_get_props(pdev->dev.of_node, pdata);
	} else {
		memcpy(pdata, dev_get_platdata(&pdev->dev), sizeof(*pdata));
	}

    /* 根据获取的设备树属性值设置开漏情况 */
	if (pdata->sda_is_open_drain) {
		gpio_direction_output(pdata->sda_pin, 1);
		bit_data->setsda = i2c_gpio_setsda_val;
	} else {
		gpio_direction_input(pdata->sda_pin);
		bit_data->setsda = i2c_gpio_setsda_dir;
	}

	if (pdata->scl_is_open_drain || pdata->scl_is_output_only) {
		gpio_direction_output(pdata->scl_pin, 1);
		bit_data->setscl = i2c_gpio_setscl_val;
	} else {
		gpio_direction_input(pdata->scl_pin);
		bit_data->setscl = i2c_gpio_setscl_dir;
	}
    /* 根据获取的设备树属性值设置时间参数 */
	if (!pdata->scl_is_output_only)
		bit_data->getscl = i2c_gpio_getscl;
	bit_data->getsda = i2c_gpio_getsda;

	if (pdata->udelay)
		bit_data->udelay = pdata->udelay;
	else if (pdata->scl_is_output_only)
		bit_data->udelay = 50;			/* 10 kHz */
	else
		bit_data->udelay = 5;			/* 100 kHz */

	if (pdata->timeout)
		bit_data->timeout = pdata->timeout;
	else
		bit_data->timeout = HZ / 10;		/* 100 ms */

	bit_data->data = pdata;

    /* 根据设备树解析的值设置i2c_adapter */
	adap->owner = THIS_MODULE;
	if (pdev->dev.of_node)
		strlcpy(adap->name, dev_name(&pdev->dev), sizeof(adap->name));
	else
		snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id);

	adap->algo_data = bit_data;
	adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
	adap->dev.parent = &pdev->dev;
	adap->dev.of_node = pdev->dev.of_node;

	adap->nr = pdev->id;

	/* 注册i2c_adapter */
	ret = i2c_bit_add_numbered_bus(adap);
...
}

进入i2c_bit_add_numbered_bus看看,这个函数在drivers\i2c\algos\i2c-algo-bit.c中定义,他调用__i2c_bit_add_bus__i2c_bit_add_bus里面设置了adap->algo = &i2c_bit_algo;,这就是i2c核心算法结构,下面继续看i2c_bit_algo

int i2c_bit_add_numbered_bus(struct i2c_adapter *adap)
{
	return __i2c_bit_add_bus(adap, i2c_add_numbered_adapter);
}

static int __i2c_bit_add_bus(struct i2c_adapter *adap,
			     int (*add_adapter)(struct i2c_adapter *))
{
...
	/* 核心算法 */
	adap->algo = &i2c_bit_algo;
...
}

i2c_bit_algo结构中bit_xfer就是gpio模拟i2c_adapter的传输函数。

const struct i2c_algorithm i2c_bit_algo = {
	.master_xfer	= bit_xfer,
	.functionality	= bit_func,
};

从上面的分析,可以知道I2C-GPIO的驱动层次如下:
在这里插入图片描述

3.2 bit_xfer传输函数分析

传输函数是根据i2c协议来完成的,i2c_start发起一个start信号,接着根据i2c_msg 来判断读写,readbytes读一个字节,sendbytes写一个字节。结束后i2c_stop发出停止信号。

static int bit_xfer(struct i2c_adapter *i2c_adap,
		    struct i2c_msg msgs[], int num)
{
	struct i2c_msg *pmsg;
	struct i2c_algo_bit_data *adap = i2c_adap->algo_data;
	int i, ret;
	unsigned short nak_ok;

	if (adap->pre_xfer) {
		ret = adap->pre_xfer(i2c_adap);
		if (ret < 0)
			return ret;
	}

	bit_dbg(3, &i2c_adap->dev, "emitting start condition\n");
	i2c_start(adap); //发出Start信号
	for (i = 0; i < num; i++) {
		pmsg = &msgs[i]; //循环取出i2c_msg 
		nak_ok = pmsg->flags & I2C_M_IGNORE_NAK;
		if (!(pmsg->flags & I2C_M_NOSTART)) {
			if (i) {
				bit_dbg(3, &i2c_adap->dev, "emitting "
					"repeated start condition\n");
				i2c_repstart(adap);
			}
			ret = bit_doAddress(i2c_adap, pmsg);
			if ((ret != 0) && !nak_ok) {
				bit_dbg(1, &i2c_adap->dev, "NAK from "
					"device addr 0x%02x msg #%d\n",
					msgs[i].addr, i);
				goto bailout;
			}
		}
		if (pmsg->flags & I2C_M_RD) {
			/* read bytes into buffer(读一个字节)*/ 
			ret = readbytes(i2c_adap, pmsg);
			if (ret >= 1)
				bit_dbg(2, &i2c_adap->dev, "read %d byte%s\n",
					ret, ret == 1 ? "" : "s");
			if (ret < pmsg->len) {
				if (ret >= 0)
					ret = -EIO;
				goto bailout;
			}
		} else {
			/* write bytes from buffer (写一个字节)*/
			ret = sendbytes(i2c_adap, pmsg);
			if (ret >= 1)
				bit_dbg(2, &i2c_adap->dev, "wrote %d byte%s\n",
					ret, ret == 1 ? "" : "s");
			if (ret < pmsg->len) {
				if (ret >= 0)
					ret = -EIO;
				goto bailout;
			}
		}
	}
	ret = i;

bailout:
	bit_dbg(3, &i2c_adap->dev, "emitting stop condition\n");
	i2c_stop(adap);

	if (adap->post_xfer)
		adap->post_xfer(i2c_adap);
	return ret;
}

字节读写再细分到位操作,在i2c_outb函数中实现。

static int i2c_outb(struct i2c_adapter *i2c_adap, unsigned char c)
{
	int i;
	int sb;
	int ack;
	struct i2c_algo_bit_data *adap = i2c_adap->algo_data;

	/* assert: scl is low */
	for (i = 7; i >= 0; i--) {
		sb = (c >> i) & 1;
		setsda(adap, sb);
		udelay((adap->udelay + 1) / 2);
		if (sclhi(adap) < 0) { /* timed out */
			bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "
				"timeout at bit #%d\n", (int)c, i);
			return -ETIMEDOUT;
		}
		/* FIXME do arbitration here:
		 * if (sb && !getsda(adap)) -> ouch! Get out of here.
		 *
		 * Report a unique code, so higher level code can retry
		 * the whole (combined) message and *NOT* issue STOP.
		 */
		scllo(adap);
	}
	sdahi(adap);
	if (sclhi(adap) < 0) { /* timeout */
		bit_dbg(1, &i2c_adap->dev, "i2c_outb: 0x%02x, "
			"timeout at ack\n", (int)c);
		return -ETIMEDOUT;
	}

	/* read ack: SDA should be pulled down by slave, or it may
	 * NAK (usually to report problems with the data we wrote).
	 */
	ack = !getsda(adap);    /* ack: sda is pulled low -> success */
	bit_dbg(2, &i2c_adap->dev, "i2c_outb: 0x%02x %s\n", (int)c,
		ack ? "A" : "NA");

	scllo(adap);
	return ack;
	/* assert: scl is low (sda undef) */
}

四、怎么使用i2c-gpio

设置设备树,在里面添加一个节点即可,示例代码看上面"设备树分析"部分,也可以参考 Linux-4.9.88\Documentation\devicetree\bindings\i2c\i2c-gpio.txt

五、总结

本文分析了gpio模拟的i2c_adapter驱动程序i2c-gpio.c。

相关文章:

  • EdgeNext模型详解及代码复现
  • 开启AI短剧新纪元!SkyReels-V1/A1双剑合璧!昆仑万维开源首个面向AI短剧的视频生成模型
  • 李宏毅机器学习课程学习笔记04 | 浅谈机器学习-宝可梦、数码宝贝分类器
  • javaEE初阶————多线程初阶(5)
  • Unity 接入本地部署的DeepSeek
  • C语言复习4:有关数组的基础常见算法
  • C语言复习8:动态内存分配
  • 流程定义和流程实例
  • rk3568 sysrq如何使用快捷键
  • 题解 | 牛客周赛83 Java ABCDEF
  • 多Agent协作智能系统
  • Flutter系列教程之(9)——Flutter调用Android原生
  • 让自己如何快速有用一台华为云桌面。
  • 2025国家护网HVV高频面试题总结来了01(题目+回答)
  • C# dll文件的反编译获取源码
  • 【蓝桥】大小写转换
  • [含文档+PPT+源码等]精品基于Python实现的微信小程序的在线医疗咨询系统
  • 我们应该如何优化UI(基于UGUI)
  • 爬虫:PhantomJS的详细使用和实战案例
  • 【电力——tarjan割点,求连通块】
  • 程序员做图网站/百度基木鱼建站
  • 定制开发网站的公司/专业做网站的公司
  • 做网站之前需要准备什么条件/苏州网站制作
  • 金寨建设工程质量监督站网站/免费搭建个人网站
  • 深圳网站建设开发需要多少钱/微信软文范例
  • 翼讯自助网站/获客软件排名前十名