为了实现设备驱动的可移植性及可重用性,在linux内核的驱动模型里, device_driver设备驱动只需实现驱动方法和使用device设备提供的硬件资源(如用到的io口,中断号等). bus总线用于装载device设备与device_driver设备驱动,并管理设备与设备驱动的匹配.
为了方便我们,内核里基于设备驱动模型又扩展出平台设备驱动模型. 在嵌入式里,平台设备驱动模型是使用率最高的驱动模型, 所有的控制器驱动都是由SOC芯片厂家实现的平台设备驱动. 在控制器驱动里,平台设备一般用于提供控制器的配置寄存器基地址、IO口、中断号等硬件相关的资源; 平台驱动都可重用的控制器驱动方法。
在全志H3平台里, i2c控制器的驱动就是使用了平台设备驱动的模型。
SOC共提供了4个控制器, 每个控制器的驱动方法都是一样,所以只需写一份可重用的控制器驱动代码即可。但每个控制器在硬件上都是有区别的,如使用的IO口,配置寄存器的基地址,中断号等等。 在驱动模型中,控制器的驱动方法可实现为平台驱动, 每个控制器的硬件资源由一个平台设备对旬来提供。
//////////////////////////////////////////////////////////////////
当控制器驱动好后,即控制器的平台设备与平台驱动匹配后,在内核里由一个i2c_adapter对象来表示一个具体的控制器。而且只需通过此i2c_adapter对象就可以实现调用i2c控制器,从而让控制器按需进行收发数据.
为了让i2c设备驱动实现可移植性和可重用性,i2c设备驱动也就必须脱离使用固定的硬件资源实现设备的驱动方法。所以i2c的设备驱动模型也是基于linux驱动模型上扩展实现的.
用于提供硬件资源的i2c设备类型:
//在内核里先用i2c_board_info对象来描述设备,在当i2c_adapter对象注册时,再根据i2c_board_info信息创建出真正的i2c_client设备对象.
struct i2c_client {
unsigned short flags; //没用. 如设备地址为10位,则设I2C_CLIENT_TEN
unsigned short addr; //i2c设备地址
char name[I2C_NAME_SIZE]; //设备的名字
struct i2c_adapter *adapter; //此指针指向此设备在具体i2c控制器的i2c_adapter对象
struct i2c_driver *driver; //指向匹配上的i2c设备驱动
struct device dev; //基于device结构体扩展而来, 驱动模型. 也可以用dev.platform_data来给设备驱动提供硬件资源
int irq; //中断号
struct list_head detected;
};
i2c设备驱动类型:
struct i2c_driver {
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
int (*detach_adapter)(struct i2c_adapter *) __deprecated;
//上面两个函数已淘汰, 不要再使用. 现由probe, remove代替.
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 *);
//上面三个函数由电源管理事件触发.
...
struct device_driver driver; //基于device_driver扩展而来. 需设备driver.name
const struct i2c_device_id *id_table; //用于指定只与哪些设备名匹配
...
};
extern int i2c_register_driver(struct module *, struct i2c_driver *); //注册i2c设备驱动对象
//也可以用宏: #define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)
extern void i2c_del_driver(struct i2c_driver *); //移除i2c设备驱动对象
i2c的设备与设备驱动对象都是挂载到i2c总线上的。它们与i2c控制器驱动的关系如下图:
/////////////////////////////////////////////////////////////////////////////////
i2c总线的源码分析:
"drivers/i2c/i2c-core.c"
316 struct bus_type i2c_bus_type = {
317 .name = "i2c",
318 .match = i2c_device_match, //匹配i2c设备与设备驱动
319 .probe = i2c_device_probe, //注意: 总线实现probe函数,则设备与设备驱动匹配上后,触发的就是总线的probe函数
320 .remove = i2c_device_remove, //当设备或设备驱动移除时,触发的是总线的remove函数
321 .shutdown = i2c_device_shutdown,
322 .pm = &i2c_device_pm_ops,
323 };
324 EXPORT_SYMBOL_GPL(i2c_bus_type);
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev); //i2c_client基于device扩展而来,这里是根据i2c_client里的dev成员的地址获取出结构体对象的首地址
struct i2c_driver *driver;
...
driver = to_i2c_driver(drv);//i2c_driver是基于device_driver扩展而来,这里根据driver成员的地址获取出对象的首地址
if (driver->id_table) //按设备驱动的id_table里指定的名字与设备名进行匹配
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
/////////////////////////////////////////////////
i2c总线实现了probe函数,所以当i2c设备与设备驱动匹配后,触发总线的probe函数,即i2c_device_probe。i2c_driver对象的probe函数是在总线的probe函数里被调用的.
static int i2c_device_probe(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
...
driver = to_i2c_driver(dev->driver);
if (!driver->probe || !driver->id_table)
return -ENODEV;
client->driver = driver;
... //这里调用了i2c_driver对象的probe函数
status = driver->probe(client, i2c_match_id(driver->id_table, client));
...
return status;
}
i2c总线实现了remove函数,所以当i2c设备或设备驱动移除时,触发总线的remove函数,即i2c_device_remove。i2c_driver对象的remove函数是在总线的remove函数里被调用的.
static int i2c_device_remove(struct device *dev)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
int status;
...
driver = to_i2c_driver(dev->driver);
if (driver->remove) {
dev_dbg(dev, "remove\n");
status = driver->remove(client); //这里调用了i2c_driver对象的remove函数
} else {
dev->driver = NULL;
status = 0;
}
...
return status;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
内核里已提供在设备驱动中调用i2c控制器对象的接口函数,基中最常用的函数是i2c_transfer
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
// msgs表示多个i2c_msg的消息数组(每个i2c_msg可示读/写的操作), num表示msgs数组的元素个数
//注意这个函数,每个i2c_msg消息都会发出开始信号,但这个函数只有在最后一条消息执行结束时才会发出一个停止信号,不管函数调用时共发出多少条消息
如:
struct i2c_msg msgs[3];
i2c_transfer(adap, msgs, 3); //发出3条消息,每条消息都带有一个开始信号,但3条消息发完后才会有一个信止信号
注意: 连续的i2c_transfer操作时有可能需要每次调用后加入延时.
struct i2c_msg {
__u16 addr; //设备地址,注意不包含读写位
__u16 flags; // 0表示写, I2C_M_RD表示读
__u16 len; //发出设备地址及读写位后,传输的数据字节数
__u8 *buf; //数据缓冲区首地址
};