您的位置:首页 > 运维架构 > Linux

关于Linux下I2C驱动的Probe方式没有自动生成i2c_client且没有自动调用i2c_driver的.probe指向的函数的问题解决

2015-05-14 16:48 881 查看

背景

项目内容是使用i2c接口的温湿度传感器SHT21,连接至mini2440上实时采集气压存储至板子上的sqlite数据库并显示一个Qt的界面。同学负责SHT21的驱动编写,博主负责上层应用的开发。

开发工具均使用mini2440官方提供的工具,交叉编译器使用gcc4.4.3 with EABI,开发板内核是linux-2.6.32.2版本,主机环境是Ubuntu12.04。

问题

某一天,博主被告知,SHT21的i2c驱动编写完成,测试一切正常。于是博主从同学那里将驱动文件SHT21.ko和测试程序文件sht21_test拷贝过来,并传入开发板。将传感器连接至mini2440的iic接口,上电,加载驱动
insmod SHT21.ko
,然后运行测试程序:
./sht21_test
,结果输出数值错误,很显然是SHT21传感器数据并未采集成功。

分析

由于sht21的驱动是注册为字符设备,遂查看设备列表
ls /dev
,发现其中并没有显示出名为SHT21的驱动。初步推断,虽然insmod成功载入了驱动,但sht21驱动未能成功在系统中注册。

查看驱动的源码,发现同学是使用i2c的probe方式实现驱动的。而向系统中注册字符设备的代码存在于一个名为
static int __devinit SHT21_probe(struct i2c_client *client, const struct i2c_device_id *dev_id)
的函数中:

[code]static int __devinit SHT21_probe(struct i2c_client *client, const struct i2c_device_id *dev_id)
{   
    //...

    //向系统获得一个主设备号并且加载对文件操作的结构体
    major = register_chrdev(0, "SHT21", &SHT21_fops);

    //...

    return 0;
}


于是怀疑此处的SHT21_probe()函数并未真正运行,在函数中使用printk()输出调试信息,然后重新编译驱动,加载,发现并未输出任何调试信息,因此确定此函数果然没有运行。

这里,SHT21_probe函数实际上是对应i2c驱动结构体i2c_driver中的.probe成员的:

[code]static struct i2c_driver SHT21_driver = {
    .driver = {
        .name   = "SHT21",
        .owner    = THIS_MODULE,
        },
    .id_table = SHT21_id,
    .probe = SHT21_probe,
    .remove = __devexit_p(SHT21_remove),
};


查阅得知,SHT21_probe()函数运行的条件是,当struct i2c_device_id里面的字符串与 I2C_BOARD_INFO里面的匹配后,SHT21_probe()函数会被自动调用。线索渐渐明朗,问题应该就出在这里,本该被自动调用的SHT21_probe()函数并未被调用,也就是说,struct i2c_device_id里面的字符串很可能未能与 I2C_BOARD_INFO中的某些内容匹配。

很容易地在sht21驱动源文件中发现了struct i2c_device_id相关代码:

[code]static const struct i2c_device_id SHT21_id[] = {
    {"SHT21",0x40},
    {}
};
MODULE_DEVICE_TABLE(i2c, SHT21_id);


可是I2C_BOARD_INFO是什么,又在哪里呢?

查阅资料得知,I2C_BOARD_INFO是一个宏,在include/linux/i2c.h文件中定义,用来初始化i2c_board_info结构体的两个必须初始化的成员变量,type和addr:

[code]#define I2C_BOARD_INFO(dev_type,dev_addr) \
   .type = dev_type, .addr = (dev_addr)


而i2c_board_info结构体的作用,就是保存I2C设备(这里应该注意是指硬件设备,对应于本项目中就是sht21气压传感器这个硬件)的相关信息。

但i2c设备信息是如何通过i2c_board_info结构体来注册的呢?查阅了网络上的很多资料,大都提到了一个函数:i2c_register_board_info();I2C设备驱动编写, struct i2c_device_id, struct i2c_driver, i2c_add_driver, i2c_register_board_info一文中提到:

1、在arch/arm/mach-xxx/ 自己的平台文件里添加i2c信息,美其名曰:i2c_board_info 例如:

static struct i2c_board_info __initdata xxxi2c_board_info[] = {

{

I2C_BOARD_INFO(“abcd1”, 0x20), /* 字符串要与后面的匹配,0x20是从设备地址 */

.platform_data = 0,

},

{

I2C_BOARD_INFO(“abcd2”, 0x21),

.platform_data = 0,

}, };


然后调用i2c_register_board_info(1, xxxi2c_board_info, ARRAY_SIZE(xxxi2c_board_info));

很容易可以发现,如果采用这种方法,那么博主需要在linux内核源码文件arch/arm/mach-s3c2440目录中任意.c文件(如mac-mini2440.c)中增加如下代码:

[code]static struct i2c_board_info __initdata sensors_i2c_board_info[] = {
    {
    // 此处字符串就是前面提到的要与struct i2c_device_id匹配的地方,其中0x40是从设备(sht21传感器)地址
        I2C_BOARD_INFO("SHT21", 0x40), 
        .platform_data = 0,
    },
};

i2c_register_board_info(1, sensors_i2c_board_info, ARRAY_SIZE(sensors_i2c_board_info));


然后重新编译内核,将新内核烧录至2440开发板。

显然,这种方法也太麻烦了点,如果以后需要再增加一个i2c接口的其他传感器,如温湿度传感器BMP180,那么又要将新传感器信息写入内核重新编译,灵活性太差。linux这么强大,一定已经考虑到了这种情况而提供了其他方法。

继续查阅资料,在struct i2c_board_info 的定义(include/linux/i2c.h)中发现了线索:

[code]251/**
252 * struct i2c_board_info - template fordevice creation
253 * @type: chip type, to initializei2c_client.name
254 * @flags: to initializei2c_client.flags
255 * @addr: stored in i2c_client.addr
256 * @platform_data: stored ini2c_client.dev.platform_data
257 * @archdata: copied intoi2c_client.dev.archdata
258 * @of_node: pointer to OpenFirmwaredevice node
259 * @acpi_node: ACPI device node
260 * @irq: stored in i2c_client.irq
261 *
262 * I2C doesn't actually support hardwareprobing, although controllers and
263 * devices may be able to useI2C_SMBUS_QUICK to tell whether or not there's
264 * a device at a given address.  Drivers commonly need more information than
265 * that, such as chip type,configuration, associated IRQ, and so on.
266 *
267 * i2c_board_info is used to buildtables of information listing I2C devices
268 * that are present.  This information is used to grow the drivermodel tree.
269 * For mainboards this is donestatically using i2c_register_board_info();
270 * bus numbers identify adapters thataren't yet available.  For add-on boards,
271 * i2c_new_device() does thisdynamically with the adapter already known.
272 */
273struct i2c_board_info {
274   char        type[I2C_NAME_SIZE];
275   unsigned short  flags;
276   unsigned short  addr;
277   void        *platform_data;
278   struct dev_archdata *archdata;
279   struct device_node *of_node;
280   struct acpi_dev_node acpi_node;
281   int     irq;
282};


从注释可以看到,i2c_board_info结构体用来保存I2C设备的相关信息,Linux根据这些信息创建I2C设备相关的设备模型树。对于mainboards,通过调用i2c_register_board_info()静态完成。对于add-on boards,通过调用i2c_new_device()动态完成。

可见,struct i2c_board_info设备信息可以还可以通过调用i2c_new_device()函数动态完成。从字面意思可以推断出来,使用这种方法可以不用增加新设备的时候重新编译内核,而是可以动态增加新i2c设备。

柳暗花明,但这种方法具体怎么使用暂且不表,因为博主同时找到了另一种更简单灵活的方法。

打开linux内核源码根目录,定位到Documentation\i2c\instantiating-devices文档,进入,发现标题是How to instantiate I2C devices,然后Method 1,一直到Method 4,发现linux共提供了4中方法用于实例化i2c设备!仔细阅读发现前两种方法,就是上文提到的i2c_register_board_info()和i2c_new_device(),第三种方法帮助文档中明确标注不推荐,看第四种方法:

Method 4: Instantiate from user-space

//——————————————–


In general, the kernel should know which I2C devices are connected and what addresses they live at. However, in certain cases, it does not, so a sysfs interface was added to let the user provide the information. This interface is made of 2 attribute files which are created in every I2C bus directory: new_device and delete_device. Both files are write only and you must write the right parameters to them in order to properly instantiate, respectively delete, an I2C device.


File new_device takes 2 parameters: the name of the I2C device (a string) and the address of the I2C device (a number, typically expressed in hexadecimal starting with 0x, but can also be expressed in decimal.)


File delete_device takes a single parameter: the address of the I2C device. As no two devices can live at the same address on a given I2C segment, the address is sufficient to uniquely identify the device to be deleted.


Example:

# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device



While this interface should only be used when in-kernel device declaration can’t be done, there is a variety of cases where it can be helpful:

* The I2C driver usually detects devices (method 3 above) but the bus segment your device lives on doesn’t have the proper class bit set and thus detection doesn’t trigger.

* The I2C driver usually detects devices, but your device lives at an unexpected address.

* The I2C driver usually detects devices, but your device is not detected, either because the detection routine is too strict, or because your device is not officially supported yet but you know it is compatible.

* You are developing a driver on a test board, where you soldered the I2C device yourself.


This interface is a replacement for the force_* module parameters some I2C drivers implement. Being implemented in i2c-core rather than in each device driver individually, it is much more efficient, and also has the advantage that you do not have to reload the driver to change a setting. You can also instantiate the device before the driver is loaded or even available, and you don’t need to know what driver the device needs.

大概读一遍后,直接看Example,echo后面的
eeprom 0x50
是不是非常熟悉,没错,一个是要增加的i2c设备名,另一个就是i2c设备地址。

Linux使用系统文件(sysfs)接口的方式让用户直接从用户空间提供i2c设备信息(即设备名和设备地址)给内核。这种接口在每个I2C总线目录下提供了两个文件结点(new_device和delete_device),这两个文件结点都是只写的,用户必须向它们写入正确的参数来(实例化)创建或者删除I2C设备。

创建I2C设备:

# echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device


删除I2C设备:

# echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device


显而易见,方法4提供了一个更加灵活方便的方式来实例化i2c设备,实现了与i2c_register_board_info()方式完全相同的功能!

这种方法的优点也已经在上述文档中说明了:

虽然说sysfs接口方法只能在内核代码无法定义设备的情况下使用,但该方法在下列情况非常有用

(1)I2C设备驱动通过枚举总线的方式来探测设备,但设备所挂的I2C总线却没有将枚举类位域置位,因为枚举将不可能发生的情况。

(2)I2C设备驱动探测设备时,设备的地址却不是预知的。

(3)I2C设备驱动探测设备时,探测过程因为探测过程太严格或者设备不支持探测(你不能预先知道设备商未支持)而失败。

(4)为实验板自己焊上的I2C设备开发驱动程序测试使用时

解决

方法找到,解决步骤非常简单:

1. 将传感器连接至mini2440的iic接口并上电

2. 实例化sht21传感器:
# echo SHT21 0x40 > /sys/bus/i2c/devices/i2c-0/new_device


3. 加载驱动
insmod SHT21.ko
,注意这时候成功出现进入SHT21_probe()函数的提示信息,在/dev中也成功出现了一个名为SHT21的设备

4. 运行测试程序:
./sht21_test
,输出数据正常,说明sht21传感器数据成功被采集到

总结

为了能够成功使用自己外接的i2c设备,需要2大步骤:

定义和注册I2C设备,也就是使用linux帮助文档中提到的实例化设备(instantiating devices)的4中方法,将需要使用i2c接口通信的硬件设备信息提供给linux系统。又分为2个小步骤:

步骤1:用i2c_board_info保存I2C设备相关信息

步骤2:调用i2c_register_board_info或通过其他3中方法注册i2c设备相关信息

定义和注册I2C设备驱动,也就是与其他驱动类似的的编写方法来实现驱动部分。又分为3个小步骤:

步骤1:定义i2c_driver结构体变量

步骤2:调用i2c_add_driver注册i2c_driver结构体变量

步骤3:实现i2c_driver中要示实现的函数

博主在linux驱动方面也是新手,为了解决这个问题花了不少时间,也走了很多弯路,如果你也对linux的i2c驱动不了解,但是又需要i2c设备驱动(特别是关于i2c接口的传感器驱动)编译、编写方法的快速入门指南的话,可以参阅博主的另一篇文章:从零开始编写、编译、载入、测试基于I2C总线接口的温湿度传感器SHT21的Linux下字符设备驱动

参考资料

关于LINUX I2C驱动的Probe方法

Linux设备驱动程序架构分析之一个I2C驱动实例

LinuxI2C子系统之一实例化IC2设备(Client)的四种方法(三、四)

基于S3C2440的嵌入式Linux驱动——AT24C02(EEPROM I2C接口)驱动解读

S3C2440 Linux驱动移植——AT24C02(EEPROM)驱动
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐