Linux中对I2C的支持非常全面,既提供了内核态的访问方式,也提供了用户态的访问方法。

Linux中对I2C的支持可以分为两个层面,一个是adapteralgorithm,对应的是i2c控制器;再一个是driverclient.Linux内核提供了丰富的接口来实添加i2c设备驱动。要添加一个i2c设备驱动,需要几个固定的步骤。首先,需要往i2c设备列表里添加一组设备ID foo_idtable

static struct i2c_device_id foo_idtable[] = {

{ "foo", my_id_for_foo },

{ "bar", my_id_for_bar },

{ }

};


MODULE_DEVICE_TABLE(i2c, foo_idtable);

然后填充i2c driver的数据结构:

static struct i2c_driver foo_driver = {

.driver = {

.name   = "foo",

.pm = &foo_pm_ops,  /* optional */

},


.id_table   = foo_idtable,

.probe      = foo_probe,

.remove     = foo_remove,

/* if device autodetection is needed: */

.class      = I2C_CLASS_SOMETHING,

.detect     = foo_detect,

.address_list  = normal_i2c,


.shutdown   = foo_shutdown, /* optional */

.command    = foo_command,  /* optional, deprecated */

}


剩下的就是逐一初始化foo_probe()foo_remove()foo_detect()等函数。一旦准备好了相应的client结构,就可以实现foo_read_valule()foo_write_value()函数,当然这两者都是基于底层公共的i2c/smbus读写函数:i2c_smbus_read/write_byte_data/word()去实现。


如果确实有i2c设备挂载在某个i2c总线上,可以通过填充i2c_board_info数据结构来构造这个设备的实例,然后调用i2c_new_device()来探测实际接入的i2c设备。当然也可能事先无法确认系统上有什么类型的i2c设备,这个时候需要定义一个call back函数,放在probe()后面,让它去确认是否有指定类型的i2c设备挂在系统上。使用完设备后,可以调用i2c_unregister_device()来注销之前注册的设备。当然执行这些所有操作的前提是设备对应的驱动已经被初始化好并且加载到内核,可以参考下面的代码来实现初始化和退出操作:

static int __init foo_init(void)

{  

return i2c_add_driver(&foo_driver);

}

module_init(foo_init);


static void __exit foo_cleanup(void)

{  

i2c_del_driver(&foo_driver);

}

module_exit(foo_cleanup);


The module_i2c_driver() macro can be used to reduce above code.


module_i2c_driver(foo_driver);


如果驱动中,需要向设备发送或者从设备接受数据,可以调用:

int i2c_master_send(struct i2c_client *client, const char *buf, int count);

int i2c_master_recv(struct i2c_client *client, char *buf, int count);

这两组函数来实现,更多的函数在linux/i2c.h中有说明。



当然linux内核中也提供了设备作为从设备的驱动,这中情况下的系统层次图如下所示:

e.g. sysfs      I2C slave events        I/O registers

+-----------+  v    +---------+     v     +--------+  v  +------------+

| Userspace +........+ Backend +-----------+ Driver +-----+ Controller |

+-----------+       +---------+           +--------+     +------------+

| |

----------------------------------------------------------------+-- I2C

--------------------------------------------------------------+---- Bus


不同于PCI/USB设备,I2C没有提供硬件上自动枚举的能力,因此在初始化i2c设备之前需要显式地指定设备的地址。内核提供了多个显式初始化一个i2c设备的方法:

1、通过bus number声明一个i2c设备:

这种情况尤其适用于i2c作为系统总线的嵌入式系统,以omp2 h4为例,用户需要注册i2c board info:

Example (from omap2 h4):


static struct i2c_board_info h4_i2c_board_info[] __initdata = {

{  

I2C_BOARD_INFO("isp1301_omap", 0x2d),

.irq        = OMAP_GPIO_IRQ(125),

},

{  /* EEPROM on mainboard */

I2C_BOARD_INFO("24c01", 0x52),

.platform_data = &m24c01,

},

{  /* EEPROM on cpu card */

I2C_BOARD_INFO("24c01", 0x57),

.platform_data = &m24c01,

},

};


static void __init omap_h4_init(void)

{

(...)

i2c_register_board_info(1, h4_i2c_board_info,

ARRAY_SIZE(h4_i2c_board_info));

(...)

}


2、通过设备树申明一个i2c设备:

这种方式需要把心建的设备节点挂在master conrollor对应的设备树上,也就是说它必须是master controller的子节点,如下面的例子所示:

i2c1: i2c@400a0000 {

/* ... master properties skipped ... */

clock-frequency = <100000>;


flash@50 {

compatible = "atmel,24c256";

reg = <0x50>;

};


pca9532: gpio@60 {

compatible = "nxp,pca9532";

gpio-controller;

#gpio-cells = <2>;

reg = <0x60>;

};

};


3、通过ACPI申明一个I2C设备:

ACPI表里能够申明I2C设备:Documentation/acpi/enumeration.txt


4、显式地实例化i2c设备:

这也是通过填充i2c_board_info数据结构,然后调用i2c_new_device()实现的,如下面的代码所示:

static struct i2c_board_info sfe4001_hwmon_info = {

I2C_BOARD_INFO("max6647", 0x4e),

};


int sfe4001_init(struct efx_nic *efx)

{

(...)

efx->board_info.hwmon_client =

i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);


(...)

}

上面的代码在i2c bus上实例化了一个i2c设备。


5、对一些特殊的设备进行自动探测

有的系统上并没有提供足够多的关于i2c设备和拓扑信息,这个时候就需要依赖i2c-core去探测设备,这就要求:


  • i2c设备驱动必须实现detect()函数;


  • 只有挂载了这种设备的i2c总线能够且允许被探测。


6、利用/sysfs通过用户态初始化i2c设备

比如我们需要把位于i2c地址0x50eerom添加到设备当中去,可以直接操作sysfs实现:

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