【驱动】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