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

发布时间:2024年01月18日

在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

文章来源:https://blog.csdn.net/qq_16933601/article/details/135686230
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。