❤Linux下IIC驱动详解

参考资料:https://www.yuanzige.com/

简介

I2C 是很常见的一种总线协议, I2C 是 NXP 公司设计的, I2C 使用两条线在主控制器和从机之间进行数
据通信。一条是 SCL(串行时钟线),另外一条是 SDA(串行数据线),这两条数据线需要接上拉电阻,总线空闲的时候 SCL 和 SDA 处于高电平。 I2C 总线标准模式下速度可以达到 100Kb/S,快速模式下可以达到 400Kb/S。 I2C 总线工作是按照一定的协议来运行的,接下来就看一下 I2C 协议。

I2C 是支持多从机的,也就是一个 I2C 控制器下可以挂多个 I2C 从设备,这些不同的 I2C从设备有不同的器件地址,这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C设备了,一个 I2C 总线连接多个 I2C 设备如图所示:
在这里插入图片描述
上图中 SDA 和 SCL 这两根线必须要接一个上拉电阻,一般是 4.7K。其余的 I2C 从器件都挂接到 SDA
和 SCL 这两根线上,这样就可以通过 SDA 和 SCL 这两根线来访问多个 I2C设备。

传输数据

1、起始位
顾名思义,也就是 I2C 通信起始标志,通过这个起始位就可以告诉 I2C 从机,“我”要开始进行 I2C 通信了。在 SCL 为高电平的时候,SDA 出现下降沿就表示为起始位。
在这里插入图片描述

2、停止位
停止位就是停止 I2C 通信的标志位,和起始位的功能相反。在 SCL 位高电平的时候, SDA出现上升沿就表示为停止位
在这里插入图片描述

起始信号和终止信号都是由主机发送的。在起始信号产生之后,总线就处于被占用的状态,在终止信号产生之后,总线就处于空闲状态

3、数据传输
I2C 总线在数据传输的时候要保证在 SCL 高电平期间, SDA 上的数据稳定,因此 SDA 上的数据变化只能在 SCL 低电平期间发生,如下图所示:
在这里插入图片描述
4、应答信号
当 I2C 主机发送完 8 位数据以后会将 SDA 设置为输入状态,等待 I2C 从机应答,也就是等到 I2C 从机
告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完
8 位数据以后紧跟着的一个时钟信号就是给应答信号使用的。从机通过将 SDA 拉低来表示发出应答信
号,表示通信成功,否则表示通信失败。

每当发送器传输完一个字节的数据之后,发送端会等待一定的时间,等接收方的应答信号。接收端通过拉低SDA数据线,给发送端发送一个应答信号,以提醒发送端我这边已经接受完成,数据可以继续传输,接下来,发送端就可以继续发送数据了。
.
每一帧的数据长度为8位,但是每发送8位数据后面必须跟一个应答位,因此IIC的数据帧长度为9bit

5、IIC写时序
通信无非就是两个操作,读和写。
(1)、开始信号。

(2)、发送 I2C 设备地址,每个 I2C 器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个 I2C 器件。这是一个 8 位的数据,其中高 7 位是设备地址,最后 1 位是读写位,为1 的话表示这 是一个读操作,为 0 的话表示这是一个写操作。

(3)、 I2C 器件地址后面跟着一个读写位,为 0 表示写操作,为 1 表示读操作。
(4)、从机发送的 ACK 应答信号。
(5)、重新发送开始信号。
(6)、发送要写写入数据的寄存器地址。
(7)、从机发送的 ACK 应答信号。
(8)、发送要写入寄存器的数据。
(9)、从机发送的 ACK 应答信号。
(10)、停止信号。
6、IIC读时序
I2C 单字节读时序比写时序要复杂一点,读时序分为 4 大步:
第一步:是发送设备地址,
第二步:是发送要读取的寄存器地址,
第三步:重新发送设备地址,
第四步:就是 I2C 从器件输出要读取的寄存器值,我们具体来看一下这步。
(1)、主机发送起始信号。
(2)、主机发送要读取的 I2C 从设备地址。
(3)、读写控制位,因为是向 I2C 从设备发送数据,因此是写信号。
(4)、从机发送的 ACK 应答信号。
(5)、重新发送 START 信号。
(6)、主机发送要读取的寄存器地址。
(7)、从机发送的 ACK 应答信号。
(8)、重新发送 START 信号。
(9)、重新发送要读取的 I2C 从设备地址。
(10)、读写控制位,这里是读信号,表示接下来是从 I2C 从设备里面读取数据。
(11)、从机发送的 ACK 应答信号。
(12)、从 I2C 器件里面读取到的数据。
(13)、主机发出 NO ACK 信号,表示读取完成,不需要从机再发送 ACK 信号了。
(14)、主机发出 STOP 信号,停止 I2C 通信。

Linux下IIC使用方法

IIC总线类似platform总线一样,将设备和驱动分开:
在这里插入图片描述
这里要用到两个重要的数据结构: i2c_adapter 和 i2c_algorithm, Linux 内核将 SOC 的 I2C 适配器(控
制器)抽象成 i2c_adapter, i2c_adapter 结构体定义在 include/linux/i2c.h 文件中,结构体内容如下:

struct i2c_adapter {
 struct module *owner;
 unsigned int class; /* classes to allow probing for */
 const struct i2c_algorithm *algo; /* 总线访问算法 */
 void *algo_data;
 /* data fields that are valid for all devices */
 struct rt_mutex bus_lock;
 int timeout; /* in jiffies */
 int retries;
 struct device dev; /* the adapter device */
 int nr;
 char name[48];
 struct completion dev_released;
 struct mutex userspace_clients_lock;
 struct list_head userspace_clients;
 struct i2c_bus_recovery_info *bus_recovery_info;
 const struct i2c_adapter_quirks *quirks;
 };

i2c_adapter结构体中有 i2c_algorithm这样有这样一个结构体,对于一个 I2C 适配器,肯定要对外提供读写 API 函数,设备驱动程序可以使用这些 API 函数来完成读写操作。 i2c_algorithm 就是 I2C 适配器与 IIC 设备进行通信的方法。

 struct i2c_algorithm {
......
 int (*master_xfer)(struct i2c_adapter *adap,
struct i2c_msg *msgs,
 int num);
 int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
 unsigned short flags, char read_write,
 u8 command, int size, union i2c_smbus_data *data);
 /* To determine what the adapter supports */
 u32 (*functionality) (struct i2c_adapter *);
......
 };

master_xfer 就是 I2C 适配器的传输函数,可以通过此函数来完成与 IIC 设备之间的通信。
smbus_xfer 就是 SMBUS 总线的传输函数。

综上所述, I2C 总线驱动,或者说 I2C 适配器驱动的主要工作就是初始化 i2c_adapter 结构体变量,
然后设置 i2c_algorithm 中的 master_xfer 函数。完成以后通过 i2c_add_numbered_adapter或
i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter,这两个函数的原型如下:

int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
这两个函数的区别在于 i2c_add_adapter 使用动态的总线号,而 i2c_add_numbered_adapter使用静态总线号。函数参数和返回值含义如下:
adapter 或 adap:要添加到 Linux 内核中的 i2c_adapter,也就是 I2C 适配器。
返回值: 0,成功;负值,失败。
如果要删除 I2C 适配器的话使用 i2c_del_adapter 函数即可,函数原型如下:
void i2c_del_adapter(struct i2c_adapter * adap)
函数参数和返回值含义如下:
adap:要删除的 I2C 适配器。
返回值: 无

一般 SOC 的 I2C 总线驱动都是由半导体厂商编写的,比如 I.MX6U 的 I2C 适配器驱动 NXP 已经编写好了,这个不需要用户去编写。因此 I2C 总线驱动对我们这些 SOC 使用者来说是被屏蔽掉的,我们只要专注于 I2C 设备驱动即可。除非你是在半导体公司上班,工作内容就是写 I2C 适配器驱动。

IIC设备驱动

I2C 设备驱动重点关注两个数据结构: i2c_client 和 i2c_driver,根据总线、设备和驱动模型,I2C 总线上一小节已经讲了。还剩下设备和驱动, i2c_client 就是描述设备信息的, i2c_driver 描述驱动内容,类似于 platform_driver。

i2c_client 结构体:

 struct i2c_client {
 unsigned short flags; /* 标志 */
 unsigned short addr; /* 芯片地址, 7 位,存在低 7 位*/
......
 char name[I2C_NAME_SIZE]; /* 名字 */
 struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
 struct device dev; /* 设备结构体 */
 int irq; /* 中断 */
 struct list_head detected;
......
 };

一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。

i2c_driver 结构体

struct i2c_driver {
1 unsigned int class;
2 int (*attach_adapter)(struct i2c_adapter *) __deprecated;
3 int (*probe)(struct i2c_client *, const struct i2c_device_id *);
4 int (*remove)(struct i2c_client *);
5 void (*shutdown)(struct i2c_client *);
6 void (*alert)(struct i2c_client *, unsigned int data);
7 int (*command)(struct i2c_client *client, unsigned int cmd,void *arg);
8 struct device_driver driver;
9 const struct i2c_device_id *id_table;
10 int (*detect)(struct i2c_client *, struct i2c_board_info *);
11 const unsigned short *address_list;
12 struct list_head clients;
 };

第3个函数:当 I2C 设备和驱动匹配成功以后 probe 函数就会执行,和 platform 驱动一样。

第8个结构体:device_driver 驱动结构体,如果使用设备树的话,需要设置 device_driver 的
of_match_table 成员变量,也就是驱动的兼容(compatible)属性。

第9个结构体:id_table 是传统的、未使用设备树的设备匹配 ID 表。

对于我们 I2C 设备驱动编写人来说,重点工作就是构建 i2c_driver,构建完成以后需要向Linux 内核注册这个 i2c_driver。 i2c_driver 注册函数为 int i2c_register_driver,此函数原型如下:

int i2c_register_driver(struct module *owner,struct i2c_driver *driver)
函数参数和返回值含义如下:
owner: 一般为 THIS_MODULE。
driver:要注册的 i2c_driver。
返回值: 0,成功;负值,失败。

另外 i2c_add_driver 也常常用于注册 i2c_driver, i2c_add_driver 是一个宏,定义如下:

 #define i2c_add_driver(driver) \
 i2c_register_driver(THIS_MODULE, driver)

i2c_add_driver 就是对 i2c_register_driver 做了一个简单的封装,只有一个参数,就是要注册的i2c_driver。
注销 I2C 设备驱动的时候需要将前面注册的 i2c_driver 从 Linux 内核中注销掉,需要用到i2c_del_driver 函数,此函数原型如下:

void i2c_del_driver(struct i2c_driver *driver)
函数参数和返回值含义如下:
driver:要注销的 i2c_driver。
返回值: 无。

i2c_driver 的注册示例代码如下:

 /* i2c 驱动的 probe 函数 */
 static int xxx_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
 /* 函数具体程序 */
 return 0;
 }
/* i2c 驱动的 remove 函数 */
 static int ap3216c_remove(struct i2c_client *client)
 {
 /* 函数具体程序 */
 return 0;
 }

 /* 传统匹配方式 ID 列表 */
 static const struct i2c_device_id xxx_id[] = {
 	{"xxx", 0},
 	{}
 };

 /* 设备树匹配列表 */
 static const struct of_device_id xxx_of_match[] = {
 { .compatible = "xxx" },
 { /* Sentinel */ }
 };

 /* i2c 驱动结构体 */
 static struct i2c_driver xxx_driver = {
 .probe = xxx_probe,
 .remove = xxx_remove,
 .driver = {
 .owner = THIS_MODULE,
 .name = "xxx",
 .of_match_table = xxx_of_match,
 },
 .id_table = xxx_id,
 };

 /* 驱动入口函数 */
 static int __init xxx_init(void)
 {
 int ret = 0;

 ret = i2c_add_driver(&xxx_driver);
 return ret;
 }
 /* 驱动出口函数 */
 static void __exit xxx_exit(void)
{
 i2c_del_driver(&xxx_driver);
 }
 module_init(xxx_init);
 module_exit(xxx_exit);

基本上就是platform那一套

I2C 设备和驱动匹配过程

I2C 设备和驱动的匹配过程是由 I2C 核心来完成的, drivers/i2c/i2c-core.c 就是 I2C 的核心部分, I2C
核心提供了一些与具体硬件无关的 API 函数,比如前面讲过的:
1、 i2c_adapter 注册/注销函数

int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)
void i2c_del_adapter(struct i2c_adapter * adap)

2、 i2c_driver 注册/注销函数

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
int i2c_add_driver (struct i2c_driver *driver)
void i2c_del_driver(struct i2c_driver *driver)

设备和驱动的匹配过程也是由 I2C 总线完成的, I2C 总线的数据结构为 i2c_bus_type,定义在
drivers/i2c/i2c-core.c 文件, i2c_bus_type 内容如下:

 struct bus_type i2c_bus_type = {
 .name = "i2c",
 .match = i2c_device_match,
 .probe = i2c_device_probe,
 .remove = i2c_device_remove,
 .shutdown = i2c_device_shutdown,
 };

.match 就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match 这个函数,此函数内容如下:

static int i2c_device_match(struct device *dev, struct,device_driver *drv)
{
 struct i2c_client *client = i2c_verify_client(dev);
 struct i2c_driver *driver;

 if (!client)
 return 0;

 /* Attempt an OF style match
of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和 
of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C设备和驱动匹配
 */
 if (of_driver_match_device(dev, drv))
 return 1;

 /* Then ACPI style match 
acpi_driver_match_device 函数用于 ACPI 形式的匹配
*/
 if (acpi_driver_match_device(dev, drv))
 return 1;
 driver = to_i2c_driver(drv);
 
 /* match on an id table if there is one */
 if (driver->id_table)
 
 /*
 i2c_match_id 函数用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C
设备名字和 i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹配。
*/

 return i2c_match_id(driver->id_table, client) != NULL;

 return 0;
 }

* I2C 适配器驱动分析

此章不重要,可跳过!!!!
I2C 适配器驱动就是 SOC 的 I2C 控制器驱动。 I2C 设备驱动是需要用户根据不同的 I2C 设备去编写,
而 I2C 适配器驱动一般都是 SOC 厂商去编写的,比如 NXP 就编写好了 I.MX6U 的I2C 适配器驱动。在
imx6ull.dtsi 文件中找到 I.MX6U 的 I2C1 控制器节点,节点内容如下所示:

i2c1: i2c@021a0000 {
 #address-cells = <1>;
 #size-cells = <0>;
 compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
 reg = <0x021a0000 0x4000>;
 interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
 clocks = <&clks IMX6UL_CLK_I2C1>;
 status = "disabled";
 }

重点关注 i2c1 节点的 compatible 属性值,因为通过 compatible 属性值可以在 Linux 源码里面找到对应
的驱动文件。这里i2c1节点的compatible属性值有两个:“fsl,imx6ul-i2c”和“fsl,imx21-i2c”,在 Linux 源码
中搜索这两个字符串即可找到对应的驱动文件。 I.MX6U 的 I2C 适配器驱动驱动文件为
drivers/i2c/busses/i2c-imx.c,在此文件中有如下内容:

 static struct platform_device_id imx_i2c_devtype[] = {
{
 .name = "imx1-i2c",
 .driver_data = (kernel_ulong_t)&imx1_i2c_hwdata,
 }, {
 .name = "imx21-i2c",
 .driver_data = (kernel_ulong_t)&imx21_i2c_hwdata,
 }, {
 /* sentinel */
	 }
 };
 MODULE_DEVICE_TABLE(platform, imx_i2c_devtype);

 static const struct of_device_id i2c_imx_dt_ids[] = {
 { .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
 { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },//dts中compatible属性匹配
 { .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
 { /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, i2c_imx_dt_ids);
......
 static struct platform_driver i2c_imx_driver = {
 .probe = i2c_imx_probe,//probe函数,获取硬件资源信息,dts中的信息
 .remove = i2c_imx_remove,
 .driver = {
 .name = DRIVER_NAME,
 .owner = THIS_MODULE,
 .of_match_table = i2c_imx_dt_ids,
 .pm = IMX_I2C_PM,
 	},
 .id_table = imx_i2c_devtype,
 };

 static int __init i2c_adap_imx_init(void)
 {
 return platform_driver_register(&i2c_imx_driver);
 }
 subsys_initcall(i2c_adap_imx_init);

 static void __exit i2c_adap_imx_exit(void)
 {
 platform_driver_unregister(&i2c_imx_driver);
 }
 module_exit(i2c_adap_imx_exit);

probe函数:

 static int i2c_imx_probe(struct platform_device *pdev)
 {
 const struct of_device_id *of_id =
 of_match_device(i2c_imx_dt_ids, &pdev->dev);
 struct imx_i2c_struct *i2c_imx;
 struct resource *res;
 struct imxi2c_platform_data *pdata =dev_get_platdata(&pdev->dev);
 void __iomem *base;
 int irq, ret;
 dma_addr_t phy_addr;

 dev_dbg(&pdev->dev, "<%s>\n", __func__);

 irq = platform_get_irq(pdev, 0);//调用 platform_get_irq 函数获取中断号。
......
/*
调用 platform_get_resource 函数从设备树中获取 I2C1 控制器寄存器物理基地址,也就是 0X021A0000。获取到
寄存器基地址以后使用 devm_ioremap_resource 函数对其进行内存映射,得到可以在 Linux 内核中使用的虚拟地址。
*/
 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//这种是没有使用dts方法,只使用了platform
 base = devm_ioremap_resource(&pdev->dev, res);


 if (IS_ERR(base))
 return PTR_ERR(base);

 phy_addr = (dma_addr_t)res->start;
 
//NXP 使用 imx_i2c_struct 结构体来表示 I.MX 系列 SOC 的 I2C 控制器,这里使用 devm_kzalloc 函数来申请内存。
 i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx),GFP_KERNEL);
 if (!i2c_imx)
 return -ENOMEM;
 if (of_id)
 i2c_imx->hwdata = of_id->data;
 else
 i2c_imx->hwdata = (struct imx_i2c_hwdata *)
 platform_get_device_id(pdev)->driver_data;

 /* Setup i2c_imx driver structure */
 strlcpy(i2c_imx->adapter.name, pdev->name,sizeof(i2c_imx->adapter.name));
 //imx_i2c_struct 结构体要有个叫做 adapter 的成员变量, adapter 就是i2c_adapter,这里初始化i2c_adapter。
 i2c_imx->adapter.owner = THIS_MODULE;
 i2c_imx->adapter.algo = &i2c_imx_algo;//设置i2c_adapter的algo成员变量为i2c_imx_algo,也就是设置 i2c_algorithm。
 i2c_imx->adapter.dev.parent = &pdev->dev;
 i2c_imx->adapter.nr = pdev->id;
 i2c_imx->adapter.dev.of_node = pdev->dev.of_node;
 i2c_imx->base = base;

 /* Get I2C clock */
 i2c_imx->clk = devm_clk_get(&pdev->dev, NULL);
......
 ret = clk_prepare_enable(i2c_imx->clk);
......
 /* Request IRQ   注册 I2C 控制器中断,中断服务函数为 i2c_imx_isr。*/
 ret = devm_request_irq(&pdev->dev, irq, i2c_imx_isr,IRQF_NO_SUSPEND, pdev->name, i2c_imx);
......
 /* Init queue */
 init_waitqueue_head(&i2c_imx->queue);

 /* Set up adapter data */
 i2c_set_adapdata(&i2c_imx->adapter, i2c_imx);

 /* Set up clock divider 设置 I2C 频率默认为 IMX_I2C_BIT_RATE=100KHz,如果设备树节点设
置了“clock-frequency”属性的话 I2C 频率就使用 clock-frequency 属性值。 */
 i2c_imx->bitrate = IMX_I2C_BIT_RATE;
 ret = of_property_read_u32(pdev->dev.of_node,"clock-frequency", &i2c_imx->bitrate);
 
 if (ret < 0 && pdata && pdata->bitrate)
 i2c_imx->bitrate = pdata->bitrate;
 /* Set up chip registers to defaults 设置 I2C1 控制的 I2CR 和 I2SR 寄存器 */
 imx_i2c_write_reg(i2c_imx->hwdata->i2cr_ien_opcode ^ I2CR_IEN,i2c_imx, IMX_I2C_I2CR);
 imx_i2c_write_reg(i2c_imx->hwdata->i2sr_clr_opcode, i2c_imx,IMX_I2C_I2SR);
 
 /* Add I2C adapter 调用 i2c_add_numbered_adapter 函数向 Linux 内核注册 i2c_adapter。*/
 ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
 if (ret < 0) {
 dev_err(&pdev->dev, "registration failed\n");
 goto clk_disable;
 }

 /* Set up platform driver data */
 platform_set_drvdata(pdev, i2c_imx);
 clk_disable_unprepare(i2c_imx->clk);
......
 /* Init DMA config if supported  申请 DMA */
 i2c_imx_dma_request(i2c_imx, phy_addr);

 return 0; /* Return OK */

 clk_disable:
 clk_disable_unprepare(i2c_imx->clk);
 return ret;
 }

i2c_imx_probe 函数主要的工作就是一下两点:
①、初始化 i2c_adapter,设置 i2c_algorithm 为 i2c_imx_algo,最后向 Linux 内核注册
i2c_adapter。
②、初始化 I2C1 控制器的相关寄存器。
i2c_imx_algo 包含 I2C1 适配器与 I2C 设备的通信函数 master_xfer, i2c_imx_algo 结构体定
义如下:

static struct i2c_algorithm i2c_imx_algo = {
 .master_xfer = i2c_imx_xfer,
 .functionality = i2c_imx_func,
 };

. functionality, functionality用于返回此I2C适配器支持什么样的通信协议,在这里 functionality 就是
i2c_imx_func 函数, i2c_imx_func 函数内容如下:

static u32 i2c_imx_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_READ_BLOCK_DATA;
}

i2c_imx_xfer 函数,因为最终就是通过此函数来完成与 I2C 设备通信的, 此函数内容如下(有省略):

static int i2c_imx_xfer(struct i2c_adapter *adapter,
 struct i2c_msg *msgs, int num)
 {
 unsigned int i, temp;
 int result;
 bool is_lastmsg = false;
 struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);
 dev_dbg(&i2c_imx->adapter.dev, "<%s>\n", __func__);

 /* Start I2C transfer 调用 i2c_imx_start 函数开启 I2C 通信。 */
 result = i2c_imx_start(i2c_imx);
 if (result)
 goto fail0;

 /* read/write data */
 for (i = 0; i < num; i++) {
 if (i == num - 1)
 is_lastmsg = true;

 if (i) {
 dev_dbg(&i2c_imx->adapter.dev, "<%s> repeated start\n", __func__);
 temp = imx_i2c_read_reg(i2c_imx, IMX_I2C_I2CR);
 temp |= I2CR_RSTA;
 imx_i2c_write_reg(temp, i2c_imx, IMX_I2C_I2CR);
 result = i2c_imx_bus_busy(i2c_imx, 1);
 if (result)
 goto fail0;
 }
 dev_dbg(&i2c_imx->adapter.dev,
 "<%s> transfer message: %d\n", __func__, i);
 /* write/read data */
......
 if (msgs[i].flags & I2C_M_RD)
 result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);//如果是从 I2C 设备读数据的话就调用 i2c_imx_read 函数。
 /*
向 I2C 设备写数据,如果要用 DMA 的话就使用 i2c_imx_dma_write 函数来
完成写数据。如果不使用 DMA 的话就使用 i2c_imx_write 函数完成写数据。
 */
 else {
 if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
 result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
 else
 result = i2c_imx_write(i2c_imx, &msgs[i]);
 }
 if (result)
 goto fail0;
 }

 fail0:
 /* Stop I2C transfer */
 i2c_imx_stop(i2c_imx);//I2C 通信完成以后调用 i2c_imx_stop 函数停止 I2C 通信

 dev_dbg(&i2c_imx->adapter.dev, "<%s> exit with: %s: %d\n",__func__,
 (result < 0) ? "error" : "success msg",
 (result < 0) ? result : num);
 return (result < 0) ? result : num;
 }

I2C 设备驱动编写流程

不使用设备树这里就不作记录了

使用设备树方式

 &i2c1 {
 clock-frequency = <100000>;
 pinctrl-names = "default";
 pinctrl-0 = <&pinctrl_i2c1>;
 status = "okay";
 mag3110@0e {
 compatible = "fsl,mag3110";
 reg = <0x0e>;
 position = <2>;
 };
......
 };

I2C 设备数据收发处理流程
I2C 设备驱动首先要做的就是初始化 i2c_driver 并向 Linux 内核注册。当设备和驱动匹配以后 i2c_driver
里面的 probe 函数就会执行, probe 函数里面所做的就是字符设备驱动那一套了。 一般需要在 probe 函
数里面初始化 I2C 设备,要初始化 I2C 设备就必须能够对 I2C 设备寄存器进行读写操作,这里就要用到
i2c_transfer 函数了。 i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函
数,对于 I.MX6U 而言就是i2c_imx_xfer 这个函数。 i2c_transfer 函数原型如下:

int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num)
函数参数和返回值含义如下:
adap: 所使用的 I2C 适配器, i2c_client 会保存其对应的 i2c_adapter。
msgs: I2C 要发送的一个或多个消息。
num: 消息数量,也就是 msgs 的数量。
返回值: 负值,失败,其他非负值,发送的 msgs 数量。

重点来看一下 msgs 这个参数,这是一个 i2c_msg 类型的指针参数, I2C 进行数据收发说白了就是消息的传递, Linux 内核使用 i2c_msg 结构体来描述一个消息。 i2c_msg 结构体定义
在 include/uapi/linux/i2c.h 文件中,结构体内容如下:

 struct i2c_msg {
 __u16 addr; /* 从机地址 */
 __u16 flags; /* 标志 */
 #define I2C_M_TEN 0x0010
 #define I2C_M_RD 0x0001
 #define I2C_M_STOP 0x8000
 #define I2C_M_NOSTART 0x4000
 #define I2C_M_REV_DIR_ADDR 0x2000
 #define I2C_M_IGNORE_NAK 0x1000
 #define I2C_M_NO_RD_ACK 0x0800
 #define I2C_M_RECV_LEN 0x0400
 __u16 len; /* 消息(本 msg)长度 */
 __u8 *buf; /* 消息数据 */
 };

使用 i2c_transfer 函数发送数据之前要先构建好 i2c_msg,使用 i2c_transfer 进行 I2C 数据收
发的示例代码如下:

 /* 设备结构体 */
 struct xxx_dev {
 ......
 /*
设备结构体,在设备结构体里面添加一个执行void的指针成员变量private_data,此成员变量用于保存设备的私有数
据。在 I2C 设备驱动中我们一般将其指向 I2C 设备对应的i2c_client。
 */
 
 void *private_data; /* 私有数据,一般会设置为 i2c_client */
 };

/*
 * @description : 读取 I2C 设备多个寄存器数据
 * @param – dev : I2C 设备
 * @param – reg : 要读取的寄存器首地址
 * @param – val : 读取到的数据
 * @param – len : 要读取的数据长度
 * @return : 操作结果
xxx_read_regs 函数用于读取 I2C 设备多个寄存器数据。第 18 行定义了一个i2c_msg 数组, 2 个数组元素,因为 
I2C 读取数据的时候要先发送要读取的寄存器地址,然后再读取数据,所以需要准备两个 i2c_msg。一个用于发送寄
存器地址,一个用于读取寄存器值。对于 msg[0],将 flags 设置为 0,表示写数据。 msg[0]的 addr 是 I2C 设备
的器件地址, msg[0]的 buf成员变量就是要读取的寄存器地址。对于 msg[1],将 flags 设置为 I2C_M_RD,表示读
取数据。msg[1]的 buf 成员变量用于保存读取到的数据, len 成员变量就是要读取的数据长度。调用i2c_transfer 
函数完成 I2C 数据读操作。
 */
 static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val,int len)
 {
 int ret;
 struct i2c_msg msg[2];
 struct i2c_client *client = (struct i2c_client *)dev->private_data;
 /* msg[0],第一条写消息,发送要读取的寄存器首地址 */
 msg[0].addr = client->addr; /* I2C 器件地址 */
 msg[0].flags = 0; /* 标记为发送数据 */
 msg[0].buf = &reg; /* 读取的首地址 */
 msg[0].len = 1; /* reg 长度 */

 /* msg[1],第二条读消息,读取寄存器数据 */
 msg[1].addr = client->addr; /* I2C 器件地址 */
 msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
 msg[1].buf = val; /* 读取数据缓冲区 */
 msg[1].len = len; /* 要读取的数据长度 */
 ret = i2c_transfer(client->adapter, msg, 2);
 if(ret == 2) {
 ret = 0;
 } else {
 ret = -EREMOTEIO;
 }
 return ret;
 }

 /*
 * @description : 向 I2C 设备多个寄存器写入数据
 * @param – dev : 要写入的设备结构体
 * @param – reg : 要写入的寄存器首地址
 * @param – val : 要写入的数据缓冲区
 * @param – len : 要写入的数据长度
 * @return : 操作结果
 xxx_write_regs 函数用于向 I2C 设备多个寄存器写数据, I2C 写操作要比读操作简单一点,因此一个 i2c_msg即
 可。数组 b 用于存放寄存器首地址和要发送的数据,设置 msg 的 addr 为 I2C 器件地址。设置msg 的 flags 为 
 0,也就是写数据。设置要发送的数据,也就是数组 b。设置 msg 的 len 为len+1,因为要加上一个字节的寄存器地
 址。最后通过 i2c_transfer 函数完成向 I2C 设备的写操作
 
 */
 static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,u8 len)
 {
 u8 b[256];
 struct i2c_msg msg;
 struct i2c_client *client = (struct i2c_client *)dev->private_data;

 b[0] = reg; /* 寄存器首地址 */
 memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */

 msg.addr = client->addr; /* I2C 器件地址 */
 msg.flags = 0; /* 标记为写数据 */

 msg.buf = b; /* 要发送的数据缓冲区 */
 msg.len = len + 1; /* 要发送的数据长度 */

 return i2c_transfer(client->adapter, &msg, 1);
 }

另外还有两个API函数分别用于I2C数据的收发操作,这两个函数最终都会调用i2c_transfer。
首先来看一下 I2C 数据发送函数 i2c_master_send,函数原型如下:

int i2c_master_send(const struct i2c_client *client,const char *buf,int count)
函数参数和返回值含义如下:
client: I2C 设备对应的 i2c_client。
buf:要发送的数据。
count: 要发送的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无
符号 16)类型的数据。
返回值: 负值,失败,其他非负值,发送的字节数。

I2C 数据接收函数为 i2c_master_recv,函数原型如下:

int i2c_master_recv(const struct i2c_client *client,char *buf,int count)
函数参数和返回值含义如下:
client: I2C 设备对应的 i2c_client。
buf:要接收的数据。
count: 要接收的数据字节数,要小于 64KB,以为 i2c_msg 的 len 成员变量是一个 u16(无符号 16)类型的数据。
返回值: 负值,失败,其他非负值,发送的字节数。

正点原子示例

dts:

&i2c1 {
 clock-frequency = <100000>;
 pinctrl-names = "default";
 pinctrl-0 = <&pinctrl_i2c1>;
 status = "okay";
 
mag3110@0e {
 compatible = "fsl,mag3110";
 reg = <0x0e>;
 position = <2>;
 };

 fxls8471@1e {
 compatible = "fsl,fxls8471";
 reg = <0x1e>;
 position = <0>;
 interrupt-parent = <&gpio5>;
 interrupts = <0 8>;
 };
 };

driver:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: ap3216c.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: AP3216C驱动程序
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/9/2 左忠凯创建
***************************************************************/
#define AP3216C_CNT	1
#define AP3216C_NAME	"ap3216c"
/*
ap3216c 设备结构体, private_data 成员变量用于存放 ap3216c 对应的 i2c_client。第 40 行的 ir、 als 和 
ps 分别存储 AP3216C 的 IR、 ALS 和 PS 数据。
*/
struct ap3216c_dev {
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	struct device_node	*nd; /* 设备节点 */
	int major;			/* 主设备号 */
	void *private_data;	/* 私有数据 */
	unsigned short ir, als, ps;		/* 三个光传感器数据 */
};

static struct ap3216c_dev ap3216cdev;//定义一个 ap3216c_dev 类型的设备结构体变量 ap3216cdev。

/*
 * @description	: 从ap3216c读取多个寄存器数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 
ap3216c_read_regs 函数实现多字节读取,但是 AP3216C 好像不支持连续多字
节读取,此函数在测试其他 I2C 设备的时候可以实现多给字节连续读取,但是在 AP3216C 上不
能连续读取多个字节。不过读取一个字节没有问题的。
*/
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->private_data;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ap3216c地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ap3216c地址 */
	msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/
	msg[1].buf = val;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
		ret = -EREMOTEIO;
	}
	return ret;
}

/*
 * @description	: 向ap3216c多个寄存器写入数据
 * @param - dev:  ap3216c设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 
 ap3216c_write_regs 函数实现连续多字节写操作。
 
 */
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	
	b[0] = reg;					/* 寄存器首地址 */
	memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/* ap3216c地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

/*
 * @description	: 读取ap3216c指定寄存器值,读取一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要读取的寄存器
 * @return 	  :   读取到的寄存器值
 
 ap3216c_read_reg 函数用于读取 AP3216C 的指定寄存器数据,用于一个寄存器的数据读取。
 
 */
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
	u8 data = 0;

	ap3216c_read_regs(dev, reg, &data, 1);
	return data;

#if 0
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	return i2c_smbus_read_byte_data(client, reg);
#endif
}

/*
 * @description	: 向ap3216c指定寄存器写入指定的值,写一个寄存器
 * @param - dev:  ap3216c设备
 * @param - reg:  要写的寄存器
 * @param - data: 要写入的值
 * @return   :    无
 ap3216c_write_reg 函数用于向 AP3216C 的指定寄存器写入数据,用于一个寄存器的数据写操作。
 */
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
	u8 buf = 0;
	buf = data;
	ap3216c_write_regs(dev, reg, &buf, 1);
}

/*
 * @description	: 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
 *				: 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms
 * @param - ir	: ir数据
 * @param - ps 	: ps数据
 * @param - ps 	: als数据 
 * @return 		: 无。

 读取 AP3216C 的 PS、 ALS 和 IR 等传感器原始数据值。
 */
void ap3216c_readdata(struct ap3216c_dev *dev)
{
	unsigned char i =0;
    unsigned char buf[6];
	
	/* 循环读取所有传感器数据 */
    for(i = 0; i < 6; i++)	
    {
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);	
    }

    if(buf[0] & 0X80) 	/* IR_OF位为1,则数据无效 */
		dev->ir = 0;					
	else 				/* 读取IR传感器的数据   		*/
		dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); 			
	
	dev->als = ((unsigned short)buf[3] << 8) | buf[2];	/* 读取ALS传感器的数据 			 */  
	
    if(buf[4] & 0x40)	/* IR_OF位为1,则数据无效 			*/
		dev->ps = 0;    													
	else 				/* 读取PS传感器的数据    */
		dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F); 
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int ap3216c_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &ap3216cdev;

	/* 初始化AP3216C */
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);		/* 复位AP3216C 			*/
	mdelay(50);														/* AP3216C复位最少10ms 	*/
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);		/* 开启ALS、PS+IR 		*/
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	short data[3];
	long err = 0;

	struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
	
	ap3216c_readdata(dev);

	data[0] = dev->ir;
	data[1] = dev->als;
	data[2] = dev->ps;
	err = copy_to_user(buf, data, sizeof(data));
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int ap3216c_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* AP3216C操作函数 */
static const struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open = ap3216c_open,
	.read = ap3216c_read,
	.release = ap3216c_release,
};

 /*
  * @description     : i2c驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * @return          : 0,成功;其他负值,失败

ap3216c_probe 函数,当 I2C 设备和驱动匹配成功以后此函数就会执行,和platform 驱动框架一样。此函数前面都
是标准的字符设备注册代码,最后面会将此函数的第一个参数 client 传递给 ap3216cdev 的 private_data 成员变
量。
  */
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/* 1、构建设备号 */
	if (ap3216cdev.major) {
		ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
		register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
	} else {
		alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
		ap3216cdev.major = MAJOR(ap3216cdev.devid);
	}

	/* 2、注册设备 */
	cdev_init(&ap3216cdev.cdev, &ap3216c_ops);
	cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);

	/* 3、创建类 */
	ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.class)) {
		return PTR_ERR(ap3216cdev.class);
	}

	/* 4、创建设备 */
	ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.device)) {
		return PTR_ERR(ap3216cdev.device);
	}

	ap3216cdev.private_data = client;

	return 0;
}

/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int ap3216c_remove(struct i2c_client *client)
{
	/* 删除设备 */
	cdev_del(&ap3216cdev.cdev);
	unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);

	/* 注销掉类和设备 */
	device_destroy(ap3216cdev.class, ap3216cdev.devid);
	class_destroy(ap3216cdev.class);
	return 0;
}

/* 传统匹配方式ID列表
ap3216c_id 匹配表, i2c_device_id 类型。用于传统的设备和驱动匹配,也就是没有使用设备树的时候。
 */
static const struct i2c_device_id ap3216c_id[] = {
	{"alientek,ap3216c", 0},  
	{}
};

/* 设备树匹配列表
ap3216c_of_match 匹配表, of_device_id 类型,用于设备树设备和驱动匹配。这里只写了一个 compatible 属
性,值为“alientek,ap3216c”。
 */
static const struct of_device_id ap3216c_of_match[] = {
	{ .compatible = "alientek,ap3216c" },
	{ /* Sentinel */ }
};

/* i2c驱动结构体 */	
static struct i2c_driver ap3216c_driver = {
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "ap3216c",
		   	.of_match_table = ap3216c_of_match, 
		   },
	.id_table = ap3216c_id,
};
		   
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init ap3216c_init(void)
{
	int ret = 0;

	ret = i2c_add_driver(&ap3216c_driver);
	return ret;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit ap3216c_exit(void)
{
	i2c_del_driver(&ap3216c_driver);
}

/* module_i2c_driver(ap3216c_driver) */

module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

新手入门,以上均总结自正点原子教程。
纯个人学习笔记,便于随时查找。感谢阅读

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux的I2C驱动程序包括一个I2C核心子系统和一个或多个I2C适配器驱动程序。I2C核心子系统提供了与I2C总线通信的基本功能,而适配器驱动程序则负责控制特定的I2C适配器硬件。 在Linux内核中,I2C适配器驱动程序通常位于`drivers/i2c/busses/`目录下。常见的I2C适配器驱动程序包括: - `i2c-algo-bit.c`:适用于通过GPIO模拟I2C总线的设备。 - `i2c-algo-pca.c`:适用于使用PCA9564等I2C总线控制器的设备。 - `i2c-algo-pcf.c`:适用于使用PCF8584等I2C总线控制器的设备。 - `i2c-designware-core.c`:适用于Intel、Altera等硬件平台上的I2C适配器。 - `i2c-imx.c`:适用于Freescale i.MX系列处理器上的I2C适配器。 - `i2c-mux.c`:适用于使用I2C多路复用器的设备。 - `i2c-mv64xxx.c`:适用于Marvell MV64xxx系列处理器上的I2C适配器。 - `i2c-omap.c`:适用于TI OMAP系列处理器上的I2C适配器。 - `i2c-xiic.c`:适用于Xilinx FPGA上的I2C适配器。 此外,还有一些I2C设备驱动程序,它们使用I2C适配器驱动程序提供的接口与I2C设备进行通信。常见的I2C设备驱动程序包括: - `i2c-core.c`:I2C核心子系统,提供了与I2C总线通信的基本功能。 - `i2c-dev.c`:用户空间访问I2C设备的接口,通过`/dev/i2c-*`设备节点提供。 - `i2c-smbus.c`:提供了SMBus协议的实现,可以通过`i2c_smbus_*()`函数访问SMBus设备。 - `i2c-rtc.c`:用于实时时钟(RTC)设备的驱动程序。 - `i2c-hid.c`:用于USB HID设备的I2C适配器驱动程序。 以上是Linux内核中常见的I2C适配器驱动程序和设备驱动程序,它们提供了完整的I2C总线支持。用户可以根据自己的需求选择适配器驱动程序和设备驱动程序,并修改配置文件来启用它们。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值