linux内核的i2c-gpio是使用GPIO模拟I2C协议的驱动,只需要配置2根GPIO即可使用。Linux的I2C子系统比较复杂,笔者暂时还没有研究。本着“实用”的目的,介绍一下如何使用这个驱动及一些注意事项。
Linux内核很多驱动都使用到I2C子系统。如EEPROM、RTC等。
GPIO模拟I2C协议的驱动位于drivers/i2c/busses目录。驱动名称为“i2c-gpio”,驱动文件为drivers/i2c/busses/i2c-gpio.c。
Device Drivers->
I2C support --->
I2C Hardware Bus support --->
<*> GPIO-based bitbanging I2C
从配置中看到将驱动整合到内核中,而不是module形式。这样能保证在其它I2C板级信息注册之前,已经存在了i2c总线。另外,还需要GPIO库支持:
[*] GPIO Support --->
否则无法不会出现选项“GPIO-based bitbanging I2C”。
本文不是深入I2C子系统的,所以抛开原理方面的描述。看一下i2c平台数据结构i2c_gpio_platform_data的声明:
/**
* struct i2c_gpio_platform_data - Platform-dependent data for i2c-gpio
* @sda_pin: GPIO pin ID to use for SDA
* @scl_pin: GPIO pin ID to use for SCL
* @udelay: signal toggle delay. SCL frequency is (500 / udelay) kHz
* @timeout: clock stretching timeout in jiffies. If the slave keeps
* SCL low for longer than this, the transfer will time out.
* @sda_is_open_drain: SDA is configured as open drain, i.e. the pin
* isn't actively driven high when setting the output value high.
* gpio_get_value() must return the actual pin state even if the
* pin is configured as an output.
* @scl_is_open_drain: SCL is set up as open drain. Same requirements
* as for sda_is_open_drain apply.
* @scl_is_output_only: SCL output drivers cannot be turned off.
*/
struct i2c_gpio_platform_data {
unsigned int sda_pin;
unsigned int scl_pin;
int udelay;
int timeout;
unsigned int sda_is_open_drain:1;
unsigned int scl_is_open_drain:1;
unsigned int scl_is_output_only:1;
};
重要的是sda_pin和scl_pin,分别表示I2C的SDA、SCL信号引脚。udelay可控制SCL频率,计算公式为:500/udelay kHZ。timeout为超时时间,单位为jiffies。sda_is_open_drain和scl_is_open_drain分别表示SDA和SCL是否为开漏电路,对此方面研究不深,不再描述。
一个实例如下:
static struct i2c_gpio_platform_data i2c_gpio_data = {
.sda_pin = 68,
.scl_pin = 88,
.timeout = 100,
.udelay = 2,
};
例子中使用的引脚分别为68和88,是由主板硬件确定的。
static struct platform_driver i2c_gpio_driver = {
.driver = {
.name = "i2c-gpio",
.owner = THIS_MODULE,
.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;
}
subsys_initcall(i2c_gpio_init);
从代码分析知,这里将GPIO模拟I2C总线当作平台设备处理。而从i2c_gpio_driver结构体中可以看到驱动名称为i2c-gpio。因此要使用这个驱动,必须另外定义一个platform设备,并调用函数platform_device_register注册。本文实例如下
static struct platform_device i2c_gpio_device = {
.name = "i2c-gpio",
.id = 0, // first bus for "i2c-gpio", so --> 0
.dev = {
.platform_data = &i2c_gpio_data,
.release = platformdev_release,
},
};
其中name表示设备名称,这里必须为“leds-gpio”,platform_data即为前面定义的i2c_gpio_data。id表示i2c-gpio的第几条I2C总线。驱动正常工作后,将生成/sys/bus/platform/devices/i2c-gpio.id目录,里面有挂载在此总线上的设备地址。
最后,注册设备——建议在板子的GPIO正常工作之后再进行注册。
platform_device_register(&i2c_gpio_device);
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
struct dev_archdata *archdata;
struct device_node *of_node;
struct acpi_dev_node acpi_node;
int irq;
};
该结构体包括了I2C设备名称、标志、地址等等信息。
static struct at24_platform_data at24_eeprom = {
.byte_len = 2 * 1024 / 8,
.page_size = 16,
.flags = 0,
};
static struct i2c_board_info my_i2c_boardinfo[] = {
{
I2C_BOARD_INFO("lm75", 0x48),
},
{
I2C_BOARD_INFO("24c02", 0x50), // 24c02 == at24 driver
.platform_data = &at24_eeprom,
},
};
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
i2c_register_board_info(0, i2c_info, ARRAY_SIZE(i2c_info));
不过如果以modules形式编译,则会提示i2c_register_board_info未定义:
WARNING: "i2c_register_board_info" [drivers/gpio/gpio-misc.ko] undefined!
struct i2c_adapter* adap = NULL;
struct i2c_client* client = NULL;
adap = i2c_get_adapter(i2c_gpio_device.id);
if (adap)
{
for (i = 0; i < ARRAY_SIZE(my_i2c_boardinfo); i++)
{
client = i2c_new_device(adap, &my_i2c_boardinfo[i]);
pr_info("Add %s to adapter %s %s.\n", my_i2c_boardinfo[i].type,
adap->name, client?"ok":"failed");
}
}
else
{
pr_info("i2c bus %d found no adapter...\n", i2c_gpio_device.id);
}
使用GPIO模拟I2C驱动前,最好保证系统的GPIO已能正常工作。
在驱动中可以注册多条i2c-gpio总线,驱动名称均为i2c-gpio,但根据id值来区别不的同总线。比如主板上有3条GPIO模拟总线,则可分别命名为0、1、2。
参考资源:
1、内核源码官网:https://www.kernel.org
2、内核源码查询:http://lxr.free-electrons.com/source/?v=3.17