分类: 嵌入式
2019-05-05 21:09:40
原文地址:怎么写I2c和SMBus设备驱动 作者:犀利哥的故事
这个小导主要面向那些想写I2c和SMBus设备驱动程序,使用linux作为协议host/master的人员。
建立一个驱动,你需要做几件事。有些是可选的,有些事情轻缓一点或者完全不同。使用这个作为一个指导,不是规则手册。
一般注解
————————————————————
试着让内核命名空间尽量的干净。最好的方法就是对于全局变量使用唯一的前缀。对于导出变量,这个特别重要,但对于非导出的符号,这个方法比较好。在这个指导中,我们将使用“foo_”这个前缀。
驱动结构体
——————————————————————————————
经常地,你将实现一个驱动结构体,然后通过它来实例化所有clients。记住,一个驱动结构体包含了一般访问规则和 零初始化数据除了你自己提供的域之外。一个client结构体持有设备特定信息就像驱动模式中得设备node 和 它的i2c地址。
static struct i2c_device_id foo_idtable[] = {
{ "foo", my_id_for_foo },
{ "bar", my_id_for_bar },
{ }
};
MODULE_DEVICE_TABLE(i2c, foo_idtable);
static struct i2c_driver foo_driver = {
.driver = {
.name = "foo",
},
.id_table = foo_ids,
.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 */
.suspend = foo_suspend, /* optional */
.resume = foo_resume, /* optional */
.command = foo_command, /* optional, deprecated */
}
名字域就是驱动的名字,不能包括空间。它应该匹配模块名字(如果驱动可以被编译成一个模块),尽量你可能使用MODULE_ALIAS(传“foo”这个例子中)来增加另一个模块的名字。如果驱动的名字不匹配模块的名字,模块不会自动加载(热插拔/冷插拔)
所有其他域用于回调函数,它将会在下面中解释
其他client数据
——————————————————————————————————
每一个client结构体有一个特殊的‘data’域,它可以指向任何结构体。你应该是这个来保持设备特定数据。
/* store the value */
void i2c_set_clientdata(struct i2c_client *client, void *data);
/* retrieve the value */
void *i2c_get_clientdata(const struct i2c_client *client);
注意从linux2.6.34内核开始,你不用设置‘data’域成NULL 在remove() 或 如果probe()失败了。I2c-core 就会自动做这个在这些情况下。只有在这个时间核心会接触这个域。
访问client
——————————————————————————————————————
比如说我们有个有效client结构体。同时,我们将从client中收集信息,或者写新的信息到client中。
我发现定义foo_read 和 foo_write 函数在这个里面。某些情况下,直接调用I2c函数会比较简单,但是更多的情况许多芯片有几种类型的注册值 ,能够被轻易的压缩。
int foo_read_value(struct i2c_client *client, u8 reg)
{
if (reg < 0x10) /* byte-sized register */
return i2c_smbus_read_byte_data(client, reg);
else /* word-sized register */
return i2c_smbus_read_word_data(client, reg);
}
int foo_write_value(struct i2c_client *client, u8 reg, u16 value)
{
if (reg == 0x10) /* Impossible to write - driver error! */
return -EINVAL;
else if (reg < 0x10) /* byte-sized register */
return i2c_smbus_write_byte_data(client, reg, value);
else /* word-sized register */
return i2c_smbus_write_word_data(client, reg, value);
}
探测和绑定
————————————————————————————
linux i2c栈本来写出来是用来支持访问PC主板上面的硬件检测芯片的。因此用于嵌入一些假想,这个也更适合SMBus(和PC)相对于I2C来说。其中的一个假想就是大多数适配器和设备驱动支持SMBUS_QUICK协议来探测设备存在。另一个就是设备和他们的驱动能够被充分地配置通过仅这个probe原始体。
由于linux和它的I2c在嵌入式系统和复杂部件如DVB适配器中的广泛使用。那么这些设想就变得有问题了。I2c设备的驱动中发出中断需要更多(和不同)配置信息,如同驱动操作芯片变体也不能被协议探测probing作为特征或者需要板级特定信息来正确操作。
设备/驱动 绑定
————————————————————————————
系统基本结构,典型的板子特定初始化代码或者启动固件,报告了存在了那些i2c设备。例如,这里可能会有一个表,在内核或从bootloader中,识别i2c设备和连接他们到板级特定配置信息关于IRQs和其他布线工件的,芯片类型等等。那个可以用来对于每一个i2c设备创建i2c_client对象。
I2c设备驱动使用这个绑定模型工作就像其他类型的linux驱动,他们提供一个probe方法来绑定这些设备,然后一个remove方法来解开。
static int foo_probe(struct i2c_client *client,
const struct i2c_device_id *id);
static int foo_remove(struct i2c_client *client);
记住I2c_driver不会创建这些client handles。这些handle可能会在foo_probe()中使用。如果foo_probe()报告成功(0不是否定状态代码)它可能会保存handle 然后一直使用它知道foo_remove返回。 那个绑定模型被许多linux驱动使用。
当id_table名字域和设备的名字匹配的时候,probe函数就会调用。他传入匹配的入口,所以驱动知道是表中哪一个匹配上了。
设备创建
————————————————————————————
如果你知道一个事实就是一个I2c设备连接到一个给定的I2c总线,你可以实例化这个设备通过使用设备地址和驱动名字简单的填充i2c_board_info结构体,然后调用i2c_new_device()。
这个会创建一个设备,然后驱动核心会处理找到正确的驱动和调用它的probe()方法。如果一个驱动支持不同的设备类型,你可以指定你想使用的类型域使用type字段。你也可以指定一个IRQ和platform data如果需要的话。
有时候你知道一个设备连接到了一个给定的总线上,但是你不知道它所使用的正确地址。这个在TV适配器上面就有,同一个驱动支持几十种稍微有点不同的模型。I2c设备地址从一个模型变到下一个。在那种情况下,你可以使用i2c_new_probed_device()变体。这个和i2c_new_device()非常类似。除了它加了一个可能的I2c地址列表用于探测。对列表中第一个响应地址,这里将会创建一个设备device。如果你想几个设备呈现在地址范围内,只需简单调用i2c_new_probed_device()多次。
设备检测
——————————————————————————————————
有时候你不会提前知道那个设备连接到了给定的I2c总线上。比如在PC的SMBus上面的硬件检测设备就是一个例子。在那种情况下,你可以像让你的驱动自动检测支持的设备。这个就是老版本模型驱动的工作方法。现在作为了一个标准驱动模型达到一个扩展了。
你只能定义一个检测回调函数,它将会尝试识别支持的设备(返回表示有支持的,-ENODEV表示没有支持的),一个地址列表用于探测,一个设备类型所以只有I2c总线可能有那个类型的设备连接将会被探测probe。例如,对于一个硬件检测芯片的一个驱动,自动检测需要并自动设置它的类至I2C_CLASS_HWMON,只有i2c适配器与一个包括I2C_CLASS_HWMON的类会被这个驱动探测。注意匹配类的缺少不会阻止在这个给定类型适配器上得设备的使用。它所防止的就是自动检测。明确的实例化设备仍然是可能的。
注意这个机制是可选的,并不适合所有设备。你需要一些可靠的办法来识别支持的设备(典型使用设备特定的,专用识别寄存器)。否则,漏检有可能出现,然后事情马上就会出错。记住 i2c协议不包括任何标准的检测在给定地址的设备的方法,更不必说一个标准的方法来识别设备。更糟糕的就是关于总线传输的机制的缺失意味着相同的传输可能被一个芯片当做读操作而被另一个当做写操作。由于这些原因,明确的设备实例化应该是首选的相对于自动检测。
设备删除
————————————————————————————————————
每一个I2c设备都会通过i2c_new_device()或i2c_new_probed_device()来创建,然后通过调用i2c_unregister_device()来取消注册。如果你不想明确地调用它,那么在I2c总线被移除之前,它会自动调用。因为一个设备不会比它父母活得长在设备驱动模型中。
实例化驱动
——————————————————————————————————
当内核已经启动或者当你的foo驱动模块已经插入了,你必须做一些实例化工作。幸运的是,只需要注册驱动模块就够了。
static int __init foo_init(void)
{
return i2c_add_driver(&foo_driver);
}
static void __exit foo_cleanup(void)
{
i2c_del_driver(&foo_driver);
}
/* 替换自己的名字和电子邮箱地址 */
MODULE_AUTHOR("Frodo Looijaard
MODULE_DESCRIPTION("Driver for Barf Inc. Foo I2C devices");
/* a few non-GPL license types are also allowed */
MODULE_LICENSE("GPL");
module_init(foo_init);
module_exit(foo_cleanup);
注意有一些函数标记了“__init”.这些函数会被移除当内核启动完成的时候。同样地,函数标有“__exit”的只有在代码建立成为内核的一部分的时候,编译器编译它们。因为它们也从不会被调用。
电源管理
——————————————————————————————————————
如果你的I2C设备需要一个特殊的操作当进入系统低功耗状态时—就像让一个收发器进入低功耗模式一样,或者激活系统唤醒机制---这个在suspend()方法中实现。而resume()方法应该和它的做法相反。
系统关闭
——————————————————————————————
如果你的设备在系统关闭或重启的时候需要一些特殊操作—例如关闭一些东西—使用shutdown()方法。
再次,这个是个标准驱动模型调用,工作就像它可以适用其他的驱动堆:调用可以睡眠,可以适用I2c信息。
命令函数
————————————————————————————
一个普通的像ioctl这样的函数回调是支持的。你很少需要这个。无论如何,它的使用是不赞成的。所以新的设计不应该使用。
发送和接受
——————————————————————————
如果你跟你的设备进行交流通信,这里有几个函数可以使用。你可以在
如果你能在普通i2c通信和SMBus级通信中进行选择,那么请使用后者。所有适配器都理解SMBus级命令,但是只有一些可以明白普通I2c。
普通I2c通信
——————————————————————————————————
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);
这些规则从client中读出或者写入一些字节。这个client包括了i2c地址,所以你可以不用包含它。第二个参数包括了读写的字节,第三个就是数量(必须小于buffer的长度,由于msg.len是u16,所以也得小于64K)。返回的是真实的读和写的数据量。
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg,
int num);
这个发送一串的消息。每一个消息都可读可以写,他们可以混合在一起。事务都是组合的。中间没有停止位。I2c_msg 结构体包含了对每个消息的client地址,消息的字节数量 和消息数据本身。
你可以读取文件’i2c协议’以获得更加详细的信息。
SMBus 交流通信
——————————————————————————————————
s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
unsigned short flags, char read_write, u8 command,
int size, union i2c_smbus_data *data);
这个是一般的SMBus函数,所有下面的函数都是按照它来实现的。从不直接使用这个函数。
s32 i2c_smbus_read_byte(struct i2c_client *client);
s32 i2c_smbus_write_byte(struct i2c_client *client, u8 value);
s32 i2c_smbus_read_byte_data(struct i2c_client *client, u8 command);
s32 i2c_smbus_write_byte_data(struct i2c_client *client,
u8 command, u8 value);
s32 i2c_smbus_read_word_data(struct i2c_client *client, u8 command);
s32 i2c_smbus_write_word_data(struct i2c_client *client,
u8 command, u16 value);
s32 i2c_smbus_process_call(struct i2c_client *client,
u8 command, u16 value);
s32 i2c_smbus_read_block_data(struct i2c_client *client,
u8 command, u8 *values);
s32 i2c_smbus_write_block_data(struct i2c_client *client,
u8 command, u8 length, const u8 *values);
s32 i2c_smbus_read_i2c_block_data(struct i2c_client *client,
u8 command, u8 length, u8 *values);
s32 i2c_smbus_write_i2c_block_data(struct i2c_client *client,
u8 command, u8 length,
const u8 *values);
这些都从i2c-core中移除了,因为没有用户,但是有需要还是可以加上的。
s32 i2c_smbus_write_quick(struct i2c_client *client, u8 value);
s32 i2c_smbus_block_process_call(struct i2c_client *client,
u8 command, u8 length, u8 *values);
所有这些传输失败会返回一个无效的errno值。‘write’传输返回0表示成功;‘read’传输返回读到的值,除了块传输,返回读到值的数量。块缓冲不需要大于32个字节。
你可以通过读文件‘smbus协议’来了解更多信息。
通用规范
——————————————————————————————
下面列出的通用规范之前没有提到。
/* 返回适配器号码对于一个特定适配器 */
int i2c_adapter_id(struct i2c_adapter *adap);