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");
四、测试
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
- Frame buffer Device --->
需要注意的是:
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]
参考文章