linux的lcd屏幕调试

发布于:2024-04-29 ⋅ 阅读:(28) ⋅ 点赞:(0)

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 驱动的编写流程
Linux 内核将所有的 Framebuffer 抽象为一个叫做 fb_info 的结构 体,fb_info 结构体包含了 Framebuffer 设备的完整属性和操作集合,因此每一个 Framebuffer 设 备都必须有一个 fb_info 。换言之就是, LCD 的驱动就是构建 fb_info ,并且向系统注册 fb_info 的过程。fb_info 结构体定义在 include/linux/fb.h 文件里面。
因此: panel_probe函数的主要工作内容为:
①、申请 fb_info
②、初始化 fb_info 结构体中的各个成员变量。
③、初始化 eLCDIF 控制器。
④、使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。

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面板,如果注册失败,则打印错误信息并跳转到错误处理。


网站公告

今日签到

点亮在社区的每一天
去签到