内核I2C驱动GM8775
一. 屏幕参数计算
屏幕型号:星科QV185FHM-N81.FY 18.5寸LVDS接口屏幕
屏幕参数计算(详见LVDS 屏幕 M215HGE-L21 在 rk3288 上的适配过程 - 大窟窿 - 博客园)
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>;我们需要的参数就是这些,在大部分屏幕手册中,会给出上面这些相关参数,也有很多屏幕是没有参数的,需要自己计算
我们这个屏幕,手册中的参数是只有下图这些参数

从图片中可以知道已知参数频率为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数组即可,其余不需要改动
#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文件,用来声明编译
我们举例说明,如果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文件增加如下内容
/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 里,用于配置显示接口的时序参数,影响分辨率、刷新率等
&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节点,启动这个驱动(文件夹附带的两个设备树文件直接覆盖原文件即可)
&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 = <>911_int>;
touchscreen-inverted-y;
touchscreen-inverted-x;
};
};./build.sh重新编译,烧录系统,开机即可正常显示,每次开机自动写配置文件。
评论