我的内核学习笔记12:linux i2c-gpio驱动应用实例

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。

二、内核配置

本文基于linux 3.17.1版本内核进行分析。
内核配置(make menuconfig)信息如下:

Device Drivers->
    I2C support  --->
        I2C Hardware Bus support  --->
            <*> GPIO-based bitbanging I2C 

从配置中看到将驱动整合到内核中,而不是module形式。这样能保证在其它I2C板级信息注册之前,已经存在了i2c总线。另外,还需要GPIO库支持:

[*] GPIO Support  --->  

否则无法不会出现选项“GPIO-based bitbanging I2C”。

三、设备注册及使用

3.1 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,是由主板硬件确定的。

3.2 平台设备

i2c-gpio驱动定义入口代码如下(drivers/i2c/busses/i2c-gpio.c):
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);

四、I2C板级信息注册

i2c设备的板级信息由i2c_board_info结构体描述,其声明如下:
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设备名称、标志、地址等等信息。
比如主板上有2个I2C设备:LM75和EEPROM,地址分别为0x48、0x50——地址根据datasheet说明及硬件接线来确定。下面定义板级信息:

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

2C_BOARD_INFO宏定义如下:
#define I2C_BOARD_INFO(dev_type, dev_addr) \
    .type = dev_type, .addr = (dev_addr)

常见注册方法有分静态注册i2c_register_board_info和动态注册i2c_new_device。大部分ARM框架都使用静态注册,使用很简单,如下:

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!

这里使用第二种动态注册的方式。先调用i2c_get_adapter获取适配器(参数为i2c总线,根据前面i2c_gpio_device定义的id确定),然后调用i2c_new_device添加到该适配器中。代码片段如下:

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

五、用户空间

驱动正常工作后,会生成/sys/bus/platform/devices/i2c-gpio.0目录(以本文为例)。里面存放着对应驱动映射的文件。比如/sys/bus/platform/devices/i2c-gpio.0/i2c-0/0-0050/eeprom即为eeprom内容。该文件属性为可读写,因此修改此文件内容,即为修改EEPROM内容(此方法有一定危险性,就不直接写出来了)。
而查看主板温度,则可以用如下命令:
cat /sys/bus/platform/devices/i2c-gpio.0/i2c-0/0-0048/hwmon/hwmon0/temp1_input
注:目录出现的0-0050表示该I2C设备在第0条总线上的0x50地址。

六、总结

使用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


李迟 2016.12.9 周五 晚

你可能感兴趣的:(内核驱动学习笔记)