利用linux驱动思想实现s5pv210的lcd控制器
本文章主要参考自韦东山老师的新一期裸板视屏中LCD显示章节
最近在看驱动,发现部分。韦老师的裸机部分已经使用了驱动的操作分层和数据分离的思想,回来再刷一遍,顺便做好记录。
1.LCD的扫描显示原理大家可以看下面这篇文章,讲的特别清晰,我就不重复。
http://www.cnblogs.com/shangdawei/p/4760933.html
2.S5PV210的LCD组成
因为S5PV210的LCD支持很多种显示功能,框图看起来比较复杂,所以我们只关注我们用到的。
我用红色框框起来了。
1.默认就使用DMA功能
2.RGB接口最大支持24根线(我的板子是RGB的TFT)
S5PV210支持多窗口显示。
因为处理器支持很多个虚拟窗口,而且每个窗口大小都可以配置,在使能多个窗口的情况下,可以实现一些复杂的图像显示。
比如下图使用三个窗口,一个是主窗口(黑框)和我们的屏幕一样。一个比较小(红框)。最后一个也比较小(紫框)。在配置好着三个串口的大小和地址后,再配置那个窗口在前,那个在后。(在前的内容可以覆盖在后的内容)
有这三个窗口叠加就可以实现,类似新闻联播中,字幕滚动,台标显示和主持人窗口,三个互补影响的叠加显示。
我们的配置不使用如此复杂,只使用一个窗口。其它多个的配置也是类似。
3.主要寄存器
VIDCON0
这个寄存器主要的几个寄存器我都画出来了,我们用RGB接口,时钟需要分频,使能
这个主要是一些引脚极性,看要不要
下面两个是时序相关的
屏幕分辨率配置
WINDOW0
SHODOWCON
窗口1左上角坐标
窗口1右下角坐标
窗口1的大小
窗口1显存的起始地址
窗口1显存的结束地址
显示要经过的路径
源码主要分两层。
一层是LCD层(类似platform_device)
另一层是LCD控制器层(类似platform_driver)
先看一下lcd层
抽象出来的一些lcd公有的参数
enum {
NORMAL = 0,
INVERT = 1,
};
/*
* lcd引脚极性
* NORMAL :正常极性
* INVERT :翻转极性
*/
struct pins_polarity {
int vclk; /* NORMAL:在下降沿获取数据 */
int vsync; /* NORMAL:在上升沿获取数据*/
int hsync; /* NORMAL:在上升沿获取数据 */
int rgb;
int de; /* NORMAL:在高电平有效 */
};
struct time_sequence {
/* vclk */
int vclk;
/* 水平方向 */
int thp; /* Horizontal Pulse Width */
int thb; /* Horizontal Back Porch */
int thf; /* Horizontal Front Porch */
/* 垂直方向 */
int tvp; /* Vertical Pulse Width */
int tvb; /* Vertical Back Porch */
int tvf; /* Vertical Front Porch */
};
/*
* lcd 参数
*/
struct lcd_params {
char *name;
/* 引脚极性 */
struct pins_polarity pins_pol;
/* 时序 */
struct time_sequence time_seq;
/* 分辨率 bpp */
int xres;
int yres;
int bpp;
/* framebuffer地址 */
unsigned int fb_base;
/* 那个窗口 */
int id;
};
为所有lcd抽象的公有的注册接口
#define LCD_NUM 10
static struct lcd_params *lcd_paramss[LCD_NUM];
static struct lcd_params *s_lcd_params;
int register_lcd(struct lcd_params *p_lcd_par)
{
int i;
for(i=0; i<LCD_NUM; i++)
{
if(!lcd_paramss[i])
{
lcd_paramss[i] = p_lcd_par;
return i;
}
}
return -1;
}
int select_lcd(char *name)
{
int i;
for(i=0; i<LCD_NUM; i++)
{
if(lcd_paramss[i] && !strcmp(lcd_paramss[i]->name,name))
{
s_lcd_params = lcd_paramss[i];
return i;
}
}
return -1;
}
struct lcd_params * get_lcd_params(void)
{
return s_lcd_params;
}
void lcd_enable(void)
{
lcd_controller_enable();
}
void lcd_disable(void)
{
lcd_controller_disable();
}
下面是soc的lcd控制器层
struct lcd_controller {
char *name;
void (*init)(struct lcd_params *lcd_par);
void (*enable)(void);
void (*disable)(void);
};
#define LCD_CONTROLLER_NUM 10
static struct lcd_controller *lcd_controllers[LCD_CONTROLLER_NUM];
static struct lcd_controller *s_lcd_controller;
/*
* 公有的注册lcd的控制器接口
*/
int register_lcd_controller(struct lcd_controller *p_lcd_ctl)
{
int i;
for(i=0; i<LCD_CONTROLLER_NUM; i++)
{
if(!lcd_controllers[i])
{
lcd_controllers[i] = p_lcd_ctl;
return i;
}
}
return -1;
}
/*
* 通过名字匹配lcd
*/
int select_lcd_controller(char *name)
{
int i;
for(i=0; i<LCD_CONTROLLER_NUM; i++)
{
if(lcd_controllers[i] && !strcmp(lcd_controllers[i]->name,name))
{
s_lcd_controller = lcd_controllers[i];
return i;
}
}
return -1;
}
/*
* 公有的lcd on接口
*/
void lcd_controller_enable(void)
{
if(s_lcd_controller)
{
s_lcd_controller->enable();
}
}
/*
* 公有的lcd off接口
*/
void lcd_controller_disable(void)
{
if(s_lcd_controller)
{
s_lcd_controller->disable();
}
}
/*
* 向上:接收不同lcd的参数
* 向下:使用这些参数设置对应的lcd控制器
*/
int lcd_controller_init(struct lcd_params *p_lcd_params)
{
if(s_lcd_controller)
{
s_lcd_controller->init(p_lcd_params);
return 0;
}
return -1;
}
/* 注册函数 */
int lcd_controller_add(void)
{
s5pv210_lcd_controller_add();
}
下面就是和硬件相关测私有数据和配置。
数据
#define FB_BASE 0x40000000
static struct lcd_params lcd_7_0_params = {
.name = "lcd_7.0",
.pins_pol = {
.vclk = INVERT, /* NORMAL:在下降沿获取数据 */
.vsync = INVERT, /* NORMAL:在上升沿获取数据*/
.hsync = INVERT, /* NORMAL:在上升沿获取数据 */
.rgb = NORMAL,
.de = NORMAL, /* NORMAL:在高电平有效 */
},
.time_seq = {
/* vclk */
.vclk = 52, /* Mhz */
/* 水平方向 */
.thp = 20, /* Horizontal Pulse Width */
.thb = 140, /* Horizontal Back Porch */
.thf = 160, /* Horizontal Front Porch */
/* 垂直方向 */
.tvp = 3, /* Vertical Pulse Width */
.tvb = 20, /* Vertical Back Porch */
.tvf = 12, /* Vertical Front Porch */
},
.xres = 1024,
.yres = 600,
.bpp = 16,
.fb_base = FB_BASE,
.id = 0,
};
int lcd_7_0_add(void)
{
return register_lcd(&lcd_7_0_params);
}
操作
#define HCLK_DSYS 166
/* lcd相关引脚初始化 */
static void x210_lcd_pins_init(void)
{
/* lcd引脚 */
__REG(GPF0CON) = 0x22222222;
__REG(GPF1CON) = 0x22222222;
__REG(GPF2CON) = 0x22222222;
__REG(GPF3CON) = 0x00222222;
/* 初始化背光引脚 */
__REG(GPD0CON) &= ~(0x0f);
__REG(GPD0CON) |= 0x01;
}
/* 根据传入的参数设置lcd控制寄存器 */
static void s5pv210_lcd_controller_init(struct lcd_params * p_lcd_params)
{
int clkval_f;
int bpp;
x210_lcd_pins_init();
__REG(S5PV210_DISPLAY_CONTROL) = 0x02;
/* [28:26] :VIDOUT RGB interface
* [18] :RGSPSEL RGB parallel format 0
* [17] :PNRMODE
* [16] :CLKVALUP Selects CLKVAL_F update timing control 0 aways
* [13:6] :CLKVAL_F VCLK = HCLK / (CLKVAL+1), where CLKVAL >= 1
* [5] :VCLKFREE Controls VCLK Free Run
* [4] :CLKDIR Selects the clock source
* [2] :CLKSEL_F Selects the video clock source.
* [1] :ENVID Enables/ disables video output and logic immediately.
* [0] :ENVID_F Enables/ disables video output and logic at current frame end
*/
//clkval_f = (HCLK_DSYS / p_lcd_params->time_seq.vclk - 1) ? (HCLK_DSYS / p_lcd_params->time_seq.vclk - 1) : 1;
clkval_f = 3; /* 最快只能为3,否则不能显示 */
__REG(S5PV210_VIDCON0) = (0<<26)|(0<<18)|(clkval_f<<6)|(1<<4)|(0<<2);
__REG(S5PV210_VIDCON0) |= (1<<1)|(1<<0);
/* [7] :IVCLK Controls the polarity of the VCLK active edge.
* [6] :IHSYNC Specifies the HSYNC pulse polarity.
* [5] :IVSYNC Specifies the VSYNC pulse polarity.
* [4] :IVDEN Specifies the VDEN signal polarity
*/
__REG(S5PV210_VIDCON1) = (p_lcd_params->pins_pol.vclk<<7) |\
(p_lcd_params->pins_pol.hsync<<6) |\
(p_lcd_params->pins_pol.vsync<<5) |\
(p_lcd_params->pins_pol.de<<4);
/* [27] :RGB_SKIP_EN Enables the RGB skip mode (only where RGBSPSEL == 1’b0)
* [21:19] :RGB_ORDER_E Controls RGB interface output order
*/
__REG(S5PV210_VIDCON2) = (0<<19);
/* [23:16] :VBPD Vertical back porch VBPD = tvb - 1
* [15:8] :VFPD Vertical front porch VFPD = tvf - 1
* [7:0] :VSPW Vertical sync pulse width VSPW = tvp - 1
*/
__REG(S5PV210_VIDTCON0) = ((p_lcd_params->time_seq.tvb-1)<<16) |\
((p_lcd_params->time_seq.tvf-1)<<8) |\
((p_lcd_params->time_seq.tvp-1)<<0);
/* [23:16] :HBPD Horizontal back porch HBPD = thb - 1
* [15:8] :HFPD Horizontal front porch HFPD = thf - 1
* [7:0] :HSPW Horizontal sync pulse width HSPW = thp - 1
*/
__REG(S5PV210_VIDTCON1) = ((p_lcd_params->time_seq.thb-1)<<16) |\
((p_lcd_params->time_seq.thf-1)<<8) |\
((p_lcd_params->time_seq.thp-1)<<0);
/* 物理屏幕分辨率
* [21:11] :LINEVAL the vertical size of display.(LINEVAL + 1) should be even.
* [10:0] :HOZVAL Determines the horizontal size of display.
*/
__REG(S5PV210_VIDTCON2) = (p_lcd_params->yres<<11)|(p_lcd_params->xres<<0);
/* [15] :WSWP_F Specifies the Word swap control bit
* [5:2] :BPPMODE_F Selects the Bits Per Pixel (BPP) mode for Window image.
* 0011 = 8 bpp ( palletized )
* 0101 = 16 bpp ( non-palletized, R:5-G:6-B:5 )
* 1011 = Unpacked 24 bpp ( non-palletized R:8-G:8-B:8 )
* [0] :ENWIN_F Enables/ disables video output and logic immediately
*/
bpp = p_lcd_params->bpp == 8 ? 0x3 : \
p_lcd_params->bpp == 16 ? 0x5 : \
0xb; /* 24/32bpp */
__REG(S5PV210_WINCON0) = (p_lcd_params->pins_pol.rgb<<15)|(bpp<<2)|(1<<0);
/* 设置屏幕左上角坐标
* [21:11] :OSD_LeftTopX_F Specifies the horizontal screen coordinate
* [10:0] :OSD_LeftTopY_F Specifies the vertical screen coordinate
*/
__REG(S5PV210_VIDOSD0A) = (0<<11)|(0<<10);
/* 设置屏幕右上角坐标
* [21:11] :OSD_RightBotX_F Specifies the horizontal screen coordinate
* [10:0] :OSD_RightBotY_F Specifies the vertical screen coordinate
*/
__REG(S5PV210_VIDOSD0B) = ((p_lcd_params->xres-1)<<11)|((p_lcd_params->yres-1)<<0);
/* 窗口0的尺寸
* OSDSIZE [23:0] Specifies the Window Size 0
* For example, Height * Width (Number of Word)
*/
__REG(S5PV210_VIDOSD0C) = p_lcd_params->xres * p_lcd_params->yres;
/* framebuffer的起始地址
* [31:0]:VBASEU_F Specifies A [31:0] of the start address for Video frame buffer.
*/
__REG(S5PV210_VIDW00ADD0B0) = p_lcd_params->fb_base;
/* framebuffer end of address
* [31:0]:VBASEL_F the end address for Video frame buffer.
* VBASEL = VBASEU +(PAGEWIDTH+OFFSIZE) x (LINEVAL+1)
*/
bpp = p_lcd_params->bpp == 8 ? 1 : \
p_lcd_params->bpp == 16 ? 2 : \
4; /* 24/32bpp */
__REG(S5PV210_VIDW00ADD0B1) = p_lcd_params->fb_base + p_lcd_params->xres * p_lcd_params->yres * bpp;
/*
* [0] :C0_EN_F Enables Channel 0
*/
__REG(S5PV210_SHADOWCON) |= (1<<p_lcd_params->id);
printf("s5pv210_lcd_controller_init\n\r");
}
static void s5pv210_lcd_controller_enable(void)
{
/* on back right */
__REG(GPD0DAT) &= ~0x01;
printf("s5pv210_lcd_controller_enable\n\r");
}
static void s5pv210_lcd_controller_disable(void)
{
/* off back right */
__REG(GPD0DAT) |= 0x01;
printf("s5pv210_lcd_controller_disable\n\r");
}
static struct lcd_controller s5pv210_lcd_controller = {
.name = "s5pv210",
.init = s5pv210_lcd_controller_init,
.enable = s5pv210_lcd_controller_enable,
.disable = s5pv210_lcd_controller_disable,
};
int s5pv210_lcd_controller_add(void)
{
return register_lcd_controller(&s5pv210_lcd_controller);
}
通用的初始化函数,今后如果换了别的型号的lcd,更改私有的数据部分就可以,然后注册就行。
更换了控制器,只需要重新实现一个lcd_controller就行,整体框架不需要改变。
void lcd_init(void)
{
/* 注册lcd */
lcd_7_0_add();
/* 注册lcd控制器 */
lcd_controller_add();
/* 选择lcd */
select_lcd("lcd_7.0");
/* 选择lcd控制器 */
select_lcd_controller("s5pv210");
/* 初始化lcd控制器 */
lcd_controller_init(s_lcd_params);
/* 开lcd */
lcd_enable();
上一篇: MySQL 8.0.20安装教程(Win10)—— 免安装版
下一篇: 在云服务器上部署项目(下)