程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux驱动移植-LCD设备驱动

由于我使用的Mini2440开发板采用的LCD为TFT屏,型号为LCD-T35(TD035STEB4)。这一节,我们将参考s3c2410fb.c编写LCD驱动程序。

一、LCD驱动编写基础函数

1.1 dma_alloc_wc

该函数定义在include/linux/dma-mapping.h:

static inline void *dma_alloc_wc(struct device *dev, size_t size,
                                 dma_addr_t *dma_addr, gfp_t gfp)

该函数用于申请一段DMA缓冲区,分配出来的内存会禁止cache缓存(因为DMA传输不需要CPU)。

返回值为:申请到的DMA缓冲区的虚拟地址,若为NULL,表示分配失败,则需要使用dma_free_wc释放内存,避免内存泄漏。

参数如下: 

  • dev:设备指针;
  • size:分配的地址大小(字节单位);
  • dma_addr:申请到的物理起始地址;
  • gfp:分配出来的内存参数,标志定义在<linux/gfp.h>,常用标志如下:
    • GFP_ATOMIC 用来从中断处理和进程上下文之外的其他代码中分配内存. 从不睡眠;
    • GFP_KERNEL 内核内存的正常分配. 可能睡眠;
    • GFP_USER 用来为用户空间页来分配内存; 它可能睡眠.;

1.2 dma_free_wc

static inline void dma_free_wc(struct device *dev, size_t size,
                               void *cpu_addr, dma_addr_t dma_addr)

该函数用于释放DMA缓冲区,参数和dma_alloc_wc一样。

1.3 framebuffer_alloc

struct fb_info *framebuffer_alloc(size_t size, struct device *dev)

动态申请一个fb_info结构体,参数如下:

  • size:额外的内存大小;
  • dev:设备指针,用于初始化fb_info->device成员;

二、LCD驱动编写步骤

2.1 入口函数

在驱动入口函数中实现:

(1) 动态分配fb_info结构体;

(2) 设置fb_info

(2.1).设置固定的参数fb_info->fix;

(2.2) 设置可变的参数fb_info->var;

(2.3) 设置操作函数fb_info->fbops;

(2.4) 设置fb_info其它的成员;

(3) 设置硬件相关的操作

(3.1) 配置LCD引脚:设置GPIO端口C和GPIO端口D用于LCD;

(3.2) 根据LCD手册设置LCD控制器时序参数;

(3.3) 分配显存(framebuffer),把显存地址告诉LCD控制器和fb_info;

(4) 开启LCD,并注册fb_info

(4.1) 开启LCD

    • 控制LCDCON5允许PWREN信号,开启背光;
    • 控制LCDCON1使能LCD;

(4.2) 注册fb_info;

2.2 出口函数

在驱动出口函数中实现:

(1) 卸载内核中的fb_info;

(2) 控制LCDCON1关闭PWREN信号,关背光,iounmap注销地址;

(3) 释放DMA缓存地址dma_free_wc;

(4) 释放注册的fb_info;

三、LCD驱动编写 

首先在/work./sambashare/drivers创建项目文件夹11.lcd_dev,然后我们在11.lcd_dev下创建lcd_dev.c文件。首先我们引入头文件,并定义一些常亮和全局变量等:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>

/*  LCD T35参数设定 */
#define     LCD_WIDTH   240             /* LCD面板的行宽 */
#define     LCD_HEIGHT  320             /* LCD面板的列宽 */

#define     VSPW        1                /* 通过计算无效行数垂直同步脉冲宽度决定VSYNC脉冲的高电平宽度 */
#define     VBPD        1                /* 垂直同步周期后的无效行数 */
#define     LINEVAL     (LCD_HEIGHT-1)  /* LCD的垂直宽度-1 */
#define     VFPD        1                /* 垂直同步周期前的的无效行数 */

#define     CLKVAL      7              /* VCLK = HCLK / [(CLKVAL  + 1)  × 2] */

#define     HSPW        9                /* 通过计算VCLK的数水平同步脉冲宽度决定HSYNC脉冲的高电平宽度 */
#define     HBPD        19               /* 描述水平后沿为HSYNC的下降沿与有效数据的开始之间的VCLK周期数 */
#define     HOZVAL      (LCD_WIDTH-1)    /* LCD的水平宽度-1 */
#define     HFPD        9                /* 水平后沿为有效数据的结束与HSYNC的上升沿之间的VCLK周期数 */

/* 定义fb_info */
static struct fb_info *s3c_lcd;
/* 调色板数组,被fb_info->pseudo_palette调用 */
static u32 pseudo_palette[16];
/* 时钟 */
static struct clk *lcd_clk;

/* GPIO相关寄存器 */
static volatile unsigned long *gpcup;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdup;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile unsigned long *gpgdat;
static volatile unsigned long *gpgup;

/* lcd相关寄存器 */
struct lcd_regs{
    unsigned long   lcdcon1;
    unsigned long    lcdcon2;
    unsigned long    lcdcon3;
    unsigned long    lcdcon4;
    unsigned long    lcdcon5;
    unsigned long    lcdsaddr1;
    unsigned long    lcdsaddr2;
    unsigned long    lcdsaddr3;
    unsigned long    redlut;
    unsigned long    greenlut;
    unsigned long    bluelut;
    unsigned long    reserved[9];
    unsigned long    dithmode;
    unsigned long    tpal;
    unsigned long    lcdintpnd;
    unsigned long    lcdsrcpnd;
    unsigned long    lcdintmsk;
    unsigned long    lpcsel;
};

static volatile struct lcd_regs* lcd_regs;

3.1 分配一个fb_info结构

   /* 1. 初始化fb_info */
   s3c_lcd = framebuffer_alloc(0,0);
   if (!s3c_lcd)
       return -ENOMEM;

3.2 设置fb_info(设置固定的参数fb_info->fix)

首先设置fb_info->fix,成员变量fix类型为struct fb_fix_screeninfo,用于保存LCD屏幕的相关参数,定义在include/uapi/linux/fb.h中:

struct fb_fix_screeninfo {
        char id[16];                    /* identification string eg "TT Builtin" */
        unsigned long smem_start;       /* Start of frame buffer mem */
                                        /* (physical address) */
        __u32 smem_len;                 /* Length of frame buffer mem */
        __u32 type;                     /* see FB_TYPE_*                */
        __u32 type_aux;                 /* Interleave for interleaved Planes */
        __u32 visual;                   /* see FB_VISUAL_*              */
        __u16 xpanstep;                 /* zero if no hardware panning  */
        __u16 ypanstep;                 /* zero if no hardware panning  */
        __u16 ywrapstep;                /* zero if no hardware ywrap    */
        __u32 line_length;              /* length of a line in bytes    */
        unsigned long mmio_start;       /* Start of Memory Mapped I/O   */
                                        /* (physical address) */
        __u32 mmio_len;                 /* Length of Memory Mapped I/O  */
        __u32 accel;                    /* Indicate to driver which     */
                                        /*  specific chip/card we have  */
        __u16 capabilities;             /* see FB_CAP_*                 */
        __u16 reserved[2];              /* Reserved for future compatibility */
};

其中参数含义如下:

  • id:唯一标识符;
  • smem_start:framebuffer缓冲区物理起始位置(一般是显示控制器DMA起始地址);
  • smem_len:framebuffer缓冲区的的长度,单位为字节;
  • type:lcd类型,默认FB_TYPE_PACKED_PIXELS即可,可选参数如下;
#define FB_TYPE_PACKED_PIXELS           0       /* Packed Pixels        */
#define FB_TYPE_PLANES                  1       /* Non interleaved planes */
#define FB_TYPE_INTERLEAVED_PLANES      2       /* Interleaved planes   */
#define FB_TYPE_TEXT                    3       /* Text/attributes      */
#define FB_TYPE_VGA_PLANES              4       /* EGA/VGA planes       */
#define FB_TYPE_FOURCC                  5       /* Type identified by a V4L2 FOURCC */
  • type_aux:附加类型,默认0即可;
  • visual:颜色设置,常用参数如下:
#define FB_VISUAL_MONO01                0       /* Monochr. 1=Black 0=White  单侧 0白色 1黑色 */
#define FB_VISUAL_MONO10                1       /* Monochr. 1=White 0=Black  单色 0黑色 1白色 */
#define FB_VISUAL_TRUECOLOR             2       /* True color   真彩 */
#define FB_VISUAL_PSEUDOCOLOR           3       /* Pseudo color (like atari) 伪彩 */
#define FB_VISUAL_DIRECTCOLOR           4       /* Direct color 直彩 */
#define FB_VISUAL_STATIC_PSEUDOCOLOR    5       /* Pseudo color readonly 只读伪彩 */
#define FB_VISUAL_FOURCC                6       /* Visual identified by a V4L2 FOURCC */
  • xpanstep:如果没有硬件panning赋值0;
  • ypanstep:如果没有硬件panning赋值0;
  • ywrapstep:若果没有硬件ywarp赋值0;
  • line_length:一行所占的字节数,例:(RGB565)240*320,那么这里就等于240*16/8;
  • mmio_start:内存映射IO的起始地址,用于应用层直接访问寄存器,可以不需要;
  • mmio_len:内存映射IO的长度,可以不需要;
  • accel:指明使用的芯片,用于硬件加速,默认FB_ACCEL_NONE即可,可选参数如下;:
#define FB_ACCEL_NONE           0       /* no hardware accelerator      */
#define FB_ACCEL_ATARIBLITT     1       /* Atari Blitter                */
#define FB_ACCEL_AMIGABLITT     2       /* Amiga Blitter                */
#define FB_ACCEL_S3_TRIO64      3       /* Cybervision64 (S3 Trio64)    */
#define FB_ACCEL_NCR_77C32BLT   4       /* RetinaZ3 (NCR 77C32BLT)      */
......
  • capabilities:查看FB_CAP_;
  • reserved:为将来的兼容保留位;

设置:

   /* 2. 设置fb_info */
   /* 2.1设置固定参数 */
    strcpy(s3c_lcd->fix.id, "mylcd");
    s3c_lcd->fix.smem_len        = LCD_WIDTH * LCD_HEIGHT * 2;   // framebuffer缓冲区的大小 每个像素两个字节
    s3c_lcd->fix.type            = FB_TYPE_PACKED_PIXELS;
    s3c_lcd->fix.type_aux        = 0;
    s3c_lcd->fix.xpanstep        = 0;
    s3c_lcd->fix.ypanstep        = 0;
    s3c_lcd->fix.ywrapstep       = 0;
    s3c_lcd->fix.accel           = FB_ACCEL_NONE;
    s3c_lcd->fix.visual          = FB_VISUAL_TRUECOLOR;      //真彩色
    s3c_lcd->fix.line_length     = LCD_WIDTH * 2;             // LCD一行所占字节数

3.3 设置fb_info(设置可变的参数fb_info->var)

然后设置fb_info->var,成员变量var类型为struct fb_var_screeninfo,用于保存LCD屏幕的相关参数,定义在include/uapi/linux/fb.h中:

struct fb_var_screeninfo {
        __u32 xres;                     /* visible resolution           */
        __u32 yres;
        __u32 xres_virtual;             /* virtual resolution           */
        __u32 yres_virtual;
        __u32 xoffset;                  /* offset from virtual to visible */
        __u32 yoffset;                  /* resolution                   */

        __u32 bits_per_pixel;           /* guess what                   */
        __u32 grayscale;                /* 0 = color, 1 = grayscale,    */
                                        /* >1 = FOURCC                  */
        struct fb_bitfield red;         /* bitfield in fb mem if true color, */
        struct fb_bitfield green;       /* else only length is significant */
        struct fb_bitfield blue;
        struct fb_bitfield transp;      /* transparency                 */

        __u32 nonstd;                   /* != 0 Non standard pixel format */

        __u32 activate;                 /* see FB_ACTIVATE_*            */

        __u32 height;                   /* height of picture in mm    */
        __u32 width;                    /* width of picture in mm     */

        __u32 accel_flags;              /* (OBSOLETE) see fb_info.flags */

        /* Timing: All values in pixclocks, except pixclock (of course) */
        __u32 pixclock;                 /* pixel clock in ps (pico seconds) */
        __u32 left_margin;              /* time from sync to picture    */
        __u32 right_margin;             /* time from picture to sync    */
        __u32 upper_margin;             /* time from sync to picture    */
        __u32 lower_margin;
        __u32 hsync_len;                /* length of horizontal sync    */
        __u32 vsync_len;                /* length of vertical sync      */
        __u32 sync;                     /* see FB_SYNC_*                */
        __u32 vmode;                    /* see FB_VMODE_*               */
        __u32 rotate;                   /* angle we rotate counter clockwise */
        __u32 colorspace;               /* colorspace for FOURCC-based modes */
        __u32 reserved[4];              /* Reserved for future compatibility */
};

其中参数含义如下:

  • xres:行分辨率
  • yres:列分辨率
  • xres_virtual:行虚拟分辨率,设置和硬件一样即可;
  • yres_virtual:列虚拟分辨率,设置和硬件一样即可;
  • xoffset:行偏移,设置为0即可;
  • yoffset:列偏移,设置为0即可;
  • bits_per_pixel:每个像素用多少位,对于s3c2440不支持18位,只支持16位;
  • grayscale:灰度值,默认即可;
  • red:RGB:565对于R的offset为从最低位(右起)偏移11位,占5bit;
  • green:RGB:565对于G的offset为从最低位(右起)偏移5位,占6bit;
  • blue:RGB:565对于B的offset为从最低位(右起)偏移0位,占5bit;
  • transp:透明度,默认即可;
  • nonstd:0标准像素格式,默认即可;
  • activate:默认即可;
  • height:LCD物理高度,单位mm;
  • width:LCD物理宽度,单位mm;
  • accel_flags:默认即可,过时参数;
  • pixclock:像素时钟,单位皮秒;
  • left_margin:行切换,从同步到绘图之间的延迟;
  • right_margin:行切换,从绘图到同步之间的延迟;
  • upper_margin:帧切换,从同步到绘图之间的延迟;
  • lower_margin:帧切换,从绘图到同步之间的延迟;
  • hsync_len:水平同步的长度;
  • vsync_len:垂直同步的长度;
  • sync:参考FB_SYNC_*;
  • vmode:参考FB_BMODE_*,默认即可;
  • rotate::时针旋转的角度;
  • colorspace:色彩空间;
  • reserved:保留值;

 设置:

    /* 2.2 设置可变参数 */
     s3c_lcd->var.xres            = LCD_WIDTH;         // 行分辨率
     s3c_lcd->var.yres            = LCD_HEIGHT;        // 列分辨率
     s3c_lcd->var.xres_virtual    = LCD_WIDTH;         // 行虚拟分辨率
     s3c_lcd->var.yres_virtual    = LCD_HEIGHT;        // 列虚拟分辨率
     s3c_lcd->var.xoffset         = 0;                 //虚拟到可见屏幕之间的行偏移
     s3c_lcd->var.yoffset         = 0;                 //虚拟到可见屏幕之间的行偏移
     s3c_lcd->var.bits_per_pixel  = 16;                // 每像素使用位数

     /* RGB:565 */
     s3c_lcd->var.red.offset      = 11;
     s3c_lcd->var.red.length      = 5;
     s3c_lcd->var.green.offset    = 5;
     s3c_lcd->var.green.length    = 6;
     s3c_lcd->var.blue.offset     = 0;
     s3c_lcd->var.blue.length     = 5;

     s3c_lcd->var.nonstd          = 0;
     s3c_lcd->var.activate        = FB_ACTIVATE_NOW;   // 使设置的值立即生效
     s3c_lcd->var.accel_flags     = 0;
     s3c_lcd->var.vmode           = FB_VMODE_NONINTERLACED;

3.4 设置fb_info(设置操作函数fb_info->fbops)

/* 调色板数组,被fb_info->pseudo_palette调用 */
static u32 pseudo_palette[16];

/*
 * 将内核单色使用bf表示
 * @param chan:单色 内核中的单色都是16位
 * @param bf:颜色位信息
 */
static inline unsigned int chan_to_field(unsigned int chan,
                     struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}

/*
 * 调色板操作函数
 *  @param regno: 调色板数组元素索引
 */
static int s3c2440fb_setcolreg(unsigned regno,
                   unsigned red, unsigned green, unsigned blue,
                   unsigned transp, struct fb_info *info)
{
    unsigned int val;
    //调色板数组不能大于16
    if (regno > 16)
         return 1;

    /* 小于16位,进行转换 */
    u32 *pal = info->pseudo_palette;

    /* 用red,green,blue三个颜色值构造出16色数据val */
    val  = chan_to_field(red,   &info->var.red);
    val |= chan_to_field(green, &info->var.green);
    val |= chan_to_field(blue,  &info->var.blue);

    /* 放到调色板数组中 */
    pseudo_palette[regno] = val;
    return 0;
}


/*
 * fb_info操作函数fbops
 */
static struct fb_ops s3c2440fb_ops = {
    .owner        = THIS_MODULE,
    .fb_setcolreg    = s3c2440fb_setcolreg,   // 调色板操作函数 设置调色板fb_info-> pseudo_palette
    .fb_fillrect    = cfb_fillrect,          // 填充矩形  函数在drivers/video/fbdev/core/cfbfillrect.c中定义
    .fb_copyarea    = cfb_copyarea,          // 复制数据  函数在drivers/video/fbdev/core/cfbcopyarea.c中定义
    .fb_imageblit    = cfb_imageblit,         // 绘制图形  函数在drivers/video/fbdev/core/cfbimgblt.c中定义
};

3.5 设置fb_info其它的成员

     /* 2.3 设置操作函数 */
     s3c_lcd->fbops               = &s3c2440fb_ops;

     /* 2.4 其他设置 */
     s3c_lcd->flags               = FBINFO_FLAG_DEFAULT;
     s3c_lcd->pseudo_palette      = pseudo_palette;             // 保存调色板数组
     s3c_lcd->screen_size         = LCD_WIDTH * LCD_HEIGHT * 2;    // framebuffer缓冲区的大小

    // 时钟相关,获取lcd时钟
    lcd_clk = clk_get(NULL, "lcd");
    if (IS_ERR(lcd_clk)) {
        printk("failed to get lcd clock source\n");
        return PTR_ERR(lcd_clk);
    }

    clk_prepare_enable(lcd_clk);       // 时钟使能
    printk("got and enabled clock\n");

3.6 设置硬件相关的操作(配置GPIO用于LCD)

参考之前介绍的Mini2440裸机开发之LCD编程(GB2312、ASCII字库制作)初始化GPIO,主要是GPCCON、GPDCON寄存器:

    /* 3.硬件相关操作 */
    /* 3.1 配置GPIO口用于LCD */
    gpcup = ioremap(0x56000028,4);
    gpccon = ioremap(0x56000020,4);
    gpdup = ioremap(0x56000038,4);
    gpdcon = ioremap(0x56000030,4);
    gpgcon = ioremap(0x56000060,12);
    gpgdat = gpgcon + 1;
    gpgup = gpgdat + 1;

    // GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND
    *gpcup = 0xffffffff;
    *gpccon = 0xaaaaaaaa;
    // GPIO管脚用于VD[23:8]
    *gpdup = 0xffffffff;
    *gpdcon = 0xaaaaaaaa;

    /* Pull - up disable */
    *gpgup |= (1 << 4);
    // 设置GPG4引脚为LCD_PWREN模式
    *gpgcon |= (3 << 8);

3.7 设置硬件相关的操作(根据LCD手册设置LCD控制器时许参数)

/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
    lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
    printk("lcd_regs=%px map_size %u\n", lcd_regs, sizeof(struct lcd_regs));

    /*  [17:8]  CLKVAL
     *  [6:5]   PNRMODE;选择显示模式
     *                  00 = 4 位双扫描显示模式   01 = 4 位单扫描显示模式(STN)
     *                  10 = 8 位单扫描显示模式   11 = TFT LCD 面板
     *  [4:1]  BPPMODE  选择BPP(位每像素)模式    1100 = TFT 的16 BPP
     *  [0]    ENVID    LCD 视频输出和逻辑使能/禁止。
     *                  0 = 禁止视频输出和LCD 控制信号  1 = 允许视频输出和LCD 控制信号
     */
    lcd_regs->lcdcon1   = (CLKVAL<<8)| (3<<5) | (0xC<<1);              /* 16 bpp for TFT */

    /*  [31:24]   VBPD:帧同步信号的后肩
     *  [23:14]   LINEVAL:LCD面板的垂直尺寸
     *  [13:6]    VFPD:帧同步信号的前肩
     *  [5:0]     VSPW:同步信号的脉宽
     */
    lcd_regs->lcdcon2 = (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW);

    /* [25:19] HBPD: 行同步信号的后肩
     * [18:8] HOZVAL: LCD面板的水平尺寸
     * [7:0] HFPD: 行同步信号的前肩
     */
    lcd_regs->lcdcon3 = (HBPD<<19)|(HOZVAL<<8)|(HFPD);
    lcd_regs->lcdcon4 = (HSPW);

    /* [11] FRM565: 此位选择16 BPP 输出视频数据的格式   0 = 5:5:5:1 格式   1= 5:6:5 格式
     * [10] STN/TFT: 此位控制VCLK 有效沿的极性
     * [9]    INVVLINE: STN/TFT:此位表明VLINE/HSYNC 脉冲极性  0 = 正常 1 = 反转
     * [8]  INVVFRAME: STN/TFT:此位表明VFRAME/VSYNC 脉冲极性 0 = 正常 1 = 反转
     * VLINE/HSYNC 脉冲极性、VFRAME/VSYNC 脉冲极性反转(LCD型号决定)
     * [0]    HWSWP: STN/TFT:半字节交换控制位   0 = 交换禁止 1 = 交换使能
     */
    lcd_regs->lcdcon5 = ((1<<11) | (1<<10) | (1 << 9) | (1 << 8) | (1 << 0));

     /*  关闭PWREN信号输出 */
     lcd_regs->lcdcon1 &= ~(1<<0);
     /* 禁止PWREN信号 */
     lcd_regs->lcdcon5  &=~(1<<3);

     /* 第一位设置为1 选择输出分片率类型0:320 * 240  1:240*320 */
     lcd_regs->lpcsel = ((0xCE6) & ~7) | 1<<1;

3.8 设置硬件相关的操作(分配显存)

    /* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
    ret = s3c2440fb_map_video_memory(s3c_lcd);
    if (ret) {
        printk("failed to allocate video RAM: %d\n", ret);
        // todo 这里应该进行资源释放,我这里就不释放了
        return -ENOMEM;
    }

    /* [29:21] LCDBANK:存放帧缓冲起始地址的[30:22]
     * [20:0] LCDBASEU: 存放帧缓冲起始地址的[21:1]
     */
    lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);

    /* 存放帧结束地址[21:1] */
    lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;

    /* [21:11] OFFSIZE:表示虚拟屏偏移尺寸  即上一行最后像素点到下一行第一个像素点之间间隔多少个像素点
    *  [10:0] PAGEWIDTH:表示行的宽度(半字为单位 16bit)
    */
    lcd_regs->lcdsaddr3 = LCD_WIDTH & 0x3ff;

其中s3c2440fb_map_video_memory:

#define samsung_device_dma_mask (*((u64[]) { DMA_BIT_MASK(32) }))

/*platform设备 */
static struct platform_device s3c_device_lcd = {
        .name           = "s3c2440-lcd",
        .id             = -1,
        .num_resources  = 0,
        .dev            = {
                .dma_mask               = &samsung_device_dma_mask,
                .coherent_dma_mask      = DMA_BIT_MASK(32),
        }
};

/*
 * s3c2440fb_map_video_memory():
 *      Allocates the DRAM memory for the frame buffer.  This buffer is
 *      remapped into a non-cached, non-buffered, memory region to
 *      allow palette and pixel writes to occur without flushing the
 *      cache.  Once this area is remapped, all virtual memory
 *      access to the video memory should occur at the new region.
 */
static int s3c2440fb_map_video_memory(struct fb_info *fbinfo)
{
        dma_addr_t map_dma;
        unsigned long map_size = PAGE_ALIGN(fbinfo->fix.smem_len);

        printk("map_video_memory(info=%px) map_size %u\n", fbinfo, map_size);

        // 第一个参数不能为空 否则会抛异常
        fbinfo->screen_base = dma_alloc_wc(&s3c_device_lcd.dev, map_size, &map_dma,
                                         GFP_KERNEL);

        if (fbinfo->screen_base) {
                /* prevent initial garbage on screen */
                printk("map_video_memory: clear %px:%08x\n",
                        fbinfo->screen_base, map_size);
                memset(fbinfo->screen_base, 0x00, map_size);

                fbinfo->fix.smem_start = map_dma;

                printk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",
                        fbinfo->fix.smem_start, fbinfo->screen_base, map_size);
        }
        else
        {
          printk("map_video_memory fail\n");
        }

        return fbinfo->screen_base ? 0 : -ENOMEM;
}

3.9 开启LCD

    /* 4.1 开启LCD */
    /* 控制LCDCON5允许PWREN信号 */
    lcd_regs->lcdcon5 |= (1 << 3);
    /* 控制LCDCON1 LCD使能 */
    lcd_regs->lcdcon1 |= (1<<0);
    /* 输出高电平, 使能背光 */
    *gpgdat |= 1<<4;

    printk("lcdcon[1] = 0x%08lx\n", lcd_regs->lcdcon1);
    printk("lcdcon[2] = 0x%08lx\n", lcd_regs->lcdcon2);
    printk("lcdcon[3] = 0x%08lx\n", lcd_regs->lcdcon3);
    printk("lcdcon[4] = 0x%08lx\n", lcd_regs->lcdcon4);
    printk("lcdcon[5] = 0x%08lx\n", lcd_regs->lcdcon5);
    printk("lcdsaddr[1]= 0x%08lx\n", lcd_regs->lcdsaddr1);
    printk("lcdsaddr[2]= 0x%08lx\n", lcd_regs->lcdsaddr2);
    printk("lcdsaddr[3]= 0x%08lx\n", lcd_regs->lcdsaddr3);

3.10 注册

    /* 4.2 注册 */
    register_framebuffer(s3c_lcd);

3.11 lcd_dev.c完整代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>

/*  LCD T35参数设定 */
#define     LCD_WIDTH   240             /* LCD面板的行宽 */
#define     LCD_HEIGHT  320             /* LCD面板的列宽 */

#define     VSPW        1                /* 通过计算无效行数垂直同步脉冲宽度决定VSYNC脉冲的高电平宽度 */
#define     VBPD        1                /* 垂直同步周期后的无效行数 */
#define     LINEVAL     (LCD_HEIGHT-1)  /* LCD的垂直宽度-1 */
#define     VFPD        1                /* 垂直同步周期前的的无效行数 */

#define     CLKVAL      7              /* VCLK = HCLK / [(CLKVAL  + 1)  × 2] */

#define     HSPW        9                /* 通过计算VCLK的数水平同步脉冲宽度决定HSYNC脉冲的高电平宽度 */
#define     HBPD        19               /* 描述水平后沿为HSYNC的下降沿与有效数据的开始之间的VCLK周期数 */
#define     HOZVAL      (LCD_WIDTH-1)    /* LCD的水平宽度-1 */
#define     HFPD        9                /* 水平后沿为有效数据的结束与HSYNC的上升沿之间的VCLK周期数 */

/* 定义fb_info */
static struct fb_info *s3c_lcd;
/* 调色板数组,被fb_info->pseudo_palette调用 */
static u32 pseudo_palette[16];
/* 时钟 */
static struct clk *lcd_clk;

/* GPIO相关寄存器 */
static volatile unsigned long *gpcup;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdup;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile unsigned long *gpgdat;
static volatile unsigned long *gpgup;

/* lcd相关寄存器 */
struct lcd_regs{
    unsigned long   lcdcon1;
    unsigned long    lcdcon2;
    unsigned long    lcdcon3;
    unsigned long    lcdcon4;
    unsigned long    lcdcon5;
    unsigned long    lcdsaddr1;
    unsigned long    lcdsaddr2;
    unsigned long    lcdsaddr3;
    unsigned long    redlut;
    unsigned long    greenlut;
    unsigned long    bluelut;
    unsigned long    reserved[9];
    unsigned long    dithmode;
    unsigned long    tpal;
    unsigned long    lcdintpnd;
    unsigned long    lcdsrcpnd;
    unsigned long    lcdintmsk;
    unsigned long    lpcsel;
};

static volatile struct lcd_regs* lcd_regs;

/*
 * 将内核单色使用bf表示
 * @param chan:单色 内核中的单色都是16位
 * @param bf:颜色位信息
 */
static inline unsigned int chan_to_field(unsigned int chan,
                     struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}

/*
 * 调色板操作函数
 *  @param regno: 调色板数组元素索引
 */
static int s3c2440fb_setcolreg(unsigned regno,
                   unsigned red, unsigned green, unsigned blue,
                   unsigned transp, struct fb_info *info)
{
    unsigned int val;
    //调色板数组不能大于15
    if (regno >= 16)
         return 1;

    /* 用red,green,blue三个颜色值构造出16色数据val */
    val  = chan_to_field(red,   &info->var.red);
    val |= chan_to_field(green, &info->var.green);
    val |= chan_to_field(blue,  &info->var.blue);

    /* 放到调色板数组中 */
    pseudo_palette[regno] = val;
    return 0;
}


#define samsung_device_dma_mask (*((u64[]) { DMA_BIT_MASK(32) }))

/*platform设备 */
static struct platform_device s3c_device_lcd = {
        .name           = "s3c2440-lcd",
        .id             = -1,
        .num_resources  = 0,
        .dev            = {
                .dma_mask               = &samsung_device_dma_mask,
                .coherent_dma_mask      = DMA_BIT_MASK(32),
        }
};

/*
 * s3c2440fb_map_video_memory():
 *      Allocates the DRAM memory for the frame buffer.  This buffer is
 *      remapped into a non-cached, non-buffered, memory region to
 *      allow palette and pixel writes to occur without flushing the
 *      cache.  Once this area is remapped, all virtual memory
 *      access to the video memory should occur at the new region.
 */
static int s3c2440fb_map_video_memory(struct fb_info *fbinfo)
{
        dma_addr_t map_dma;
        unsigned long map_size = PAGE_ALIGN(fbinfo->fix.smem_len);

        printk("map_video_memory(info=%px) map_size %u\n", fbinfo, map_size);

        // 第一个参数不能为空 否则会抛异常
        fbinfo->screen_base = dma_alloc_wc(&s3c_device_lcd.dev, map_size, &map_dma,
                                         GFP_KERNEL);

        if (fbinfo->screen_base) {
                /* prevent initial garbage on screen */
                printk("map_video_memory: clear %px:%08x\n",
                        fbinfo->screen_base, map_size);
                memset(fbinfo->screen_base, 0x00, map_size);

                fbinfo->fix.smem_start = map_dma;

                printk("map_video_memory: dma=%08lx cpu=%p size=%08x\n",
                        fbinfo->fix.smem_start, fbinfo->screen_base, map_size);
        }
        else
        {
          printk("map_video_memory fail\n");
        }

        return fbinfo->screen_base ? 0 : -ENOMEM;
}


/*
 * fb_info操作函数fbops
 */
static struct fb_ops s3c2440fb_ops = {
    .owner        = THIS_MODULE,
    .fb_setcolreg    = s3c2440fb_setcolreg,   // 调色板操作函数 设置调色板fb_info-> pseudo_palette
    .fb_fillrect    = cfb_fillrect,          // 填充矩形  函数在drivers/video/fbdev/core/cfbfillrect.c中定义
    .fb_copyarea    = cfb_copyarea,          // 复制数据  函数在drivers/video/fbdev/core/cfbcopyarea.c中定义
    .fb_imageblit    = cfb_imageblit,         // 绘制图形  函数在drivers/video/fbdev/core/cfbimgblt.c中定义
};


/*
 * lcd驱动模块入口
 */
static int lcd_init(void)
{
   int ret;
   printk("lcd device registered\n");

   /* 1. 初始化fb_info */
   s3c_lcd = framebuffer_alloc(0,NULL);
       if (!s3c_lcd)
       {
           return -ENOMEM;
       }

       printk("s3c_lcd=%px\n", s3c_lcd);

   /* 2. 设置fb_info */
   /* 2.1设置固定参数 */
       strcpy(s3c_lcd->fix.id, "mylcd");
    s3c_lcd->fix.smem_len        = LCD_WIDTH * LCD_HEIGHT * 2;   // framebuffer缓冲区的大小 每个像素两个字节
       s3c_lcd->fix.type            = FB_TYPE_PACKED_PIXELS;
    s3c_lcd->fix.type_aux        = 0;
    s3c_lcd->fix.xpanstep        = 0;
    s3c_lcd->fix.ypanstep        = 0;
    s3c_lcd->fix.ywrapstep       = 0;
    s3c_lcd->fix.accel           = FB_ACCEL_NONE;
    s3c_lcd->fix.visual          = FB_VISUAL_TRUECOLOR;      //真彩色
    s3c_lcd->fix.line_length     = LCD_WIDTH * 2;             // LCD一行所占字节数

    /* 2.2 设置可变参数 */
     s3c_lcd->var.xres            = LCD_WIDTH;         // 行分辨率
     s3c_lcd->var.yres            = LCD_HEIGHT;        // 列分辨率
     s3c_lcd->var.xres_virtual    = LCD_WIDTH;         // 行虚拟分辨率
     s3c_lcd->var.yres_virtual    = LCD_HEIGHT;        // 列虚拟分辨率
     s3c_lcd->var.xoffset         = 0;                 //虚拟到可见屏幕之间的行偏移
     s3c_lcd->var.yoffset         = 0;                 //虚拟到可见屏幕之间的行偏移
     s3c_lcd->var.bits_per_pixel  = 16;                // 每像素使用位数

     /* RGB:565 */
     s3c_lcd->var.red.offset      = 11;
     s3c_lcd->var.red.length      = 5;
     s3c_lcd->var.green.offset    = 5;
     s3c_lcd->var.green.length    = 6;
     s3c_lcd->var.blue.offset     = 0;
     s3c_lcd->var.blue.length     = 5;

     s3c_lcd->var.nonstd          = 0;
     s3c_lcd->var.activate        = FB_ACTIVATE_NOW;   // 使设置的值立即生效
     s3c_lcd->var.accel_flags     = 0;
     s3c_lcd->var.vmode           = FB_VMODE_NONINTERLACED;

     /* 2.3 设置操作函数 */
     s3c_lcd->fbops               = &s3c2440fb_ops;

     /* 2.4 其他设置 */
     s3c_lcd->flags               = FBINFO_FLAG_DEFAULT;
     s3c_lcd->pseudo_palette      = pseudo_palette;             // 保存调色板数组
     s3c_lcd->screen_size         = LCD_WIDTH * LCD_HEIGHT * 2;    // framebuffer缓冲区的大小

    // 时钟相关,获取lcd时钟
    lcd_clk = clk_get(NULL, "lcd");
    if (IS_ERR(lcd_clk)) {
        printk("failed to get lcd clock source\n");
        return PTR_ERR(lcd_clk);
    }

    clk_prepare_enable(lcd_clk);       // 时钟使能
    printk("got and enabled clock\n");

    /* 3.硬件相关操作 */
    /* 3.1 配置GPIO口用于LCD */
    gpcup = ioremap(0x56000028,4);
    gpccon = ioremap(0x56000020,4);
    gpdup = ioremap(0x56000038,4);
    gpdcon = ioremap(0x56000030,4);
    gpgcon = ioremap(0x56000060,12);
    gpgdat = gpgcon + 1;
    gpgup = gpgdat + 1;

    // GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND
    *gpcup = 0xffffffff;
    *gpccon = 0xaaaaaaaa;
    // GPIO管脚用于VD[23:8]
    *gpdup = 0xffffffff;
    *gpdcon = 0xaaaaaaaa;

    /* Pull - up disable */
    *gpgup |= (1 << 4);
    // 设置GPG4引脚为LCD_PWREN模式
    *gpgcon |= (3 << 8);

    /* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
    lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));
    printk("lcd_regs=%px map_size %u\n", lcd_regs, sizeof(struct lcd_regs));

    /*  [17:8]  CLKVAL
     *  [6:5]   PNRMODE;选择显示模式
     *                  00 = 4 位双扫描显示模式   01 = 4 位单扫描显示模式(STN)
     *                  10 = 8 位单扫描显示模式   11 = TFT LCD 面板
     *  [4:1]  BPPMODE  选择BPP(位每像素)模式    1100 = TFT 的16 BPP
     *  [0]    ENVID    LCD 视频输出和逻辑使能/禁止。
     *                  0 = 禁止视频输出和LCD 控制信号  1 = 允许视频输出和LCD 控制信号
     */
    lcd_regs->lcdcon1   = (CLKVAL<<8)| (3<<5) | (0xC<<1);              /* 16 bpp for TFT */

    /*  [31:24]   VBPD:帧同步信号的后肩
     *  [23:14]   LINEVAL:LCD面板的垂直尺寸
     *  [13:6]    VFPD:帧同步信号的前肩
     *  [5:0]     VSPW:同步信号的脉宽
     */
    lcd_regs->lcdcon2 = (VBPD<<24)|(LINEVAL<<14)|(VFPD<<6)|(VSPW);

    /* [25:19] HBPD: 行同步信号的后肩
     * [18:8] HOZVAL: LCD面板的水平尺寸
     * [7:0] HFPD: 行同步信号的前肩
     */
    lcd_regs->lcdcon3 = (HBPD<<19)|(HOZVAL<<8)|(HFPD);
    lcd_regs->lcdcon4 = (HSPW);

    /* [11] FRM565: 此位选择16 BPP 输出视频数据的格式   0 = 5:5:5:1 格式   1= 5:6:5 格式
     * [10] STN/TFT: 此位控制VCLK 有效沿的极性
     * [9]    INVVLINE: STN/TFT:此位表明VLINE/HSYNC 脉冲极性  0 = 正常 1 = 反转
     * [8]  INVVFRAME: STN/TFT:此位表明VFRAME/VSYNC 脉冲极性 0 = 正常 1 = 反转
     * VLINE/HSYNC 脉冲极性、VFRAME/VSYNC 脉冲极性反转(LCD型号决定)
     * [0]    HWSWP: STN/TFT:半字节交换控制位   0 = 交换禁止 1 = 交换使能
     */
    lcd_regs->lcdcon5 = ((1<<11) | (1<<10) | (1 << 9) | (1 << 8) | (1 << 0));

     /*  关闭PWREN信号输出 */
     lcd_regs->lcdcon1 &= ~(1<<0);
     /* 禁止PWREN信号 */
     lcd_regs->lcdcon5  &=~(1<<3);

     /* 第一位设置为1 选择输出分片率类型0:320 * 240  1:240*320 */
     lcd_regs->lpcsel = ((0xCE6) & ~7) | 1<<1;

    /* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
    ret = s3c2440fb_map_video_memory(s3c_lcd);
    if (ret) {
        printk("failed to allocate video RAM: %d\n", ret);
        // todo 这里应该进行资源释放,我这里就不释放了
        return -ENOMEM;
    }

    /* [29:21] LCDBANK:存放帧缓冲起始地址的[30:22]
     * [20:0] LCDBASEU: 存放帧缓冲起始地址的[21:1]
     */
    lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);

    /* 存放帧结束地址[21:1] */
    lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;

    /* [21:11] OFFSIZE:表示虚拟屏偏移尺寸  即上一行最后像素点到下一行第一个像素点之间间隔多少个像素点
    *  [10:0] PAGEWIDTH:表示行的宽度(半字为单位 16bit)
    */
    lcd_regs->lcdsaddr3 = LCD_WIDTH & 0x3ff;

    /* 4.1 开启LCD */
    /* 控制LCDCON5允许PWREN信号 */
    lcd_regs->lcdcon5 |= (1 << 3);
    /* 控制LCDCON1 LCD使能 */
    lcd_regs->lcdcon1 |= (1<<0);
    /* 输出高电平, 使能背光 */
    *gpgdat |= 1<<4;

    printk("lcdcon[1] = 0x%08lx\n", lcd_regs->lcdcon1);
    printk("lcdcon[2] = 0x%08lx\n", lcd_regs->lcdcon2);
    printk("lcdcon[3] = 0x%08lx\n", lcd_regs->lcdcon3);
    printk("lcdcon[4] = 0x%08lx\n", lcd_regs->lcdcon4);
    printk("lcdcon[5] = 0x%08lx\n", lcd_regs->lcdcon5);
    printk("lcdsaddr[1]= 0x%08lx\n", lcd_regs->lcdsaddr1);
    printk("lcdsaddr[2]= 0x%08lx\n", lcd_regs->lcdsaddr2);
    printk("lcdsaddr[3]= 0x%08lx\n", lcd_regs->lcdsaddr3);

    /* 4.2 注册 */
    register_framebuffer(s3c_lcd);

    return 0;
}

/*
 * lcd驱动模块出口
 */
static void __exit lcd_exit(void)
{
    printk("lcd device unregistered\n");

    unregister_framebuffer(s3c_lcd);
     /* 禁止LCD使能 */
    lcd_regs->lcdcon1 &= ~(1<<0);
    /* 关闭背光 */
    *gpgdat &= ~(1<<4);
    dma_free_wc(&s3c_device_lcd.dev, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
    iounmap(lcd_regs);
    iounmap(gpcup);
    iounmap(gpccon);
    iounmap(gpdup);
    iounmap(gpdcon);
    iounmap(gpgcon);
    framebuffer_release(s3c_lcd);
}

module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
View Code

四、测试

4.1 配置内核

我们切换到linux内核目录下,

cd /wqoroot@zhengyang:~# cd /work/sambashare/linux-5.2.8/

在linux内核根目录下执行,生成默认配置文件.config:

make distclean
make s3c2440_defconfig # 这个是之前我之前配置的

进行内核配置:

root@zhengyang:/work/sambashare/linux-5.2.8# make menuconfig

配置步骤如下:

 Device Drivers  --->

  • Graphics support  --->
    • Frame buffer Device  --->
      • <Y>Support for frame buffer devices
      •  <M> S3C2410 LCD framebuffer support
      •  <M> Samsung S3C framebuffer support

需要注意的是:

Support for frame buffer devices:这个设置成Y就行,如果将这个编译进内核,同时会将cfbcopyarea.c、cfbfillrect.c、cfbimgblt.c、font_8x16(lib/fonts路径下)编译进内核,这样就不用单独安装这些了

具体参考drivers/video/fbdev/Kconfig:

config FB_CMDLINE
        bool

config FB_NOTIFY
        bool

menuconfig FB
        tristate "Support for frame buffer devices"
        select FB_CMDLINE
        select FB_NOTIFY
        ---help---
          The frame buffer device provides an abstraction for the graphics
          hardware. It represents the frame buffer of some video hardware and
          allows application software to access the graphics hardware through
          a well-defined interface, so the software doesn't need to know
          anything about the low-level (hardware register) stuff.

          Frame buffer devices work identically across the different
          architectures supported by Linux and make the implementation of
          application programs easier and more portable; at this point, an X
          server exists which uses the frame buffer device exclusively.
          On several non-X86 architectures, the frame buffer device is the
          only way to use the graphics hardware.

          The device is accessed through special device nodes, usually located
          in the /dev directory, i.e. /dev/fb*.

         ...

config FB_CFB_FILLRECT
        tristate
        depends on FB
        ---help---
          Include the cfb_fillrect function for generic software rectangle
          filling. This is used by drivers that don't provide their own
          (accelerated) version.

config FB_CFB_COPYAREA
        tristate
        depends on FB
        ---help---
          Include the cfb_copyarea function for generic software area copying.
          This is used by drivers that don't provide their own (accelerated)
          version.

config FB_CFB_IMAGEBLIT
        tristate
        depends on FB
        ---help---
          Include the cfb_imageblit function for generic software image
          blitting. This is used by drivers that don't provide their own
          (accelerated) version.

修改完配置后,保存文件,输入文件名s3c2440_defconfig,在当前路径下生成s3c2440_defconfig:存档:

mv s3c2440_defconfig ./arch/arm/configs/

此时重新执行:

make s3c2440_defconfig

查看.config文件可以看到:

4.2 编译内核和模块

编译内核:

make V=1 uImage

将uImage复制到tftp服务器路径下:

 cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

4.3 烧录内核

开发板uboot启动完成后,内核启动前,按下任意键,进入uboot,可以通过print查看uboot中已经设置的环境变量。

设置开发板ip地址,从而可以使用网络服务:

SMDK2440 # set ipaddr 192.168.0.105
SMDK2440 # save
Saving Environment to NAND...
Erasing NAND...

Erasing at 0x40000 -- 100% complete.
Writing to NAND... OK
SMDK2440 # ping 192.168.0.200
dm9000 i/o: 0x20000000, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 08:00:3e:26:0a:5b
operating at unknown: 0 mode
Using dm9000 device
host 192.168.0.200 is alive

设置tftp服务器地址,也就是我们ubuntu服务器地址:

set serverip 192.168.0.200
save

下载内核到内存,并写NAND FLASH:

tftp 30000000 uImage
nand erase.part kernel
nand write 30000000 kernel

运行结果如下:

SMDK2440 # tftp 30000000 uImage
dm9000 i/o: 0x20000000, id: 0x90000a46 
DM9000: running in 16 bit mode
MAC: 08:00:3e:26:0a:5b
operating at unknown: 0 mode
Using dm9000 device
TFTP from server 192.168.0.200; our IP address is 192.168.0.188
Filename 'uImage'.
Load address: 0x30000000
Loading: *#################################################################
     #################################################################
     #################################################################
     ##############################################################
     429.7 KiB/s
done
Bytes transferred = 3766128 (397770 hex)
SMDK2440 # nand erase.part kernel

NAND erase.part: device 0 offset 0x60000, size 0x400000

Erasing at 0x60000 --   3% complete.
...
OK
SMDK2440 # nand write 30000000 kernel

NAND write: device 0 offset 0x60000, size 0x400000
 4194304 bytes written: OK

4.4 编译LCD驱动

在11.lcd_dev路径下编译:

root@zhengyang:/work/sambashare/drivers/11.lcd_dev# cd /work/sambashare/drivers/11.lcd_dev
root@zhengyang:/work/sambashare/drivers/11.lcd_dev# make

拷贝驱动文件到nfs文件系统:

root@zhengyang:/work/sambashare/drivers/11.lcd_dev# cp /work/sambashare/drivers/11.lcd_dev/lcd_dev.ko /work/nfs_root/rootfs/ 

4.5 安装驱动

重启开发板,加载lcd驱动,执行如下命令:

insmod lcd_dev.ko

加载之后我们发现输出的LCD寄存器信息均是0x00,这里很奇怪,说明我们并没有成功修改LCD寄存器的值,这个问题排查了很久也没有找到原因。

挂载LCD驱动后, 如下图,可以通过如下命令查看已挂载的LCD设备节点::

ls -l /dev/fb*  

五、编译LCD驱动到内核

5.1 LCD驱动编译进内核

我们直接将lcd_dev.ko源码拷贝到linux源码下:
cp lcd_dev.c /work/sambashare/linux-5.2.8/drivers/video/fbdev/

5.2 修改Kconfig

cd /work/sambashare/linux-5.2.8/drivers/video/fbdev
vim Kconfig

新增如下内容:

config MY_LCD
    tristate "my_lcd_dev"
    depends on FB && ARCH_S3C2410
    select FB_CFB_FILLRECT
    select FB_CFB_COPYAREA
    select FB_CFB_IMAGEBLIT
        default n
        help
          my_lcd driver    

在源码顶层运行make menuconfig,在Device Drivers -> Graphics support -> Frame buffer Devices可以看到:

勾选这,并保存。

5.3 修改Makefile

cd /work/sambashare/linux-5.2.8/drivers/video/fbdev
vim Makefile

新增如下内容:

obj-$(CONFIG_MY_LCD)             += lcd_dev.o

5.4 编译内核

在源码顶层运行:

make uImage

复制到nfs文件系统:

 cp /work/sambashare/linux-5.2.8/arch/arm/boot/uImage /work/tftpboot/

5.5 测试运行

重新下载内核到开发板:

tftp 30000000 uImage
nand erase.part kernel
nand write 30000000 kernel
bootm

启动输出寄存器信息:

lcd device registered
s3c_lcd=c39a0c00
lcd_regs=c48a4000 map_size 104
map_video_memory(info=c39a0c00) map_size 155648
map_video_memory: clear c48a6000:00026000
map_video_memory: dma=339c0000 cpu=(ptrval) size=00026000
lcdcon[1] = 0x00000779
lcdcon[2] = 0x014fc041
lcdcon[3] = 0x0098ef09
lcdcon[4] = 0x00000009
lcdcon[5] = 0x00004f09
lcdsaddr[1]= 0x19ce0000
lcdsaddr[2]= 0x000f2c00
lcdsaddr[3]= 0x000000f0

可以在LCD上看到企鹅logo(向像上一节一样配置了Bootup logo)。但是过一会屏幕就什么都没了,针对该问题,目前问题还未排查到原因。

六、代码下载

Young / s3c2440_project[drivers]

参考文章

[1]16.Linux-LCD驱动(详解)

[2]十二、Linux驱动之LCD驱动

[3]S3C2440之LCD驱动

posted @ 2022-04-20 22:44  大奥特曼打小怪兽  阅读(974)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步