内核I2C驱动GM8775

一. 屏幕参数计算

屏幕型号:星科QV185FHM-N81.FY 18.5寸LVDS接口屏幕
屏幕参数计算(详见LVDS 屏幕 M215HGE-L21 在 rk3288 上的适配过程 - 大窟窿 - 博客园

1
2
3
4
5
6
7
8
9
10
11
12
13
clock-frequency = <74000000>;
hactive = <1920>;
vactive = <1080>;
hfront-porch = <100>;
hsync-len = <5>;
hback-porch = <35>;
vfront-porch = <20>;
vsync-len = <10>;
vback-porch = <15>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <1>;

我们需要的参数就是这些,在大部分屏幕手册中,会给出上面这些相关参数,也有很多屏幕是没有参数的,需要自己计算
我们这个屏幕,手册中的参数是只有下图这些参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

从图片中可以知道已知参数频率为clock-frequency为74.25Mhz

disp_timings 中的 hback-porch, hfront-porch, vback-porch, vfront-porch, hsync-len, vsync-len 的值。它们之间的关系如下:
Tv = vback-porch + vfront-porch + vsync-len + Tvd
Th = hback-porch + hfront-porch + hsync-len + Thd


vback-porch + vfront-porch + vsync-len = 1125 - 1080 = 45
hback-porch + hfront-porch + hsync-len = 1100 - 960 = 140

这里只需要保证这 3 个参数之和等于那个数就可以了,对具体的值没有特别的要求

故得到以下参数:
hback-porch = <35>;
hfront-porch = <100>;
hsync-len = <5>;

vfront-porch = <20>;
vsync-len = <10>;
vback-porch = <15>;

二. 生成配置指令

2.1 GM8775C:MIPI转双路LVDS芯片

  • 配置方法:该芯片有一款上位机生成初始化指令的软件,分为两种方式
    I2C指令与MIPI指令

  • I2C指令:指的是使用I2C写入芯片配置参数来使得芯片将SOC的MIPI信号正确转换为LVDS信号,即上面生成的参数

  • MIPI指令:指的是上电初始化时使用MIPI写入芯片配置参数来使得芯片将SOC的MIPI信号正确转换为LVDS信号,即上面生成的参数(目前MIPI写入配置方式存在问题,待供应商支持)

  • 软件获取参数使用方法:首先填入屏幕参数,根据上面求得的参数,配置视频格式中的参考时钟、行场等参数,最后创建寄存器表,若配置方式选择I2C,直接用USB转I2C连接器写入即可显示,注意此芯片所有配置掉电丢失,也就是说每次开机都要写入配置

ID看GM8775C的手册,I2C_ADDR 管脚通过上下拉选择 I2C 器件地址,拉高为:0x5A,拉低为:0x58

Link01相关的参数看屏幕手册,VESA与8bit参数可在屏幕手册中找到

    `配置LINK EVEN / ODD奇偶场,如果屏幕显示两侧未连续,则需要交换EVEN / ODD`
    `0x27是mipi指令的密码解锁地址,0x00是I2C指令的密码解锁地址`

按照图片中的指示与上述描述,在软件上填好并生成寄存器指令表

三. 内核与设备树

3.1 编写内核代码

  • 下列代码其实就是i2c驱动示例代码,加上前面生成寄存器指令表所写的gm8775c_reg_defaults数组,通过i2c驱动形式,将数组配置写入芯片,用以配置MIPI转LVDS,在此板卡对于不同型号的屏幕,只需要改gm8775c_reg_defaults数组即可,其余不需要改动
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/of_gpio.h>
#include <linux/proc_fs.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/regmap.h>

#define REGISTER_NUM 34

#define GPIO_PIN_509 509
#define GPIO_PIN_511 511
#define GPIO_PIN_493 493

struct gm8775c_priv {
struct regmap *regmap;
struct i2c_client *client;
int gpio_509;
int gpio_511;
int gpio_493;
};

static const struct reg_default gm8775c_reg_defaults[] = {
{0x00,0xAA},
{0x48,0x02},
{0xB6,0x20},
{0x01,0x80},
{0x02,0x38},
{0x03,0x47},
{0x04,0x64},
{0x05,0x05},
{0x06,0x23},
{0x07,0x00},
{0x08,0x14},
{0x09,0x0A},
{0x0A,0x0F},
{0x0B,0x82},
{0x0C,0x13},
{0x0D,0x01},
{0x0E,0x80},
{0x0F,0x20},
{0x10,0x20},
{0x11,0x03},
{0x12,0x1B},
{0x13,0x63},
{0x14,0x01},
{0x15,0x23},
{0x16,0x40},
{0x17,0x00},
{0x18,0x01},
{0x19,0x23},
{0x1A,0x40},
{0x1B,0x00},
{0x1E,0x46},
{0x51,0x30},
{0x1F,0x10},
{0x2A,0x01},
};

static int gm8775c_read(u8 reg, u8 * rt_value, struct i2c_client *client)
{
int ret;
u8 read_cmd[3] = { 0 };
u8 cmd_len = 0;

read_cmd[0] = reg;
cmd_len = 1;

if (client->adapter == NULL)
printk("gm8775c_read client->adapter==NULL\n");

ret = i2c_master_send(client, read_cmd, cmd_len);
if (ret != cmd_len) {
printk("gm8775c_read error1\n");
return -1;
}

ret = i2c_master_recv(client, rt_value, 1);
if (ret != 1) {
printk("gm8775c_read error2, ret = %d.\n", ret);
return -1;
}

return 0;
}

static int gm8775c_write(u8 reg, unsigned char value, struct i2c_client *client)
{
int ret = 0;
u8 write_cmd[2] = { 0 };

write_cmd[0] = reg;
write_cmd[1] = value;

ret = i2c_master_send(client, write_cmd, 2);
if (ret != 2) {
printk("gm8775c_write error->[REG-0x%02x,val-0x%02x]\n",
reg, value);
return -1;
}

return 0;

}


static const struct of_device_id gm8775_of_match[] = {
{
.compatible = "gm8775c,mipi_to_lvds",
},
{ }
};
MODULE_DEVICE_TABLE(of, gm8775_of_match);

static const struct i2c_device_id gm8775_i2c_id[] = {
{"GM8775C_MTL", 0},
{}
};

static ssize_t gm8775c_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
int val=0, flag=0;
u8 i=0, reg, num, value_w, value_r;


struct gm8775c_priv *gm8775c = dev_get_drvdata(dev);

val = simple_strtol(buf, NULL, 16);
flag = (val >> 16) & 0xFF;

if (flag) {
reg = (val >> 8) & 0xFF;
value_w = val & 0xFF;
printk("\nWrite: start REG:0x%02x,val:0x%02x,count:0x%02x\n", reg, value_w, flag);
while(flag--) {
gm8775c_write(reg, value_w, gm8775c->client);
printk("Write 0x%02x to REG:0x%02x\n", value_w, reg);
reg++;
}
} else {
reg = (val >> 8) & 0xFF;
num = val & 0xff;
printk("\nRead: start REG:0x%02x,count:0x%02x\n", reg, num);
do {
value_r = 0;
gm8775c_read(reg, &value_r, gm8775c->client);
printk("REG[0x%02x]: 0x%02x; ", reg, value_r);
reg++;
i++;
if ((i==num) || (i%4==0)) printk("\n");
} while (i<num);
}

return count;
}

static ssize_t gm8775c_show(struct device *dev, struct device_attribute *attr, char *buf)
{
printk("echo flag|reg|val > gm8775c\n");
printk("eg read star addres=0x06,count 0x10:echo 0610 >gm8775c\n");
printk("eg write star addres=0x90,value=0x3c,count=4:echo 4903c >gm8775c\n");
return 0;
}

static DEVICE_ATTR(gm8775c, 0644, gm8775c_show, gm8775c_store);

static struct attribute *egm8775c_debug_attrs[] = {
&dev_attr_gm8775c.attr,
NULL,
};

static struct attribute_group gm8775c_debug_attr_group = {
.name = "gm8775c_debug",
.attrs = egm8775c_debug_attrs,
};


static int gm8775c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct gm8775c_priv *gm8775c;
int i;
int ret;

dev_info(&client->dev, "Probing gm8775c at address 0x%02x\n", client->addr);

if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK |
I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "doesn't support I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_I2C_BLOCK\n");
return -ENODEV;
}

gm8775c = devm_kzalloc(&client->dev, sizeof(*gm8775c), GFP_KERNEL);
if (!gm8775c)
return -ENOMEM;

gm8775c->client = client;
i2c_set_clientdata(client, gm8775c);

gm8775c->gpio_509 = GPIO_PIN_509;
ret = gpio_request(gm8775c->gpio_509, "gm8775c_gpio_509");
if (ret) {
dev_err(&client->dev, "Failed to request GPIO %d\n", gm8775c->gpio_509);
return ret;
}

ret = gpio_direction_output(gm8775c->gpio_509, 1);
if (ret) {
dev_err(&client->dev, "Failed to set GPIO %d as output\n", gm8775c->gpio_509);
gpio_free(gm8775c->gpio_509);
return ret;
}
dev_info(&client->dev, "Configured GPIO %d as output and set to 1\n", gm8775c->gpio_509);

gm8775c->gpio_511 = GPIO_PIN_511;
ret = gpio_request(gm8775c->gpio_511, "gm8775c_gpio_511");
if (ret) {
dev_err(&client->dev, "Failed to request GPIO %d\n", gm8775c->gpio_511);
gpio_free(gm8775c->gpio_509);
return ret;
}

ret = gpio_direction_output(gm8775c->gpio_511, 0);
if (ret) {
dev_err(&client->dev, "Failed to set GPIO %d as output\n", gm8775c->gpio_511);
gpio_free(gm8775c->gpio_511);
gpio_free(gm8775c->gpio_509);
return ret;
}
dev_info(&client->dev, "Configured GPIO %d as output and set to 0\n", gm8775c->gpio_511);

gm8775c->gpio_493 = GPIO_PIN_493;
ret = gpio_request(gm8775c->gpio_493, "gm8775c_gpio_493");
if (ret) {
dev_err(&client->dev, "Failed to request GPIO %d\n", gm8775c->gpio_493);
gpio_free(gm8775c->gpio_511);
gpio_free(gm8775c->gpio_509);
return ret;
}

ret = gpio_direction_output(gm8775c->gpio_493, 1);
if (ret) {
dev_err(&client->dev, "Failed to set GPIO %d as output\n", gm8775c->gpio_493);
gpio_free(gm8775c->gpio_493);
gpio_free(gm8775c->gpio_511);
gpio_free(gm8775c->gpio_509);
return ret;
}
dev_info(&client->dev, "Configured GPIO %d as output and set to 1\n", gm8775c->gpio_493);

for (i = 0; i < REGISTER_NUM; i++) {
ret = gm8775c_write(gm8775c_reg_defaults[i].reg, gm8775c_reg_defaults[i].def, gm8775c->client);

dev_info(&client->dev, "Wrote and verified 0x%02x to register 0x%02x\n",
gm8775c_reg_defaults[i].def, gm8775c_reg_defaults[i].reg);
}

ret = sysfs_create_group(&client->dev.kobj, &gm8775c_debug_attr_group);
if (ret) {
dev_err(&client->dev, "failed to create attr group\n");

}

dev_info(&client->dev, "gm8775c driver loaded successfully.\n");
return 0;


gpio_free(gm8775c->gpio_493);
gpio_free(gm8775c->gpio_511);
gpio_free(gm8775c->gpio_509);
return ret;
}

static void gm8775c_remove(struct i2c_client *client)
{
struct gm8775c_priv *gm8775c = i2c_get_clientdata(client);

sysfs_remove_group(&client->dev.kobj, &gm8775c_debug_attr_group);

gpio_free(gm8775c->gpio_493);
gpio_free(gm8775c->gpio_511);
gpio_free(gm8775c->gpio_509);

dev_info(&client->dev, "gm8775c driver removed.\n");
}

static struct i2c_driver gm8775c_driver = {
.driver = {
.name = "gm8775c-mipi-to-lvds",
.of_match_table = of_match_ptr(gm8775_of_match),
},
.probe = gm8775c_probe,
.remove = gm8775c_remove,
.id_table = gm8775_i2c_id,
};

static int __init gm8775c_modinit(void)
{
int ret;
ret = i2c_add_driver(&gm8775c_driver);
if (ret != 0)
printk("Failed to register gm8775c i2c driver : %d \n", ret);
return ret;
}
late_initcall(gm8775c_modinit);

static void __exit gm8775c_exit(void)
{
i2c_del_driver(&gm8775c_driver);
}

module_exit(gm8775c_exit);


//module_i2c_driver(gm8775c_driver);

MODULE_AUTHOR("goa");
MODULE_DESCRIPTION("GM8775C I2C Client Driver");
MODULE_LICENSE("GPL");

3.2 写Makefile与Kconfig,代码加入内核

  • 上述代码应该创建为名为gm8775c.c的文件,需要提前创建一个名为gm8775c的文件夹用来存放gm8775c.c,同时在gm8775c文件夹下我们需要创建Makefile与Kconfig文件,用来声明编译
1
2
3
4
5
6
7
8
我们举例说明,如果SDK的内核目录是
LinuxSDK/rk3576_linux6.1_release/kernel-6.1/
那么此文件夹可以是
LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/input/gm8775c
文件夹下应该有以下内容
root:~/LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/input/gm8775c$ ls
gm8775c.c Makefile Kconfig

  • 同时在gm8775c文件夹的上级目录,比如LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/input/gm8775c,上级目录就是input,对input下的Makefile与Kconfig文件增加如下内容
1
2
3
4
5
/LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/input/Makefile
+ obj-y += gm8775c/
/LinuxSDK/rk3576_linux6.1_release/kernel-6.1/drivers/input/Kconfig
+ source "drivers/input/gm8775c/Kconfig"

完成上述操作,使用./build.sh kernel-config命令,将gm8775c配置选中后保存,进入到下一步

3.3 设备树增加对应声明

  • 将前面的屏幕参数填在对应的接口时序参数节点dsi_timing0 里,用于配置显示接口的时序参数,影响分辨率、刷新率等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
&dsi_timing0 {
clock-frequency = <74000000>;
hactive = <1920>;
vactive = <1080>;
hfront-porch = <100>;
hsync-len = <5>;
hback-porch = <35>;
vfront-porch = <20>;
vsync-len = <10>;
vback-porch = <15>;
hsync-active = <0>;
vsync-active = <0>;
de-active = <0>;
pixelclk-active = <1>;
};
  • 在设备树MIPI转LVDS芯片所连接的i2c节点中新加gm8775c节点,启动这个驱动(文件夹附带的两个设备树文件直接覆盖原文件即可)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
&i2c6 {
status = "okay";
gm8775c: gm8775c@2d {
compatible = "gm8775c,mipi_to_lvds";
reg = <0x2d>;
};
gt911: gt911@5d {
status = "okay";
compatible = "goodix,gt911";
reg = <0x5d>;
interrupt-parent = <&gpio1>;
interrupts = <18 0>;
irq-gpios = <&gpio1 RK_PC2 0>;
reset-gpios = <&xl8574t 0 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&gt911_int>;
touchscreen-inverted-y;
touchscreen-inverted-x;
};
};

./build.sh重新编译,烧录系统,开机即可正常显示,每次开机自动写配置文件。