笔者使用开发板是TQ2440,所针对的是天嵌4.3寸TFT屏幕,基于Linux-2.6.30.4内核,驱动代码如下:
#include <linux/module.h>
#include <linux/kernel.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/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/div64.h>
#include <asm/mach/map.h>
#include <mach/regs-lcd.h>
#include <mach/regs-gpio.h>
#include <mach/fb.h>
#ifdef CONFIG_PM
#include <linux/pm.h>
#endif
#include "s3c2410fb.h"
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 tconsel;
};
static int s3c_lcdfb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info);
static struct fb_ops s3c_lcdfb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = s3c_lcdfb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static volatile struct lcd_regs *lcdregs;
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 u32 pseudo_palette[16];
static int s3c_lcdfb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info)
{
unsigned int val;
if (regno >= 16)
return 1;
else if (regno < 16) {
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;
}
static struct fb_info *s3cfb_info;
//GPC
static volatile unsigned long *gpccon = NULL;
//static volatile unsigned long *gpcdat = NULL;
static volatile unsigned long *gpcup = NULL;
//GPD
static volatile unsigned long *gpdcon = NULL;
//static volatile unsigned long *gpddat = NULL;
static volatile unsigned long *gpdup = NULL;
//GPG
static volatile unsigned long *gpgcon = NULL;
static volatile unsigned long *gpgdat = NULL;
static volatile unsigned long *gpgup = NULL;
static void lcd_gpio_init(void)
{
gpccon = ioremap(0x56000020,4);
//gpcdat = gpccon + 1;
gpcup = gpccon + 2;
*gpccon = 0xaaaaaaaa;
*gpcup = 0x00000000;
gpdcon = ioremap(0x56000030,4);
//gpddat = gpdcon + 1;
gpdup = gpdcon + 2;
*gpdcon = 0xaaaaaaaa;
*gpdup = 0x00000000;
//Enable LCD power
gpgcon = ioremap(0x56000060,4);
gpgdat = gpgcon + 1;
gpgup = gpgcon + 2;
//GPG4 is setted as LCD_PWREN 0:Pull-up enable;1:Pull-up disable
//set GPG4 Pull-up disable
*gpgup &= (~(1<<4));
*gpgup |= (1<<4);
//set GPG4 input mode
*gpgcon &= (~(3<<8));
*gpgcon |= (3<<8);
//set GPG4 = 1
//*gpgdat |= (1<<4);
}
static int lcd_init(void)
{
//1. 分配一个fb_info
s3cfb_info = framebuffer_alloc(0, NULL);
//2. 设置fb_info
//2.1 设置固定的参数
strcpy(s3cfb_info->fix.id, "mylcd"); //s3cfb_info->fix.id = "mylcd"; mylcd -- 驱动名字
s3cfb_info->fix.smem_len = 480 * 272 * 32 / 8; //显存的长度 /* TQ2440的LCD位宽是24,但是2440里会分配4字节即32位(浪费1字节) */
s3cfb_info->fix.type = FB_TYPE_PACKED_PIXELS;
s3cfb_info->fix.type_aux = 0; //附加的...
s3cfb_info->fix.xpanstep = 0;
s3cfb_info->fix.ypanstep = 0;
s3cfb_info->fix.ywrapstep = 0;
s3cfb_info->fix.visual = FB_VISUAL_TRUECOLOR; //TFT
s3cfb_info->fix.line_length = 480 * 4; //一行的字节数
//2.2 设置可变的参数
s3cfb_info->var.xres = 480;
s3cfb_info->var.yres = 272;
s3cfb_info->var.xres_virtual = 480;
s3cfb_info->var.yres_virtual = 272;
s3cfb_info->var.bits_per_pixel = 32; //每个像素占用的bit数
//RGB设置
s3cfb_info->var.red.offset = 16;
s3cfb_info->var.red.length = 8;
s3cfb_info->var.red.msb_right = 0; //0 - 最重要的一位在左边,1- 最重要的一位在右边
s3cfb_info->var.green.offset = 8;
s3cfb_info->var.green.length = 8;
s3cfb_info->var.green.msb_right = 0; //0 - 最重要的一位在左边,1- 最重要的一位在右边
s3cfb_info->var.blue.offset = 0;
s3cfb_info->var.blue.length = 8;
s3cfb_info->var.blue.msb_right = 0; //0 - 最重要的一位在左边,1- 最重要的一位在右边
s3cfb_info->var.activate = FB_ACTIVATE_NOW;
//2.3 设置fbops操作函数
s3cfb_info->fbops = &s3c_lcdfb_ops;
//2.4 其他设置
s3cfb_info->pseudo_palette = pseudo_palette;
//s3cfb_info->screen_base = ; //显存的虚拟地址,s3cfb_info->fix.smem_start是显存的物理地址
s3cfb_info->screen_size = 480 * 272 * 32 / 8;
//3. 硬件相关设置
//3.1 配置GPIO用于LCD
//管脚配置,根据原理图和数据手册
lcd_gpio_init();
//3.2 根据LCD手册设置LCD控制器
lcdregs = ioremap(0x4D000000, sizeof(struct lcd_regs));
/*
对于LCDCON1,一些有用的位:
bit[27:18] -- LINECNT,只读,每输出一个有效行减1,从LCDECNT减到0
bit[17:8] -- CLKVAL,用于设置VCLK(像素时钟),VCLK = HCLK / [(CLKVAL + 1) * 2]
对于时钟参数的设置,参考芯片手册可以知道VCLK的典型值为9.0MHz,最大值为15MHZ,
这里就以10.0MHz作为参考,HCLK为100时,CLKVAL取值4
bit[6:5] -- PNRMODE,设置LCD的类型,对于TFT LCD设为0b11
bit[4:1] -- 设置BPP,对于TFT,32bpp对应0b1101
bit[0] -- ENVID,LCD信号输出使能位,0:禁止;1:使能
*/
lcdregs->lcdcon1 = (4<<8) | (3<<5) | (0x0d<<1) | 0; //?LINECNT没意义?
/*
对于LCDCON2,一些有用的位:(以TQ4.3 LCD为例)
bit[31:24] -- VBPD,VSYNC信号脉冲之后,还要经过(VBPD + 1)个HSYNC信号周期(无效区域),有效行数据才出现。VBPD = 2
bit[23:14] -- LINEVAL,LCD的垂直宽度:(LINEVAL + 1)行。LINEVAL = 272 - 1
bit[13:6] -- VFPD,一帧中的有效数据完结后,到下一个VSYNC信号有效前的无效行数目:(VFPD + 1)。VFPD = 4
bit[5:0] -- VSPW,表示VSYNC信号的脉冲宽度为(VSPW + 1)个HSYNC信号周期,即(VSPW + 1)行,这(VSPW + 1)行的数据无效。VSPW = 8
*/
/*
#define HBPD (10) //水平同步信号的后肩
#define HFPD (19) //水平同步信号的前肩
#define HSPW (30) //水平同步信号的脉宽
*/
lcdregs->lcdcon2 = (2<<24) | (271<<14) | (4<<6) | (8);
/*
对于LCDCON3,一些有用的位:(以TQ4.3 LCD为例)
bit[25:19] -- HBPD,HSYNC信号脉冲之后,还要经过(HBPD + 1)个VCLK信号周期(无效区域),有效像素才出现。HBPD = 10
bit[18:8] -- HOZVAL,LCD的水平宽度:(HOZVAL + 1)列(像素)。HOZVAL = 480 - 1
bit[7:0] -- HFPD,一帧中的有效数据完结后,到下一个VSYNC信号有效前的无效行数目:(VFPD + 1)。HFPD = 19
*/
lcdregs->lcdcon3 = (10<<19) | (479<<8) | 19;
/*
对于LCDCON3,一些有用的位:(以TQ4.3 LCD为例)
bit[7:0] -- HSPW,表示脉冲宽度为(HSPW + 1)个VCLK周期。HSPW = 30
*/
lcdregs->lcdcon4 = 30;
/* 对于lcdcon5,设置内容比较多,此处略过 */
lcdregs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (0<<7) | (0<<6) | (1<<3) | (0<<1) | (1);
//3.3 显存设置,分配framebuffer(从内存中分配),并把地址告诉LCD控制器 对于PC机,它往往有特别的显存,512M之类的
//分配显存很重要的一点是要让物理地址连续,毕竟DMA没有那么聪明
//返回所分配存储的虚拟地址
//dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
s3cfb_info->screen_base = dma_alloc_writecombine(NULL, s3cfb_info->fix.smem_len, &s3cfb_info->fix.smem_start, GFP_KERNEL);
/*
对于lcdsaddr1,有用的位:
bit[29:21] -- LCDBANK,用来保存帧内存起始地址A[30:22]
bit[[20:0] -- LCDBASEU,对于TFTLCD,用来保存视口所对应的内存起始地址A[21:1],也即帧缓冲区
*/
lcdregs->lcdsaddr1 = (s3cfb_info->fix.smem_start >> 1) & (~(3<<30));
/*
对于lcdsaddr2,有用的位:
bit[[20:0] -- LCDBASEL,对于TFTLCD,用来保存视口所对应的内存结束地址A[21:1],也即帧缓冲区
LCDBASEL = LCDBASEU + (PAGEWIDTH + OFFSIZE) * (LINEVAL + 1)
*/
lcdregs->lcdsaddr2 = ((s3cfb_info->fix.smem_start + s3cfb_info->fix.smem_len) >> 1) & (0x1fffff);
/*
对于lcdsaddr3,有用的位:
bit[21:11] -- OFFSIZE,表示上一行最后一个有效像素数据与下一行第一个有效像素数据间地址差值,即以半字为单位的地址差
bit[10:0] -- PAGEWIDTH,视口的宽度,以半字为单位
*/
lcdregs->lcdsaddr3 = ((s3cfb_info->var.xres - s3cfb_info->var.xres_virtual)<<11) | (s3cfb_info->var.xres);
/*
对于tpal,有用的位:
bit[24] -- TPALEN,调色板寄存器使能位。0:禁止;1:使能
bit[23:0] -- TPALVAL,颜色值,TPALVAL[23:16]:红色;TPALVAL[15:8]:绿色;TPALVAL[7:0]:蓝色
*/
//使能LCD
lcdregs->lcdcon1 |= (1<<0);
lcdregs->lcdcon5 |= (1<<3); /* 使能LCD本身: LCD_PWREN */
//使能背光灯
//...
//4. 注册
register_framebuffer(s3cfb_info);
}
static void lcd_exit(void)
{
unregister_framebuffer(s3cfb_info);
lcdregs->lcdcon1 &= ~(1<<0); /* 关闭LCD控制器 */
lcdregs->lcdcon1 &= ~(1<<3); /* 关闭LCD本身 */
dma_free_writecombine(NULL, s3cfb_info->fix.smem_len, s3cfb_info->screen_base, s3cfb_info->fix.smem_start);
iounmap(lcdregs);
iounmap(gpccon);
iounmap(gpdcon);
iounmap(gpgcon);
framebuffer_release(s3cfb_info);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
将此驱动程序成模块,然后将模块文件拷贝到根文件系统烧录到开发板中~
然后就是测试了,但问题还没这么简单!
因为对于内核编译,笔者使用的.config拷贝自s3c2410_defconfig,默认情况下已经将默认LCD驱动编译进内核了,为了排除干扰,得取消这个:执行“make menuconfig”取消对s3c2410 lcd的支持,即改为手动加载自定义的LCD驱动,如下(以Linux-2.6.30.4为例):
Device Drivers
Graphics support
Support for frame buffer device
[M] S3C2410 LCD framebuffer support
然后重新编译内核并烧写到开发板中。
除此之外,参考韦东山Linux学习视频,还得将cfbcopyarea.ko、cfbfillrect.ko、cfbimgblt.ko三个文件拷贝到根文件系统中,它们不是真正的驱动,只是封装了一些函数,而上文LCD驱动代码会用到它们,如下:
static struct fb_ops s3c_lcdfb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = s3c_lcdfb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
根据视频指示,对配置文件进行上述设置之后,执行“make modules”会在drivers/video/下产生所需要的ko文件,但事实并非如此,啥都没有产生!
笔者猜想是不是Linux版本差异问题(韦东山Linux学习视频采用的是Linux-2.6.22.6),所以笔者查看drivers/video/Makefile,看到如下信息:
obj-$(CONFIG_FB_CFB_FILLRECT) += cfbfillrect.o
obj-$(CONFIG_FB_CFB_COPYAREA) += cfbcopyarea.o
obj-$(CONFIG_FB_CFB_IMAGEBLIT) += cfbimgblt.o
再回到内核根目录下查看.config文件得到如下信息:
CONFIG_FB_CFB_FILLRECT)=y
CONFIG_FB_CFB_COPYAREA=y
CONFIG_FB_CFB_IMAGEBLIT=y
如此看来,这三个文件依然被编译进内核中了。于是笔者无知手贱修改drivers/video/Makefile如下:
obj-m += cfbfillrect.o
obj-m += cfbcopyarea.o
obj-m += cfbimgblt.o
再重新执行“make modules”果然得到三个所需的ko文件,然后把它们拷贝到根文件系统中,在开发板下加载,却加载失败,读到如下信息:
笔者乱了,百度google求答案无果,纠结了半天,这几个云里雾里的报错信息令人不安。于是笔者启用Linux-2.6.22.6内核,执行“make menuconfig”命令对配置文件修改如下:
Device Drivers
Graphics support
[M] S3C2410 LCD framebuffer support
编译内核,然后编译模块,果然,和视频中的展示一样,得到了所需的三个ko文件,以为成了,然后拷贝到根文件系统并烧录到开发板中加载,依然出现了如下错误:
思忖良久,估摸着是不是内核已经存在这些函数了?!最终终于发现了问题所在!
对于Linux-2.6.22.6版本内核,在使用s3c2410_defconfig配置文件的基础上,若想防止LCD驱动的相关一切被编译进内核,除了上文所述修改之外,还得在“make menuconfig”中去掉两个选项(红框标记),如下:
默认情况下,这两项是被选中的,在启动信息中可以看到默认LCD驱动程序被加载进内核的提示信息,执行“ls -l /dev/fb*”可以看到存在/dev/fb0这个节点,如图两项被去掉之后,这个问题便不存在了~
对于Linux-2.6.30.4版本的内核,cfbcopyarea、cfbfillrect、cfbimgblt这三个LCD驱动程序所需要的程序模块是被默认加载到内核中的,所以不需要进行韦东山Linux学习视频上的一些手动加载cfbcopyarea.ko、cfbfillrect.ko、cfbimgblt.ko这些步骤~
其实,上文所示的加载cfbcopyarea.ko等的提示信息表示“表示内核里已经有这些模块,就不用再insmod了”,问题就是这么简单!
问题找出来了,手动加载自行设计的LCD驱动便不是问题了,然后便是测试工作了!下一节见~