系列:

I2C设备驱动程序框架

IIC设备驱动是对IIC硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的IIC适配器上,通过IIC适配器与CPU交换数据。设备驱动则是与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。

IIC设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。

其中,i2c_client由dts自动生成,如果设备在设备树中有 i2c@xxxyour_device@address 的节点,内核会根据设备树生成 i2c_client 实例,然后和匹配的 i2c_driver 执行绑定。

接口

i2c_driver

首先看下最关键的结构体,这是我们用来和Linux交互的关键核心。

struct i2c_driver {
unsigned int class;

/* Standard driver model interfaces */
int (*probe)(struct i2c_client *client);
void (*remove)(struct i2c_client *client);


/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *client);

/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
* For the SMBus Host Notify protocol, the data corresponds to the
* 16-bit payload data reported by the slave device acting as master.
*/
void (*alert)(struct i2c_client *client, enum i2c_alert_protocol protocol,
unsigned int data);

/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

struct device_driver driver;
const struct i2c_device_id *id_table;

/* Device detection callback for automatic device creation */
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中的nameof_match_table

API

i2c_check_functionality
/* Return 1 if adapter supports everything we need, 0 if not. */
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的语法糖,实现如下:

/**
* module_i2c_driver() - Helper macro for registering a modular I2C driver
* @__i2c_driver: i2c_driver struct
*
* Helper macro for I2C drivers which do not do anything special in module
* init/exit. This eliminates a lot of boilerplate. Each module may only
* use this macro once, and calling it replaces module_init() and module_exit()
*/
#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_datai2c_smbus_read_byte_data 主要用于发送和接收单字节数据。
  • i2c_smbus_write_i2c_block_datai2c_smbus_read_i2c_block_data 允许发送和接收多个字节的数据,适用于较大数据块的传输。

如果你只是做简单的字节流传输,可以使用 **i2c_master_sendi2c_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", },
{ /* sentinel */ }
};
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设备驱动源码实现