I2C是Phillips开发的2线的串行总线协议。通常应用在嵌入式系统中让不同的组件通信,PC主板可以通过I2C来与不同的传感器通信。这些传感器通常报告风扇速度,处理器温度和整个硬件系统的信息,这个协议也可以用在RAM chips上,向操作系统提供DIMM的信息。
在2.0时I2C的kernel源码不在内核里的,2.4内核包括了一点对I2C的支持,主要是视频驱动。
在2.6内核里,大量的I2C代码加入到了内核里。感谢很多内核开发者的努力,他们让接口更容易被内核社区接受。有些驱动仍在外部的CVS树里,没有放入kernel.org里,但是他们被移植只是时间的问题。
I2C的kernel源码被分成几个部分:the I2C core, I2C bus drivers,I2C algorithm drivers and I2C chip drivers。我们将忽略I2C core部分,而是关注如何编写a bus and algorithm 驱动。
I2C Bus Drivers 总线驱动
I2C总线驱动用一个结构i2c_adapter来描述,该结构在文件include/linux/i2c.h里定义。
以下的字段需要在总线驱动里设置:
struct module *owner; -set to the value (THIS_MODULE) that allows the proper module reference counting.
unsigned int class; -the type of I2C class devices that this driver supports. Usually this is set to the value I2C_ADAP_CLASS_SMBUS.
struct i2c_algorithm *algo; -a pointer to the struct i2c_algorithm structure that describes the way data is transferred through this I2C bus controller. More information on this structure is provided below.
char name[I2C_NAME_SIZE]; -set to a descriptive name of the I2C bus driver. This value shows up in the sysfs filename associated with this I2C adapter.
以下的代码来自一个I2C adapter驱动的例子tiny_i2c_adap.c,
可以在the Linux Journal FTP site [ftp.ssc.com/pub/lj/listings/issue116/7136.tgz [1]] 获得。
来看看struct i2c_adapter 如何被设置:
static struct i2c_adapter tiny_adapter = {
.owner = THIS_MODULE,
.class = I2C_ADAP_CLASS_SMBUS,
.algo = &tiny_algorithm,
.name = "tiny adapter",
};
要注册I2C adapter,驱动调用函数i2c_add_adapter,参数是struct i2c_adapter指针。
retval = i2c_add_adapter(&tiny_adapter);
要注销一个I2C adapter, 驱动调用i2c_del_adapter,参数是struct i2c_adapter指针。像这样:
i2c_del_adapter(&tiny_adapter);
I2C Algorithm Drivers
I2C algorithm是为了I2C总线驱动和I2C总线之间能够对话,很多I2C总线驱动定义和使用它们自己的Algorithm。因为他们联系紧密,实现了总线驱动如何和特定类型的硬件对话。对于一些I2C总线驱动来说,很多I2C algorithm已经写好了。
其中的一些例子:
ITE adapters, 在目录drivers/i2c/i2c-algo-ite.c;
IBM PPC 405 adapters 在目录drivers/i2c/i2c-algo-ibm_ocp.c;
a generic I2C bit shift algorithm 在目录drivers/i2c/i2c-algo-bit.c;
所有这些已经写好的Algorithm有它们自己的函数,I2C总线驱动需要注册来使用。
要想得到这方面更多的信息,可以查看内核源码目录drivers/i2c/i2c-algo-*.c。
我们将创建自己的I2C algorithm驱动,一个algorithm驱动被结构i2c_algorithm 定义,该结构被定义在
include/linux/i2c.h,以下是常用字段的描述:
char name[32];: the name of the algorithm.
unsigned int id;: description of the type of algorithm this structure defines. These different types are defined in the include/linux/i2c-id.h file and start with the characters I2C_ALGO_.
int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg msgs[], int num);: a function pointer to be set if this algorithm driver can do I2C direct-level accesses. If it is set, this function is called whenever an I2C chip driver wants to communicate with the chip device. If it is set to NULL, the smbus_xfer function is used instead.
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);: a function pointer to be set if this algorithm driver can do SMB bus accesses. Most PCI-based I2C bus drivers are able to do this, and they should set this function pointer. If it is set, this function is called whenever an I2C chip driver wants to communicate with the chip device. If it is set to NULL, the master_xfer function is used instead.
u32 (*functionality) (struct i2c_adapter *);: a function pointer called by the I2C core to determine what kind of reads and writes the I2C adapter driver can do.
在我们的I2C adapter驱动里,i2c_adapter结构引用了tiny_algorithm的变量,结构定义如下:
static struct i2c_algorithm tiny_algorithm = {
.name = "tiny algorithm",
.id = I2C_ALGO_SMBUS,
.smbus_xfer = tiny_access,
.functionality = tiny_func,
};
函数tiny_func很简单,告诉I2C core这个algorithm 能支持什么类型的I2C messages。
在这个驱动里,我们要支持很多不同的I2C messages类型:
static u32 tiny_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_SMBUS_QUICK |
I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA |
I2C_FUNC_SMBUS_BLOCK_DATA;
}
所有的I2C message类型都在include/linux/i2c.h定义,以I2C_FUNC_开头。
当I2C client驱动要和I2C总线对话是,函数tiny_access会被调用。我们的例子函数很简单,
它仅仅记录I2C chip驱动的请求到syslog,同时返回成功给调用者。
这个日志能让你看到I2C chip驱动请求的所有地址和数据类型。实现如下:
static s32 tiny_access(struct i2c_adapter *adap,
u16 addr,
unsigned short flags,
char read_write,
u8 command,
int size,
union i2c_smbus_data *data)
{
int i, len;
dev_info(&adap->dev, "%s was called with the "
"following parameters:\n",
__FUNCTION__);
dev_info(&adap->dev, "addr = %.4x\n", addr);
dev_info(&adap->dev, "flags = %.4x\n", flags);
dev_info(&adap->dev, "read_write = %s\n",
read_write == I2C_SMBUS_WRITE ?
"write" : "read");
dev_info(&adap->dev, "command = %d\n",
command);
switch (size) {
case I2C_SMBUS_PROC_CALL:
dev_info(&adap->dev,
"size = I2C_SMBUS_PROC_CALL\n");
break;
case I2C_SMBUS_QUICK:
dev_info(&adap->dev,
"size = I2C_SMBUS_QUICK\n");
break;
case I2C_SMBUS_BYTE:
dev_info(&adap->dev,
"size = I2C_SMBUS_BYTE\n");
break;
case I2C_SMBUS_BYTE_DATA:
dev_info(&adap->dev,
"size = I2C_SMBUS_BYTE_DATA\n");
if (read_write == I2C_SMBUS_WRITE)
dev_info(&adap->dev,
"data = %.2x\n", data->byte);
break;
case I2C_SMBUS_WORD_DATA:
dev_info(&adap->dev,
"size = I2C_SMBUS_WORD_DATA\n");
if (read_write == I2C_SMBUS_WRITE)
dev_info(&adap->dev,
"data = %.4x\n", data->word);
break;
case I2C_SMBUS_BLOCK_DATA:
dev_info(&adap->dev,
"size = I2C_SMBUS_BLOCK_DATA\n");
if (read_write == I2C_SMBUS_WRITE) {
dev_info(&adap->dev, "data = %.4x\n",
data->word);
len = data->block[0];
if (len < 0)
len = 0;
if (len > 32)
len = 32;
for (i = 1; i <= len; i++)
dev_info(&adap->dev,
"data->block[%d] = %x\n",
i, data->block[i]);
}
break;
}
return 0;
}
现在tiny_i2c_adap驱动已经建立并装载,它能做什么呢?就它自己,什么也做不了。
除了位于sysfs树里,I2C总线驱动需要I2C client驱动来做任何工作。
因此,如果lm75 I2C client驱动被装载,它会通过tiny_i2c_adap驱动找到要为之些驱动的芯片。
$ modprobe lm75
$ tree /sys/bus/i2c/
/sys/bus/i2c/
|-- devices
| |-- 0-0048 -> ../../../devices/legacy/i2c-0/0-0048
| |-- 0-0049 -> ../../../devices/legacy/i2c-0/0-0049
| |-- 0-004a -> ../../../devices/legacy/i2c-0/0-004a
| |-- 0-004b -> ../../../devices/legacy/i2c-0/0-004b
| |-- 0-004c -> ../../../devices/legacy/i2c-0/0-004c
| |-- 0-004d -> ../../../devices/legacy/i2c-0/0-004d
| |-- 0-004e -> ../../../devices/legacy/i2c-0/0-004e
| `-- 0-004f -> ../../../devices/legacy/i2c-0/0-004f
`-- drivers
|-- i2c_adapter
`-- lm75
|-- 0-0048 -> ../../../../devices/legacy/i2c-0/0-0048
|-- 0-0049 -> ../../../../devices/legacy/i2c-0/0-0049
|-- 0-004a -> ../../../../devices/legacy/i2c-0/0-004a
|-- 0-004b -> ../../../../devices/legacy/i2c-0/0-004b
|-- 0-004c -> ../../../../devices/legacy/i2c-0/0-004c
|-- 0-004d -> ../../../../devices/legacy/i2c-0/0-004d
|-- 0-004e -> ../../../../devices/legacy/i2c-0/0-004e
`-- 0-004f -> ../../../../devices/legacy/i2c-0/0-004f
因为tiny_i2c_adap对于读写请求都返回成功,因此lm75 I2C chip认为它
在每一个已知的地址上都找到了lm75 chip。这就是为什么0-0048到0-004f的I2C设备被创建了。
如果我们看看这些设备的一个目录,传感相关的文件将显示出来:
$ tree /sys/devices/legacy/i2c-0/0-0048/
/sys/devices/legacy/i2c-0/0-0048/
|-- detach_state
|-- name
|-- power
| `-- state
|-- temp_input
|-- temp_max
`-- temp_min
文件detach_state和目录power会被内核驱动核心创建,用来做电源管理,而不是被lm75驱动创建。
如果我们询问lm75驱动temp_max当前值,得到如下:
$ cat /sys/devices/legacy/i2c-0/0-0048/temp_max
1000:
为了得到该值,lm75驱动要求tiny_i2c_adap去读I2C总线上的一些地址,这个请求可以在syslog中看到:
$ dmesg
i2c_adapter i2c-0: tiny_access was called with the following parameters:
i2c_adapter i2c-0: addr = 0048
i2c_adapter i2c-0: flags = 0000
i2c_adapter i2c-0: read_write = read
i2c_adapter i2c-0: command = 0
i2c_adapter i2c-0: size = I2C_SMBUS_WORD_DATA
i2c_adapter i2c-0: tiny_access was called with the following parameters:
i2c_adapter i2c-0: addr = 0048
i2c_adapter i2c-0: flags = 0000
i2c_adapter i2c-0: read_write = read
i2c_adapter i2c-0: command = 3
i2c_adapter i2c-0: size = I2C_SMBUS_WORD_DATA
i2c_adapter i2c-0: tiny_access was called with the following parameters:
i2c_adapter i2c-0: addr = 0048
i2c_adapter i2c-0: flags = 0000
i2c_adapter i2c-0: read_write = read
i2c_adapter i2c-0: command = 2
i2c_adapter i2c-0: size = I2C_SMBUS_WORD_DATA
日志文件表明函数tiny_access被执行3次。第一次命令读取寄存器0的值,设备地址在0048,
第二次和第三次命令在同样的设备上读取寄存器3和寄存器2的值。
在drivers/i2c/chips/lm75.c的lm75_update_client函数里,可以看到跟命令匹配的代码:
data->temp_input = lm75_read_value(client,
LM75_REG_TEMP);
data->temp_max = lm75_read_value(client,
LM75_REG_TEMP_OS);
data->temp_hyst = lm75_read_value(client,
LM75_REG_TEMP_HYST);
lm75_read_value函数在同样的文件里,如下:
/* All registers are word-sized, except for the
configuration register. LM75 uses a high-byte
first convention, which is exactly opposite to
the usual practice. */
static int lm75_read_value(struct i2c_client
*client, u8 reg)
{
if (reg == LM75_REG_CONF)
return i2c_smbus_read_byte_data(client,
reg);
else
return swap_bytes(
i2c_smbus_read_word_data(client,
reg));
}
因此,当lm75驱动要读取最大温度的值,它调用lm75_read_value函数三次,参数是寄存器编号。
该函数调用I2C core函数i2c_smbus_read_word_data。该I2C core函数查看client设备在哪条总线上,
然后调用跟该总线关联的I2C algorithm层来实现数据传输。
方式就是这样,tiny_i2c_adap驱动被要求完成传输。
如果要想同样的文件写入,lm75驱动要求tiny_i2c_adap驱动写一些数据到I2C总线特定的地址上,跟读的方式一样。
这个请求可以在syslog中看到:
$ echo 300 > /sys/devices/legacy/i2c-0/0-0048/temp_max
$ dmesg
i2c_adapter i2c-0: tiny_access was called with the following parameters:
i2c_adapter i2c-0: addr = 0048
i2c_adapter i2c-0: flags = 0000
i2c_adapter i2c-0: read_write = write
i2c_adapter i2c-0: command = 3
i2c_adapter i2c-0: size = I2C_SMBUS_WORD_DATA
i2c_adapter i2c-0: data = 8000
驱动源码:
/*
* Tiny I2C Adapter Driver
*
* Copyright (C) 2003 Greg Kroah-Hartman ([email protected])
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 2 of the License.
*
* This driver shows how to create a minimal I2C bus and algorithm driver.
*
* Compile this driver with:
echo "obj-m := tiny_i2c_adap.o" > Makefile
make -C SUBDIRS=$PWD modules
*/
#include
#include
#include
#include
#include
#include
static s32 tiny_access(struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data)
{
int i;
int len;
dev_info(&adap->dev, "%s was called with the following parameters:\n",
__FUNCTION__);
dev_info(&adap->dev, "addr = %.4x\n", addr);
dev_info(&adap->dev, "flags = %.4x\n", flags);
dev_info(&adap->dev, "read_write = %s\n",
read_write == I2C_SMBUS_WRITE ? "write" : "read");
dev_info(&adap->dev, "command = %d\n", command);
switch (size) {
case I2C_SMBUS_PROC_CALL:
dev_info(&adap->dev, "size = I2C_SMBUS_PROC_CALL\n");
break;
case I2C_SMBUS_QUICK:
dev_info(&adap->dev, "size = I2C_SMBUS_QUICK\n");
break;
case I2C_SMBUS_BYTE:
dev_info(&adap->dev, "size = I2C_SMBUS_BYTE\n");
break;
case I2C_SMBUS_BYTE_DATA:
dev_info(&adap->dev, "size = I2C_SMBUS_BYTE_DATA\n");
if (read_write == I2C_SMBUS_WRITE)
dev_info(&adap->dev, "data = %.2x\n", data->byte);
break;
case I2C_SMBUS_WORD_DATA:
dev_info(&adap->dev, "size = I2C_SMBUS_WORD_DATA\n");
if (read_write == I2C_SMBUS_WRITE)
dev_info(&adap->dev, "data = %.4x\n", data->word);
break;
case I2C_SMBUS_BLOCK_DATA:
dev_info(&adap->dev, "size = I2C_SMBUS_BLOCK_DATA\n");
if (read_write == I2C_SMBUS_WRITE) {
dev_info(&adap->dev, "data = %.4x\n", data->word);
len = data->block[0];
if (len < 0)
len = 0;
if (len > 32)
len = 32;
for (i = 1; i <= len; i++)
dev_info(&adap->dev, "data->block[%d] = %.2x\n",
i, data->block[i]);
}
break;
}
return 0;
}
static u32 tiny_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE |
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA |
I2C_FUNC_SMBUS_BLOCK_DATA;
}
static struct i2c_algorithm tiny_algorithm = {
.name = "tiny algorithm",
.id = I2C_ALGO_SMBUS,
.smbus_xfer = tiny_access,
.functionality = tiny_func,
};
static struct i2c_adapter tiny_adapter = {
.owner = THIS_MODULE,
.class = I2C_ADAP_CLASS_SMBUS,
.algo = &tiny_algorithm,
.name = "tiny adapter",
};
static int __init tiny_init(void)
{
return i2c_add_adapter(&tiny_adapter);
}
static void __exit tiny_exit(void)
{
i2c_del_adapter(&tiny_adapter);
}
MODULE_AUTHOR("Greg Kroah-Hartman ");
MODULE_DESCRIPTION("Tiny i2c adapter");
MODULE_LICENSE("GPL");
module_init(tiny_init);
module_exit(tiny_exit);