# I2C 使用说明

## 概述

K230 SoC 集成了 5 个 Synopsys DesignWare I2C 控制器，具体信息如下：

| I2C 控制器 | 基地址     | 默认时钟频率 | 默认状态 |
|------------|------------|--------------|----------|
| I2C0       | 0x91405000 | 100kHz       | disabled |
| I2C1       | 0x91406000 | 400kHz       | disabled |
| I2C2       | 0x91407000 | 400kHz       | disabled |
| I2C3       | 0x91408000 | 400kHz       | disabled |
| I2C4       | 0x91409000 | 400kHz       | disabled |

> **注意**：所有 I2C 控制器默认状态均为 `disabled`，需在设备树中启用后方可使用。

## 设备树配置

### 设备树别名配置

在设备树的 `aliases` 节点中定义 I2C 总线别名，便于内核和其他设备树文件引用。

**CanMV-K230 开发板示例：**

```dts
aliases {
    serial3 = &uart3;
    i2c0 = &i2c4;   // 对应 /dev/i2c-0
    i2c1 = &i2c3;   // 对应 /dev/i2c-1
    mmc0 = &mmc_sd0;
    mmc1 = &mmc_sd1;
};
```

**说明：**

- 别名名称（`i2c0`、`i2c1`）由内核或板级配置定义
- 别名指向具体的 I2C 控制器节点（`&i2c4`、`&i2c3`）
- 内核会根据别名创建对应编号的 I2C 总线，设备节点可通过 `&i2c0` 引用
- 此方式提高了设备树的可移植性，板级配置更改时无需修改所有引用

### 启用 I2C 控制器

```dts
&i2c2 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&i2c2_pins>;
    clock-frequency = <400000>;  // 400kHz 快速模式
};
```

### 配置引脚复用

```dts
&iomux {
    i2c2_pins: i2c2_pins {
        pins = K230_IO11, K230_IO12;
        function = "alt3";
        bias-pull-up;
    };
};
```

### 添加 I2C 设备节点

在启用的 I2C 总线下添加设备节点：

```dts
&i2c2 {
    status = "okay";

    // 示例：添加 24C02 EEPROM
    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
    };

    // 示例：添加温度传感器 TMP102
    tmp102@48 {
        compatible = "ti,tmp102";
        reg = <0x48>;
    };
};
```

### CanMV-K230 完整配置示例

参考文件：`arch/riscv/boot/dts/canaan/k230-canmv-01studio.dts`

```dts
// 初始化别名
aliases {
    serial3 = &uart3;
    i2c0 = &i2c4;
    i2c1 = &i2c3;
    mmc0 = &mmc_sd0;
    mmc1 = &mmc_sd1;
};

// 引脚配置
&iomux {
    i2c2_pins: i2c2_pins {
        pins = K230_IO11, K230_IO12;
        function = "alt3";
    };
};

// 启用 I2C3（用于 LT9611 HDMI 桥接）
&i2c3 {
    status = "okay";

    lt9611: hdmi-bridge@3b {
        compatible = "lontium,lt9611";
        reg = <0x3b>;
        reset-gpios = <&gpio0_ports 22 GPIO_ACTIVE_HIGH>;
        interrupt-parent = <&gpio0_ports>;
        interrupts = <23 IRQ_TYPE_EDGE_FALLING>;
    };
};

// 启用 I2C4
&i2c4 {
    status = "okay";
};
```

## 用户空间操作

### 列出 I2C 总线

```bash
i2cdetect -l
```

输出示例：

```bash
[root@canaan ~]# i2cdetect -l
i2c-1 i2c        Synopsys DesignWare I2C adapter  I2C adapter
i2c-2 i2c        Synopsys DesignWare I2C adapter  I2C adapter
i2c-0 i2c        Synopsys DesignWare I2C adapter  I2C adapter
```

### 扫描 I2C 设备

```bash
i2cdetect -y -r -a 2
```

输出示例：

```bash
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
```

### 读取寄存器

```bash
# 读取单个字节：从 I2C 总线 2 上地址为 0x50 的设备，读取偏移量为 0x00 的数据
i2cget -y 2 0x50 0x00

# 读取指定寄存器的字节数据（b = byte）
i2cget -y 2 0x50 0x01 b

# 读取字数据（w = word，16 位）
i2cget -y 2 0x50 0x00 w
```

### 写入寄存器

```bash
# 写入单个字节
i2cset -y 2 0x50 0x00 0x55

# 写入字数据（w = word）
i2cset -y 2 0x50 0x00 0x1234 w
```

### 设备文件接口

I2C 设备在 `/dev/` 中以以下形式出现：

```bash
ls -l /dev/i2c-*
```

输出示例：

```bash
crw-rw---- 1 root i2c 89, 0 Jun 10 10:00 /dev/i2c-0
crw-rw---- 1 root i2c 89, 1 Jun 10 10:00 /dev/i2c-1
```

## 编程示例

### C 语言示例

```c
#include <linux/i2c-dev.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>

void print_usage(const char *prog)
{
    printf("Usage: %s [I2C_BUS]\n", prog);
    printf("  -h, --help    Show this help message\n");
    printf("  I2C_BUS       I2C bus device (default: /dev/i2c-0)\n");
}

int main(int argc, char *argv[])
{
    // 解析 -h 参数
    for (int i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
            print_usage(argv[0]);
            return 0;
        }
    }

    int file;
    char *filename = "/dev/i2c-0";

    if (argc > 1) {
        filename = argv[1];
    }

    // 打开 I2C 设备
    file = open(filename, O_RDWR);
    if (file < 0) {
        perror("Failed to open i2c device");
        return -1;
    }

    printf("Scanning I2C bus: %s\n", filename);
    printf("Address  Device\n");
    printf("-------  ------\n");
    printf("\n7-bit addresses (0x00-0xff):\n");

    // 扫描所有 0x00 到 0xFF 的地址
    for (int addr = 0x00; addr <= 0xFF; addr++) {
        // 设置从设备地址
        if (ioctl(file, I2C_SLAVE, addr) < 0) {
            continue;
        }

        // 尝试读取 1 个字节
        // 此操作是安全的，不会影响设备状态
        unsigned char buf[1];
        int result = read(file, buf, 1);

        // 如果读取成功，说明设备存在
        if (result == 1) {
            printf("  0x%02X   Present\n", addr);
        }
    }

    printf("\nNote: 0x78-0x7F are reserved addresses.\n");

    close(file);
    return 0;
}
```

### Python 示例

#### 使用 Adafruit Blinka busio

```python

import time
import sys
import board
import busio

def main(bus_num=0):
    """Scan I2C devices on specified bus."""
    # SCL and SDA pins for different I2C buses

    print(f"Hello Blinka! Using I2C bus {bus_num}")

    _, scl, sda = board.pin.i2cPorts[bus_num]
    i2c = busio.I2C(scl, sda)

    while not i2c.try_lock():
        pass

    try:
        devices = i2c.scan()
        print(f"I2C devices found on bus {bus_num}: {[hex(i) for i in devices]}")
    finally:
        i2c.unlock()


if __name__ == "__main__":
    bus = 0
    if len(sys.argv) > 1:
        if sys.argv[1] in ["-h", "--help"]:
            print("Usage: python pi_busio_i2c.py [bus_num]")
            print("  bus_num  I2C bus number (default: 0)")
            print("           0 - I2C0 (SCL/SDA)")
            print("           1 - I2C1 (SCL1/SDA1)")
            print("           2 - I2C2 (SCL2/SDA2)")
            sys.exit(0)
        bus = int(sys.argv[1])

    main(bus)

```

#### 不使用库扫描设备

```python
#!/usr/bin/env python3
import fcntl
import sys

I2C_SLAVE = 0x0703

def i2c_scan(bus_num=0):
    with open(f"/dev/i2c-{bus_num}", "r+b", buffering=0) as f:
        print(f"Scanning I2C bus {bus_num}:")
        print("Address  Device")
        print("-------  ------")
        for addr in range(0x00, 0x100):
            try:
                fcntl.ioctl(f.fileno(), I2C_SLAVE, addr)
                f.read(1)  # 尝试读取一个字节
                print(f"  0x{addr:02X}   Present")
            except:
                pass

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] in ["-h", "--help"]:
        print("Usage: python i2c.py [bus_num]")
        print("  bus_num  I2C bus number (default: 0)")
        print("  -h, --help  Show this help message")
        sys.exit(0)

    bus = 0
    if len(sys.argv) > 1:
        bus = int(sys.argv[1])
    i2c_scan(bus)
```

## 相关文件

- 设备树文件: `arch/riscv/boot/dts/canaan/k230.dtsi`
- 平台驱动: `drivers/i2c/busses/i2c-designware-platdrv.c`
- 核心驱动: `drivers/i2c/busses/i2c-designware-core.h`
- Kconfig: `drivers/i2c/busses/Kconfig`
