Linux学习笔记(24)—— LCD驱动程序

有关LCD接口的相关文档,已有许多优秀博文讲解,本文不再赘述,下面仅浅显地罗列出LCD驱动程序、测试程序等
  1. LCD设备驱动程序关键点

LCD驱动程序的核心就是:

  • 分配fb_info

  • 设置fb_info

  • 注册fb_info

  • 硬件相关的设置

    硬件相关的设置又可以分为3部分:

    • 引脚设置
    • 时钟设置
    • LCD控制器设置
/**
 * 文件    : lcd_drv.c
 * 作者    : glen  
 * 描述    : lcd driver文件
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dmaengine.h>
#include <linux/dma-attrs.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/cpufreq.h>
#include <linux/io.h>
#include <video/display_timing.h>
#include <video/of_display_timing.h>
#include <linux/gpio/consumer.h>

#include <asm/div64.h>
#include <asm/mach/map.h>

struct imx6ull_lcdif {
    volatile unsigned int CTRL;
    volatile unsigned int CTRL_SET;                        
    volatile unsigned int CTRL_CLR;                         
    volatile unsigned int CTRL_TOG;                         
    volatile unsigned int CTRL1;                             
    volatile unsigned int CTRL1_SET;                         
    volatile unsigned int CTRL1_CLR;                       
    volatile unsigned int CTRL1_TOG;                       
    volatile unsigned int CTRL2;                            
    volatile unsigned int CTRL2_SET;                       
    volatile unsigned int CTRL2_CLR;                        
    volatile unsigned int CTRL2_TOG;                        
    volatile unsigned int TRANSFER_COUNT;   
    unsigned char RESERVED_0[12];
    volatile unsigned int CUR_BUF;                          
    unsigned char RESERVED_1[12];
    volatile unsigned int NEXT_BUF;                        
    unsigned char RESERVED_2[12];
    volatile unsigned int TIMING;                          
    unsigned char RESERVED_3[12];
    volatile unsigned int VDCTRL0;                         
    volatile unsigned int VDCTRL0_SET;                      
    volatile unsigned int VDCTRL0_CLR;                     
    volatile unsigned int VDCTRL0_TOG;                     
    volatile unsigned int VDCTRL1;                          
    unsigned char RESERVED_4[12];
    volatile unsigned int VDCTRL2;                          
    unsigned char RESERVED_5[12];
    volatile unsigned int VDCTRL3;                          
    unsigned char RESERVED_6[12];
    volatile unsigned int VDCTRL4;                           
    unsigned char RESERVED_7[12];
    volatile unsigned int DVICTRL0;    
    unsigned char RESERVED_8[12];
    volatile unsigned int DVICTRL1;                         
    unsigned char RESERVED_9[12];
    volatile unsigned int DVICTRL2;                        
    unsigned char RESERVED_10[12];
    volatile unsigned int DVICTRL3;                        
    unsigned char RESERVED_11[12];
    volatile unsigned int DVICTRL4;                          
    unsigned char RESERVED_12[12];
    volatile unsigned int CSC_COEFF0;  
    unsigned char RESERVED_13[12];
    volatile unsigned int CSC_COEFF1;                        
    unsigned char RESERVED_14[12];
    volatile unsigned int CSC_COEFF2;                        
    unsigned char RESERVED_15[12];
    volatile unsigned int CSC_COEFF3;                        
    unsigned char RESERVED_16[12];
    volatile unsigned int CSC_COEFF4;   
    unsigned char RESERVED_17[12];
    volatile unsigned int CSC_OFFSET;  
    unsigned char RESERVED_18[12];
    volatile unsigned int CSC_LIMIT;  
    unsigned char RESERVED_19[12];
    volatile unsigned int DATA;                              
    unsigned char RESERVED_20[12];
    volatile unsigned int BM_ERROR_STAT;                     
    unsigned char RESERVED_21[12];
    volatile unsigned int CRC_STAT;                        
    unsigned char RESERVED_22[12];
    volatile  unsigned int STAT;                             
    unsigned char RESERVED_23[76];
    volatile unsigned int THRES;                             
    unsigned char RESERVED_24[12];
    volatile unsigned int AS_CTRL;                           
    unsigned char RESERVED_25[12];
    volatile unsigned int AS_BUF;                            
    unsigned char RESERVED_26[12];
    volatile unsigned int AS_NEXT_BUF;                     
    unsigned char RESERVED_27[12];
    volatile unsigned int AS_CLRKEYLOW;                    
    unsigned char RESERVED_28[12];
    volatile unsigned int AS_CLRKEYHIGH;                   
    unsigned char RESERVED_29[12];
    volatile unsigned int SYNC_DELAY;
};

struct lcd_regs {
    volatile unsigned int fb_base_phys;
    volatile unsigned int fb_xres;
    volatile unsigned int fb_yres;
    volatile unsigned int fb_bpp;
};

static struct lcd_regs *plcd_regs;
static struct fb_info *pfb_info;
static unsigned int pseudo_palette[32];

static struct gpio_desc *bl_gpio;
static struct clk *clk_pix;
static struct clk *clk_axi;

static void lcd_controller_enable(struct imx6ull_lcdif *lcdif)
{
    lcdif->CTRL |= (1 << 0);
}

static int lcd_controller_init(struct imx6ull_lcdif *lcdif, struct display_timing *dt, int lcd_bpp, int fb_bpp, unsigned int fb_phy)
{
    int bus_width;
    int fb_width;
    int vsync_pol;
    int hsync_pol;
    int de_pol;
    int clk_pol;

    if (dt->flags & DISPLAY_FLAGS_HSYNC_HIGH)
        hsync_pol = 1;

    if (dt->flags & DISPLAY_FLAGS_VSYNC_HIGH)
        vsync_pol = 1;

    if (dt->flags & DISPLAY_FLAGS_DE_HIGH)
        de_pol = 1;

    if (dt->flags & DISPLAY_FLAGS_PIXDATA_POSEDGE)
        clk_pol = 1;

    if (lcd_bpp == 24)
        bus_width = 0x3;
    else if (lcd_bpp == 18)
        bus_width = 0x2;
    else if (lcd_bpp == 8)
        bus_width = 0x1;
    else if (lcd_bpp == 16)
        bus_width = 0x0;
    else 
        return -1;

    if (fb_bpp == 24 || fb_bpp == 32)
        fb_width = 0x3;
    else if (fb_bpp == 18)
        fb_width = 0x2;
    else if (fb_bpp == 8)
        fb_width = 0x1;
    else if (fb_bpp == 16)
        fb_width = 0x0;
    else 
        return -1;
        
    /**
     * 初始化LCD控制器的CTRL寄存器
     * [19]     1 : DOTCLK和DVI modes需要设置为1 
     * [17]     1 : 设置为1工作在DOTCLK模式
     * [15:14] 00 : 输入数据不交换(小端模式)默认就为0,不需设置
     * [13:12] 00 : CSC数据不交换(小端模式)默认就为0,不需设置
     * [11:10] 11 : 数据总线为24bit
     * [9:8]        根据显示屏资源文件bpp来设置:8位0x1, 16位0x0, 24位0x3
     * [5]      1 : 设置elcdif工作在主机模式
     * [1]      0 : 24位数据均是有效数据,默认就为0,不需设置
     */ 
    lcdif->CTRL = (1 << 19) | (1 << 17) | (bus_width << 10) | \
        (fb_width << 8) | (1 << 5);

    /**
     * 设置ELCDIF的寄存器CTRL1
     * 根据bpp设置, bpp为24或32才设置
     * [19:16]: 111表示传输24位无压缩数据, A通道不传输
     */
    if (fb_bpp == 24 || fb_bpp == 32) {
        lcdif->CTRL1 &= ~(0xf << 16);
        lcdif->CTRL1 |=  (0x7 << 16);
    } else 
        lcdif->CTRL1 |= (0xf << 16);

    /**
     * 设置eLCDIF的寄存器TRANSFER_COUNT寄存器
     * [31:16]  : 垂直方向像素个数
     * [15:0]   : 水平方向像素个数
     */
    lcdif->TRANSFER_COUNT = (dt->vactive.typ << 16) | (dt->hactive.typ << 0);

    /**
     * 设置eLCDIF的VDCTRL0寄存器
     * [29] 0 : VSYNC输出, 默认为0,无需设置
     * [28] 1 : 在DOTCLK模式下, 设置1硬件会产生使能ENABLE输出
     * [27] 0 : VSYNC低电平有效, 根据屏幕配置文件将其设置为0
     * [26] 0 : HSYNC低电平有效, 根据屏幕配置文件将其设置为0
     * [25] 1 : DOTCLK下降沿有效, 根据屏幕配置文件将其设置为1
     * [24] 1 : ENABLE信号高电平有效, 根据屏幕配置文件将其设置为1
     * [21] 1 : 帧同步周期单位, DOTCLK mode设置为1
     * [20] 1 : 帧同步脉冲宽度单位, DOTCLK mode设置为1
     * [17:0] : vysnc脉冲宽度 
     */
    lcdif->VDCTRL0 = (1 << 28) | (vsync_pol << 27) | (hsync_pol << 26) \
        | (clk_pol << 25) | (de_pol << 24) | (1 << 21) | (1 << 20) \
        | (dt->vsync_len.typ << 0);
    
   /**
	* 设置eLCDIF的VDCTRL1寄存器
	* 设置垂直方向的总周期: 上黑框tvb + 垂直同步脉冲tvp + 垂直有效高度yres + 下黑框tvf
	*/	  
	lcdif->VDCTRL1 = dt->vback_porch.typ + dt->vsync_len.typ + dt->vactive.typ + dt->vfront_porch.typ;  

    /**
     * 设置eLCDIF的VDCTRL2寄存器
     * [31:18]  : 水平同步信号脉冲宽度
     * [17:0]   : 水平方向总周期
     * 设置水平方向的总周期: 左黑框thb + 水平同步脉冲thp + 水平有效高度xres + 右黑框thf
     */
    lcdif->VDCTRL2 = (dt->hsync_len.typ << 18) | (dt->hback_porch.typ + dt->hsync_len.typ + dt->hactive.typ + dt->hfront_porch.typ);

    /**
     * 设置eLCDIF的VDCTRL3寄存器
     * [27:16] : 水平方向等待时钟数 = thb + thp
     * [15: 0] : 垂直方向等待时钟数 = tvb + tvp
     */
    lcdif->VDCTRL3 = ((dt->hback_porch.typ + dt->hsync_len.typ) << 16) | (dt->vback_porch.typ + dt->vsync_len.typ);

    /**
     * 设置eLCDIF的VDCTRL4寄存器
     * [18]     : 使用VSHYNC、HSYNC、DOTCLK模式此位置1
     * [17:0]   : 水平方向的宽度
     */
    lcdif->VDCTRL4 = (1 << 18) | (dt->hactive.typ);

    /**
     * 设置eLCDIF的CUR_BUF和NEXT_BUT寄存器
     * CUR_BUF  : 当前显存地址
     * NEXT_BUF : 下一帧显存地址
     */
    lcdif->CUR_BUF  = fb_phy;
    lcdif->NEXT_BUF = fb_phy;

    return 0;
}

static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
    chan &= 0xffff;
    chan >>= 16 - bf->length;
    return chan << bf->offset;
}

static int lcd_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue,
    unsigned transp, struct fb_info *info)
{
    unsigned int val;

    /**
     * dprintk("setcol:regno=%d, rgb=%d,%d,%d\n", regno, red, green, blue); 
     */
    switch (info->fix.visual) {
    case FB_VISUAL_TRUECOLOR:
        /* true-color, use pseudo - palette */
        if (regno < 16) {
            u32 *pal = info->pseudo_palette;
            val  = chan_to_field(red,    &info->var.red);
            val |= chan_to_field(green,  &info->var.green);
            val |= chan_to_field(blue,   &info->var.blue);

            pal[regno] = val;
        }
        break;
    
    default:
        return 1;
    }

    return 0;
}

static struct fb_ops fbops = {
    .owner         = THIS_MODULE,
    .fb_setcolreg  = lcd_setcolreg,
    .fb_fillrect   = cfb_fillrect,
    .fb_copyarea   = cfb_copyarea,
    .fb_imageblit  = cfb_imageblit,
};

static int lcd_probe(struct platform_device *pdev)
{
    struct device_node *display_np;
    dma_addr_t phy_addr;
    int ret;
    int width;
    int bpp;
    struct display_timings *timings = NULL;
    struct display_timing  *dt = NULL;
    struct imx6ull_lcdif   *lcdif;
    struct resource *res;

    /**
     * 从设备树获取LCD显示设备相关信息 
     */
    display_np = of_parse_phandle(pdev->dev.of_node, "display", 0);

    /* get common info */
    ret = of_property_read_u32(display_np, "bus-width", &width);
    ret = of_property_read_u32(display_np, "bits-per-pixel", &bpp);

    /* get timming */
    timings = of_get_display_timings(display_np);
    dt      = timings->timings[timings->native_mode];

    /* get gpio from device tree */
    bl_gpio = gpiod_get(&pdev->dev, "backlight", 0);

    /* config bl_gpio as output */
    gpiod_direction_output(bl_gpio, 1);

    /* set val: gpiod_set_value(bl_gpio, status); */

    /* get clk from device tree */
    clk_pix = devm_clk_get(&pdev->dev, "pix");
    clk_axi = devm_clk_get(&pdev->dev, "axi");

    /* set clk rate */
    clk_set_rate(clk_pix, dt->pixelclock.typ);

    /* enable clk */
    clk_prepare_enable(clk_pix);
    clk_prepare_enable(clk_axi);

    /* 分配fb_info */
    pfb_info = framebuffer_alloc(0, NULL);

    /* 设置fb_info */
    /* a. var  */
    pfb_info->var.xres_virtual = pfb_info->var.xres = dt->hactive.typ;
    pfb_info->var.yres_virtual = pfb_info->var.yres = dt->vactive.typ;

    pfb_info->var.bits_per_pixel = 24;
    pfb_info->var.red.offset = 16;
    pfb_info->var.red.length = 8;
    pfb_info->var.green.offset = 8;
    pfb_info->var.green.length = 8;
    pfb_info->var.blue.offset  = 0;
    pfb_info->var.blue.length  = 8;

    /* b. fix */
    strcpy(pfb_info->fix.id, "fb_lcd");
    pfb_info->fix.smem_len = pfb_info->var.xres * pfb_info->var.yres * \
        pfb_info->var.bits_per_pixel / 8;
    
    if (pfb_info->var.bits_per_pixel == 24)
        pfb_info->fix.smem_len = pfb_info->var.xres * pfb_info->var.yres * 4;
    
    /* fb virtual address */
    pfb_info->screen_base = /*dma_alloc_wc(NULL, pfb_info->fix.smem_len, &phy_addr, 
        GFP_KERNEL); */
        dma_alloc_writecombine(pfb_info->device, pfb_info->fix.smem_len, (dma_addr_t *)&pfb_info->fix.smem_start, GFP_DMA | GFP_KERNEL);

    /* pfb_info->fix.smem_start = phy_addr; */
    phy_addr = pfb_info->fix.smem_start;

    pfb_info->fix.type = FB_TYPE_PACKED_PIXELS;
    pfb_info->fix.visual = FB_VISUAL_TRUECOLOR;

    pfb_info->fix.line_length = pfb_info->var.xres * pfb_info->var.bits_per_pixel / 8;
    if (pfb_info->var.bits_per_pixel == 24) 
        pfb_info->fix.line_length = pfb_info->var.xres * 4;
    
    /* c. fbops */
    pfb_info->fbops = &fbops;
    pfb_info->pseudo_palette = pseudo_palette;

    /* 注册fb_info */
    register_framebuffer(pfb_info);

    /* 硬件操作 */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    lcdif = devm_ioremap_resource(&pdev->dev, res);

    lcd_controller_init(lcdif, dt, bpp, 24, phy_addr);
    lcd_controller_enable(lcdif);
    gpiod_set_value(bl_gpio, 1);

    return 0;
}

static int lcd_remove(struct platform_device *pdev)
{
    /* 反过来操作 */
    /* 反注册fb_info */
    unregister_framebuffer(pfb_info);

    /* 释放fb_info */
    framebuffer_release(pfb_info);
    iounmap(plcd_regs);

    return 0;
}

static const struct of_device_id lcd_of_match[] = {
    {.compatible = "glen,lcd_drv", }, 
    { },
};
MODULE_DEVICE_TABLE(of, lcd_of_match);

static struct platform_driver lcd_driver = {
    .driver = {
        .name = "lcd",
        .of_match_table = lcd_of_match,
    },
    .probe = lcd_probe,
    .remove = lcd_remove,
};

static int __init lcd_drv_init(void)
{
    int ret = 0;
    ret = platform_driver_register(&lcd_driver);
        return ret;
    
    return 0;
}

static void __exit lcd_drv_exit(void)
{
    platform_driver_unregister(&lcd_driver);
}

module_init(lcd_drv_init);
module_exit(lcd_drv_exit);

MODULE_DESCRIPTION("Framebuffer driver for the Linux");
/* insert author information for module */
MODULE_AUTHOR("glen");
/* insert license for module */
MODULE_LICENSE("GPL");
  1. 设备树文件——在根节点下添加如下节点

fb_lcd {
		compatible = "glen,lcd_drv";
		reg = <0x021c8000 0x4000>;
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_lcdif_dat
		             &pinctrl_lcdif_ctrl
					 >;
		backlight-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
		clocks = <&clks IMX6UL_CLK_LCDIF_PIX>,
				 <&clks IMX6UL_CLK_LCDIF_APB>;
		clock-names = "pix", "axi";

		display = <&displaya>;
		displaya: display {
			bits-per-pixel = <24>;
			bus-width = <24>;
			display-timings {
				native-mode = <&timinga>;
				timinga: timinga_1024x600 {
				clock-frequency = <51200000>;
				hactive = <1024>;
				vactive = <600>;
				hfront-porch = <160>;
				hback-porch = <140>;
				hsync-len = <20>;
				vback-porch = <20>;
				vfront-porch = <12>;
				vsync-len = <3>;

				hsync-active = <0>;
				vsync-active = <0>;
				de-active = <1>;
				pixelclk-active = <0>;
				};
			};
		};
	};
  1. 测试程序

/*
 * 文件名   :  lcd_drv_test.c
 * 作者     :  glen
 * 描述     :  lcd_drv_test应用程序
 */

#include "stdio.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include <signal.h>
#include <sys/select.h>
#include <sys/time.h>
#include <linux/input.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <linux/fb.h>

typedef struct _lcd_color {
    unsigned char blue;
    unsigned char green;
    unsigned char red;
    unsigned char alpha;
} lcd_color;

/**
 * @brief   : main函数
 * @par     : argc  argv数组元素的个数
 *            argv  参数数组
 * @retval  : 0 成功    其它 失败
 */
int main(int argc, char *argv[])
{
    int fd = 0;
    int ret = 0;
    long screensize = 0;
    struct fb_var_screeninfo vinfo;
    struct fb_fix_screeninfo finfo; 
    char *fbp = 0;
    int x = 0, y = 0;
    long location = 0;

    fd = open("/dev/fb0", O_RDWR);
    if (fd < 0) {
        printf("ERROR: Can't open framebuffer device \n");
        exit(1);
    }

    if (ioctl(fd, FBIOGET_FSCREENINFO, &finfo)) {
        printf("Error reading fixed information \n");
        exit(2);
    }

    if (ioctl(fd, FBIOGET_VSCREENINFO, &vinfo)) {
        printf("Error reading variable information \n");
        exit(3);
    }

    screensize = vinfo.xres * vinfo.yres * vinfo.bits_per_pixel / 8;

    fbp = (char *)mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if ((int)fbp == -1) {
        printf("ERROR: failed to map framebuffer device to memory.\n");
        exit(4);
    }

    int i = 0;
    lcd_color clear_color = {0, 0, 255, 0};
    clear_color.red = 255;
    clear_color.green = 0;
    clear_color.blue = 0;
    for (i = 0; i < screensize; i += 4) 
        *((lcd_color *)(fbp + i)) = clear_color;

    usleep(1000*2000);

    clear_color.red = 0;
    clear_color.green = 255;
    clear_color.blue = 0;
    for (i = 0; i < screensize; i += 4) 
        *((lcd_color *)(fbp + i)) = clear_color;

    usleep(1000*2000);

    clear_color.red = 0;
    clear_color.green = 0;
    clear_color.blue = 255;
    for (i = 0; i < screensize; i += 4) 
        *((lcd_color *)(fbp + i)) = clear_color;

    usleep(1000*2000);

    munmap(fbp, screensize);

    /* 关闭文件 */
    ret = close(fd);

    return 0;
}

  1. 试验

基于正点原子Linux Alpha开发板平台,执行下面操作,按应用程序执行屏幕被刷成红、绿、蓝色

/drv_module # ls
btn_drv_test    imx6_io_drv.ko  lcd_drv_test
button_drv.ko   lcd_drv.ko
/drv_module # insmod lcd_drv.ko
Console: switching to colour frame buffer device 128x37
/drv_module # ./lcd_drv_test

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值