• Linux设备驱动开发


    一、S3C6410 LCD驱动裸机代码
    LCD控制器初始化:

     1 unsigned long VideoBuffer[LCD_LOW][LCD_COL] = {0};
     2 void lcd_init(void)
     3 {
     4     /* 1.初始化IO端口为LCD端口 */
     5     /* GPIO configure */
     6     GPICON = 0xAAAAAAAA;
     7     GPJCON = 0x00AAAAAA;
     8     
     9     /* 2.使能LCD时钟 */
    10     //HCLK_GATE |= (1 << 3);    //默认打开
    11     
    12     MIFPCON &=~(1 << 3);
    13     
    14     /* 3.设置I/F类型 */
    15     SPCON &=~(3 << 0);
    16     SPCON |= (1 << 0);
    17     
    18     VIDCON0 = (CLK_DIV << 6) | (1 << 4) | (1 << 1) | (1 << 0);
    19     VIDCON1 |= (1 << 6) | (1 << 5);
    20     
    21     /* 4.LCD控制器相关寄存器配置 */
    22     VIDTCON0 = (VBPD << 16) | (VFPD << 8) | (VSPW << 0);
    23     VIDTCON1 = (HBPD << 16) | (HFPD << 8) | (HSPW << 0);
    24     VIDTCON2 = (LINEVAL << 11) | (HOZVAL << 0);
    25 
    26     WINCON0 |= (0xB << 2) | (1 << 0);
    27     VIDOSD0A = (0 << 11) | (0 << 0);
    28     VIDOSD0B = (HOZVAL << 11) | (LINEVAL << 0);
    29     VIDOSD0C = LCD_LOW * LCD_COL;
    30     
    31     VIDW00ADD0B0 = ((unsigned long)VideoBuffer);
    32     VIDW00ADD1B0 = ((unsigned long)VideoBuffer + LCD_LOW * LCD_COL * 4) & 0xFFFFFF;
    33 }

    LCD控制器驱动程序有几个重要步骤:
    1、初始化IO端口为LCD端口,通过GPICON和GPJCON将相应的为设置成LCD VD[n]模式。

    2、使能LCD时钟,HCLK_GATE寄存器默认是打开的,所以不需要设置。

    3、设置I/F类型,根据芯片手册上的描述,需要设置SPCON寄存器把I/F模式设置成RGB I/F style。

    4、LCD控制器相关寄存器配置,这一步是根据LCD规格书设置时序相关的时间参数,包括水平时间参数和垂直时间参数,下面是我所使用的LCD时间参数:

    根据这张图可以确定VSPW的值即为VS pulse width的值,这里取个中间值10。
    VFPD的值即为VS Front Porch的值,取典型值22。
    VBPD的值没有明确给出,但是通过下图可以知道VBPD = tvb - tvpw,从上图中可以知道tvb就是VS Blanking等于23,tvpw为VSPW = 10,所以可以确定VBPD = 23 - 10。

    水平方向这三个方向的分析和垂直方向是一样的,最终得出的数据如下:

    1 #define VBPD    (23 - 10 - 1)    //tvb - tvpw = tvb - VSPW
    2 #define VFPD    (22 - 1)    
    3 #define VSPW    (10 - 1)
    4 #define HBPD    (46 - 20 - 1)    //thb - thpw = thb - HSPW
    5 #define HFPD    (210 - 1)
    6 #define HSPW    (20 - 1)
    7 #define LINEVAL    (480 - 1)
    8 #define HOZVAL    (800 - 1)

    LINEVAL为屏幕垂直方向的像素,HOZVAL为屏幕水平方向的像素。在芯片手册上可以看到下面的描述,所以上面的参数都要进行减一操作。

    最后VIDW00ADD0B0和VIDW00ADD1B0寄存器指定了LCD缓冲区。


    二、Linux内核LCD设备驱动
    Linux为LCD显示设备提供了一个帧缓冲接口,所谓的帧缓冲其实就是想裸机代码一样在内存中分配一段空间,这段空间就描述了整个屏幕像素点的信息,往缓冲区中与现实点对应的位置写入颜色值,对应的颜色就会在LCD上显示。帧缓冲设备实质上是一个字符设备,主设备号29,对应于/dev/fbn设备文件。
    上面简单概括了LCD控制器裸机程序相关寄存器的操作,下面看看Linux内核里面是怎样实现LCD设备驱动的。
    Linux内核LCD设备驱动程序在s3cfb.c文件里面,从s3cfb.c里面的module_init()函数入手,找到平台驱动结构体里面的s3cfb_probe()函数,在之前的博客中已经提到,分析平台设备驱动首先要分析的函数就是probe函数。进入s3cfb_probe()函数中,找到了一个s3cfb_init_fbinfo()函数,这个函数又调用了s3cfb_init_hw()函数,在s3cfb_init_hw()函数中调用了s3cfb_set_fimd_info()函数和s3cfb_set_gpio()函数,s3cfb_set_fimd_info()函数中初始化了一个s3cfb_fimd_info_t结构体,里面存储的数据就包括上面裸机程序里面时序相关的时间参数,这个结构体里面包含着所有的LCD控制器相关的参数。s3cfb_set_gpio()函数部分代码如下:

     1 int s3cfb_set_gpio(void)
     2 {
     3     ...
     4     
     5     /* 2.使能LCD时钟 */
     6     /* enable clock to LCD */
     7     val = readl(S3C_HCLK_GATE);
     8     val |= S3C_CLKCON_HCLK_LCD;
     9     writel(val, S3C_HCLK_GATE);
    10 
    11     /* 3.设置I/F类型 */
    12     /* select TFT LCD type (RGB I/F) */
    13     val = readl(S3C64XX_SPCON);
    14     val &= ~0x3;
    15     val |= (1 << 0);
    16     writel(val, S3C64XX_SPCON);
    17 
    18     /* 1.初始化IO端口为LCD端口 */
    19     /* VD */
    20     for (i = 0; i < 16; i++)
    21         s3c_gpio_cfgpin(S3C64XX_GPI(i), S3C_GPIO_SFN(2));
    22 
    23     for (i = 0; i < 12; i++)
    24         s3c_gpio_cfgpin(S3C64XX_GPJ(i), S3C_GPIO_SFN(2));
    25 
    26     ...
    27 
    28     return 0;
    29 }

    在s3cfb_set_gpio()函数中实现了LCD控制器裸机操作中的第一、第二和第三个步骤。
    退回到s3cfb_probe()函数中,调用了s3cfb_map_video_memory()函数,在这个函数中调用了dma_alloc_writecombine()函数完成帧缓冲设备显示缓冲区的分配。为什么是dma_alloc_writecombine()函数?因为系统对数据的搬移采用的是DMA方式。
    再退回到s3cfb_probe()函数,调用了s3cfb_init_registers()函数:

     1 /* 4.LCD控制器相关寄存器配置 */
     2 int s3cfb_init_registers(s3cfb_info_t *fbi)
     3 {
     4     ...
     5 
     6     if (win_num == 0) {
     7         s3cfb_fimd.vidcon0 = s3cfb_fimd.vidcon0 & ~(S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE);
     8         writel(s3cfb_fimd.vidcon0, S3C_VIDCON0);
     9 
    10         lcd_clock = clk_get(NULL, "lcd");
    11         s3cfb_fimd.vidcon0 |= S3C_VIDCON0_CLKVAL_F((int) (s3cfb_fimd.pixclock));
    12     ...
    13      }
    14 
    15     writel(video_phy_temp_f1, S3C_VIDW00ADD0B0 + (0x08 * win_num));
    16     writel(S3C_VIDWxxADD1_VBASEL_F((unsigned long) video_phy_temp_f1 + (page_width + offset) * (var->yres)), S3C_VIDW00ADD1B0 + (0x08 * win_num));
    17     writel(S3C_VIDWxxADD2_OFFSIZE_F(offset) | (S3C_VIDWxxADD2_PAGEWIDTH_F(page_width)), S3C_VIDW00ADD2 + (0x04 * win_num));
    18 
    19     if (win_num < 2) {
    20         writel(video_phy_temp_f2, S3C_VIDW00ADD0B1 + (0x08 * win_num));
    21         writel(S3C_VIDWxxADD1_VBASEL_F((unsigned long) video_phy_temp_f2 + (page_width + offset) * (var->yres)), S3C_VIDW00ADD1B1 + (0x08 * win_num));
    22     }
    23 
    24     switch (win_num) {
    25     case 0:
    26         writel(s3cfb_fimd.wincon0, S3C_WINCON0);
    27         writel(s3cfb_fimd.vidcon0, S3C_VIDCON0);
    28         writel(s3cfb_fimd.vidcon1, S3C_VIDCON1);
    29         writel(s3cfb_fimd.vidtcon0, S3C_VIDTCON0);
    30         writel(s3cfb_fimd.vidtcon1, S3C_VIDTCON1);
    31         writel(s3cfb_fimd.vidtcon2, S3C_VIDTCON2);
    32         writel(s3cfb_fimd.dithmode, S3C_DITHMODE);
    33         writel(s3cfb_fimd.vidintcon0, S3C_VIDINTCON0);
    34         writel(s3cfb_fimd.vidintcon1, S3C_VIDINTCON1);
    35         writel(s3cfb_fimd.vidosd0a, S3C_VIDOSD0A);
    36         writel(s3cfb_fimd.vidosd0b, S3C_VIDOSD0B);
    37         writel(s3cfb_fimd.vidosd0c, S3C_VIDOSD0C);
    38         writel(s3cfb_fimd.wpalcon, S3C_WPALCON);
    39 
    40         s3cfb_onoff_win(fbi, ON);
    41         break;
    42     ...
    43     }
    44 
    45     local_irq_restore(flags);
    46 
    47     return 0;
    48  }

    在s3cfb_init_registers()函数里面进行了裸机操作的第四步操作,s3cfb_fimd结构体里面的数据就来源于上面所说的s3cfb_set_fimd_info()函数。
    最后调用了register_framebuffer()函数注册了一个帧缓冲设备,剩下的事情就交给内核来完成了。

  • 相关阅读:
    数据结构实现时的注意事项
    用编程解决生活中的问题
    用编程解决生活中的问题
    中英文对照 —— 生物学基本概念
    中英文对照 —— 生物学基本概念
    面向对象 —— 对类(class)的理解
    面向对象 —— 对类(class)的理解
    百家姓 —— 特别的姓氏与姓氏的由来
    百家姓 —— 特别的姓氏与姓氏的由来
    英文段子
  • 原文地址:https://www.cnblogs.com/ape-ming/p/5125070.html
Copyright © 2020-2023  润新知