一、前提准备:
1、将video/samsung/s3cfb.c文件s3cfb_probe函数里的内容注释掉,目的是去掉原来的lcd驱动。
2、将/drivers/gpu/pvr的Makefile里的obj-$(CONFIG_ARCH_S5PV210) += s3c_bc.o 注释掉,在s3c_bc.c文件S3C_BC_Register函数里使用到registered_fb,但我们上一步将原来的lcd驱动去掉,所以不应该链接这个文件,否则会出现段错误。如下图。
3、重新编译内核
。
二、驱动编写流程
1、首先先把总线平台框架编写好、在probe函数里按下面顺序编写
1)、lcd电源设置.
2)、分配fb_info
3)、设置fb_info
4)、lcd io口映射、设置
5)、lcd 时钟设置
6)、lcd 控制器映射、设置
7)、打开lcd控制器&&背光
8)、注册
9)、测试
2、分析
2.1、lcd电源设置。
要使用lcd控制器,首先要设置lcd的电源,linux内核是通过Regulator管理电源。若不设置的话,无法操作lcd控制器的寄存器。这里涉及到三个函数,regulator_get()、regulator_is_enabled()、regulator_enable()。
struct regulator *regulator_get(struct device *dev, const char *id):获取该驱动对应的regulator
struct device *dev:驱动对应的设备指针
const char *id:电源id
如:regulator_get(&pdev->dev, "pd") 。 &pdev->dev:总线平台的驱动,驱动的init_name,要和power_domain.c的s5pv210_pd_lcd_supply的dev_name设置一样。 "pd":要和power_domain.c的s5pv210_pd_lcd_supply的supply匹配。如下图。
int regulator_is_enabled(struct regulator *regulator):通过此接口判断电源输出是否已被使能。返回值大于0时已使能。
int regulator_enable(struct regulator *regulator):使能电源
代码如下:
/******************************************1、lcd电源设置********************************************/
lcd_regulator = regulator_get(&pdev->dev, "pd");//获取该驱动对应的regulator
printk("lcd_regulator = %p \n",lcd_regulator);
if (!lcd_regulator) {
printk("failed to get lcd_regulator\n");
ret = -EINVAL;
}
ret = regulator_is_enabled(lcd_regulator);//通过此接口判断电源输出是否已被使能。返回值大于0时已使能。
printk("ret = %d \n",ret);
if(ret == 0)
{
ret = regulator_enable(lcd_regulator);//使能lcd电源输出。
if (ret < 0) {
printk("failed to enable regulator\n");
ret = -EINVAL;
}
}
2.2、分配fb_info
lcd_info = framebuffer_alloc(0,NULL);
2.3、设置fb_info
strcpy(lcd_info->fix.id,"s5p-lcd");
// lcd_info->fix.smem_start = //显存物理地址
lcd_info->fix.smem_len = 1024*600*4;//显存大小
lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;
lcd_info->fix.visual = FB_VISUAL_TRUECOLOR;//真彩色
lcd_info->fix.line_length = 1024*4;//每行的字节数
lcd_info->var.xres = 1024;//lcd宽度
lcd_info->var.yres = 600;//lcd高度
lcd_info->var.xres_virtual = 1024;//虚拟屏幕的宽度
lcd_info->var.yres_virtual = 600;//虚拟屏幕的高度
lcd_info->var.bits_per_pixel = 32;//bpp, ALPHA0-R-G-B
lcd_info->var.red.length = 8;//位宽
lcd_info->var.red.offset = 16;//偏移
lcd_info->var.green.length = 8;
lcd_info->var.green.offset = 8;
lcd_info->var.blue.length = 8;
lcd_info->var.blue.offset = 0;
lcd_info->var.transp.offset = 24;
lcd_info->var.transp.length = 8;
lcd_info->var.activate = FB_ACTIVATE_NOW;
/*设置操作函数*/
lcd_info->fbops = &s3cfb_ops;
lcd_info->pseudo_palette = pseudo_palette;/* Fake palette of 16 colors */ //调试板
//lcd_info->screen_base = //显存虚拟大小
lcd_info->screen_size = 1024*600*4;//显存虚拟大小
2.4、lcd io口映射、设置。
背光引脚GPD0_0
设置显示模式RGB=FIMD
代码如下:
/******************************************4、lcd io口映射、设置******************************************/
// 配置引脚用于LCD功能GPF0_0-GPF3_5
// 背光引脚 GPD0_0(PWMTOUT0)
gpf0_con = ioremap(0xE0200120, 4);//GPF0_CON
if (!gpf0_con) {
printk(KERN_ERR "failed to request io GPF0_CON\n");
ret = -EINVAL;
return ret;
}
gpf1_con = ioremap(0xE0200140 , 4);//GPF1_CON
if (!gpf1_con) {
printk(KERN_ERR "failed to request io GPF1_CON\n");
ret = -EINVAL;
return ret;
}
gpf2_con = ioremap(0xE0200160 , 4);//GPF2_CON
if (!gpf2_con) {
printk(KERN_ERR "failed to request io GPF2_CON\n");
ret = -EINVAL;
return ret;
}
gpf3_con = ioremap(0xE0200180 , 4);//GPF3_CON
if (!gpf3_con) {
printk(KERN_ERR "failed to request io GPF3_CON\n");
ret = -EINVAL;
return ret;
}
gpd0_con = ioremap(0xE02000A0 , 8);//GPD0_CON
if (!gpd0_con) {
printk(KERN_ERR "failed to request io GPD0_CON\n");
ret = -EINVAL;
return ret;
}
gpd0_dat = gpd0_con + 1;//GPD0_DAT
//配置lcd引脚
*gpf0_con = 0x22222222;
*gpf1_con = 0x22222222;
*gpf2_con = 0x22222222;
*gpf3_con = 0x22222222;
//配置背光引脚
*gpd0_con &= ~(0xf<<0);
*gpd0_con |= (1<<0);
*gpd0_dat |= (1<<0);//关闭背光
display_con = ioremap(0xE0107008, 4);
if (!display_con) {
printk(KERN_ERR "failed to remap display_con\n");
ret = -EINVAL;
}
/*DISPLAY_CONTROL 0xE010_7008
* DISPLAY_PATH_SEL [1:0] --> 10: RGB=FIMD I80=FIMD ITU=FIMD //Display path selection
*/
*display_con = 2<<0;//显示模式RGB=FIMD
2.5、lcd 时钟设置。
lcd的时钟源可以选择HCLK或者SCLK_FIMD。这里选择SCLK_FIMD。
struct clk *clk_get(struct device *dev, const char *id):获取XXX时钟结构体
unsigned long clk_get_rate(struct clk *clk):获取时钟的频率
int clk_set_parent(struct clk *clk, struct clk *parent):设置XXX时钟的时钟源,也就是从哪里分频。
int clk_set_rate(struct clk *clk, unsigned long rate):设置时钟频率
int clk_enable(struct clk *clk):使能时钟
/******************************************5、lcd 时钟设置******************************************/
lcd_clk = clk_get(NULL, "sclk_fimd"); //获取sclk_fimd域的时钟clk结构体
if (!lcd_clk || IS_ERR(lcd_clk)) {
printk(KERN_INFO "failed to get lcd clock source\n");
}
new_rate = clk_get_rate(lcd_clk);//获取sclk_fimd域当前的时钟频率
printk(KERN_INFO "old_rate = %d \n",new_rate);
clk_set_parent(lcd_clk, clk_get(NULL, "mout_mpll"));//设置sclk_fimd域的时钟源mout_mpll
clk_set_rate(lcd_clk, 166750000);//设置sclk_fimd域的时钟频率为166MHZ
clk_enable(lcd_clk);//使能时钟
2.6、lcd 控制器映射、设置。
s5pv210有0-4屏幕选择,我选择的是屏幕2。
主要设置的寄存器有:VIDCON0、VIDCON1、VIDTCON0、VIDTCON1、VIDTCON2、WINCON2、VIDOSD2A、VIDOSD2B、VIDOSD2D、VIDW02ADD0、VIDW02ADD1、VIDW02ADD2。
VIDCON0:设置lcd时钟源为SCLK_FIMD,CLKVAL_F = 1选择对lcd时钟进行分频,CLKVAL = 3,则VCLK = 166M/(1+3) = 41.5MHZ。
VIDCON1:IHSYNC和IVSYNC设为1,即 HSYNC 和 VSYNC极性翻转,看lcd屏幕的手册。
VIDTCON0:帧同步信号。VBPD、VFPD、VSPW值设置
VIDTCON1:行同步信号。HBPD、HFPD、HSPW值设置
VIDTCON2:LINEVAL、HOZVAL设置。具体的值如下面的图。
WINCON2:ALPHA_SEL_F = 1,24位到32位用来存放透明度值;BPPMODE_F =1101,Unpacked 25 bpp ( non-palletized A:1-R:8-G:8-B:8 );BLD_PIX_F = 1,透明度混合。WSWP_F = 1,Swap Enable,显存的低位放低字节。看下图。
VIDOSD2A:显存左上角的XY坐标。
VIDOSD2B:显存右下角的XY坐标。
VIDOSD2D:屏幕大小。
VIDW02ADD0:显存地址,一定是要映射后的,不能直接操作物理地址。用这个函数dma_alloc_writecombine映射。
VIDW02ADD1:显存长度, 字节单位
VIDW02ADD2:PAGEWIDTH_F:屏幕宽度(字节);OFFSIZE_F: 屏幕显示的位置在虚拟显存的位置。
这里我的lcd屏幕的时序参数表
当WINCON2的WSWP_F置1后,低位放在显存的低字节里。PXXX表示RGB、ALPHA value表示透明度值。
//lcd控制器
res = request_mem_region(S5P_PA_LCD,S5P_SZ_LCD, "s5p-lcd");
if (!res) {
printk(KERN_ERR "failed to request io memory region\n");
ret = -EINVAL;
}
lcd_regs = ioremap(S5P_PA_LCD, S5P_SZ_LCD);
if (!lcd_regs) {
printk(KERN_ERR "failed to remap io region\n");
ret = -EINVAL;
}
printk(KERN_INFO "lcd_regs = %p \n",lcd_regs);
lcd_debug();//当前寄存器值查看
/*VIDCON0
* DSI_EN [30] --> 0 = disable MIPI DSI.
* VIDOUT [28:26] --> 000 = RGB interface
* RGSPSEL [18] --> 0 = RGB parallel format
* CLKVAL_F [13:6] --> 3 = VCLK = HCLK / (CLKVAL+1),即VCLK = 166M/(1+3) = 41.5MHZ
* CLKDIR [4] --> 1 = Divided by CLKVAL_F ,选择对lcd时钟进行分频,若为0则不分频
* CLKSEL_F [2] --> 1 = SCLK_FIMD Selects the video clock source. 0 = HCLK
* ENVID [1] --> 1 = Enables the video output and display control signal. //使能lcd控制器
* ENVID_F [0] -->1 = Enables the video output and display control signal. //当前帧结束后使能lcd控制器
*
NOTE: Display On: ENVID and ENVID_F are set to “1”.
Direct Off: ENVID and ENVID_F are set to “0” simultaneously.
Per Frame Off: ENVID_F is set to “0” and ENVID is set to “1”.
*/
cfg = (1<<2) | (1<< 4)| (3<<6);
writel(cfg, lcd_regs + S3C_VIDCON0);
/*VIDCON1
*IVCLK [7] -->0 = Video data is fetched at VCLK falling edge
*IHSYNC [6] -->1 = Inverted Specifies the HSYNC pulse polarity HSYNC极性翻转,看lcd屏幕的手册
*IVSYNC [5] -->1 = Inverted Specifies the VSYNC pulse polarity. VSYNC极性翻转,看lcd屏幕的手册
*IVDEN [4] -->0 = Normal Specifies the VDEN signal polarity.
*/
cfg = (1<<5) | (1<< 6);
writel(cfg, lcd_regs + S3C_VIDCON1);
/*VIDCON2
* RGB_ORDER_E [21:19] --> 000 = RGB Even line
* RGB_ORDER_O [18:16] --> 000 = RGB Odd Line
*/
/*VIDTCON0 看lcd屏幕的手册,关于时序部分帧同步信号
* VBPD [23:16] --> 17-1 = 16
* VFPD [15:8] --> 12-1=11
* VSPW [7:0] --> 3-1=2
*/
cfg = ( (16<<16) | (11<<8) | (2<<0) );
writel(cfg, lcd_regs + S3C_VIDTCON0);
/*VIDTCON1 看lcd屏幕的手册,关于时序部分行同步信号
* HBPD [23:16] --> 120-1=119
* HFPD [15:8] --> 160-1=159
* HSPW [7:0] --> 20-1=19
*/
cfg = ( (119<<16) | (159<<8) | (19<<0));
writel(cfg, lcd_regs + S3C_VIDTCON1);
/*VIDTCON2
* LINEVAL [21:11] --> 600-1=599
* HOZVAL [10:0] --> 1024-1*1023
*/
cfg = ( (599<<11) | (1023<<0) );
writel(cfg, lcd_regs + S3C_VIDTCON2);
/*WINCON2
* InRGB [13] --> 0 = RGB
* WSWP_F [15] -->1 = Swap Enable //Specifies the Word swap control bit. 显存的低位放低字节
* BLD_PIX_F [6] --> 1 = Per pixel blending //混合
* BPPMODE_F [5:2] --> *1101 = Unpacked 25 bpp ( non-palletized A:1-R:8-G:8-B:8 )
* ALPHA_SEL_F [1] -->1 = Using DATA[31:24] data in word boundary (only when BPPMODE_F = 4’b1101) 24位到32位用来存放透明度
* ENWIN_F [0] -->1 = Enables the video output and video control signal. //Enables/ disables video output and logic immediately
*/
cfg = (1<<1) | (13<<2) | (1<<6) | (1<<15);
writel(cfg, lcd_regs + S3C_WINCON2);
/*WINCHMAP2
* CH0FISEL [18:16] --> 001 = Window 0 //Selects Channel 0’s channel.
* W0FISEL [2:0] --> 001 = Channel 0 //Selects Window 0’s channel. 001
*/
/*VIDOSD2A 显存左上角的XY坐标
* OSD_LeftTopX_F [21:11] --> 0
* OSD_LeftTopY_F [10:0] --> 0
*/
cfg = (0<<11) | (0 << 0);
writel(cfg, lcd_regs + S3C_VIDOSD2A);
/*VIDOSD2B 显存右下角的XY坐标
* OSD_RightBotX_F [21:11] --> 1024 - 1 = 1023
* OSD_RightBotY_F [10:0] --> 600 - 1 = 599
*/
cfg = ( (1023<<11) | (599<<0) );
writel(cfg, lcd_regs + S3C_VIDOSD2B);
/*VIDOSD2D 屏幕大小
* OSDSIZE [23:0] --> (1024) * (600) //Specifies the Window Size
*/
cfg = 1024*600;
writel(cfg, lcd_regs + S3C_VIDOSD2D);
/*分配显存(framebuffer), 并把地址告诉LCD控制器 */
lcd_info->screen_base = dma_alloc_writecombine(NULL, lcd_info->fix.smem_len,&lcd_info->fix.smem_start, GFP_KERNEL);
printk(KERN_INFO "lcd_info->fix.smem_start = %x \n",lcd_info->fix.smem_start);
printk(KERN_INFO "lcd_info->screen_base = %p \n",lcd_info->screen_base);
/*VIDW02ADD0 显存的映射后的地址
* VBASEL_F [31:0] -->显存地址 Specifies A [31:0] of the start address for Video frame buffer.
*/
cfg = lcd_info->fix.smem_start;
writel(cfg, lcd_regs + S3C_VIDW02ADD0);
/*VIDW02ADD1 显存长度 1024*600*4 字节单位
* VBASEU_F [31:0] -->((((HOZVAL + 1)*4 + 0) * (LINEVAL + 1)) & (0xffffff)); //Specifies A [31:0] of the start address for Video frame buffer.
*/
cfg = lcd_info->fix.smem_len;
writel(cfg, lcd_regs + S3C_VIDW02ADD1);
/*VIDW02ADD2,
*OFFSIZE_F [25:13] --> 0 屏幕显示的位置在虚拟显存的位置
*PAGEWIDTH_F [12:0] --> 1024 * 4 屏幕宽度(字节)
*/
cfg = (1024*4 << 0);
writel(cfg, lcd_regs + S3C_VIDW02ADD2);
2.7、打开lcd控制器&&背光。
/******************************************7、打开lcd控制器&&背光***********************************/
*gpd0_dat &= ~(1<<0);//打开背光
/*SHODOWCON
* W0_SHADOW _PROTECT [10] --> 0 = Updates shadow register per frame
* C0_EN_F [2] --> 1 = Enables //Enables Channel 2. 使能通道2传输
*/
cfg |= (1<<2);
writel(cfg, lcd_regs + S3C_WINSHMAP);
cfg = readl(lcd_regs + S3C_WINCON2);
cfg |= (1<<0);
writel(cfg, lcd_regs + S3C_WINCON2);
cfg = readl(lcd_regs + S3C_VIDCON0);
cfg |= ((1<<0) | (1<< 1));
writel(cfg, lcd_regs + S3C_VIDCON0);
2.8、注册。
/******************************************7、注册***********************************/
ret = register_framebuffer(lcd_info);
printk(KERN_INFO "register_framebuffer\n");
2.9、测试。
测试效果为,在屏幕上显示分三块,分别显示红绿蓝。
/******************************************8、测试***********************************/
LCD_Fill(0,0,1024,200,0x00ff0000);
LCD_Fill(0,200,1024,400,0x0000ff00);
LCD_Fill(0,400,1024,600,0x000000ff);
三、全部代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/fs.h>
#include <linux/irq.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/ctype.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/memory.h>
#include <linux/cpufreq.h>
#include <plat/clock.h>
#include <plat/cpu-freq.h>
#include <plat/media.h>
#ifdef CONFIG_HAS_WAKELOCK
#include <linux/wakelock.h>
#include <linux/earlysuspend.h>
#include <linux/suspend.h>
#endif
#include <mach/map.h>
#include <plat/regs-fb.h>
struct regulator* lcd_regulator;
struct device lcd_dev;
struct clk *lcd_clk;
void __iomem *lcd_regs;
struct fb_info* lcd_info;
static u32 pseudo_palette[16];
static volatile unsigned int* gpf0_con;
static volatile unsigned int* gpf1_con;
static volatile unsigned int* gpf2_con;
static volatile unsigned int* gpf3_con;
static volatile unsigned int* gpd0_con;
static volatile unsigned int* gpd0_dat;
static volatile unsigned int* display_con;
static int s5pfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *fb);
struct fb_ops s3cfb_ops = {
.owner = THIS_MODULE,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_setcolreg = s5pfb_setcolreg,
};
static unsigned int __chan_to_field(unsigned int chan,struct fb_bitfield bf)
{
chan &= 0xffff;
chan >>= 16 - bf.length;
return chan << bf.offset;
}
static int s5pfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *fb)
{
unsigned int val = 0;
if (regno < 16) {
val |= __chan_to_field(red, fb->var.red);
val |= __chan_to_field(green, fb->var.green);
val |= __chan_to_field(blue, fb->var.blue);
val |= __chan_to_field(transp, fb->var.transp);
pseudo_palette[regno] = val;
}
return 0;
}
void LCD_Set_Point(unsigned int X_Pos,unsigned int Y_Pos,unsigned int color)
{
*((unsigned int*)lcd_info->screen_base + Y_Pos*1024+X_Pos) = color;
}
//在指定区域内填充单个颜色
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)
//color:要填充的颜色
void LCD_Fill(unsigned int sx,unsigned int sy,unsigned int ex,unsigned int ey,unsigned int color)
{
unsigned int i,j;
unsigned int xlen=0;
xlen=ex-sx;
for(i=sy;i<ey;i++)
{
for(j=0;j<xlen;j++)LCD_Set_Point(j,i,color); //显示颜色
}
}
//lcd寄存器调试
void lcd_debug(void)
{
printk("S3C_VIDCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDCON0)));
printk("S3C_VIDCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDCON1)));
printk("S3C_VIDCON2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDCON2)));
printk("S3C_PRTCON = %x \n ",*((unsigned int*)(lcd_regs + S3C_PRTCON)));
printk("S3C_VIDTCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDTCON0)));
printk("S3C_VIDTCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDTCON1)));
printk("S3C_VIDTCON2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDTCON2)));
printk("S3C_WINCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINCON0)));
printk("S3C_WINCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINCON1)));
printk("S3C_WINCON2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINCON2)));
printk("S3C_WINCON3 = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINCON3)));
printk("S3C_WINCON4 = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINCON4)));
printk("S3C_WINSHMAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINSHMAP)));
printk("S3C_VIDOSD0A = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD0A)));
printk("S3C_VIDOSD0B = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD0B)));
printk("S3C_VIDOSD0C = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD0C)));
printk("S3C_VIDOSD1A = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD1A)));
printk("S3C_VIDOSD1B = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD1B)));
printk("S3C_VIDOSD1C = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD1C)));
printk("S3C_VIDOSD1D = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD1D)));
printk("S3C_VIDOSD2A = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD2A)));
printk("S3C_VIDOSD2B = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD2B)));
printk("S3C_VIDOSD2C = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD2C)));
printk("S3C_VIDOSD2D = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD2D)));
printk("S3C_VIDOSD3A = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD3A)));
printk("S3C_VIDOSD3B = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD3B)));
printk("S3C_VIDOSD3C = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD3C)));
printk("S3C_VIDOSD4A = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD4A)));
printk("S3C_VIDOSD4B = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD4B)));
printk("S3C_VIDOSD4C = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD4C)));
printk("S3C_VIDW00ADD0B0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW00ADD0B0)));
printk("S3C_VIDW00ADD0B1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW00ADD0B1)));
printk("S3C_VIDW01ADD0B0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW01ADD0B0)));
printk("S3C_VIDW01ADD0B1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW01ADD0B1)));
printk("S3C_VIDW02ADD0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW02ADD0)));
printk("S3C_VIDW03ADD0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW03ADD0)));
printk("S3C_VIDW04ADD0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW04ADD0)));
printk("S3C_VIDW00ADD1B0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW00ADD1B0)));
printk("S3C_VIDW00ADD1B1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW00ADD1B1)));
printk("S3C_VIDW01ADD1B0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW01ADD1B0)));
printk("S3C_VIDW01ADD1B1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW01ADD1B1)));
printk("S3C_VIDW02ADD1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW02ADD1)));
printk("S3C_VIDW03ADD1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW03ADD1)));
printk("S3C_VIDW04ADD1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW04ADD1)));
printk("S3C_VIDW00ADD2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW00ADD2)));
printk("S3C_VIDW01ADD2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW01ADD2)));
printk("S3C_VIDW02ADD2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW02ADD2)));
printk("S3C_VIDW03ADD2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW03ADD2)));
printk("S3C_VIDW04ADD2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW04ADD2)));
printk("S3C_VP1TCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VP1TCON0)));
printk("S3C_VP1TCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VP1TCON1)));
printk("S3C_VIDINTCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDINTCON0)));
printk("S3C_VIDINTCON1 = %x \n ",*(((unsigned int*)lcd_regs + S3C_VIDINTCON1)));
printk("S3C_W1KEYCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W1KEYCON0)));
printk("S3C_W1KEYCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W1KEYCON1)));
printk("S3C_W2KEYCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W2KEYCON0)));
printk("S3C_W2KEYCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W2KEYCON1)));
printk("S3C_W3KEYCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W3KEYCON0)));
printk("S3C_W3KEYCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W3KEYCON1)));
printk("S3C_W4KEYCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W4KEYCON0)));
printk("S3C_W4KEYCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W4KEYCON1)));
printk("S3C_W1KEYALPHA = %x \n ",*((unsigned int*)(lcd_regs + S3C_W1KEYALPHA)));
printk("S3C_W2KEYALPHA = %x \n ",*((unsigned int*)(lcd_regs + S3C_W2KEYALPHA)));
printk("S3C_W3KEYALPHA = %x \n ",*((unsigned int*)(lcd_regs + S3C_W3KEYALPHA)));
printk("S3C_W4KEYALPHA = %x \n ",*((unsigned int*)(lcd_regs + S3C_W4KEYALPHA)));
printk("S3C_DITHMODE = %x \n ",*((unsigned int*)(lcd_regs + S3C_DITHMODE)));
printk("S3C_WIN0MAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WIN0MAP)));
printk("S3C_WIN1MAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WIN1MAP)));
printk("S3C_WIN2MAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WIN2MAP)));
printk("S3C_WIN3MAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WIN3MAP)));
printk("S3C_WIN4MAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WIN4MAP)));
printk("S3C_WPALCON_H = %x \n ",*((unsigned int*)(lcd_regs + S3C_WPALCON_H)));
printk("S3C_WPALCON_L = %x \n ",*((unsigned int*)(lcd_regs + S3C_WPALCON_L)));
printk("S3C_VIDW0ALPHA0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW0ALPHA0)));
printk("S3C_VIDW0ALPHA1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW0ALPHA1)));
printk("S3C_VIDW1ALPHA0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW1ALPHA0)));
printk("S3C_VIDW1ALPHA1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW1ALPHA1)));
printk("S3C_VIDW2ALPHA0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW2ALPHA0)));
printk("S3C_VIDW2ALPHA1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW2ALPHA1)));
printk("S3C_VIDW3ALPHA0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW3ALPHA0)));
printk("S3C_VIDW3ALPHA1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW3ALPHA1)));
printk("S3C_VIDW4ALPHA0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW4ALPHA0)));
printk("S3C_VIDW4ALPHA1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW4ALPHA1)));
printk("S3C_BLENDEQ1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_BLENDEQ1)));
printk("S3C_BLENDEQ2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_BLENDEQ2)));
printk("S3C_BLENDEQ3 = %x \n ",*((unsigned int*)(lcd_regs + S3C_BLENDEQ3)));
printk("S3C_BLENDEQ4 = %x \n ",*((unsigned int*)(lcd_regs + S3C_BLENDEQ4)));
printk("S3C_BLENDCON = %x \n ",*((unsigned int*)(lcd_regs + S3C_BLENDCON)));
}
static int s5pfb_probe(struct platform_device *pdev)
{
int ret;
struct resource *res;
u32 cfg;
long new_rate = 0;
/******************************************1、lcd电源设置********************************************/
lcd_regulator = regulator_get(&pdev->dev, "pd");//获取该驱动对应的regulator
printk("lcd_regulator = %p \n",lcd_regulator);
if (!lcd_regulator) {
printk("failed to get lcd_regulator\n");
ret = -EINVAL;
}
ret = regulator_is_enabled(lcd_regulator);//通过此接口判断电源输出是否已被使能。返回值大于0时已使能。
printk("ret = %d \n",ret);
if(ret == 0)
{
ret = regulator_enable(lcd_regulator);//使能lcd电源输出。
if (ret < 0) {
printk("failed to enable regulator\n");
ret = -EINVAL;
}
}
/******************************************2、分配fb_info********************************************/
lcd_info = framebuffer_alloc(0,NULL);
/******************************************3、设置***************************************************/
strcpy(lcd_info->fix.id,"s5p-lcd");
// lcd_info->fix.smem_start = //显存物理地址
lcd_info->fix.smem_len = 1024*600*4;//显存大小
lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;
lcd_info->fix.visual = FB_VISUAL_TRUECOLOR;//真彩色
lcd_info->fix.line_length = 1024*4;//每行的字节数
lcd_info->var.xres = 1024;//lcd宽度
lcd_info->var.yres = 600;//lcd高度
lcd_info->var.xres_virtual = 1024;//虚拟屏幕的宽度
lcd_info->var.yres_virtual = 600;//虚拟屏幕的高度
lcd_info->var.bits_per_pixel = 32;//bpp, ALPHA0-R-G-B
lcd_info->var.red.length = 8;//位宽
lcd_info->var.red.offset = 16;//偏移
lcd_info->var.green.length = 8;
lcd_info->var.green.offset = 8;
lcd_info->var.blue.length = 8;
lcd_info->var.blue.offset = 0;
lcd_info->var.transp.offset = 24;
lcd_info->var.transp.length = 8;
lcd_info->var.activate = FB_ACTIVATE_NOW;
/*设置操作函数*/
lcd_info->fbops = &s3cfb_ops;
lcd_info->pseudo_palette = pseudo_palette;/* Fake palette of 16 colors */ //调试板
//lcd_info->screen_base = //显存虚拟大小
lcd_info->screen_size = 1024*600*4;//显存虚拟大小
/******************************************4、lcd io口映射、设置******************************************/
// 配置引脚用于LCD功能GPF0_0-GPF3_5
// 背光引脚 GPD0_0(PWMTOUT0)
gpf0_con = ioremap(0xE0200120, 4);//GPF0_CON
if (!gpf0_con) {
printk(KERN_ERR "failed to request io GPF0_CON\n");
ret = -EINVAL;
return ret;
}
gpf1_con = ioremap(0xE0200140 , 4);//GPF1_CON
if (!gpf1_con) {
printk(KERN_ERR "failed to request io GPF1_CON\n");
ret = -EINVAL;
return ret;
}
gpf2_con = ioremap(0xE0200160 , 4);//GPF2_CON
if (!gpf2_con) {
printk(KERN_ERR "failed to request io GPF2_CON\n");
ret = -EINVAL;
return ret;
}
gpf3_con = ioremap(0xE0200180 , 4);//GPF3_CON
if (!gpf3_con) {
printk(KERN_ERR "failed to request io GPF3_CON\n");
ret = -EINVAL;
return ret;
}
gpd0_con = ioremap(0xE02000A0 , 8);//GPD0_CON
if (!gpd0_con) {
printk(KERN_ERR "failed to request io GPD0_CON\n");
ret = -EINVAL;
return ret;
}
gpd0_dat = gpd0_con + 1;//GPD0_DAT
//配置lcd引脚
*gpf0_con = 0x22222222;
*gpf1_con = 0x22222222;
*gpf2_con = 0x22222222;
*gpf3_con = 0x22222222;
//配置背光引脚
*gpd0_con &= ~(0xf<<0);
*gpd0_con |= (1<<0);
*gpd0_dat |= (1<<0);//关闭背光
display_con = ioremap(0xE0107008, 4);
if (!display_con) {
printk(KERN_ERR "failed to remap display_con\n");
ret = -EINVAL;
}
/*DISPLAY_CONTROL 0xE010_7008
* DISPLAY_PATH_SEL [1:0] --> 10: RGB=FIMD I80=FIMD ITU=FIMD //Display path selection
*/
*display_con = 2<<0;//显示模式RGB=FIMD
/******************************************5、lcd 时钟设置******************************************/
lcd_clk = clk_get(NULL, "sclk_fimd"); //获取sclk_fimd域的时钟clk结构体
if (!lcd_clk || IS_ERR(lcd_clk)) {
printk(KERN_INFO "failed to get lcd clock source\n");
}
new_rate = clk_get_rate(lcd_clk);//获取sclk_fimd域当前的时钟频率
printk(KERN_INFO "old_rate = %d \n",new_rate);
clk_set_parent(lcd_clk, clk_get(NULL, "mout_mpll"));//设置sclk_fimd域的时钟源mout_mpll
clk_set_rate(lcd_clk, 166750000);//设置sclk_fimd域的时钟频率为166MHZ
clk_enable(lcd_clk);//使能时钟
new_rate = clk_get_rate(lcd_clk);
printk(KERN_INFO "new_rate = %d \n",new_rate);
/******************************************6、lcd 控制器映射、设置***********************************/
//lcd控制器
res = request_mem_region(S5P_PA_LCD,S5P_SZ_LCD, "s5p-lcd");
if (!res) {
printk(KERN_ERR "failed to request io memory region\n");
ret = -EINVAL;
}
lcd_regs = ioremap(S5P_PA_LCD, S5P_SZ_LCD);
if (!lcd_regs) {
printk(KERN_ERR "failed to remap io region\n");
ret = -EINVAL;
}
printk(KERN_INFO "lcd_regs = %p \n",lcd_regs);
//lcd_debug();//当前寄存器值查看
/*VIDCON0
* DSI_EN [30] --> 0 = disable MIPI DSI.
* VIDOUT [28:26] --> 000 = RGB interface
* RGSPSEL [18] --> 0 = RGB parallel format
* CLKVAL_F [13:6] --> 3 = VCLK = HCLK / (CLKVAL+1),即VCLK = 166M/(1+3) = 41.5MHZ
* CLKDIR [4] --> 1 = Divided by CLKVAL_F ,选择对lcd时钟进行分频,若为0则不分频
* CLKSEL_F [2] --> 1 = SCLK_FIMD Selects the video clock source. 0 = HCLK
* ENVID [1] --> 1 = Enables the video output and display control signal. //使能lcd控制器
* ENVID_F [0] --> 1 = Enables the video output and display control signal. //当前帧结束后使能lcd控制器
*
NOTE: Display On: ENVID and ENVID_F are set to “1”.
Direct Off: ENVID and ENVID_F are set to “0” simultaneously.
Per Frame Off: ENVID_F is set to “0” and ENVID is set to “1”.
*/
cfg = (1<<2) | (1<< 4)| (3<<6);
writel(cfg, lcd_regs + S3C_VIDCON0);
/*VIDCON1
*IVCLK [7] -->0 = Video data is fetched at VCLK falling edge
*IHSYNC [6] -->1 = Inverted Specifies the HSYNC pulse polarity HSYNC极性翻转,看lcd屏幕的手册
*IVSYNC [5] -->1 = Inverted Specifies the VSYNC pulse polarity. VSYNC极性翻转,看lcd屏幕的手册
*IVDEN [4] -->0 = Normal Specifies the VDEN signal polarity.
*/
cfg = (1<<5) | (1<< 6);
writel(cfg, lcd_regs + S3C_VIDCON1);
/*VIDCON2
* RGB_ORDER_E [21:19] --> 000 = RGB Even line
* RGB_ORDER_O [18:16] --> 000 = RGB Odd Line
*/
/*VIDTCON0 看lcd屏幕的手册,关于时序部分帧同步信号
* VBPD [23:16] --> 17-1 = 16
* VFPD [15:8] --> 12-1=11
* VSPW [7:0] --> 3-1=2
*/
cfg = ( (16<<16) | (11<<8) | (2<<0) );
writel(cfg, lcd_regs + S3C_VIDTCON0);
/*VIDTCON1 看lcd屏幕的手册,关于时序部分行同步信号
* HBPD [23:16] --> 120-1=119
* HFPD [15:8] --> 160-1=159
* HSPW [7:0] --> 20-1=19
*/
cfg = ( (119<<16) | (159<<8) | (19<<0));
writel(cfg, lcd_regs + S3C_VIDTCON1);
/*VIDTCON2
* LINEVAL [21:11] --> 600-1=599
* HOZVAL [10:0] --> 1024-1*1023
*/
cfg = ( (599<<11) | (1023<<0) );
writel(cfg, lcd_regs + S3C_VIDTCON2);
/*WINCON2
* InRGB [13] --> 0 = RGB
* WSWP_F [15] -->1 = Swap Enable //Specifies the Word swap control bit. 显存的低位放低字节
* BLD_PIX_F [6] --> 1 = Per pixel blending //混合
* BPPMODE_F [5:2] --> *1101 = Unpacked 25 bpp ( non-palletized A:1-R:8-G:8-B:8 )
* ALPHA_SEL_F [1] -->1 = Using DATA[31:24] data in word boundary (only when BPPMODE_F = 4’b1101) 24位到32位用来存放透明度
* ENWIN_F [0] -->1 = Enables the video output and video control signal. //Enables/ disables video output and logic immediately
*/
cfg = (1<<1) | (13<<2) | (1<<6) | (1<<15);
writel(cfg, lcd_regs + S3C_WINCON2);
/*WINCHMAP2
* CH0FISEL [18:16] --> 001 = Window 0 //Selects Channel 0’s channel.
* W0FISEL [2:0] --> 001 = Channel 0 //Selects Window 0’s channel. 001
*/
/*VIDOSD2A 显存左上角的XY坐标
* OSD_LeftTopX_F [21:11] --> 0
* OSD_LeftTopY_F [10:0] --> 0
*/
cfg = (0<<11) | (0 << 0);
writel(cfg, lcd_regs + S3C_VIDOSD2A);
/*VIDOSD2B 显存右下角的XY坐标
* OSD_RightBotX_F [21:11] --> 1024 - 1 = 1023
* OSD_RightBotY_F [10:0] --> 600 - 1 = 599
*/
cfg = ( (1023<<11) | (599<<0) );
writel(cfg, lcd_regs + S3C_VIDOSD2B);
/*VIDOSD2D 屏幕大小
* OSDSIZE [23:0] --> (1024) * (600) //Specifies the Window Size
*/
cfg = 1024*600;
writel(cfg, lcd_regs + S3C_VIDOSD2D);
/*分配显存(framebuffer), 并把地址告诉LCD控制器 */
lcd_info->screen_base = dma_alloc_writecombine(NULL, lcd_info->fix.smem_len,&lcd_info->fix.smem_start, GFP_KERNEL);
printk(KERN_INFO "lcd_info->fix.smem_start = %x \n",lcd_info->fix.smem_start);
printk(KERN_INFO "lcd_info->screen_base = %p \n",lcd_info->screen_base);
/*VIDW02ADD0 显存的映射后的地址
* VBASEL_F [31:0] -->显存地址 Specifies A [31:0] of the start address for Video frame buffer.
*/
cfg = lcd_info->fix.smem_start;
writel(cfg, lcd_regs + S3C_VIDW02ADD0);
/*VIDW02ADD1 显存长度 1024*600*4 字节单位
* VBASEU_F [31:0] -->((((HOZVAL + 1)*4 + 0) * (LINEVAL + 1)) & (0xffffff)); //Specifies A [31:0] of the start address for Video frame buffer.
*/
cfg = lcd_info->fix.smem_len;
writel(cfg, lcd_regs + S3C_VIDW02ADD1);
/*VIDW02ADD2,
*OFFSIZE_F [25:13] --> 0 屏幕显示的位置在虚拟显存的位置
*PAGEWIDTH_F [12:0] --> 1024 * 4 屏幕宽度(字节)
*/
cfg = (1024*4 << 0);
writel(cfg, lcd_regs + S3C_VIDW02ADD2);
/******************************************7、打开lcd控制器&&背光***********************************/
*gpd0_dat &= ~(1<<0);//打开背光
/*SHODOWCON
* W0_SHADOW _PROTECT [10] --> 0 = Updates shadow register per frame
* C0_EN_F [2] --> 1 = Enables //Enables Channel 2. 使能通道2传输
*/
cfg = (1<<2);
writel(cfg, lcd_regs + S3C_WINSHMAP);
cfg = readl(lcd_regs + S3C_WINCON2);
cfg |= (1<<0);
writel(cfg, lcd_regs + S3C_WINCON2);
cfg = readl(lcd_regs + S3C_VIDCON0);
cfg |= ((1<<0) | (1<< 1));
writel(cfg, lcd_regs + S3C_VIDCON0);
/******************************************7、注册***********************************/
ret = register_framebuffer(lcd_info);
printk(KERN_INFO "register_framebuffer\n");
/******************************************8、测试***********************************/
LCD_Fill(0,0,1024,200,0x00ff0000);
LCD_Fill(0,200,1024,400,0x0000ff00);
LCD_Fill(0,400,1024,600,0x000000ff);
//lcd_debug();
return 0;
}
static int s5pfb_remove(struct platform_device *pdev)
{
unregister_framebuffer(lcd_info);
dma_free_writecombine(NULL,lcd_info->fix.smem_len, lcd_info->screen_base, lcd_info->fix.smem_start);
iounmap(display_con);
iounmap(lcd_regs);
release_mem_region(S5P_PA_LCD,S5P_SZ_LCD);
iounmap(gpf0_con);//GPF0_CON
iounmap(gpf1_con);//GPF1_CON
iounmap(gpf2_con);//GPF2_CON
iounmap(gpf3_con);//GPF3_CON
iounmap(gpd0_con);//GPD0_CON
framebuffer_release(lcd_info);
regulator_disable(lcd_regulator);
regulator_put(lcd_regulator);
}
static void s5p_lcd_release(struct device * dev)
{
}
struct platform_device s5p_device_fb = {
.name = "s5p-lcd",//总线平台名称
.id = -1,
.dev = {
.init_name = "s5p-lcd",//设备init_name,用来获取电源管理pd的regulator。与power-domain.c文件的s5pv210_pd_lcd_supply里dev_name对应
.release = s5p_lcd_release,
}
};
static struct platform_driver s5pfb_driver = {
.probe = s5pfb_probe,
.remove = s5pfb_remove,
.driver = {
.name = "s5p-lcd", //总线平台名称
.owner = THIS_MODULE,
},
};
static int __init s5pfb_register(void)
{
platform_driver_register(&s5pfb_driver);
platform_device_register(&s5p_device_fb);
return 0;
}
static void __exit s5pfb_unregister(void)
{
platform_driver_unregister(&s5pfb_driver);
platform_device_unregister(&s5p_device_fb);
}
module_init(s5pfb_register);
module_exit(s5pfb_unregister);
MODULE_LICENSE("GPL");