1. 摘要
.主要介绍Msm7227平台上I2C驱动结构。
2. 介绍
Linux内核中I2C驱动包含三个层面,按调用顺序排列为:
设备驱动->I2C框架层->I2C适配器驱动,
适配器驱动实现的是处理器的I2C控制器部分的驱动,包括I2C的时钟频率,各种寄存器控制和状态信息,读写实现,并提供了I2C框架层读写算法的基本接口。本文主要介绍基于I2C框架层实现一个I2C设备驱动的基本过程,并不关心具体的实现细节。
3. I2C设备驱动实现
3.1 I2C驱动的注册与卸载
按照常规驱动注册方法实现:
static int __devinit mxt224_module_init(void)
{
int ret = 0;
printk("%s:%d\n", __FUNCTION__,__LINE__);
ret = i2c_add_driver(&mxt224_i2c_driver);/*剩下的就是实现自己的i2c_driver结构了。
return ret;
}
static void __exit mxt224_module_exit(void)
{
i2c_del_driver(&mxt224_i2c_driver);
}
module_init(mxt224_module_init);
module_exit(mxt224_module_exit);
3.2 struct i2c_driver
struct i2c_driver {
int id;
unsigned int class;
/*旧的驱动探测方案,新内核以不建议使用*/
int (*attach_adapter)(struct i2c_adapter *);
int (*detach_adapter)(struct i2c_adapter *);
/*旧的卸载驱动函数,已被声明为不赞成使用,使用时有警告*/
int (*detach_client)(struct i2c_client *) __deprecated;
/*新的设备探测接口*/
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/*电源管理相关接口*/
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/*一个类似于ioctl的接口*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
/*在bus_type中的match回调中匹配驱动*/
const struct i2c_device_id *id_table;
/*设备自动探测的回调*/
int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);
const struct i2c_client_address_data *address_data;
struct list_head clients;
};
示例:
static struct i2c_device_id mxt224_idtable[] = {
{MT_DEVICE_NAME, 0 },
{ }
};
static struct i2c_driver mxt224_i2c_driver = {
.driver = {
.owner = THIS_MODULE,
.name =MT_DEVICE_NAME
},
.id_table = mxt224_idtable,
.probe = mxt224_i2c_probe,
.remove = mxt224_i2c_remove,
};
1.1 I2C的设备注册
设备注册即填充一个i2c_board_info结构,用于在主板初始话时构建I2C的设备链表,供I2C总线初始化时加载指定i2c的设备的驱动程序。
msm7227平台的板级初始过程在arch\arm\mach-msm\board-msm7x27.c中,
将设备信息加在static struct i2c_board_info i2c_devices[] = {}中即可。
struct i2c_board_info {
char type[I2C_NAME_SIZE]; /*i2c_client.name,与驱动程序的设备名保持一致*/
unsigned short flags;/*i2c_client.flags*/
unsigned short addr; /*i2c_client.addr,I2C设备地址*/
void *platform_data;/*保存在i2c_client.dev.platform_data中*/
struct dev_archdata *archdata;
int irq; /*i2c_client.irq */
};
主要填充红色部分即可,蓝色部分可根据自己的需填充。
Type和addr的填充可由下面的宏完成:
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = (dev_type), .addr = (dev_addr)
示例:
static struct i2c_board_info i2c_devices[] = {
#ifdef CONFIG_MT9D112
{
I2C_BOARD_INFO("mt9d112",0x78 >> 1),/* 0x78是7位地址,设备地址是0x3C*/
},
#endif
…
#if defined(CONFIG_M_TOUCHSCREEN_MXT224)
{
I2C_BOARD_INFO("mxt224", 0x4a),
},
#endif
};
保证MT_DEVICE_NAME和“mxt224”名字是一致的,否则将无法探测设备
1.2 probe操作
I2C的probe中要进行设备的探测,如果发现无法进入proble函数,需要检查,
1、是否注册i2c_board_info,
2、driver name是否和i2c_board_info中的type一致
进入probe后参数包含struct i2c_client *client和const struct i2c_device_id *id
Client中的i2c_adapter指定了当前使用的适配器,而id则是为了区分多个设备共用一个驱动时,当前的设备id,和注册时的i2c_device_id有关。
进入probe后的第一歩操作就是检查当前适配器功能:
if (!i2c_check_functionality(adapter,I2C_FUNC_I2C)) {
printk("%s:%d\n", __FUNCTION__,__LINE__);
return -ENODEV;
}
之后要读器件信息,如:
err = i2c_mxt224_read(client, 0, &family_id, 7);
if ( err < 0 ) {
printk("%s:%d,errno=%d\n", __FUNCTION__,__LINE__,err);
goto exit;
}
如果失败。那么该设备的探测结束,要与硬件配合检查。
之后就开始自己驱动的主要工作了,包括申请私有数据空间,初始化,中断注册等操作,或者注册一个新的其他类型的设备驱动,任君发挥了。
1.3 与设备的通信(读写寄存器)
通过系统的内建命令:
s32 i2c_smbus_read_byte(struct i2c_client *client)读1byte
s32 i2c_smbus_write_byte(struct i2c_client *client, u8 value)写1byte
s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 command)交互式读1byte
s32 i2c_smbus_write_byte_data(struct i2c_client *client, u8 command, u8 value)交互式写1byte
s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 command)读2byte
s32 i2c_smbus_write_word_data(struct i2c_client *client, u8 command, u16 value)写2byte
s32 i2c_smbus_read_block_data(struct i2c_client *client, u8 command,
u8 *values)读一个数据块,最大32byte
s32 i2c_smbus_write_block_data(struct i2c_client *client, u8 command,
u8 length, const u8 *values)写数据块,最大32byte
自己封装读写函数,例:
static int i2c_mxt224_read(struct i2c_client *client, uint16_t address, uint8_t *data, uint8_t length)
{
int retry;
uint8_t addr[2];
struct i2c_msg msg[] = {
{
.addr = client->addr,
.flags = 0,
.len = 2,
.buf = addr,
},
{
.addr = client->addr,
.flags = I2C_M_RD,
.len = length,
.buf = data,
}
};
addr[0] = address & 0xFF;
addr[1] = (address >> 8) & 0xFF; /*寄存器地址*/
for (retry = 0; retry < MXT224_RETRY_COUNT; retry++) {
if (i2c_transfer(client->adapter, msg, 2) == 2)
break;
mdelay(10);
}
if (retry == MXT224_RETRY_COUNT) {
printk("i2c_read_block retry over %d\n",
MXT224_RETRY_COUNT);
return -EIO;
}
return 0;
}
static int i2c_mxt224_write(struct i2c_client *client, uint16_t address, uint8_t *data, uint8_t length)
{
int retry, loop_i;
uint8_t buf[length + 2];
struct i2c_msg msg[] = {
{
.addr = client->addr,
.flags = 0,
.len = length + 2,
.buf = buf,
}
};
buf[0] = address & 0xFF;
buf[1] = (address >> 8) & 0xFF;
for (loop_i = 0; loop_i < length; loop_i++)
buf[loop_i + 2] = data[loop_i];
for (retry = 0; retry < MXT224_RETRY_COUNT; retry++) {
if (i2c_transfer(client->adapter, msg, 1) == 1)
break;
mdelay(10);
}
if (retry == MXT224_RETRY_COUNT) {
printk("i2c_write_block retry over %d\n",
MXT224_RETRY_COUNT);
return -EIO;
}
return 0;
}