首页 > 其他分享 >【驱动】I2C驱动分析(五)-模拟I2C驱动

【驱动】I2C驱动分析(五)-模拟I2C驱动

时间:2024-01-18 23:03:53浏览次数:33  
标签:scl i2c pin pdata gpio 驱动 I2C data 模拟

在drivers/i2c/busses下包含各种I2C总线驱动,使用GPIO模拟I2C总线的驱动i2c-gpio.c,这里只分析i2c-gpio.c

i2c-gpio.c它是gpio模拟I2C总线的驱动,总线也是个设备,在这里将总线当作平台设备处理,那驱动当然是平台设备驱动,看它的驱动注册和注销函数。

i2c_gpio_init

i2c_gpio_init 调用platform_driver_register初始化 i2c-gpio 驱动程序,i2c_gpio_driver结构体就是标准的platform-device驱动模型。

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,
};

static int __init i2c_gpio_init(void)
{
	int ret;

	ret = platform_driver_register(&i2c_gpio_driver);
	if (ret)
		printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret);

	return ret;
}

i2c_gpio_probe

i2c_gpio_probe是在系统启动时探测并初始化GPIO模拟的I2C总线,并将其注册为一个I2C适配器。通过读取设备树或平台数据来获取SDA和SCL引脚的GPIO编号和其他配置信息,并根据配置进行引脚的初始化和设置。最后,将适配器添加到I2C核心框架中,并保存相关数据供后续使用。

static int i2c_gpio_probe(struct platform_device *pdev)
{
	struct i2c_gpio_private_data *priv;
	struct i2c_gpio_platform_data *pdata;
	struct i2c_algo_bit_data *bit_data;
	struct i2c_adapter *adap;
	unsigned int sda_pin, scl_pin;
	int ret;

	/* First get the GPIO pins; if it fails, we'll defer the probe. */
	if (pdev->dev.of_node) {
		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;
	}

	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;
	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;

	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;
	ret = i2c_bit_add_numbered_bus(adap);
	if (ret)
		return ret;

	platform_set_drvdata(pdev, priv);

	dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n",
		 pdata->sda_pin, pdata->scl_pin,
		 pdata->scl_is_output_only
		 ? ", no clock stretching" : "");

	return 0;
}

  • 判断传入的platform_device结构体是否具有设备树节点(of_node),如果有,则通过of_i2c_gpio_get_pins函数获取SDA和SCL引脚的GPIO编号,如果失败则返回错误码。

  • 如果没有设备树节点,则通过dev_get_platdata函数获取平台数据结构体指针pdata,并从中获取SDA和SCL引脚的GPIO编号。

  • 使用devm_gpio_request函数请求SDA和SCL引脚的GPIO资源,并将其设置为"sda"和"scl"的功能。如果请求失败,则根据错误码判断是否需要延迟探测。

  • 使用devm_kzalloc函数为私有数据结构体priv分配内存,并将适配器结构体指针adap和位算法数据结构体指针bit_data指向对应的内存空间。

  • 根据是否具有设备树节点,分别将SDA和SCL引脚的GPIO编号保存到平台数据结构体pdata的相应字段中,并通过of_i2c_gpio_get_props函数从设备树节点中获取其他属性。

  • 根据平台数据结构体pdata中的配置,设置SDA和SCL引脚的方向和输出值,并根据需要设置位算法数据结构体bit_data的相应回调函数。

  • 根据平台数据结构体pdata中的配置,设置位算法数据结构体bit_data的时序参数,包括时延(udelay)和超时时间(timeout)。

  • 将平台数据结构体pdata指针保存到位算法数据结构体bit_datadata字段中。

  • 设置适配器结构体adap的各个字段,包括所有者(owner)、名称(name)、算法数据(algo_data)、类别(class)、父设备(parent)和设备树节点(of_node),设置适配器结构体adap的编号(nr)为设备的ID。

  • 调用i2c_bit_add_numbered_bus函数将适配器添加到I2C核心框架,并返回添加结果。

  • 调用platform_set_drvdata函数将私有数据结构体priv与平台设备结构体pdev相关联。

  • 打印使用的SDA和SCL引脚的信息,包括引脚编号和是否支持时钟拉伸,最后返回0表示探测成功。

of_i2c_gpio_get_props

of_i2c_gpio_get_props作用是从设备树中读取I2C GPIO模拟总线的相关属性,这些属性包括时延、超时时间以及SDA和SCL引脚的特性,然后将这些属性值保存到平台数据结构体中,供后续的初始化和配置使用。

static void of_i2c_gpio_get_props(struct device_node *np,
				  struct i2c_gpio_platform_data *pdata)
{
	u32 reg;

	of_property_read_u32(np, "i2c-gpio,delay-us", &pdata->udelay);

	if (!of_property_read_u32(np, "i2c-gpio,timeout-ms", &reg))
		pdata->timeout = msecs_to_jiffies(reg);

	pdata->sda_is_open_drain =
		of_property_read_bool(np, "i2c-gpio,sda-open-drain");
	pdata->scl_is_open_drain =
		of_property_read_bool(np, "i2c-gpio,scl-open-drain");
	pdata->scl_is_output_only =
		of_property_read_bool(np, "i2c-gpio,scl-output-only");
}
  • of_property_read_u32函数从设备树节点np中读取名为"i2c-gpio,delay-us"的属性值,并将其保存到pdata->udelay字段中。该属性指定了I2C总线中时延的微秒数。

  • of_property_read_u32函数从设备树节点np中读取名为"i2c-gpio,timeout-ms"的属性值,并将其保存到pdata->timeout字段中。该属性指定了I2C总线的超时时间,单位为毫秒。函数内部将毫秒转换为jiffies的值,并赋给pdata->timeout

  • 使用of_property_read_bool函数从设备树节点np中读取名为"i2c-gpio,sda-open-drain"的布尔属性值,并将其保存到pdata->sda_is_open_drain字段中。该属性指示SDA引脚是否为开漏输出。

  • 使用of_property_read_bool函数从设备树节点np中读取名为"i2c-gpio,scl-open-drain"的布尔属性值,并将其保存到pdata->scl_is_open_drain字段中。该属性指示SCL引脚是否为开漏输出。

  • 使用of_property_read_bool函数从设备树节点np中读取名为"i2c-gpio,scl-output-only"的布尔属性值,并将其保存到pdata->scl_is_output_only字段中。该属性指示SCL引脚是否仅为输出模式。

of_i2c_gpio_get_pins

of_i2c_gpio_get_pins从给定的设备节点中获取 I2C 总线的 GPIO 引脚

static int of_i2c_gpio_get_pins(struct device_node *np,
				unsigned int *sda_pin, unsigned int *scl_pin)
{
	if (of_gpio_count(np) < 2)
		return -ENODEV;

	*sda_pin = of_get_gpio(np, 0);
	*scl_pin = of_get_gpio(np, 1);

	if (*sda_pin == -EPROBE_DEFER || *scl_pin == -EPROBE_DEFER)
		return -EPROBE_DEFER;

	if (!gpio_is_valid(*sda_pin) || !gpio_is_valid(*scl_pin)) {
		pr_err("%s: invalid GPIO pins, sda=%d/scl=%d\n",
		       np->full_name, *sda_pin, *scl_pin);
		return -ENODEV;
	}

	return 0;
}
  • of_gpio_count() 函数检查给定设备节点 np 是否至少包含两个 GPIO 引脚。如果 GPIO 引脚数量小于 2,那么返回错误码 -ENODEV,表示设备不支持。
  • of_get_gpio() 函数分别获取设备节点 np 中索引为 0 和 1 的 GPIO 引脚,并将结果保存在 sda_pinscl_pin 变量中。
  • 检查获取的 GPIO 引脚是否为延迟探测状态(-EPROBE_DEFER)。如果是延迟探测状态,函数返回 -EPROBE_DEFER,表示需要延迟进一步处理。
  • gpio_is_valid() 函数检查获取的 GPIO 引脚是否有效。如果任何一个或两个 GPIO 引脚无效,函数打印错误信息,并返回错误码 -ENODEV,表示无效的 GPIO 引脚。

i2c_gpio_setscl_dir

static void i2c_gpio_setscl_dir(void *data, int state)
{
	struct i2c_gpio_platform_data *pdata = data;

	if (state)
		gpio_direction_input(pdata->scl_pin);
	else
		gpio_direction_output(pdata->scl_pin, 0);
}

static void i2c_gpio_setsda_dir(void *data, int state)
{
	struct i2c_gpio_platform_data *pdata = data;

	if (state)
		gpio_direction_input(pdata->sda_pin);
	else
		gpio_direction_output(pdata->sda_pin, 0);
}

i2c_gpio_setsda_diri2c_gpio_setscl_dir会调用gpio_direction_input 设置sda,scl的方向。

本文参考

https://www.cnblogs.com/schips/p/linux_driver_i2c-gpio.html

https://www.bilibili.com/read/cv26992165/

https://www.cnblogs.com/yuzaipiaofei/archive/2011/08/26/4124354.html

标签:scl,i2c,pin,pdata,gpio,驱动,I2C,data,模拟
From: https://www.cnblogs.com/dongxb/p/17973621

相关文章

  • 【驱动】I2C驱动分析(四)-关键API解析
    简介在Linux内核源代码中的driver目录下包含一个i2c目录i2c-core.c这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。i2c-dev.c实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的主设备号都为89,次设备号为0-255。I2c-dev.c......
  • 【驱动】I2C驱动分析(六)-I2C驱动模板
    前言LinuxI2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如RTC实时时钟芯片、音视频采集芯片、音视频输出芯片、EEROM芯片、AD/DA转换芯片等等。下面我们看下如何写一个基本的I2C驱动。内......
  • 【驱动】I2C驱动分析(一)-I2C驱协议简介
    什么是I²CI²C叫集成电路总线它是一种串行通信接口,具有双向两线同步串行总线,通常由两根线组成——SDA(串行数据线)和SCL(串行时钟线)和上拉电阻。它们用于需要许多不同部件(例如传感器、引脚、扩展和驱动程序)协同工作的项目,因为它们可以将多达128个设备连接到主板,同时保持清晰......
  • R语言做复杂金融产品的几何布朗运动的模拟
    原文链接:http://tecdat.cn/?p=5334原文出处:拓端数据部落公众号  几何布朗运动(GBM)是模拟大多数依赖某种形式的路径依赖的金融工具的标准主力。虽然GBM基于有根据的理论,但人们永远不应忘记它的最初目的-粒子运动的建模遵循严格的正态分布脉冲。基本公式由下式给出: 标准......
  • 洛谷题单指南-模拟和高精度-P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布
    原题链接:https://www.luogu.com.cn/problem/P1328题意解读:非常简单的一道题,核心考点就是循环数组以及评分规则的构建。评分规则:甲vs乙,1表示甲赢,-1表示甲输,-0表示平转化为数组:intrule[5][5]={0,-1,1,1,-1,1,0,-1,1,-1,-1,1,0,-1,1,-1,......
  • 洛谷题单指南-模拟和高精度-P4924 [1007] 魔法少女小Scarlet
    原题链接:https://www.luogu.com.cn/problem/P4924题意解读:根据题意,通过模拟法,枚举每一个要旋转的矩阵,执行旋转操作即可,关键点在于如何进行矩阵旋转。设定矩阵inta[][],临时矩阵intt[][]用于保存旋转后的矩阵,矩阵长度为len。先考虑要旋转的区域左上角是a[0][0]的情况,区域内每......
  • 世微AP2121太阳能草坪灯驱动芯片
    概述AP2121是一款专为太阳能LED草坪灯设计的专用集成电路。AP2121仅需一个外接电感即可组成太阳能照明装置。AP2121由开关型驱动电路、光控开关电路、过放保护电路、内部集成的肖特基二极管等电路组成。AP2121采用专利技术,使得欠压关断时LED灯无闪烁AP2121工......
  • 恭贺弘博创新-领导力沙盘模拟3.0特训营顺利举行
    2024年1月14日,弘博创新举办领导力沙盘模拟3.0特训营,汇聚了来自各行各业的精英,共同探讨领导力的发展与实践。 本次特训营邀请了国内著名领导力发展与企业管理效能提升专家王兵围老师通过生动的案例、启发式的沙盘演练和沉浸式的互动讨论等形式,引导大家深入了解领导力的内涵与实践。......
  • 无容量限制的出栈序列验证——模拟
    #include<iostream>#include<stack>usingnamespacestd;//stack<int>q;//栈qintn,m,t;constintN=1100;inta[N],sum=1;//入栈队列a,待检验队列b,计数器sumintmain(){ cin>>m>>n>>t;for(inti=1;i<=n;i++) cin>>a[i]; whi......
  • systemtap统计 探测linux驱动中某个函数的执行时间
    直接上脚本:#!/usr/bin/stapglobalstart,endprobemodule("your_driver").function("your_function"){start[tid()]=gettimeofday_ns()}probemodule("your_driver").function("your_function").return{end[tid()]=get......