x2600-lcd的sat101cp50d24b1屏幕的驱动调试
1.硬件关联
屏幕型号:sat101cp50d24b1
原理图:
很显然,这是RGB666显示格式的屏幕,RGB管脚DATA0-DATA17--为数据线
DEN 数据使能线。VSYNC 垂直同步信号线。HSYNC 水平同步信号线。PCLK 像素时钟信号线。DEN 、 VSYNC 、 HSYNC 和 PCLK 这四根是控制信号线;RGB LCD 一般有两种驱动模式: DE 模式和 HV 模式,这两个模式的区别是 DE 模式需要用到 DEN 信号线,而 HV 模式不需要用到 DEN 信号线,在 DE 模式下是可以不需要 HSYNC 信号线的,即使不接 HSYNC 信号线 LCD 也可以正常工作。
2.dts设备树配置
RGB_LCD_SAT101CP50D24B1.dtsi的内容如下
"DPU"的设备,通常指的是负责图像处理和输出到显示面板的硬件单元。
•status = "okay" 表示该设备应当被初始化并启用。
•ingenic,disable-rdma-fb = <1> 可能指禁用RDMA(Remote Direct Memory Access)帧缓冲功能。
•ingenic,rot_angle = <0> 设置初始旋转角度为0度。
•ingenic,layer-exported, layer-frames, layer-framesize 等属性配置了图层的导出状态、帧缓存数量和最大帧尺寸等,表明DPU支持多图层渲染和管理。
•memory-region = <&reserved_memory> 指定了DPU使用的内存区域。
•port 部分定义了与面板之间的连接端点,用于视频数据传输
&dpu {
status = "okay";
ingenic,disable-rdma-fb = <1>;
ingenic,rot_angle = <0>;
//Defines the init state of composer fb export infomations.
ingenic,layer-exported = <1 0 0 0>;
ingenic,layer-frames = <2 2 2 2>;
ingenic,layer-framesize = <1024 600>, <1024 600>, <1024 600>, <1024 600>; //Max framesize for each layer.
layer,color_mode = <0 0 0 0>; //src fmt,
layer,src-size = <1024 600>, <1024 600>, <1024 600>, <1024 600>; //Layer src size should smaller than framesize
layer,target-size = <1024 600>, <1024 600>, <1024 600>, <1024 600>; //Target Size should smaller than src_size.
layer,target-pos = <0 0>, <0 0>, <0 0>, <0 0>; //target pos , the start point of the target panel.
layer,enable = <1 0 0 0>; //layer enabled or disabled.
ingenic,logo-pan-layer = <0>; //on which layer should init logo pan on.
memory-region=<&reserved_memory>;
port {
dpu_out_ep: endpoint {
remote-endpoint = <&panel_sat101cp50d24b1_ep>;
};
};
};
/ {
display-dbi {
compatible = "simple-bus";
#interrupt-cells = <1>;
#address-cells = <1>;
#size-cells = <1>;
ranges = <>;
panel_sat101cp50d24b1 {
compatible = "ingenic,sat101cp50d24b1-rgb";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&tft_lcd_pb_18bit>;
ingenic,vdd-en-gpio = <&gpc 28 GPIO_ACTIVE_HIGH INGENIC_GPIO_NOBIAS>;
/*ingenic,lcd-pwm-gpio = <&gpc 11 GPIO_ACTIVE_HIGH INGENIC_GPIO_NOBIAS>;*/
port {
panel_sat101cp50d24b1_ep: endpoint {
remote-endpoint = <&dpu_out_ep>;
};
};
};
};
backlight {
compatible = "pwm-backlight";
pinctrl-names = "default";
pinctrl-0 = <&pwm12_pc>;
pwms = <&pwm 12 1000000>; /* arg1: pwm channel id [0~15]. arg2: period in ns. */
brightness-levels = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15>;
default-brightness-level = <10>;
};
};
其中,管脚定义:
3. dts和设备驱动的匹配关联
屏幕面板的适配关键词是:compatible = "ingenic,sat101cp50d24b1-rgb";
编写的驱动代码:
static const struct of_device_id panel_of_match[] = {
{ .compatible = "ingenic,sat101cp50d24b1-bgr", .data = (struct tft_config *)&sat101cp50d24b1_bgr_cfg, },
{ .compatible = "ingenic,sat101cp50d24b1-rgb", .data = (struct tft_config *)&sat101cp50d24b1_rgb_cfg, },
{},
};...........................
static struct platform_driver panel_driver = {
.probe = panel_probe,
.remove = panel_remove,
.driver = {
.name = "sat101cp50d24b1",
.of_match_table = panel_of_match,
},
};当驱动和设备匹配以后 panel_probe 函数就会执行。在看 panel_probe 函数之前我们先简单了解一下 Linux 下 Framebuffer 驱动的编写流程
4.完整的驱动代码
遵循Linux内核的标准接口和编程规范的Linux驱动程序,涉及设备初始化、电源管理、GPIO控制等功能,为sat101cp50d24b1型号的LCD面板提供驱动支持:
panel_dev结构体:该结构体整合了设备信息、LCD面板信息、通用LCD框架、背光控制、电源状态以及多个GPIO控制对象(如VDD_EN、BL、RST、PWM)
panel_probe:探测并初始化LCD面板设备,注册到内核中;通过匹配设备树节点,获取相关GPIO引脚并进行初始化,注册LCD面板到内核的LCD框架中,同时处理面板的电源状态,以及可能的背光控制。
/*
*
* Copyright (C) 2016 Ingenic Semiconductor Inc.
*
* Author:zhxiao<zhihao.xiao@ingenic.com>
*
* This program is free software, you can redistribute it and/or modify it
*
* under the terms of the GNU General Public License version 2 as published by
*
* the Free Software Foundation.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
#include <linux/pwm_backlight.h>
#include <linux/delay.h>
#include <linux/lcd.h>
#include <linux/of_gpio.h>
#include <linux/fb.h>
#include <linux/backlight.h>
#include "../ingenicfb.h"
#include "../jz_dsim.h"
struct board_gpio {
short gpio;
short active_level;
};
struct panel_dev {
/* ingenic frame buffer */
struct device *dev;
struct lcd_panel *panel;
/* common lcd framework */
struct lcd_device *lcd;
struct backlight_device *backlight;
int power;
struct regulator *vcc;
struct board_gpio vdd_en;
struct board_gpio bl;
struct board_gpio rst;
struct board_gpio pwm;
struct board_gpio disp;
};
static struct panel_dev *panel;
static void sat101cp50d24b1_power_on(struct lcd_panel *ppanel)
{
struct panel_dev *panel_sat101cp50d24b1 = dev_get_drvdata(panel->dev);
if(panel_sat101cp50d24b1->bl.gpio > 0){
gpio_direction_output(panel_sat101cp50d24b1->bl.gpio, panel_sat101cp50d24b1->bl.active_level);
}
if(panel_sat101cp50d24b1->vdd_en.gpio > 0){
gpio_direction_output(panel_sat101cp50d24b1->vdd_en.gpio, panel_sat101cp50d24b1->vdd_en.active_level);
}
if(panel_sat101cp50d24b1->disp.gpio > 0){
gpio_direction_output(panel_sat101cp50d24b1->disp.gpio, panel_sat101cp50d24b1->disp.active_level);
}
}
static void sat101cp50d24b1_power_off(struct lcd_panel *ppanel)
{
struct panel_dev *panel_sat101cp50d24b1 = dev_get_drvdata(panel->dev);
if(panel_sat101cp50d24b1->bl.gpio > 0){
gpio_direction_output(panel_sat101cp50d24b1->bl.gpio, !panel_sat101cp50d24b1->bl.active_level);
}
if(panel_sat101cp50d24b1->vdd_en.gpio > 0){
gpio_direction_output(panel_sat101cp50d24b1->vdd_en.gpio, !panel_sat101cp50d24b1->vdd_en.active_level);
}
if(panel_sat101cp50d24b1->disp.gpio > 0){
gpio_direction_output(panel_sat101cp50d24b1->disp.gpio, !panel_sat101cp50d24b1->disp.active_level);
}
return;
}
static struct lcd_panel_ops sat101cp50d24b1_ops = {
.enable = (void*)sat101cp50d24b1_power_on,
.disable = (void*)sat101cp50d24b1_power_off,
};
static struct fb_videomode sat_videomode[] = {
[0] = {
.name = "1024x600",
.refresh = 60,
.xres = 1024,
.yres = 600,
.pixclock = (51200000),//KHz
.left_margin = 160,
.right_margin = 160,
.upper_margin = 23,
.lower_margin = 12,
.hsync_len = 70,
.vsync_len = 10,
.sync = ~FB_SYNC_HOR_HIGH_ACT & ~FB_SYNC_VERT_HIGH_ACT,
.vmode = FB_VMODE_NONINTERLACED,
.flag = 0,
},
};
struct lcd_panel lcd_panel = {
.name = "sat101cp50d24b1",
.num_modes = ARRAY_SIZE(sat_videomode),
.modes = sat_videomode,
.lcd_type = LCD_TYPE_TFT,
.bpp = 18,
.width = 1024,
.height = 600,
/*T40 RGB real line is 666, so, for RGB888, we discard low 2 bytes, config as follows:*/
.dither_enable = 0,
.dither.dither_red = 0,
.dither.dither_green = 0,
.dither.dither_blue = 0,
.ops = &sat101cp50d24b1_ops,
};
#define RESET(n)\
gpio_direction_output(panel->rst.gpio, n)
#define POWER_IS_ON(pwr) ((pwr) <= FB_BLANK_NORMAL)
static int panel_set_power(struct lcd_device *lcd, int power)
{
return 0;
}
static int panel_get_power(struct lcd_device *lcd)
{
struct panel_dev *panel = lcd_get_data(lcd);
return panel->power;
}
/**
* @ panel_sat101cp50d24b1_lcd_ops, register to kernel common backlight/lcd.c frameworks.
*/
static struct lcd_ops panel_lcd_ops = {
.set_power = panel_set_power,
.get_power = panel_get_power,
};
static int of_panel_parse(struct device *dev)
{
struct panel_dev *panel = dev_get_drvdata(dev);
struct device_node *np = dev->of_node;
enum of_gpio_flags flags;
panel->vdd_en.gpio = of_get_named_gpio_flags(np, "ingenic,vdd-en-gpio", 0, &flags);
if(gpio_is_valid(panel->vdd_en.gpio)) {
panel->vdd_en.active_level = (flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
if(devm_gpio_request(dev, panel->vdd_en.gpio, "vdd-en") < 0) {
printk("Failed to request vdd_en pin!\n");
}
} else {
dev_warn(dev, "invalid gpio vdd_en.gpio: %d\n", panel->vdd_en.gpio);
}
panel->bl.gpio = of_get_named_gpio_flags(np, "ingenic,lcd-pwm-gpio", 0, &flags);
if (gpio_is_valid(panel->bl.gpio)) {
panel->bl.active_level = (flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
if (devm_gpio_request(dev, panel->bl.gpio, "backlight") < 0) {
printk("Failed to request backlight pin!\n");
}
} else {
dev_warn(dev, "invalid gpio backlight.gpio: %d\n", panel->bl.gpio);
}
panel->disp.gpio = of_get_named_gpio_flags(np, "ingenic,disp-gpio", 0, &flags);
if (gpio_is_valid(panel->disp.gpio)) {
panel->disp.active_level = (flags & OF_GPIO_ACTIVE_LOW) ? 0 : 1;
if (devm_gpio_request(dev, panel->disp.gpio, "display") < 0) {
printk("Failed to request display pin!\n");
}
} else {
dev_warn(dev, "invalid gpio backlight.gpio: %d\n", panel->disp.gpio);
}
return 0;
}
static struct tft_config sat101cp50d24b1_bgr_cfg = {
.pix_clk_inv = 0,
.de_dl = 0,
.sync_dl = 0,
.vsync_dl = 0,
.color_even = TFT_LCD_COLOR_EVEN_BGR,
.color_odd = TFT_LCD_COLOR_ODD_BGR,
.mode = TFT_LCD_MODE_PARALLEL_666,
};
static struct tft_config sat101cp50d24b1_rgb_cfg = {
.pix_clk_inv = 0,
.de_dl = 0,
.sync_dl = 0,
.vsync_dl = 0,
.color_even = TFT_LCD_COLOR_EVEN_RGB,
.color_odd = TFT_LCD_COLOR_ODD_RGB,
.mode = TFT_LCD_MODE_PARALLEL_666,
};
static const struct of_device_id panel_of_match[] = {
{ .compatible = "ingenic,sat101cp50d24b1-bgr", .data = (struct tft_config *)&sat101cp50d24b1_bgr_cfg, },
{ .compatible = "ingenic,sat101cp50d24b1-rgb", .data = (struct tft_config *)&sat101cp50d24b1_rgb_cfg, },
{},
};
/**
* @panel_probe
*
* 1. register to ingenicfb.
* 2. register to lcd.
* 3. register to backlight if possible.
*
* @pdev
*
* @return -
* */
static int panel_probe(struct platform_device *pdev)
{
int ret = 0;
struct backlight_properties props; //backlight properties
const struct of_device_id *priv;
memset(&props, 0, sizeof(props));
panel = kzalloc(sizeof(struct panel_dev), GFP_KERNEL);
if (NULL == panel) {
dev_err(&pdev->dev, "Failed to alloc memory!");
return -ENOMEM;
}
panel->dev = &pdev->dev;
dev_set_drvdata(&pdev->dev, panel);
/* panel pinctrl parse */
ret = of_panel_parse(&pdev->dev);
if (ret < 0) {
goto err_of_parse;
}
panel->lcd = lcd_device_register("panel_lcd", &pdev->dev, panel, &panel_lcd_ops);
if (IS_ERR_OR_NULL(panel->lcd)) {
dev_err(&pdev->dev, "Error register lcd!");
ret = -EINVAL;
goto err_of_parse;
}
priv = of_match_node(panel_of_match, pdev->dev.of_node);
lcd_panel.tft_config = (struct tft_config *)priv->data;
/* TODO: should this power status sync from uboot */
panel->power = FB_BLANK_POWERDOWN;
panel_set_power(panel->lcd, FB_BLANK_UNBLANK);
ret = ingenicfb_register_panel(&lcd_panel);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register lcd panel!\n");
goto err_lcd_register;
}
return 0;
err_lcd_register:
lcd_device_unregister(panel->lcd);
err_of_parse:
kfree(panel);
return ret;
}
static int panel_remove(struct platform_device *pdev)
{
struct panel_dev *panel = dev_get_drvdata(&pdev->dev);
panel_set_power(panel->lcd, FB_BLANK_POWERDOWN);
return 0;
}
static struct platform_driver panel_driver = {
.probe = panel_probe,
.remove = panel_remove,
.driver = {
.name = "sat101cp50d24b1",
.of_match_table = panel_of_match,
},
};
module_platform_driver(panel_driver);
5.驱动代码的解析
panel_probe函数:
1. 初始化变量:定义并初始化backlight_properties结构体props,用于存储背光设备的属性。初始化返回值ret为0,表示成功。
2. 分配内存:使用kzalloc为panel_dev结构体分配内存,如果分配失败,则打印错误信息并返回-ENOMEM错误码。
3. 设置设备驱动数据:将平台设备的dev赋值给panel->dev,并用dev_set_drvdata设置设备驱动的私有数据为panel。
4. 解析设备树:调用of_panel_parse函数解析设备树中的面板信息。如果解析失败,则记录错误并跳转到错误处理部分。
5. 注册LCD设备:尝试使用lcd_device_register函数注册LCD设备,如果注册失败,返回-EINVAL错误码并跳转到错误处理。
6. 获取平台设备匹配信息:使用of_match_node函数获取设备树中与当前设备匹配的信息,并据此设置LCD面板的配置。
7. 初始化电源状态:设置面板的初始电源状态为FB_BLANK_POWERDOWN,然后调用panel_set_power函数将面板设置为可用状态FB_BLANK_UNBLANK。这里有一个待办事项(TODO)提示,表明电源状态通常应该从引导加载程序(如uboot)同步,但当前实现没有这样做。
8. 注册LCD面板:调用ingenicfb_register_panel函数注册LCD面板,如果注册失败,则打印错误信息并跳转到错误处理。