I2C

1 用户态工具

查看 i2c 设备

1
2
3
4
5
6
7
8
9
10
11
12
$ i2cdetect 
Usage: i2cdetect -l | -F I2CBUS | [-ya] [-q|-r] I2CBUS [FIRST LAST]

Detect I2C chips

-l List installed buses
-F BUS# List functionalities on this bus
-y Disable interactive mode
-a Force scanning of non-regular addresses
-q Use smbus quick write commands for probing (default)
-r Use smbus read byte commands for probing
FIRST and LAST limit probing range

查看 I2C 接口.

1
2
3
$ i2cdetect -l
i2c-2 i2c rk3x-i2c I2C adapter
i2c-0 i2c rk3x-i2c I2C adapter

i2c-0 这是总线编号, rk3x-i2c 这是控制器驱动的名称.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# -y 自动确认
# -r read byte
# 0 总线编号
# 这显示的是已经加载了驱动, 在 /dev/ 下生成了节点的 I2C 控制器
$ i2cdetect -y -r 2
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

输出的行代表地址的高四位, 列代表地址的低四位.

  • -- 表示无响应.

  • 数值, 表示有设备存在, 可以读写查看.

  • uu 表示有设备, 并且被接管了.

然后我们可以通过 i2cget, i2cset, i2cdump 处理.

2 基本模板

rk3506g 为例. 开发板的资源只有这 3 组 IIC 控制器.

1
2
3
4
5
6
7
8
9
10
11
12
13
$ grep -Irn i2c ~/Desktop/works/Embedded/rk3506_linux6.1_sdk_v1.2.0/kernel/arch/arm/boot/dts/rk3502.dtsi 
28: i2c0 = &i2c0;
29: i2c1 = &i2c1;
30: i2c2 = &i2c2;
531: i2c0: i2c@ff040000 {
532: compatible = "rockchip,rk3506-i2c", "rockchip,rk3399-i2c";
538: clock-names = "i2c", "pclk";
542: i2c1: i2c@ff050000 {
543: compatible = "rockchip,rk3506-i2c", "rockchip,rk3399-i2c";
549: clock-names = "i2c", "pclk";
553: i2c2: i2c@ff060000 {
554: compatible = "rockchip,rk3506-i2c", "rockchip,rk3399-i2c";
560: clock-names = "i2c", "pclk";

同时我们可以看看开发板的 iic 是如何设置的, 这和我们在开发板使用 i2cdetect 检测到的是一致的.

下面是 i2c 控制器, 由芯片厂商提供.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
i2c0: i2c@ff040000 {
compatible = "rockchip,rk3506-i2c", "rockchip,rk3399-i2c";
reg = <0xff040000 0x1000>;
interrupts = <GIC_SPI 40 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&cru CLK_I2C0>, <&cru PCLK_I2C0>;
clock-names = "i2c", "pclk";
status = "disabled";
};

i2c1: i2c@ff050000 {
compatible = "rockchip,rk3506-i2c", "rockchip,rk3399-i2c";
reg = <0xff050000 0x1000>;
interrupts = <GIC_SPI 41 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;
clock-names = "i2c", "pclk";
status = "disabled";
};

i2c2: i2c@ff060000 {
compatible = "rockchip,rk3506-i2c", "rockchip,rk3399-i2c";
reg = <0xff060000 0x1000>;
interrupts = <GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&cru CLK_I2C2>, <&cru PCLK_I2C2>;
clock-names = "i2c", "pclk";
status = "disabled";
};

下面是开发板上使用 i2c 资源.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
&i2c0 {
clock-frequency = <400000>;
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&rm_io25_i2c0_scl &rm_io24_i2c0_sda>;

rtc_RS4C1339: rtc@68 {
compatible = "dallas,ds1339";
reg = <0x68>;
status = "okay";
};
};

&i2c2 {
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&rm_io4_i2c2_scl &rm_io5_i2c2_sda>;
status = "okay";

gt1x: gt1x@14 {
compatible = "goodix,gt1x";
reg = <0x14>;
gtp_ics_slot_report;
goodix,rst-gpio = <&gpio0 RK_PA7 GPIO_ACTIVE_HIGH>;
//goodix,irq-gpio = <&gpio0 RK_PA6 GPIO_ACTIVE_LOW>;
status = "disabled";
};
};
......
&i2c2
{
status = "okay";
pinctrl-0 = <&rm_io4_i2c2_scl &rm_io5_i2c2_sda>;

myts@38 {
status = "okay";
compatible = "hyn,cst128a";
reg = <0x38>;
tp-size = <89>;
max-x = <480>;
max-y = <800>;
touch-gpio = <&gpio0 RK_PB1 IRQ_TYPE_LEVEL_LOW>;
reset-gpio = <&gpio1 RK_PB4 GPIO_ACTIVE_HIGH>;
};
};

这两个 iic 的使用处.

1
2
3
4
$ grep -r "dallas,ds1339" /home/jvle/Desktop/works/Embedded/rk3506_linux6.1_sdk_v1.2.0/kernel/drivers/
/home/jvle/Desktop/works/Embedded/rk3506_linux6.1_sdk_v1.2.0/kernel/drivers/rtc/rtc-ds1307.c: .compatible = "dallas,ds1339",
$ grep -r "goodix,gt1x" /home/jvle/Desktop/works/Embedded/rk3506_linux6.1_sdk_v1.2.0/kernel/drivers/input/touchscreen/
/home/jvle/Desktop/works/Embedded/rk3506_linux6.1_sdk_v1.2.0/kernel/drivers/input/touchscreen/gt1x/gt1x.c: {.compatible = "goodix,gt1x",},

实验. 在 iic 加入一个自己的 iic 设备.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
&i2c2 {
status = "okay";

my_complex_sensor@50 {
compatible = "myvendor,mysensor";
reg = <0x50>;

max-x = <480>;
max-y = <800>;

vendor-name = "Jvle-Tech";
device-mode = "high-performance";

enable-gesture;

touch-thresholds = <10 20 30 40>;

touch-gpio = <&gpio0 RK_PB1 IRQ_TYPE_LEVEL_LOW>;
reset-gpio = <&gpio1 RK_PB4 GPIO_ACTIVE_HIGH>;

interrupt-parent = <&gpio0>;
interrupts = <RK_PB1 IRQ_TYPE_EDGE_FALLING>;
};
};

内核代码, 这里写了一个字符设备驱动来作为测试.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/property.h>
#include <linux/gpio/consumer.h>
#include <linux/device.h>

#define DEVICE_NAME "my_i2c_device"
#define CLASS_NAME "my_i2c_class"

static int major;
static struct class *i2c_class = NULL;
static struct device *i2c_device = NULL;
static struct i2c_client *global_client = NULL;

static int my_i2c_read(struct i2c_client *client, char *buf, int count) {
struct i2c_msg msgs[1];
msgs[0].addr = client->addr;
msgs[0].flags = I2C_M_RD;
msgs[0].len = count;
msgs[0].buf = buf;
return (i2c_transfer(client->adapter, msgs, 1) == 1) ? 0 : -EIO;
}

static int my_i2c_write(struct i2c_client *client, const char *buf, int count) {
struct i2c_msg msgs[1];
msgs[0].addr = client->addr;
msgs[0].flags = 0;
msgs[0].len = count;
msgs[0].buf = (char *)buf;
return (i2c_transfer(client->adapter, msgs, 1) == 1) ? 0 : -EIO;
}

static ssize_t dev_read(struct file *file, char __user *buf, size_t count, loff_t *offset) {
char kbuf[64];
int ret;
if (count > 64) count = 64;
ret = my_i2c_read(global_client, kbuf, count);
if (ret < 0) return ret;
if (copy_to_user(buf, kbuf, count)) return -EFAULT;
return count;
}

static ssize_t dev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset) {
char kbuf[64];
if (count > 64) count = 64;
if (copy_from_user(kbuf, buf, count)) return -EFAULT;
if (my_i2c_write(global_client, kbuf, count) < 0) return -EIO;
return count;
}

static struct file_operations fops = {
.owner = THIS_MODULE,
.read = dev_read,
.write = dev_write,
};

static int my_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
u32 val32, array[4];
const char *str;
bool gesture_enabled;

global_client = client;

if (!device_property_read_u32(dev, "max-x", &val32))
dev_info(dev, "DTS Max X: %u\n", val32);

if (!device_property_read_string(dev, "vendor-name", &str))
dev_info(dev, "DTS Vendor: %s\n", str);

gesture_enabled = device_property_read_bool(dev, "enable-gesture");
dev_info(dev, "DTS Gesture: %s\n", gesture_enabled ? "Yes" : "No");

if (!device_property_read_u32_array(dev, "touch-thresholds", array, 4))
dev_info(dev, "DTS Thresholds: %u, %u, %u, %u\n", array[0], array[1], array[2], array[3]);

struct gpio_desc *ts_gpio = devm_gpiod_get_optional(dev, "touch", GPIOD_IN);
if (!IS_ERR(ts_gpio) && ts_gpio)
dev_info(dev, "DTS Touch GPIO obtained!\n");

if (client->irq > 0)
dev_info(dev, "DTS IRQ Number: %d\n", client->irq);

major = register_chrdev(0, DEVICE_NAME, &fops);
if (major < 0) return major;

i2c_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(i2c_class)) {
unregister_chrdev(major, DEVICE_NAME);
return PTR_ERR(i2c_class);
}

i2c_device = device_create(i2c_class, NULL, MKDEV(major, 0), NULL, DEVICE_NAME);
if (IS_ERR(i2c_device)) {
class_destroy(i2c_class);
unregister_chrdev(major, DEVICE_NAME);
return PTR_ERR(i2c_device);
}

dev_info(dev, "Device /dev/%s created successfully\n", DEVICE_NAME);
return 0;
}

static void my_i2c_remove(struct i2c_client *client) {
device_destroy(i2c_class, MKDEV(major, 0));
class_destroy(i2c_class);
unregister_chrdev(major, DEVICE_NAME);
dev_info(&client->dev, "I2C Device Removed\n");
}

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

static struct i2c_driver my_driver = {
.driver = {
.name = "my_custom_i2c_driver",
.of_match_table = my_i2c_of_match,
},
.probe = my_i2c_probe,
.remove = my_i2c_remove,
};

module_i2c_driver(my_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jvle");

运行效果.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
root@rk3506-buildroot:/tmp# insmod iic_driver.ko 
root@rk3506-buildroot:/tmp# dmesg
[ 47.414739] iic_driver: loading out-of-tree module taints kernel.
[ 47.415390] my_i2c_driver 2-0050: DTS Max X: 480
[ 47.415424] my_i2c_driver 2-0050: DTS Vendor: Jvle-Tech
[ 47.415438] my_i2c_driver 2-0050: DTS Gesture: Yes
[ 47.415451] my_i2c_driver 2-0050: DTS Thresholds: 10, 20, 30, 40
[ 47.415492] my_i2c_driver 2-0050: DTS IRQ Number: 70
[ 47.417087] my_i2c_driver 2-0050: Device /dev/my_i2c_device created successfully
root@rk3506-buildroot:/tmp# i2cdetect -y -r 2
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: UU -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
root@rk3506-buildroot:/tmp# rmmod iic_driver.ko
root@rk3506-buildroot:/tmp# dmesg
[ 47.414739] iic_driver: loading out-of-tree module taints kernel.
[ 47.415390] my_i2c_driver 2-0050: DTS Max X: 480
[ 47.415424] my_i2c_driver 2-0050: DTS Vendor: Jvle-Tech
[ 47.415438] my_i2c_driver 2-0050: DTS Gesture: Yes
[ 47.415451] my_i2c_driver 2-0050: DTS Thresholds: 10, 20, 30, 40
[ 47.415492] my_i2c_driver 2-0050: DTS IRQ Number: 70
[ 47.417087] my_i2c_driver 2-0050: Device /dev/my_i2c_device created successfully
[ 66.907365] my_i2c_driver 2-0050: I2C Device Removed