【驱动】I2C驱动分析(五)-模拟I2C驱动
在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_data
的data
字段中。 -
设置适配器结构体
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", ®)) 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_pin
和scl_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_dir
,i2c_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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架