系列:
I2C设备驱动程序框架 IIC设备驱动是对IIC硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的IIC适配器上,通过IIC适配器与CPU交换数据。设备驱动则是与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。
IIC设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
其中,i2c_client由dts自动生成,如果设备在设备树中有 i2c@xxx
和 your_device@address
的节点,内核会根据设备树生成 i2c_client
实例 ,然后和匹配的 i2c_driver
执行绑定。
接口 i2c_driver 首先看下最关键的结构体,这是我们用来和Linux交互的关键核心。
struct i2c_driver { unsigned int class ; int (*probe)(struct i2c_client *client); void (*remove)(struct i2c_client *client); void (*shutdown)(struct i2c_client *client); void (*alert)(struct i2c_client *client, enum i2c_alert_protocol protocol, unsigned int data); int (*command)(struct i2c_client *client, unsigned int cmd, void *arg); struct device_driver driver ; const struct i2c_device_id *id_table ; int (*detect)(struct i2c_client *client, struct i2c_board_info *info); const unsigned short *address_list; struct list_head clients ; u32 flags; };
其中我们需要关心的有probe
, remove
以及driver
中的name
和of_match_table
。
API i2c_check_functionality static inline int i2c_check_functionality (struct i2c_adapter *adap, u32 func) { return (func & i2c_get_functionality(adap)) == func; }
i2c_check_functionality
是一个 I2C 核心函数,用于检查 I2C 控制器是否支持某些特定的功能或操作。它通常在设备驱动中使用,用于验证当前的 I2C 控制器是否支持所需的操作(如读写操作、多个字节的传输等)。
I2C控制器支持的功能:
I2C_FUNC_I2C
:标准的 I2C 总线操作。
I2C_FUNC_SMBUS
:SMBus 操作。
I2C_FUNC_10BIT_ADDR
:支持 10 位地址模式。
I2C_FUNC_PROTOCOL_MANGLING
:支持某些协议扩展功能。
等等……
一般来说,我们只需要检查I2C_FUNC_I2C
i2c_set_clientdata static inline void i2c_set_clientdata (struct i2c_client *client, void *data) { dev_set_drvdata(&client->dev, data); }
设置i2c客户端的私有数据,在其他函数中可以通过i2c_get_clientdata获取私有数据。
module_i2c_driver module_i2c_driver其实就是module_init的语法糖,实现如下:
#define module_i2c_driver(__i2c_driver) \ module_driver(__i2c_driver, i2c_add_driver, \ i2c_del_driver) #define module_driver(__driver, __register, __unregister, ...) \ static int __init __driver##_init(void) \ { \ return __register(&(__driver) , ##__VA_ARGS__); \ } \ module_init(__driver##_init); \ static void __exit __driver##_exit(void) \ { \ __unregister(&(__driver) , ##__VA_ARGS__); \ } \ module_exit(__driver##_exit);
所以module_i2c_driver(my_i2c_driver)就等价于:
static int __init my_i2c_driver_init (void ) { return i2c_add_driver(&my_i2c_driver); } static void __exit my_i2c_driver_exit (void ) { i2c_del_driver(&my_i2c_driver); } module_init(my_i2c_driver_init); module_exit(my_i2c_driver_exit);
收发数据
函数名
描述
适用场景
i2c_master_send
发送一个数据缓冲区到 I2C 设备,不涉及协议层的细节。
适用于发送简单的字节流,通常没有特殊协议要求。
i2c_master_recv
从 I2C 设备接收数据,接收指定长度的数据缓冲区。
用于从设备接收字节流。
i2c_smbus_write_byte_data
通过 SMBus 协议,向设备写入单个字节数据到指定寄存器。
适用于简单的寄存器写操作,数据小且是单字节操作。
i2c_smbus_read_byte_data
通过 SMBus 协议,从设备读取单个字节数据。
用于读取单个字节数据(如寄存器的值)。
i2c_smbus_write_i2c_block_data
通过 SMBus 协议,向设备写入多个字节的数据。
适用于需要向设备发送多个字节的数据块。
i2c_smbus_read_i2c_block_data
通过 SMBus 协议,从设备读取多个字节的数据。
适用于从设备读取多个字节数据。
i2c_smbus_write_word_data
通过 SMBus 协议,向设备写入一个 16 位数据(2 字节)。
适用于寄存器写操作,数据大小为 16 位。
i2c_smbus_read_word_data
通过 SMBus 协议,从设备读取一个 16 位数据(2 字节)。
用于读取 16 位(2 字节)的寄存器值。
i2c_master_send
和 **i2c_master_recv
**:
这些是标准的 I2C 接口函数,不依赖于 SMBus 协议。你可以用它们来发送或接收任意长度的数据缓冲区。
i2c_master_send
用于发送数据,i2c_master_recv
用于接收数据。
这两个函数不关心协议或设备的寄存器地址,只处理简单的字节流交换。
SMBus 特定函数(如 i2c_smbus_write_byte_data
) :
SMBus 是 I2C 的一个子集,添加了专门的操作和协议要求。这些函数能够处理特定的协议细节,例如寄存器地址和数据的交换。
i2c_smbus_write_byte_data
和 i2c_smbus_read_byte_data
主要用于发送和接收单字节数据。
i2c_smbus_write_i2c_block_data
和 i2c_smbus_read_i2c_block_data
允许发送和接收多个字节的数据,适用于较大数据块的传输。
如果你只是做简单的字节流传输,可以使用 **i2c_master_send
和 i2c_master_recv
**。
如果你的设备支持 SMBus 或你需要做寄存器级别的操作(如读取设备的状态寄存器或设置设备的配置寄存器),则应该使用 i2c_smbus_\*
系列函数。
简单驱动demo 这里使用了register_chardev,而不是cdev。
只是作为一个小的demo,如果实际使用还是要使用cdev或者i2c的regmap。
i2c的regmap后面再写。
#include "linux/dev_printk.h" #include "linux/device.h" #include "linux/device/class.h" #include "linux/fs.h" #include "linux/kdev_t.h" #include "linux/uaccess.h" #include <linux/platform_device.h> #include <linux/of_address.h> #include <linux/i2c.h> static struct i2c_client *my_client ;static int major;struct device *my_device ;#define MAX_TRANS_LEN 16 static ssize_t my_i2c_read (struct file *file, char __user *buf, size_t count, loff_t *ppos) { char my_buf[MAX_TRANS_LEN]; int ret; if (count > MAX_TRANS_LEN) return -ENOMEM; if (copy_from_user(my_buf, buf, count)) return -EFAULT; ret = i2c_master_recv(my_client, my_buf, count); if (ret < 0 ) { dev_err(&my_client->dev, "failed to read" ); return ret; } *ppos += ret; return ret; } static ssize_t my_i2c_write (struct file *file, const char __user *buf, size_t count, loff_t *ppos) { char my_buf[MAX_TRANS_LEN]; int ret; if (count > MAX_TRANS_LEN) return -ENOMEM; if (copy_from_user(my_buf, buf, count)) return -EFAULT; ret = i2c_master_send(my_client, my_buf, count); if (ret < 0 ) { dev_err(&my_client->dev, "failed to read" ); return ret; } *ppos += ret; return ret; } struct file_operations my_i2c_ops = { .read = my_i2c_read, .write = my_i2c_write, }; static char *my_i2c_class_devnode (const struct device *dev, umode_t *mode) { if (mode) *mode = 0777 ; return NULL ; } static const struct class my_i2c_class = { .name = "my_i2c" , .devnode = my_i2c_class_devnode, }; static int my_i2c_probe (struct i2c_client *client) { struct device *dev = &client->dev; int retval; major = register_chrdev(0 , "my_i2c" , &my_i2c_ops); if (major < 0 ) { dev_err_probe(dev, major, "failed to register chardev" ); } retval = class_register(&my_i2c_class); if (retval) { printk(KERN_ERR"class register failed\n" ); goto failed1; } my_device = device_create(&my_i2c_class, NULL , MKDEV(major, 0 ), NULL , "my_i2c" ); if (!my_device) { printk(KERN_ERR"device create failed\n" ); retval = PTR_ERR(dev); goto failed2; } my_client = client; return 0 ; failed2: class_unregister(&my_i2c_class); failed1: unregister_chrdev(major, "my_i2c" ); return retval; } static void my_i2c_remove (struct i2c_client *client) { device_destroy(&my_i2c_class, MKDEV(major, 0 )); unregister_chrdev(major, "my_i2c" ); } static const struct of_device_id my_i2c_of_match [] = { { .compatible = "my,i2c" , }, { } }; MODULE_DEVICE_TABLE(of, my_i2c_of_match); static struct i2c_driver my_i2c_driver = { .probe = my_i2c_probe, .remove = my_i2c_remove, .driver = { .name = "i2c-my" , .of_match_table = my_i2c_of_match, } }; module_i2c_driver(my_i2c_driver) MODULE_LICENSE("GPL" ); MODULE_DESCRIPTION("my i2c driver for test" );
Ref linux i2c 驱动框架分析 全面总结Linux内核下的IIC子系统架构!
linux设备驱动程序-i2c(0)-i2c设备驱动源码实现