s3c6410 LCD在uboot下的驱动

时间:2021-05-21 12:21:46
uboot下有现成的LCD驱动模板,原来是用于MPC823和PXA250的,我们需要在s3c6410这个ARM11的uboot中增加LCD的驱动,可以在这个基础上修改。具体分以下几步:

        1.  修改配置头文件,是整个工程支持LCD。

         在修改include/configs/smdk6410.h 增加一行

        #define CONFIG_LCD

        这行会把common/lcd.c加入编译,同时影响lib_arm/board.c,lib_arm/armlinux.c。在文件cpu/s3c64xx/cpu.c中,还有进入linux内核前的关于lcd控制的准备工作lcd_disable(),lcd_panel_disable()。

        2. 修改common/lcd.c

        在lcd.c中准备函数calc_fbsize(),这个函数会在armlinux.c中调用,用于计算lcd显示缓冲需要的内存。注意,如果我们使用的现实缓冲比较大,就不能使用这个方法来分配。uboot中malloc函数最大分配的内存,不可能超过uboot使用的总内存512K。如果显示分辨率只有320x240,这个占用内存只有153K,这个方法还是可行的。

        先定义一个LCD的显示控制结构,用于各种函数的计算。

        typedef struct vidinfo {
            ushort    vl_col;        /*每行点数,如320*/
            ushort    vl_row;        /*整屏行数,如340 */
            ushort    vl_width;    /* 虚拟屏宽度,我们简化,不用虚拟屏*/
            ushort    vl_height;    /* 虚拟屏高度*/
            u_char    vl_bpix;    /*每点位数,如16*/
        } vidinfo_t;

        我们使用宏结构定义各种参数:

        //1376 //1178
#define S3CFB_HFP        52//40    /*前沿点数*/
#define S3CFB_HSW        50//48    /* hsync脉冲宽度 */
#define S3CFB_HBP        200//240//40    /* 后沿点数 */
//805 //807
#define S3CFB_VFP        13//13    /* 前沿行数 */
#define S3CFB_VSW        1//3    /* vsync脉冲宽度 */
#define S3CFB_VBP        25//29    /* 后沿行数 */

#define S3CFB_HRES        1024    /* x方向分辨率*/
#define S3CFB_VRES        768    /* y方向分辨率*/
#define S3CFB_VFRAME_FREQ         60    /* 帧频率*/
#define PIXELBITS               16    /*每点使用位数*/

#define S3CFB_IVCLK             CFG_LOW  /*VCLK极性控制*/
#define S3CFB_IHSYNC            CFG_HIGH /*HSYNC极性控制*/
#define S3CFB_IVSYNC            CFG_HIGH /*VSYNC极性控制*/
#define S3CFB_IVDEN             CFG_LOW  /*DE 数据有效极性控制*/

#define S3CFB_PIXEL_CLOCK    (S3CFB_VFRAME_FREQ * (S3CFB_HFP + S3CFB_HSW + S3CFB_HBP + S3CFB_HRES) * (S3CFB_VFP + S3CFB_VSW + S3CFB_VBP + S3CFB_VRES))

        我们使用的LCD比较大,我们直接指定显示缓冲区。

#define LCD_FRAMEBUFFER         (TEXT_BASE - 0x300000)    

 

       准备参数结构

vidinfo_t panel_info = {
  S3CFB_HRES, 
  S3CFB_VRES, 
           0, 
           0, 
   PIXELBITS,
};

        需要在uboot中,通过计算分配显示缓冲,需要加入下面的宏:

DECLARE_GLOBAL_DATA_PTR;

        现在我们完成calc_fbsize函数

ulong calc_fbsize (void)
{
  ulong size;
 
  int line_length = (panel_info.vl_col * panel_info.vl_bpix) / 8;
  size = line_length * panel_info.vl_row;
#ifdef LCD_FRAMEBUFFER
  size = 4096;  //直接给一个固定值
#endif
  return size;
}

        3.  继续修改lcd.c,准备函数lcd_setmem,给board.c调用

ulong lcd_setmem (ulong addr)
{

    ulong size;
    int line_length = (panel_info.vl_col * panel_info.vl_bpix) / 8;

    debug ("LCD panel info: %d x %d, %d bit/pix\n",
        panel_info.vl_col, panel_info.vl_row, NBITS (panel_info.vl_bpix) );

    size = line_length * panel_info.vl_row;
#ifdef LCD_FRAMEBUFFER
        size = 4096;  //直接给一个固定值
#endif
    /* Round up to nearest full page */
    size = (size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);

    /* Allocate pages for the frame buffer. */
    addr -= size;

    debug ("Reserving %ldk for LCD Framebuffer at: %08lx\n", size>>10, addr);

    return (addr);
}

        4.  为了简化我们的工作,我们使用linux内核中的头文件,来替换我们重新定义寄存器。

        从linux2.6.28文件包中,提取arch/arm/plat-s3c/include/plat/regs-lcd.h

       开头部分用下面替换

#include <common.h>
#include <regs.h>

/***************************************************************************/
/* LCD Registers for S3C2443/2450/S3C6400/6410 */

#define S3C_LCDREG(x)        __REG((x) + ELFIN_LCD_BASE)

/* LCD control registers */
#define S3C_VIDCON0        S3C_LCDREG(0x00)      /* Video control 0 register */
#define S3C_VIDCON1        S3C_LCDREG(0x04)      /* Video control 1 register */

    增加MIFPCON_REG 和SPCON_REG寄存器的定义

#define MIFPCON_REG                    __REG(0x7410800C)  
  #define  SEL_BYPASS_MASK             0x00000008      
#define SPCON_REG                      __REG(ELFIN_GPIO_BASE + SPCON_OFFSET)
  #define LCD_SEL_MASK                 0x00000003         

  #define RGB_IF_STYLE_MASK            0x00000001  

5.  增加函数lcd_disable,lcd_panel_disable用于进入linux内核的准备工作

void lcd_disable (void)
{
  S3C_VIDCON0 &= (~(S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE));
}

void lcd_Enable (void)
{
  S3C_VIDCON0 |= (S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE);
}


void lcd_panel_disable(void)
{
  MIFPCON_REG |= SEL_BYPASS_MASK;
}

    6.  准备驱动函数

/************************************************************************/
/* ** GENERIC Initialization Routines                    */
/************************************************************************/

int drv_lcd_init (void)
{
    device_t lcddev;
    int rc;

    lcd_base = (void *)(gd->fb_base);
#ifdef LCD_FRAMEBUFFER
        lcd_base = (void*)LCD_FRAMEBUFFER;
#endif
    lcd_line_length = (panel_info.vl_col * panel_info.vl_bpix) / 8;
        lcd_color_fg = 0xffff;
        lcd_color_bg = 0x0000;

    lcd_init (lcd_base);        /* LCD initialization */
        //画线,测试一下显示效果
        lcd_vline(0,0,S3CFB_HRES,0x001f);
        lcd_vline(0,S3CFB_VRES-1,S3CFB_HRES,0x07e0);
        lcd_hline(0,0,S3CFB_VRES,0xf800);
        lcd_hline(S3CFB_HRES-1,0,S3CFB_VRES,0xffff);


#ifdef    CONFIG_NO_VIDEO_CONSOLE        
    return 0;
#endif

    /* Device initialization */
    memset (&lcddev, 0, sizeof (lcddev));

    strcpy (lcddev.name, "lcd");
    lcddev.ext   = 0;            /* No extensions */
    lcddev.flags = DEV_FLAGS_OUTPUT;    /* Output only */
    lcddev.putc  = lcd_putc;        /* 'putc' function */
    lcddev.puts  = lcd_puts;        /* 'puts' function */

    rc = device_register (&lcddev); //如果需要uboot的打印信息使用LCD,需要登记设备。

    return (rc == 0) ? 1 : rc;
}

    7.  准备lcd_init 初始化屏显位置信息,图片等(为简化略过显示图片)

static int lcd_init (void *lcdbase)
{
    /* Initialize the lcd controller */
    debug ("[LCD] Initializing LCD frambuffer at %p\n", lcdbase);

    lcd_ctrl_init (lcdbase);
    
        //lcd_clear (NULL, 1, 1, NULL);    /* dummy args */
    //lcd_enable ();

    /* Initialize the console */
    console_col = 0;
#ifdef CONFIG_LCD_INFO_BELOW_LOGO
    console_row = 7 + BMP_LOGO_HEIGHT / VIDEO_FONT_HEIGHT;
#else
    console_row = 1;    /* leave 1 blank line below logo */
#endif
    lcd_is_enabled = 1;

    return 0;
}

        8 . 寄存器相关的初始化

static int lcd_ctrl_init(void *lcdbase)
{
  ulong freq_lcdclk;
  ulong freq_Hclk;
  ulong fb_size;
  unsigned char nn;
  unsigned short *pp;
  int i;

  //初始化管脚分配
  GPICON_REG = 0xaaaaaaaa;
  GPIPUD_REG = 0xaaaaaaaa;
  GPJCON_REG = 0xaaaaaaaa;
  GPJPUD_REG = 0xaaaaaaaa;

  lcd_disable();
  S3C_WINCON0 &= (~(S3C_WINCONx_ENWIN_F_ENABLE));
 
  按照手册初始化寄存器
  // (1)MOFPCON:SEL_BYPASS[3] value@0x7410800C  必须设置为0
  MIFPCON_REG &= (~SEL_BYPASS_MASK);
  //(2)SPCON:LCD_SEL[1:0]value@0x74F0081A0 选择RGB I/F类型
  SPCON_REG &= (~LCD_SEL_MASK);
  SPCON_REG |= (RGB_IF_STYLE_MASK);
  //(3)VIDCON0:配置视频输出格式 

 freq_lcdclk = S3CFB_PIXEL_CLOCK;
  freq_Hclk = get_HCLK();
  nn = (unsigned char)(freq_Hclk / freq_lcdclk) - 1;
  if(freq_lcdclk < freq_Hclk/2)
  {
    S3C_VIDCON0 = S3C_VIDCON0_INTERLACE_F_PROGRESSIVE + S3C_VIDCON0_VIDOUT_RGB_IF + \
            S3C_VIDCON0_PNRMODE_RGB_P + S3C_VIDCON0_CLKVALUP_ST_FRM + S3C_VIDCON0_CLKVAL_F(nn) + \
            S3C_VIDCON0_CLKDIR_DIVIDED + S3C_VIDCON0_CLKSEL_F_HCLK;
  }else
  {
    S3C_VIDCON0 = S3C_VIDCON0_INTERLACE_F_PROGRESSIVE + S3C_VIDCON0_VIDOUT_RGB_IF + \
            S3C_VIDCON0_PNRMODE_RGB_P + S3C_VIDCON0_CLKVALUP_ST_FRM + S3C_VIDCON0_CLKVAL_F(0) + \
            S3C_VIDCON0_CLKDIR_DIRECTED  + S3C_VIDCON0_CLKSEL_F_HCLK;
  }
  //(4)VIDCON1:RGB I/F 控制信号
  nn = 0;
  if(S3CFB_IVCLK)
  {
    nn += S3C_VIDCON1_IVCLK_RISE_EDGE;
  }
  if(S3CFB_IHSYNC)
  {
    nn += S3C_VIDCON1_IHSYNC_INVERT;
  }
  if(S3CFB_IVSYNC)
  {
    nn += S3C_VIDCON1_IVSYNC_INVERT;
  }
  if(S3CFB_IVDEN)
  {
    nn += S3C_VIDCON1_IVDEN_INVERT;
  }
  S3C_VIDCON1 = (unsigned int)nn;
  S3C_VIDCON2 = 0;
  //(5)I80IFCONx:i80系统I/F信号控制

  //(6)ITUIFCON0:ITU(BT.601)接口控制
  //(7)VIDTCONx:视频输出时序和显示尺寸
  S3C_VIDTCON0 = S3C_VIDTCON0_VBPD(S3CFB_VBP - 1) | S3C_VIDTCON0_VFPD(S3CFB_VFP - 1) | S3C_VIDTCON0_VSPW(S3CFB_VSW - 1);
  S3C_VIDTCON1 = S3C_VIDTCON1_HBPD(S3CFB_HBP - 1) | S3C_VIDTCON1_HFPD(S3CFB_HFP -1) | S3C_VIDTCON1_HSPW(S3CFB_HSW - 1);
  S3C_VIDTCON2 = S3C_VIDTCON2_LINEVAL(S3CFB_VRES - 1) | S3C_VIDTCON2_HOZVAL(S3CFB_HRES - 1);
  //(8)WINCONx:窗口格式控制,为简化只使用窗口0,不使用双缓冲

  S3C_WINCON0 = S3C_WINCONx_BPPMODE_F_16BPP_565;
  S3C_WINCON1 = 0;
  S3C_WINCON2 = 0;
  S3C_WINCON3 = 0;
  S3C_WINCON4 = 0;

  //(9)VIDOSDxA ,VIDOSDxB:窗口位置控制
  S3C_VIDOSD0A = S3C_VIDOSDxA_OSD_LTX_F(0) + S3C_VIDOSDxA_OSD_LTY_F(0);
  S3C_VIDOSD0B = S3C_VIDOSDxB_OSD_RBX_F(S3CFB_HRES - 1) | S3C_VIDOSDxB_OSD_RBY_F(S3CFB_VRES - 1);
  S3C_VIDOSD0C = S3C_VIDOSD0C_OSDSIZE(S3CFB_HRES*S3CFB_VRES);
 
  S3C_VIDOSD1A = 0;
  S3C_VIDOSD1B = 0;
  S3C_VIDOSD1C = 0;
  S3C_VIDOSD1D = 0;
  S3C_VIDOSD2A = 0;
  S3C_VIDOSD2B = 0;
  S3C_VIDOSD2C = 0;
  S3C_VIDOSD2D = 0;
  S3C_VIDOSD3A = 0;
  S3C_VIDOSD3B = 0;
  S3C_VIDOSD3C = 0;
  S3C_VIDOSD4A = 0;
  S3C_VIDOSD4B = 0;
  S3C_VIDOSD4C = 0;

  //(10)VIDOSDxC:alpha 值设置

  //(11)VIDWxxADDx:源图像地址设置

  fb_size = calc_fbsize();
#ifdef LCD_FRAMEBUFFER
  fb_size =   (panel_info.vl_col * panel_info.vl_bpix) / 8 * panel_info.vl_row;
#endif
 // S3C_VIDW00ADD0B0 = (unsigned int)(lcdbase) | S3C_VIDWxxADD0_VBANK_F((unsigned int)lcdbase);
  S3C_VIDW00ADD0B0 = virt_to_phys(lcdbase);
  S3C_VIDW00ADD0B1 = 0;
  S3C_VIDW01ADD0B0 = 0;
  S3C_VIDW01ADD0B1 = 0;
  S3C_VIDW02ADD0 = 0;
  S3C_VIDW03ADD0 = 0;
  S3C_VIDW04ADD0 = 0;

 // S3C_VIDW00ADD1B0 = S3C_VIDWxxADD1_VBASEL_F((unsigned int)(lcdbase) + fb_size);
  S3C_VIDW00ADD1B0 = virt_to_phys((unsigned int)(lcdbase) + fb_size);
  S3C_VIDW00ADD1B1 = 0;
  S3C_VIDW01ADD1B0 = 0;
  S3C_VIDW01ADD1B1 = 0;
  S3C_VIDW02ADD1 = 0;
  S3C_VIDW03ADD1 = 0;
  S3C_VIDW04ADD1 = 0;
 
  S3C_VIDW00ADD2 = S3C_VIDWxxADD2_OFFSIZE_F(0) | (S3C_VIDWxxADD2_PAGEWIDTH_F(panel_info.vl_col*panel_info.vl_bpix/8));
  S3C_VIDW01ADD2 = 0;
  S3C_VIDW02ADD2 = 0;
  S3C_VIDW03ADD2 = 0;
  S3C_VIDW04ADD2 = 0;
  //(12)WxKEYCONx:色键寄存器
  //(13)WINxMAP:窗口颜色控制
  //(14)WPALCON:调色板控制
  //(15)WxPDATAxx:调色板数据
#if 1
  memset(lcdbase,0x55,fb_size);//增加一个底色
#else
  pp = lcdbase;
  for(i=0;i< S3CFB_HRES * S3CFB_VRES;i++)
  {
    *pp = 0xf100;
    pp++;
  }
#endif
  lcd_Enable();
  S3C_WINCON0 |= S3C_WINCONx_ENWIN_F_ENABLE;
  return (0);
}

    这样屏上应该有显示了,下面仔细根据屏的类型调整宏机构就可以了。(完)