《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》 第 15 章 Linux I2C 核心、总线与设备驱动

《Linux 设备驱动开发详解:基于最新的 Linux 4.0 内核》

第 15 章 Linux I2C 核心、总线与设备驱动

参考:宋宝华 著,机械工业出版社,2015年版


15.1 Linux I2C 体系结构

15.1.1 I2C 总线回顾

I2C(Inter-Integrated Circuit)是由 Philips(现 NXP)公司开发的两线制同步串行总线,只需 SDA(数据线)和 SCL(时钟线)即可连接多个设备:

复制代码
I2C 总线物理连接:

VCC
 │
 ├── 上拉电阻(4.7kΩ)── SDA ──┬──────────────┬──────────────┐
 │                              │              │              │
 └── 上拉电阻(4.7kΩ)── SCL ──┼──────────────┼──────────────┤
                                │              │              │
                           主设备          从设备0         从设备1
                          (Master)       (Addr:0x48)    (Addr:0x50)
                          SoC I2C控制器  LM75温度传感器  AT24C02 EEPROM

I2C 总线特性:
  - 只需 2 根信号线(SDA + SCL)
  - 支持多主多从(通过仲裁机制)
  - 每个从设备有唯一的 7 位或 10 位地址
  - 标准模式:100kbps;快速模式:400kbps;高速模式:3.4Mbps
  - 开漏输出 + 上拉电阻(允许多设备共享总线)

15.1.2 Linux I2C 体系结构概述

Linux 将 I2C 驱动分为三个层次,形成清晰的分层架构:

复制代码
Linux I2C 体系结构:

┌─────────────────────────────────────────────────────────────┐
│                      用户空间                                │
│  应用程序通过 /dev/i2c-N 或设备驱动接口访问 I2C 设备         │
└──────────────────────────┬──────────────────────────────────┘
                           │
┌──────────────────────────▼──────────────────────────────────┐
│                   I2C 设备驱动层                              │
│  (针对具体 I2C 设备的驱动,如温度传感器、EEPROM 等)         │
│  i2c_driver + i2c_client                                    │
│  实现:probe/remove/read/write 等操作                        │
└──────────────────────────┬──────────────────────────────────┘
                           │ i2c_transfer() / i2c_smbus_xxx()
┌──────────────────────────▼──────────────────────────────────┐
│                   I2C 核心层(I2C Core)                     │
│  提供统一的 API,管理适配器和设备的注册/注销                  │
│  i2c_add_adapter / i2c_register_driver                      │
│  i2c_transfer / i2c_smbus_read_byte_data 等                 │
└──────────────────────────┬──────────────────────────────────┘
                           │ master_xfer()
┌──────────────────────────▼──────────────────────────────────┐
│                   I2C 适配器驱动层                            │
│  (针对具体 SoC 的 I2C 控制器驱动)                          │
│  i2c_adapter + i2c_algorithm                                │
│  实现:master_xfer(实际的 I2C 时序操作)                    │
└──────────────────────────┬──────────────────────────────────┘
                           │ 操作硬件寄存器
┌──────────────────────────▼──────────────────────────────────┐
│                   I2C 硬件层                                  │
│  SoC 内置 I2C 控制器(如 i.MX6 的 I2C1/I2C2/I2C3)          │
│  外部 I2C 设备(LM75、AT24C02、MPU6050 等)                  │
└─────────────────────────────────────────────────────────────┘

15.1.3 I2C 体系结构的三个核心概念

复制代码
三个核心概念:

1. i2c_adapter(I2C 适配器)
   对应 SoC 上的一个 I2C 控制器
   每个 I2C 总线对应一个 adapter
   例如:i2c-0(/dev/i2c-0)、i2c-1(/dev/i2c-1)

2. i2c_client(I2C 客户端/设备)
   对应 I2C 总线上的一个从设备
   包含设备地址、所属 adapter 等信息
   例如:地址 0x48 的 LM75 温度传感器

3. i2c_driver(I2C 驱动)
   对应一类 I2C 设备的驱动程序
   通过 id_table 或 of_match_table 与 i2c_client 匹配
   实现 probe/remove 等函数

三者的关系:
  i2c_adapter(总线)
      ├── i2c_client(设备0,地址0x48)← i2c_driver(LM75驱动)
      ├── i2c_client(设备1,地址0x50)← i2c_driver(AT24C02驱动)
      └── i2c_client(设备2,地址0x68)← i2c_driver(MPU6050驱动)

15.2 Linux I2C 核心

15.2.1 I2C 核心的作用

I2C 核心(I2C Core)是 Linux I2C 子系统的中间层,提供:

  1. 适配器管理:注册/注销 I2C 适配器
  2. 设备管理:注册/注销 I2C 设备
  3. 驱动管理:注册/注销 I2C 驱动,完成设备与驱动的匹配
  4. 通信 API:提供统一的 I2C 传输接口

15.2.2 i2c_adapter ------ I2C 适配器

c 复制代码
#include <linux/i2c.h>

/*
 * i2c_adapter:描述一个 I2C 总线控制器
 * 每个 SoC 的 I2C 控制器对应一个 i2c_adapter
 */
struct i2c_adapter {
    struct module   *owner;
    unsigned int     class;         /* 适配器类型 */
    const struct i2c_algorithm *algo; /* 算法(实现实际传输)*/
    void            *algo_data;     /* 算法私有数据 */

    int              nr;            /* 适配器编号(/dev/i2c-N 中的 N)*/
    char             name[48];      /* 适配器名称 */

    struct device    dev;           /* 内嵌设备结构体 */
    /* ... */
};

/*
 * i2c_algorithm:I2C 传输算法
 * 适配器驱动必须实现 master_xfer 函数
 */
struct i2c_algorithm {
    /*
     * master_xfer:执行 I2C 传输
     * adap:适配器
     * msgs:消息数组
     * num:消息数量
     * 返回:成功传输的消息数,负值表示错误
     */
    int (*master_xfer)(struct i2c_adapter *adap,
                       struct i2c_msg *msgs, int num);

    /* 功能查询(返回适配器支持的功能标志)*/
    u32 (*functionality)(struct i2c_adapter *adap);

    /* SMBus 传输(可选,不实现则由 master_xfer 模拟)*/
    int (*smbus_xfer)(struct i2c_adapter *adap, u16 addr,
                      unsigned short flags, char read_write,
                      u8 command, int size, union i2c_smbus_data *data);
};

15.2.3 i2c_msg ------ I2C 消息

c 复制代码
/*
 * i2c_msg:描述一次 I2C 传输消息
 * 一次完整的 I2C 传输可能包含多个消息(如写地址后读数据)
 */
struct i2c_msg {
    __u16   addr;    /* 从设备地址(7位或10位)*/
    __u16   flags;   /* 传输标志 */
    __u16   len;     /* 数据长度(字节)*/
    __u8   *buf;     /* 数据缓冲区 */
};

/* flags 常用值 */
#define I2C_M_RD        0x0001  /* 读操作(从设备→主设备)*/
#define I2C_M_TEN       0x0010  /* 10位地址 */
#define I2C_M_NOSTART   0x4000  /* 不发送 START 条件 */
#define I2C_M_STOP      0x8000  /* 强制发送 STOP 条件 */

15.2.4 I2C 核心 API

c 复制代码
/* ── 适配器管理 ──────────────────────────────────────────── */

/* 注册 I2C 适配器(动态分配编号)*/
int i2c_add_adapter(struct i2c_adapter *adap);

/* 注册 I2C 适配器(指定编号)*/
int i2c_add_numbered_adapter(struct i2c_adapter *adap);

/* 注销 I2C 适配器 */
void i2c_del_adapter(struct i2c_adapter *adap);

/* ── 驱动管理 ──────────────────────────────────────────── */

/* 注册 I2C 驱动 */
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
/* 简化宏 */
#define i2c_add_driver(driver) \
    i2c_register_driver(THIS_MODULE, driver)

/* 注销 I2C 驱动 */
void i2c_del_driver(struct i2c_driver *driver);
/* 简化宏 */
#define module_i2c_driver(__i2c_driver) \
    module_driver(__i2c_driver, i2c_add_driver, i2c_del_driver)

/* ── 数据传输 ──────────────────────────────────────────── */

/*
 * i2c_transfer:执行 I2C 传输(最底层接口)
 * adap:适配器
 * msgs:消息数组
 * num:消息数量
 * 返回:成功传输的消息数,负值表示错误
 */
int i2c_transfer(struct i2c_adapter *adap,
                 struct i2c_msg *msgs, int num);

/*
 * i2c_master_send:向从设备发送数据
 * client:I2C 客户端
 * buf:数据缓冲区
 * count:字节数
 */
int i2c_master_send(const struct i2c_client *client,
                    const char *buf, int count);

/*
 * i2c_master_recv:从从设备接收数据
 */
int i2c_master_recv(const struct i2c_client *client,
                    char *buf, int count);

/* ── SMBus 接口(更高层,基于 i2c_transfer 实现)──────── */

/* 读取一个字节 */
s32 i2c_smbus_read_byte(const struct i2c_client *client);

/* 写入一个字节 */
s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value);

/* 读取指定寄存器的一个字节 */
s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command);

/* 向指定寄存器写入一个字节 */
s32 i2c_smbus_write_byte_data(const struct i2c_client *client,
                               u8 command, u8 value);

/* 读取指定寄存器的两个字节(16位)*/
s32 i2c_smbus_read_word_data(const struct i2c_client *client, u8 command);

/* 向指定寄存器写入两个字节 */
s32 i2c_smbus_write_word_data(const struct i2c_client *client,
                               u8 command, u16 value);

/* 读取多个字节(块读)*/
s32 i2c_smbus_read_i2c_block_data(const struct i2c_client *client,
                                   u8 command, u8 length, u8 *values);

/* 写入多个字节(块写)*/
s32 i2c_smbus_write_i2c_block_data(const struct i2c_client *client,
                                    u8 command, u8 length,
                                    const u8 *values);

/* 检查适配器是否支持某功能 */
int i2c_check_functionality(struct i2c_adapter *adap, u32 func);

15.2.5 i2c_transfer 的使用案例

c 复制代码
/*
 * 案例:使用 i2c_transfer 读取 AT24C02 EEPROM
 * AT24C02 读操作:
 *   1. 发送写命令(设备地址 + W)+ 字节地址
 *   2. 发送读命令(设备地址 + R)+ 读取数据
 */
static int at24c02_read(struct i2c_client *client,
                         u8 reg_addr, u8 *data, int len)
{
    struct i2c_msg msgs[2];
    int ret;

    /* 消息1:写字节地址(告诉 EEPROM 从哪里开始读)*/
    msgs[0].addr  = client->addr;   /* 设备地址(如 0x50)*/
    msgs[0].flags = 0;              /* 写操作(无 I2C_M_RD 标志)*/
    msgs[0].len   = 1;              /* 发送1字节(寄存器地址)*/
    msgs[0].buf   = &reg_addr;      /* 寄存器地址 */

    /* 消息2:读数据(重复 START + 读命令)*/
    msgs[1].addr  = client->addr;
    msgs[1].flags = I2C_M_RD;      /* 读操作 */
    msgs[1].len   = len;            /* 读取字节数 */
    msgs[1].buf   = data;           /* 接收缓冲区 */

    /* 执行传输(两个消息,中间有 Repeated START)*/
    ret = i2c_transfer(client->adapter, msgs, 2);
    if (ret != 2) {
        dev_err(&client->dev, "I2C 读取失败:%d\n", ret);
        return ret < 0 ? ret : -EIO;
    }

    return 0;
}

/*
 * 案例:使用 i2c_smbus_xxx 读取 LM75 温度传感器
 * LM75 温度寄存器地址:0x00
 */
static int lm75_read_temp(struct i2c_client *client, int *temp_mc)
{
    s32 ret;

    /* 读取温度寄存器(16位)*/
    ret = i2c_smbus_read_word_data(client, 0x00);
    if (ret < 0) {
        dev_err(&client->dev, "读取温度失败:%d\n", ret);
        return ret;
    }

    /*
     * LM75 温度数据格式:
     * 高字节:温度整数部分(有符号)
     * 低字节:bit7 = 0.5°C 分辨率
     * 字节序:大端(需要交换)
     */
    s16 raw = (s16)swab16((u16)ret);
    raw >>= 5;  /* 右移5位,取高11位 */

    /* 转换为毫摄氏度 */
    *temp_mc = (raw / 8) * 1000 + (raw % 8) * 125;

    return 0;
}

15.3 Linux I2C 适配器驱动

15.3.1 适配器驱动的作用

I2C 适配器驱动负责控制 SoC 内置的 I2C 控制器硬件,实现实际的 I2C 时序操作。每个 SoC 厂商都需要为自己的 I2C 控制器编写适配器驱动。

15.3.2 适配器驱动的框架

c 复制代码
/*
 * i2c_adapter_driver.c ------ I2C 适配器驱动框架
 * 以简化的 I2C 控制器为例
 */

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/completion.h>

/* I2C 控制器寄存器定义 */
#define I2C_CTRL_REG    0x00   /* 控制寄存器 */
#define I2C_STATUS_REG  0x04   /* 状态寄存器 */
#define I2C_DATA_REG    0x08   /* 数据寄存器 */
#define I2C_ADDR_REG    0x0C   /* 地址寄存器 */
#define I2C_CLK_REG     0x10   /* 时钟分频寄存器 */

/* 控制寄存器位定义 */
#define I2C_CTRL_EN     BIT(0)  /* 使能 I2C 控制器 */
#define I2C_CTRL_START  BIT(1)  /* 发送 START 条件 */
#define I2C_CTRL_STOP   BIT(2)  /* 发送 STOP 条件 */
#define I2C_CTRL_READ   BIT(3)  /* 读操作 */
#define I2C_CTRL_ACK    BIT(4)  /* 发送 ACK */
#define I2C_CTRL_INTEN  BIT(5)  /* 使能中断 */

/* 状态寄存器位定义 */
#define I2C_STATUS_BUSY  BIT(0)  /* 总线忙 */
#define I2C_STATUS_DONE  BIT(1)  /* 传输完成 */
#define I2C_STATUS_NACK  BIT(2)  /* 收到 NACK */
#define I2C_STATUS_ARB   BIT(3)  /* 仲裁失败 */

/* 适配器私有数据 */
struct my_i2c_dev {
    struct i2c_adapter  adapter;    /* 内嵌 i2c_adapter */
    void __iomem       *base;       /* 寄存器基地址 */
    int                 irq;        /* 中断号 */
    struct clk         *clk;        /* 时钟 */
    struct completion   completion; /* 传输完成信号 */
    int                 msg_err;    /* 传输错误码 */
    spinlock_t          lock;
};

/* ── 中断处理函数 ──────────────────────────────────────────── */

static irqreturn_t my_i2c_irq_handler(int irq, void *dev_id)
{
    struct my_i2c_dev *i2c_dev = dev_id;
    u32 status = readl(i2c_dev->base + I2C_STATUS_REG);

    /* 清除中断 */
    writel(status, i2c_dev->base + I2C_STATUS_REG);

    if (status & I2C_STATUS_NACK) {
        i2c_dev->msg_err = -ENXIO;  /* 设备无应答 */
    } else if (status & I2C_STATUS_ARB) {
        i2c_dev->msg_err = -EAGAIN; /* 仲裁失败 */
    } else if (status & I2C_STATUS_DONE) {
        i2c_dev->msg_err = 0;       /* 传输成功 */
    }

    /* 通知等待的传输函数 */
    complete(&i2c_dev->completion);

    return IRQ_HANDLED;
}

/* ── 发送 START 条件 ──────────────────────────────────────── */

static int my_i2c_start(struct my_i2c_dev *i2c_dev,
                         u8 addr, int read)
{
    u32 ctrl;
    unsigned long timeout;

    /* 等待总线空闲 */
    timeout = jiffies + msecs_to_jiffies(100);
    while (readl(i2c_dev->base + I2C_STATUS_REG) & I2C_STATUS_BUSY) {
        if (time_after(jiffies, timeout))
            return -ETIMEDOUT;
        msleep(1);
    }

    /* 设置从设备地址 */
    writel((addr << 1) | (read ? 1 : 0),
           i2c_dev->base + I2C_ADDR_REG);

    /* 发送 START 条件 */
    reinit_completion(&i2c_dev->completion);
    ctrl = I2C_CTRL_EN | I2C_CTRL_START | I2C_CTRL_INTEN;
    if (read)
        ctrl |= I2C_CTRL_READ;
    writel(ctrl, i2c_dev->base + I2C_CTRL_REG);

    /* 等待传输完成 */
    if (!wait_for_completion_timeout(&i2c_dev->completion,
                                      msecs_to_jiffies(100)))
        return -ETIMEDOUT;

    return i2c_dev->msg_err;
}

/* ── 核心传输函数(master_xfer)──────────────────────────── */

static int my_i2c_master_xfer(struct i2c_adapter *adap,
                               struct i2c_msg *msgs, int num)
{
    struct my_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
    int i, j, ret;

    for (i = 0; i < num; i++) {
        struct i2c_msg *msg = &msgs[i];
        int read = (msg->flags & I2C_M_RD) ? 1 : 0;

        /* 发送 START + 地址 */
        ret = my_i2c_start(i2c_dev, msg->addr, read);
        if (ret) {
            dev_err(&adap->dev, "START 失败:%d\n", ret);
            goto out_stop;
        }

        /* 传输数据 */
        for (j = 0; j < msg->len; j++) {
            reinit_completion(&i2c_dev->completion);

            if (read) {
                /* 读操作:触发读取 */
                u32 ctrl = I2C_CTRL_EN | I2C_CTRL_READ | I2C_CTRL_INTEN;
                /* 最后一个字节发送 NACK */
                if (j == msg->len - 1)
                    ctrl &= ~I2C_CTRL_ACK;
                else
                    ctrl |= I2C_CTRL_ACK;
                writel(ctrl, i2c_dev->base + I2C_CTRL_REG);

                /* 等待完成 */
                if (!wait_for_completion_timeout(&i2c_dev->completion,
                                                  msecs_to_jiffies(100))) {
                    ret = -ETIMEDOUT;
                    goto out_stop;
                }

                /* 读取数据 */
                msg->buf[j] = readl(i2c_dev->base + I2C_DATA_REG) & 0xFF;
            } else {
                /* 写操作:写入数据并触发发送 */
                writel(msg->buf[j], i2c_dev->base + I2C_DATA_REG);
                writel(I2C_CTRL_EN | I2C_CTRL_INTEN,
                       i2c_dev->base + I2C_CTRL_REG);

                /* 等待完成 */
                if (!wait_for_completion_timeout(&i2c_dev->completion,
                                                  msecs_to_jiffies(100))) {
                    ret = -ETIMEDOUT;
                    goto out_stop;
                }

                if (i2c_dev->msg_err) {
                    ret = i2c_dev->msg_err;
                    goto out_stop;
                }
            }
        }
    }

    ret = num;  /* 成功传输的消息数 */

out_stop:
    /* 发送 STOP 条件 */
    writel(I2C_CTRL_EN | I2C_CTRL_STOP, i2c_dev->base + I2C_CTRL_REG);
    udelay(10);

    return ret;
}

/* ── 功能查询函数 ──────────────────────────────────────────── */

static u32 my_i2c_functionality(struct i2c_adapter *adap)
{
    return I2C_FUNC_I2C |
           I2C_FUNC_SMBUS_EMUL |      /* 通过 master_xfer 模拟 SMBus */
           I2C_FUNC_10BIT_ADDR;       /* 支持10位地址 */
}

/* ── I2C 算法 ──────────────────────────────────────────────── */

static const struct i2c_algorithm my_i2c_algorithm = {
    .master_xfer   = my_i2c_master_xfer,
    .functionality = my_i2c_functionality,
};

/* ── probe 函数 ──────────────────────────────────────────────── */

static int my_i2c_probe(struct platform_device *pdev)
{
    struct my_i2c_dev *i2c_dev;
    struct resource *res;
    int ret;

    /* 分配私有数据 */
    i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL);
    if (!i2c_dev)
        return -ENOMEM;

    /* 获取并映射寄存器 */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    i2c_dev->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(i2c_dev->base))
        return PTR_ERR(i2c_dev->base);

    /* 获取中断号 */
    i2c_dev->irq = platform_get_irq(pdev, 0);
    if (i2c_dev->irq < 0)
        return i2c_dev->irq;

    /* 申请中断 */
    ret = devm_request_irq(&pdev->dev, i2c_dev->irq,
                            my_i2c_irq_handler, 0, "my-i2c", i2c_dev);
    if (ret)
        return ret;

    /* 获取并使能时钟 */
    i2c_dev->clk = devm_clk_get(&pdev->dev, NULL);
    if (IS_ERR(i2c_dev->clk))
        return PTR_ERR(i2c_dev->clk);
    clk_prepare_enable(i2c_dev->clk);

    /* 初始化完成量和自旋锁 */
    init_completion(&i2c_dev->completion);
    spin_lock_init(&i2c_dev->lock);

    /* 初始化 I2C 适配器 */
    i2c_dev->adapter.owner   = THIS_MODULE;
    i2c_dev->adapter.algo    = &my_i2c_algorithm;
    i2c_dev->adapter.dev.parent = &pdev->dev;
    i2c_dev->adapter.dev.of_node = pdev->dev.of_node;
    snprintf(i2c_dev->adapter.name, sizeof(i2c_dev->adapter.name),
             "my-i2c");

    /* 设置适配器私有数据 */
    i2c_set_adapdata(&i2c_dev->adapter, i2c_dev);

    /* 配置 I2C 时钟(100kHz)*/
    u32 clk_rate = clk_get_rate(i2c_dev->clk);
    writel(clk_rate / (100000 * 2), i2c_dev->base + I2C_CLK_REG);

    /* 使能 I2C 控制器 */
    writel(I2C_CTRL_EN, i2c_dev->base + I2C_CTRL_REG);

    /* 注册 I2C 适配器 */
    ret = i2c_add_adapter(&i2c_dev->adapter);
    if (ret) {
        dev_err(&pdev->dev, "注册 I2C 适配器失败:%d\n", ret);
        clk_disable_unprepare(i2c_dev->clk);
        return ret;
    }

    platform_set_drvdata(pdev, i2c_dev);
    dev_info(&pdev->dev, "I2C 适配器注册成功,编号 %d\n",
             i2c_dev->adapter.nr);
    return 0;
}

static int my_i2c_remove(struct platform_device *pdev)
{
    struct my_i2c_dev *i2c_dev = platform_get_drvdata(pdev);

    i2c_del_adapter(&i2c_dev->adapter);
    clk_disable_unprepare(i2c_dev->clk);
    return 0;
}

static const struct of_device_id my_i2c_of_match[] = {
    { .compatible = "myvendor,my-i2c" },
    {}
};
MODULE_DEVICE_TABLE(of, my_i2c_of_match);

static struct platform_driver my_i2c_driver = {
    .probe  = my_i2c_probe,
    .remove = my_i2c_remove,
    .driver = {
        .name           = "my-i2c",
        .of_match_table = my_i2c_of_match,
    },
};
module_platform_driver(my_i2c_driver);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("My I2C 适配器驱动");

15.4 Linux I2C 设备驱动

15.4.1 i2c_driver 和 i2c_client

c 复制代码
/*
 * i2c_driver:I2C 设备驱动
 * 类似 platform_driver,通过 id_table 或 of_match_table 匹配设备
 */
struct i2c_driver {
    unsigned int class;

    /* 设备与驱动匹配时调用 */
    int (*probe)(struct i2c_client *client,
                 const struct i2c_device_id *id);

    /* 设备移除时调用 */
    int (*remove)(struct i2c_client *client);

    /* 设备 ID 表(用于非设备树匹配)*/
    const struct i2c_device_id *id_table;

    struct device_driver driver;
};

/*
 * i2c_client:I2C 从设备
 * 由 I2C 核心根据设备树或 board 文件创建
 */
struct i2c_client {
    unsigned short  flags;      /* 标志(I2C_CLIENT_TEN 等)*/
    unsigned short  addr;       /* 从设备地址(7位或10位)*/
    char            name[I2C_NAME_SIZE]; /* 设备名称 */
    struct i2c_adapter *adapter; /* 所属适配器 */
    struct device   dev;        /* 内嵌设备结构体 */
    int             irq;        /* 中断号 */
    /* ... */
};

15.4.2 完整的 I2C 设备驱动案例(LM75 温度传感器)

c 复制代码
/*
 * lm75.c ------ LM75 温度传感器 I2C 设备驱动
 *
 * LM75 是一款常见的 I2C 温度传感器:
 * - I2C 地址:0x48~0x4F(由 A2A1A0 引脚决定)
 * - 温度范围:-55°C ~ +125°C
 * - 分辨率:0.5°C(9位)
 * - 温度寄存器:0x00(只读,16位)
 * - 配置寄存器:0x01(读写,8位)
 * - 过温阈值寄存器:0x03(读写,16位)
 * - 滞后寄存器:0x02(读写,16位)
 */

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/slab.h>
#include <linux/err.h>

/* LM75 寄存器地址 */
#define LM75_REG_TEMP   0x00   /* 温度寄存器 */
#define LM75_REG_CONF   0x01   /* 配置寄存器 */
#define LM75_REG_THYST  0x02   /* 滞后寄存器 */
#define LM75_REG_TOS    0x03   /* 过温阈值寄存器 */

/* 驱动私有数据 */
struct lm75_data {
    struct i2c_client   *client;
    struct device       *hwmon_dev;
    struct mutex         update_lock;
    bool                 valid;         /* 缓存是否有效 */
    unsigned long        last_updated;  /* 上次更新时间 */

    /* 缓存的传感器数据 */
    s16                  temp[3];       /* [0]=当前温度, [1]=滞后, [2]=过温 */
};

/* ── 读取温度寄存器 ──────────────────────────────────────── */

static s16 lm75_read_reg(struct i2c_client *client, u8 reg)
{
    s32 ret = i2c_smbus_read_word_data(client, reg);
    if (ret < 0)
        return ret;

    /* LM75 使用大端字节序,需要交换 */
    return (s16)swab16((u16)ret);
}

static int lm75_write_reg(struct i2c_client *client, u8 reg, s16 value)
{
    return i2c_smbus_write_word_data(client, reg, swab16((u16)value));
}

/* ── 温度转换函数 ──────────────────────────────────────────── */

/* 将 LM75 原始值转换为毫摄氏度 */
static int lm75_reg_to_mc(s16 reg)
{
    /* LM75 温度格式:高9位有效,bit7=0.5°C */
    return (reg >> 7) * 500;  /* 单位:毫摄氏度 */
}

/* 将毫摄氏度转换为 LM75 寄存器值 */
static s16 lm75_mc_to_reg(int mc)
{
    return (s16)((mc / 500) << 7);
}

/* ── 更新缓存数据 ──────────────────────────────────────────── */

static struct lm75_data *lm75_update_device(struct device *dev)
{
    struct lm75_data *data = dev_get_drvdata(dev);
    struct i2c_client *client = data->client;

    mutex_lock(&data->update_lock);

    /* 每秒最多更新一次(缓存机制)*/
    if (time_after(jiffies, data->last_updated + HZ) || !data->valid) {
        s16 val;

        val = lm75_read_reg(client, LM75_REG_TEMP);
        if (val < 0) goto out;
        data->temp[0] = val;

        val = lm75_read_reg(client, LM75_REG_THYST);
        if (val < 0) goto out;
        data->temp[1] = val;

        val = lm75_read_reg(client, LM75_REG_TOS);
        if (val < 0) goto out;
        data->temp[2] = val;

        data->last_updated = jiffies;
        data->valid = true;
    }
out:
    mutex_unlock(&data->update_lock);
    return data;
}

/* ── sysfs 属性(通过 hwmon 框架)──────────────────────────── */

/* 读取当前温度 */
static ssize_t temp1_input_show(struct device *dev,
                                 struct device_attribute *attr,
                                 char *buf)
{
    struct lm75_data *data = lm75_update_device(dev);
    return sprintf(buf, "%d\n", lm75_reg_to_mc(data->temp[0]));
}

/* 读取过温阈值 */
static ssize_t temp1_max_show(struct device *dev,
                               struct device_attribute *attr,
                               char *buf)
{
    struct lm75_data *data = lm75_update_device(dev);
    return sprintf(buf, "%d\n", lm75_reg_to_mc(data->temp[2]));
}

/* 设置过温阈值 */
static ssize_t temp1_max_store(struct device *dev,
                                struct device_attribute *attr,
                                const char *buf, size_t count)
{
    struct lm75_data *data = dev_get_drvdata(dev);
    struct i2c_client *client = data->client;
    long val;
    int ret;

    ret = kstrtol(buf, 10, &val);
    if (ret)
        return ret;

    mutex_lock(&data->update_lock);
    data->temp[2] = lm75_mc_to_reg(val);
    lm75_write_reg(client, LM75_REG_TOS, data->temp[2]);
    mutex_unlock(&data->update_lock);

    return count;
}

/* 读取滞后温度 */
static ssize_t temp1_max_hyst_show(struct device *dev,
                                    struct device_attribute *attr,
                                    char *buf)
{
    struct lm75_data *data = lm75_update_device(dev);
    return sprintf(buf, "%d\n", lm75_reg_to_mc(data->temp[1]));
}

/* 设置滞后温度 */
static ssize_t temp1_max_hyst_store(struct device *dev,
                                     struct device_attribute *attr,
                                     const char *buf, size_t count)
{
    struct lm75_data *data = dev_get_drvdata(dev);
    struct i2c_client *client = data->client;
    long val;
    int ret;

    ret = kstrtol(buf, 10, &val);
    if (ret)
        return ret;

    mutex_lock(&data->update_lock);
    data->temp[1] = lm75_mc_to_reg(val);
    lm75_write_reg(client, LM75_REG_THYST, data->temp[1]);
    mutex_unlock(&data->update_lock);

    return count;
}

/* 定义 sysfs 属性 */
static DEVICE_ATTR_RO(temp1_input);
static DEVICE_ATTR_RW(temp1_max);
static DEVICE_ATTR_RW(temp1_max_hyst);

static struct attribute *lm75_attrs[] = {
    &dev_attr_temp1_input.attr,
    &dev_attr_temp1_max.attr,
    &dev_attr_temp1_max_hyst.attr,
    NULL
};
ATTRIBUTE_GROUPS(lm75);

/* ── probe 函数 ──────────────────────────────────────────────── */

static int lm75_probe(struct i2c_client *client,
                       const struct i2c_device_id *id)
{
    struct lm75_data *data;
    int ret;

    /* 检查 I2C 适配器功能 */
    if (!i2c_check_functionality(client->adapter,
                                  I2C_FUNC_SMBUS_BYTE_DATA |
                                  I2C_FUNC_SMBUS_WORD_DATA)) {
        dev_err(&client->dev, "I2C 功能不支持\n");
        return -ENODEV;
    }

    /* 分配驱动私有数据 */
    data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    data->client = client;
    mutex_init(&data->update_lock);

    /* 将私有数据保存到 client */
    i2c_set_clientdata(client, data);

    /* 读取初始温度,验证设备是否正常 */
    ret = lm75_read_reg(client, LM75_REG_TEMP);
    if (ret < 0) {
        dev_err(&client->dev, "读取温度失败,设备可能不存在\n");
        return ret;
    }

    /* 注册到 hwmon 子系统 */
    data->hwmon_dev = devm_hwmon_device_register_with_groups(
        &client->dev,
        client->name,
        data,
        lm75_groups);

    if (IS_ERR(data->hwmon_dev)) {
        dev_err(&client->dev, "注册 hwmon 设备失败\n");
        return PTR_ERR(data->hwmon_dev);
    }

    dev_info(&client->dev, "LM75 温度传感器初始化成功,地址 0x%02x\n",
             client->addr);

    /* 读取并打印初始温度 */
    int temp_mc = lm75_reg_to_mc((s16)ret);
    dev_info(&client->dev, "当前温度:%d.%d°C\n",
             temp_mc / 1000, abs(temp_mc % 1000) / 100);

    return 0;
}

static int lm75_remove(struct i2c_client *client)
{
    dev_info(&client->dev, "LM75 驱动卸载\n");
    return 0;
}

/* ── 设备 ID 表 ──────────────────────────────────────────────── */

static const struct i2c_device_id lm75_id[] = {
    { "lm75",  0 },
    { "lm75a", 0 },
    { "lm75b", 0 },
    {}
};
MODULE_DEVICE_TABLE(i2c, lm75_id);

/* 设备树匹配表 */
static const struct of_device_id lm75_of_match[] = {
    { .compatible = "national,lm75" },
    { .compatible = "national,lm75a" },
    { .compatible = "ti,tmp75" },
    {}
};
MODULE_DEVICE_TABLE(of, lm75_of_match);

/* ── I2C 驱动结构体 ──────────────────────────────────────────── */

static struct i2c_driver lm75_driver = {
    .driver = {
        .name           = "lm75",
        .of_match_table = lm75_of_match,
    },
    .probe    = lm75_probe,
    .remove   = lm75_remove,
    .id_table = lm75_id,
};

/* 简化的模块注册宏 */
module_i2c_driver(lm75_driver);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("参考宋宝华《Linux设备驱动开发详解》");
MODULE_DESCRIPTION("LM75 温度传感器 I2C 驱动");

15.4.3 设备树中的 I2C 设备描述

dts 复制代码
/* 设备树中描述 I2C 总线和设备 */
&i2c1 {
    clock-frequency = <400000>;   /* I2C 时钟频率:400kHz(快速模式)*/
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    /* LM75 温度传感器,地址 0x48(A2A1A0=000)*/
    lm75: temperature-sensor@48 {
        compatible = "national,lm75";
        reg = <0x48>;             /* I2C 从设备地址 */
        /* 可选:中断引脚(OS/INT 引脚)*/
        interrupt-parent = <&gpio1>;
        interrupts = <5 IRQ_TYPE_LEVEL_LOW>;
    };

    /* AT24C02 EEPROM,地址 0x50(A2A1A0=000)*/
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
        pagesize = <8>;           /* 页大小:8字节 */
    };

    /* MPU6050 六轴传感器,地址 0x68(AD0=0)*/
    mpu6050@68 {
        compatible = "invensense,mpu6050";
        reg = <0x68>;
        interrupt-parent = <&gpio2>;
        interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
    };
};

15.4.4 I2C 设备驱动的数据传输案例

c 复制代码
/*
 * 案例:MPU6050 六轴传感器驱动(读取加速度和陀螺仪数据)
 * MPU6050 I2C 地址:0x68(AD0=0)或 0x69(AD0=1)
 */

/* MPU6050 寄存器定义 */
#define MPU6050_REG_SMPLRT_DIV   0x19  /* 采样率分频 */
#define MPU6050_REG_CONFIG       0x1A  /* 配置 */
#define MPU6050_REG_GYRO_CONFIG  0x1B  /* 陀螺仪配置 */
#define MPU6050_REG_ACCEL_CONFIG 0x1C  /* 加速度计配置 */
#define MPU6050_REG_ACCEL_XOUT_H 0x3B  /* 加速度 X 轴高字节 */
#define MPU6050_REG_GYRO_XOUT_H  0x43  /* 陀螺仪 X 轴高字节 */
#define MPU6050_REG_TEMP_OUT_H   0x41  /* 温度高字节 */
#define MPU6050_REG_PWR_MGMT_1   0x6B  /* 电源管理1 */
#define MPU6050_REG_WHO_AM_I     0x75  /* 设备 ID(应为 0x68)*/

/* 初始化 MPU6050 */
static int mpu6050_init(struct i2c_client *client)
{
    int ret;

    /* 验证设备 ID */
    ret = i2c_smbus_read_byte_data(client, MPU6050_REG_WHO_AM_I);
    if (ret < 0 || ret != 0x68) {
        dev_err(&client->dev, "无效的设备 ID:0x%02x\n", ret);
        return -ENODEV;
    }

    /* 唤醒设备(清除睡眠位)*/
    ret = i2c_smbus_write_byte_data(client, MPU6050_REG_PWR_MGMT_1, 0x00);
    if (ret) return ret;

    /* 设置采样率:1kHz / (1 + 7) = 125Hz */
    ret = i2c_smbus_write_byte_data(client, MPU6050_REG_SMPLRT_DIV, 0x07);
    if (ret) return ret;

    /* 设置陀螺仪量程:±250°/s */
    ret = i2c_smbus_write_byte_data(client, MPU6050_REG_GYRO_CONFIG, 0x00);
    if (ret) return ret;

    /* 设置加速度计量程:±2g */
    ret = i2c_smbus_write_byte_data(client, MPU6050_REG_ACCEL_CONFIG, 0x00);
    if (ret) return ret;

    dev_info(&client->dev, "MPU6050 初始化成功\n");
    return 0;
}

/* 读取加速度和陀螺仪数据(使用块读,一次读取14字节)*/
static int mpu6050_read_data(struct i2c_client *client,
                              s16 *accel, s16 *gyro, s16 *temp)
{
    u8 buf[14];
    int ret;

    /*
     * 使用 i2c_smbus_read_i2c_block_data 一次读取14字节:
     * ACCEL_XOUT_H, ACCEL_XOUT_L, ACCEL_YOUT_H, ACCEL_YOUT_L,
     * ACCEL_ZOUT_H, ACCEL_ZOUT_L, TEMP_OUT_H, TEMP_OUT_L,
     * GYRO_XOUT_H, GYRO_XOUT_L, GYRO_YOUT_H, GYRO_YOUT_L,
     * GYRO_ZOUT_H, GYRO_ZOUT_L
     */
    ret = i2c_smbus_read_i2c_block_data(client,
                                         MPU6050_REG_ACCEL_XOUT_H,
                                         14, buf);
    if (ret != 14) {
        dev_err(&client->dev, "读取传感器数据失败:%d\n", ret);
        return ret < 0 ? ret : -EIO;
    }

    /* 解析加速度数据(大端字节序)*/
    accel[0] = (s16)((buf[0] << 8) | buf[1]);   /* X 轴 */
    accel[1] = (s16)((buf[2] << 8) | buf[3]);   /* Y 轴 */
    accel[2] = (s16)((buf[4] << 8) | buf[5]);   /* Z 轴 */

    /* 解析温度数据 */
    *temp = (s16)((buf[6] << 8) | buf[7]);

    /* 解析陀螺仪数据 */
    gyro[0] = (s16)((buf[8]  << 8) | buf[9]);   /* X 轴 */
    gyro[1] = (s16)((buf[10] << 8) | buf[11]);  /* Y 轴 */
    gyro[2] = (s16)((buf[12] << 8) | buf[13]);  /* Z 轴 */

    return 0;
}

15.5 I2C 设备驱动与用户空间交互

15.5.1 通过 /dev/i2c-N 直接访问

Linux 提供了 /dev/i2c-N 设备文件,允许用户空间程序直接访问 I2C 总线,无需编写内核驱动:

c 复制代码
/*
 * 用户空间通过 /dev/i2c-N 访问 I2C 设备
 * 需要包含 <linux/i2c-dev.h>
 */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

int main(void)
{
    int fd;
    u8 buf[2];
    int ret;

    /* 打开 I2C 总线 */
    fd = open("/dev/i2c-1", O_RDWR);
    if (fd < 0) {
        perror("open /dev/i2c-1 failed");
        return -1;
    }

    /* 设置从设备地址(LM75,地址 0x48)*/
    ret = ioctl(fd, I2C_SLAVE, 0x48);
    if (ret < 0) {
        perror("ioctl I2C_SLAVE failed");
        close(fd);
        return -1;
    }

    /* 方法一:使用 read/write 系统调用 */

    /* 写:发送寄存器地址(选择温度寄存器 0x00)*/
    buf[0] = 0x00;
    ret = write(fd, buf, 1);
    if (ret != 1) {
        perror("write failed");
        close(fd);
        return -1;
    }

    /* 读:读取2字节温度数据 */
    ret = read(fd, buf, 2);
    if (ret != 2) {
        perror("read failed");
        close(fd);
        return -1;
    }

    /* 解析温度(大端字节序)*/
    s16 raw = (s16)((buf[0] << 8) | buf[1]);
    raw >>= 5;
    int temp_mc = (raw / 2) * 1000 + (raw & 1) * 500;
    printf("LM75 温度:%d.%d°C\n", temp_mc / 1000, abs(temp_mc % 1000) / 100);

    /* 方法二:使用 I2C_RDWR ioctl(支持复合消息)*/
    struct i2c_rdwr_ioctl_data rdwr;
    struct i2c_msg msgs[2];
    u8 reg_addr = 0x00;
    u8 data[2];

    msgs[0].addr  = 0x48;
    msgs[0].flags = 0;          /* 写 */
    msgs[0].len   = 1;
    msgs[0].buf   = &reg_addr;

    msgs[1].addr  = 0x48;
    msgs[1].flags = I2C_M_RD;  /* 读 */
    msgs[1].len   = 2;
    msgs[1].buf   = data;

    rdwr.msgs  = msgs;
    rdwr.nmsgs = 2;

    ret = ioctl(fd, I2C_RDWR, &rdwr);
    if (ret < 0) {
        perror("ioctl I2C_RDWR failed");
        close(fd);
        return -1;
    }

    raw = (s16)((data[0] << 8) | data[1]);
    raw >>= 5;
    temp_mc = (raw / 2) * 1000 + (raw & 1) * 500;
    printf("LM75 温度(I2C_RDWR):%d.%d°C\n",
           temp_mc / 1000, abs(temp_mc % 1000) / 100);

    /* 方法三:使用 SMBus ioctl */
    union i2c_smbus_data smbus_data;
    struct i2c_smbus_ioctl_data smbus_args;

    smbus_args.read_write = I2C_SMBUS_READ;
    smbus_args.command    = 0x00;  /* 温度寄存器 */
    smbus_args.size       = I2C_SMBUS_WORD_DATA;
    smbus_args.data       = &smbus_data;

    ret = ioctl(fd, I2C_SMBUS, &smbus_args);
    if (ret < 0) {
        perror("ioctl I2C_SMBUS failed");
        close(fd);
        return -1;
    }

    printf("LM75 温度(SMBus):原始值 0x%04x\n", smbus_data.word);

    close(fd);
    return 0;
}

15.5.2 通过 sysfs 访问 I2C 设备

当 I2C 设备驱动加载后,可以通过 sysfs 访问设备属性:

bash 复制代码
# 查看 I2C 总线上的设备
ls /sys/bus/i2c/devices/
# i2c-0  i2c-1  1-0048  1-0050  1-0068
# 格式:总线号-设备地址(十六进制)

# 查看 LM75 温度传感器(总线1,地址0x48)
ls /sys/bus/i2c/devices/1-0048/
# driver  hwmon  modalias  name  power  subsystem  uevent

# 通过 hwmon 接口读取温度
ls /sys/bus/i2c/devices/1-0048/hwmon/hwmon0/
# device  name  power  subsystem  temp1_input  temp1_max  temp1_max_hyst  uevent

# 读取当前温度(单位:毫摄氏度)
cat /sys/bus/i2c/devices/1-0048/hwmon/hwmon0/temp1_input
# 25500   ← 25.5°C

# 读取过温阈值
cat /sys/bus/i2c/devices/1-0048/hwmon/hwmon0/temp1_max
# 80000   ← 80°C

# 设置过温阈值
echo 75000 > /sys/bus/i2c/devices/1-0048/hwmon/hwmon0/temp1_max

# 查看 AT24C02 EEPROM
ls /sys/bus/i2c/devices/1-0050/
# driver  eeprom  modalias  name  power  subsystem  uevent

# 读取 EEPROM 内容
hexdump -C /sys/bus/i2c/devices/1-0050/eeprom | head -5
# 00000000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|

15.5.3 i2c-tools 工具集

Linux 提供了 i2c-tools 工具集,方便调试 I2C 设备:

bash 复制代码
# 安装 i2c-tools
sudo apt-get install i2c-tools

# ── i2cdetect:扫描 I2C 总线上的设备 ──────────────────────
sudo i2cdetect -y 1
#      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
# 00:          -- -- -- -- -- -- -- -- -- -- -- -- --
# 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
# 50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# 60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
# 70: -- -- -- -- -- -- -- --
# 0x48 = LM75, 0x50 = AT24C02, 0x68 = MPU6050

# ── i2cget:读取 I2C 设备寄存器 ────────────────────────────
# 读取 LM75(总线1,地址0x48)温度寄存器(0x00),字模式
sudo i2cget -y 1 0x48 0x00 w
# 0x1900   ← 大端字节序,需要交换:0x0019 = 25°C

# 读取 MPU6050 设备 ID 寄存器(0x75)
sudo i2cget -y 1 0x68 0x75 b
# 0x68   ← 设备 ID,确认是 MPU6050

# ── i2cset:写入 I2C 设备寄存器 ────────────────────────────
# 设置 LM75 过温阈值寄存器(0x03)为 80°C
# 80°C = 0x5000(大端)
sudo i2cset -y 1 0x48 0x03 0x5000 w

# 唤醒 MPU6050(写电源管理寄存器 0x6B = 0x00)
sudo i2cset -y 1 0x68 0x6B 0x00 b

# ── i2cdump:转储 I2C 设备所有寄存器 ───────────────────────
sudo i2cdump -y 1 0x68 b
#      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
# 00: XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
# ...
# 70: XX XX XX XX XX 68 XX XX XX XX XX XX XX XX XX XX
# 0x75 = 0x68(WHO_AM_I 寄存器)

# ── i2ctransfer:执行复合 I2C 传输 ─────────────────────────
# 写1字节(寄存器地址0x00),然后读2字节(温度数据)
sudo i2ctransfer -y 1 w1@0x48 0x00 r2@0x48
# 0x19 0x00   ← 25°C(大端)

15.5.4 通过字符设备接口访问 I2C 设备

驱动可以创建字符设备文件,提供更友好的用户空间接口:

c 复制代码
/*
 * 在 I2C 设备驱动中创建字符设备接口
 * 允许用户空间通过 /dev/lm75 访问温度传感器
 */

static int lm75_cdev_open(struct inode *inode, struct file *filp)
{
    struct lm75_data *data = container_of(inode->i_cdev,
                                           struct lm75_data, cdev);
    filp->private_data = data;
    return 0;
}

static ssize_t lm75_cdev_read(struct file *filp, char __user *buf,
                               size_t count, loff_t *ppos)
{
    struct lm75_data *data = filp->private_data;
    char temp_str[32];
    int temp_mc;
    ssize_t len;

    /* 读取温度 */
    s16 raw = lm75_read_reg(data->client, LM75_REG_TEMP);
    temp_mc = lm75_reg_to_mc(raw);

    len = snprintf(temp_str, sizeof(temp_str), "%d.%d\n",
                   temp_mc / 1000, abs(temp_mc % 1000) / 100);

    if (copy_to_user(buf, temp_str, len))
        return -EFAULT;

    return len;
}

static const struct file_operations lm75_cdev_fops = {
    .owner = THIS_MODULE,
    .open  = lm75_cdev_open,
    .read  = lm75_cdev_read,
};

/* 在 probe 中创建字符设备 */
static int lm75_probe(struct i2c_client *client,
                       const struct i2c_device_id *id)
{
    struct lm75_data *data;
    /* ... 其他初始化 ... */

    /* 创建字符设备 */
    alloc_chrdev_region(&data->devno, 0, 1, "lm75");
    cdev_init(&data->cdev, &lm75_cdev_fops);
    cdev_add(&data->cdev, data->devno, 1);

    data->class = class_create(THIS_MODULE, "lm75");
    device_create(data->class, NULL, data->devno, NULL, "lm75");

    return 0;
}

/* 用户空间访问 */
/* cat /dev/lm75 */
/* 25.5 */

本章小结

章节 核心知识点 关键 API
15.1 I2C体系结构 I2C总线物理连接;Linux I2C三层架构图;i2c_adapter/i2c_client/i2c_driver三个核心概念及关系 概念理解
15.2 I2C核心 i2c_adapter/i2c_algorithm/i2c_msg结构体;适配器/驱动管理API;i2c_transfer底层接口;SMBus高层接口(read_byte_data/write_byte_data/read_word_data等) i2c_transfer()i2c_smbus_read_byte_data()i2c_smbus_read_i2c_block_data()
15.3 I2C适配器驱动 master_xfer实现(START/数据传输/STOP);中断+completion同步;i2c_add_adapter注册;完整适配器驱动代码 i2c_add_adapter()i2c_set_adapdata()i2c_get_adapdata()
15.4 I2C设备驱动 i2c_driver/i2c_client结构体;LM75完整驱动(probe/sysfs属性/hwmon注册);MPU6050块读案例;设备树配置 i2c_set_clientdata()i2c_get_clientdata()module_i2c_driver()
15.5 用户空间交互 /dev/i2c-N直接访问(I2C_SLAVE/I2C_RDWR/I2C_SMBUS ioctl);sysfs hwmon接口;i2c-tools工具集(i2cdetect/i2cget/i2cset/i2cdump/i2ctransfer);字符设备接口 ioctl(I2C_SLAVE)ioctl(I2C_RDWR)i2cdetecti2cget

I2C 驱动开发要点

复制代码
1. 选择合适的传输接口
   简单读写 → i2c_smbus_read/write_byte_data(最简单)
   16位数据 → i2c_smbus_read/write_word_data
   批量数据 → i2c_smbus_read_i2c_block_data(最多32字节)
   复杂传输 → i2c_transfer(最灵活,支持任意消息组合)

2. 检查适配器功能
   probe 中调用 i2c_check_functionality 验证所需功能

3. 使用 devm_ 系列函数
   devm_kzalloc 分配私有数据,自动释放

4. 设备树配置
   reg = <地址>(必须)
   clock-frequency(I2C 总线频率)
   interrupts(中断引脚,可选)

5. 调试工具
   i2cdetect 扫描设备
   i2cget/i2cset 读写寄存器
   i2cdump 转储所有寄存器

参考文献:宋宝华《Linux设备驱动开发详解:基于最新的Linux 4.0内核》,机械工业出版社,2015年