首先编译环境配置
linux 2.6.32源码
arm-linux-gcc 交叉编译器
有mini2440的根文件系统
这里默认环境都配好了
打开linux源码文件夹,进入后,找到对应开发板的配置文件
将其复制一份重命名为.config
cp config_mini2440_n35 .config
然后执行内核配置,更加自己需要增减模块后退出
make menuconfig
将默认的LCD配置去掉,换上我们自己的驱动 完成后 exit退出
找到Device Drivers ->Graphics support->support for frame buffer devices->
去掉默认的s3c2440 lcd frame buffer support
勾上我们自己添加的mylcd lcd frame buffer support
这里讲讲怎么添加自己的选项
打开目录 linux2.6.32/drivers/video
修改里面的Kconfig文件和makefile文件
在Kconfig中找到config FB_S3C2410附近的行添加
config FB_MYLCD
tristate "MY LCD framebuffer support"
depends on FB && ARCH_S3C2410
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
---help---
Frame buffer driver for the built-in LCD controller in the Samsung
S3C2410 processor.
This driver is also available as a module ( = code which can be
inserted and removed from the running kernel whenever you want). The
module will be called s3c2410fb. If you want to compile it as a module,
say M here and read <file:Documentation/kbuild/modules.txt>.
If unsure, say N.
添加后是这样
打开makefile
在最下面添加,这里的mylcd2是我写的驱动文件,也是在此目录下的
obj-$(CONFIG_FB_MYLCD) += mylcd2.o
添加后是这样(obj-m 表示模块编译,obj-y 表示编译进内核)
接下来下lcd的驱动,在linux2.6.32/drivers/video目录下,创建mylcd2.c文件
#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/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>
#include <asm/mach/map.h>
#include <mach/regs-lcd.h>
#include <mach/regs-gpio.h>
#include <mach/fb.h>
/*LCD : 240*320 */
#define LCD_xres 240 //LCD 行分辨率
#define LCD_yres 320 //LCD列分辨率
/*
根据像素时钟频率计算行频(HSF)和场频(VSF):
一行刷新率 HSF=VCLK÷[(HSPW+1)+(HSPD+1)+(HFPD+1)+(HOZVAL+1)]
帧率 VSF=HSF÷[(VSPW+1)+(VBPD+1)+(VFPD+1)+(LINEVAL+1)]
*/
//LCDCON1
#define LCD_CLKVAL 8 //[17:8] 5.6MHZ的像素时钟频率=8
#define LCD_PNRMODE 3 //[6:5] =0b11 tft
#define LCD_BPPMODE 12 //[4:1] =0b1100 16bpp
//LCDCON2
#define LCD_VBPD (1-1) //[31:24] 这个值越大 显示的图像就越往下偏,根据屏幕的典型值配置即可
#define LCD_LINEVAL (LCD_yres-1) //[23:14]
#define LCD_VFPD (5-1) //[13:6]
#define LCD_VSPW (1-1) //[5:0]
//LCDCON3
#define LCD_HBPD (36-1) //[25:19] 这个值越大 显示的图像就越往右偏
#define LCD_HOZVAL (LCD_xres-1) //[18:8]
#define LCD_HFPD (19-1)//(2-1) //[7:0]
//LCDCON4
#define LCD_HSPW (5-1) //[7:0]
//LCDCON5
#define LCD_FRM565 1 //[11] =0,5:5:5:1; =1,5:6:5
#define LCD_INVVCLK 1 //[10]
#define LCD_INVLINE 1 //[9]
#define LCD_INVVFRAME 1 //[8]
#define LCD_BSWP 0 //[1]
#define LCD_HWSWP 1 //[0]
/* GPIO prot */
static unsigned long *GPBcon;
static unsigned long *GPCcon;
static unsigned long *GPDcon;
static unsigned long *GPGcon; //GPG4:控制LCD信号
static unsigned long *GPBdat; //GPB0: 控制背光
/* LCD control */
struct lcd_reg{
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 struct lcd_reg *lcd_reg;
static struct fb_info *my_lcd; //定义一个全局变量
static u32 pseudo_palette[16]; //调色板数组,被fb_info->pseudo_palette调用
static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf)
{
/*内核中的单色都是16位,默认从左到右排列,比如G颜色[0x1f],那么chan就等于0XF800*/
chan &= 0xffff;
chan >>= 16 - bf->length; //右移,将数据靠到位0上
return chan << bf->offset; //左移一定偏移值,放入16色数据中对应的位置
}
static int my_lcdfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *info) //设置调色板函数,供内核调用
{
unsigned int val;
if (regno >=16) //调色板数组不能大于15
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);
((u32 *)(info->pseudo_palette))[regno] = val; //放到调色板数组中
return 0;
}
static struct fb_ops my_lcdfb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = my_lcdfb_setcolreg,//调用my_lcdfb_setcolreg()函数,来设置调色板fb_info-> pseudo_palette
.fb_fillrect = cfb_fillrect, //填充矩形
.fb_copyarea = cfb_copyarea, //复制数据
.fb_imageblit = cfb_imageblit, //绘画图形,
};
static int lcd_init(void)
{
/*1.申请一个fb_info结构体*/
my_lcd= framebuffer_alloc(0,0);
/*2.设置fb_info*/
/* 2.1设置固定的参数fb_info-> fix */
/*my_lcd->fix.smem_start 物理地址后面注册MDA缓存区设置*/
strcpy(my_lcd->fix.id, "mylcd"); //名字
my_lcd->fix.smem_len =LCD_xres*LCD_yres*2; //地址长
my_lcd->fix.type =FB_TYPE_PACKED_PIXELS;
my_lcd->fix.visual =FB_VISUAL_TRUECOLOR; //真彩色
my_lcd->fix.line_length =LCD_xres*2; //LCD 一行的字节
/* 2.2 设置可变的参数fb_info-> var */
my_lcd->var.xres =LCD_xres; //可见屏X 分辨率
my_lcd->var.yres =LCD_yres; //可见屏y 分辨率
my_lcd->var.xres_virtual =LCD_xres; //虚拟屏x分辨率
my_lcd->var.yres_virtual =LCD_yres; //虚拟屏y分辨率
my_lcd->var.xoffset = 0; //虚拟到可见屏幕之间的行偏移
my_lcd->var.yoffset = 0; //虚拟到可见屏幕之间的行偏移
my_lcd->var.bits_per_pixel = 16; //像素为16BPP
my_lcd->var.grayscale = 0; //灰色比例
my_lcd->var.red.offset = 11;
my_lcd->var.red.length = 5;
my_lcd->var.green.offset = 5;
my_lcd->var.green.length = 6;
my_lcd->var.blue.offset = 0;
my_lcd->var.blue.length = 5;
/* 2.3 设置操作函数fb_info-> fbops */
my_lcd->fbops = &my_lcdfb_ops;
/* 2.4 设置fb_info 其它的成员 */
/*my_lcd->screen_base 虚拟地址在后面注册MDA缓存区设置*/
my_lcd->pseudo_palette =pseudo_palette; //保存调色板数组
my_lcd->screen_size =LCD_xres * LCD_yres *2; //虚拟地址长
/*3 设置硬件相关的操作*/
/*3.1 配置LCD引脚*/
GPBcon = ioremap(0x56000010, 8);
GPBdat = GPBcon+1;
GPCcon = ioremap(0x56000020, 4);
GPDcon = ioremap(0x56000030, 4);
GPGcon = ioremap(0x56000060, 4);
*GPBcon &=~(0x03<<(1*2));
*GPBcon |= (0x01<<(1*2)); //PGB1背光
*GPBdat &=~(0X1<<1); //关背光
*GPCcon =0xaaaaaaaa;
*GPDcon =0xaaaaaaaa;
*GPGcon |=(0x03<<(4*2)); //GPG4:LCD信号
/*3.2 根据LCD手册设置LCD控制器,参考之前的裸机驱动*/
lcd_reg=ioremap(0X4D000000, sizeof( lcd_reg) );
//240*320
/*
TFT LCD的TTL信号
VSYNC 垂直同步信号
HSYNC 水平同步信号
HCLK 像素时钟信号
VD[23:0] 数据信号
LEND 行结束信号(非必须)
PWREN 电源开关信号
HSYNC表示“是跳到最左边的时候了”,VSYNC表示“是跳到最上边的时候了”
VSYNC可以理解为一帧图像有效地信号,在它低电平有效的时候,要连续发出(LINEVAL+1)
个行有效数据HSYNC信号;而在HSYNC有效地时候,要连续发出(HOZVAL+1)个像素有效数据,
这时像素数据的频率是由VCLK控制,VCLK是作为时序图的基准信号。我们开发板上用的屏是
240*320的3.5寸触摸屏,那么LINEVAL+1=240,HOZVAL+1=320。
lcd时钟信号频率,是像素时钟
VCLK=HCLK/[(CLKVAL+1)*2]
TFT:VCLK = 101.25M / [(CLKVAL + 1) × 2] = 6.4M CLKVAL = 7
bit[17:8] CLKVAL = 7
bit[6:5] PNRMODE=0b11 (tft lcd)
bit[4:1] BPPMODE=0b1100 (tft 16bpp)
bit[0] ENDIV=0 lcd使能信号,一开始先关闭使能
*/
lcd_reg->lcdcon1 = (LCD_CLKVAL<<8) | (3<<5) | (12<<1);
/*
水平信号参数
(T0-T2-T1=VBPD+1)
bit[31:24] VBPD=1 (upper_margin)VBPD表示VSYNC信号脉冲之后,还要经过VBPD+1个HSYNC信号周期,有效行才出现。
bit[23:14] LINEVAL=319 y-1
bit[13:6] VFPD=1 (T5-T2=vfpd+1)(lower_margin)VFPD表示在连续发出LINEVAL+1(lineecal=y-1)行有效数据后,还要经过VFPD+1个无效行,之后完整的一帧结束。
bit[5:0] VSPW=0 VSPW表示VSYNC信号的脉冲宽度为(VSPW+1)个,1帧图像周期
*/
//lcd_reg->lcdcon2 = (1<<24) | (319<<14) | (1<<6) | (1<<0);
lcd_reg->lcdcon2 = (LCD_VBPD<<24) | (LCD_LINEVAL<<14) | (LCD_VFPD<<6) | (LCD_VSPW<<0);
/*
hoz信号参数
T6-T7-T8
bit[25:19] HBPD=19 HBPD表示在HSYNC信号脉冲之后,还要经过HBPD+1个VCLK信号周期,有效像素才能够出现。
bit[18:8] HOZVAL=239
bit[7:0] HFPD==9 (T8-T11)HFPD表示在连续发出HOZVAL+1个像素的有效数据之后,还要发出(HFPD+1)个无效像素,完整的一行结束。
*/
//lcd_reg->lcdcon3 = (19<<19) | (239<<8) | (9<<0);
lcd_reg->lcdcon3 = (LCD_HBPD<<19) | (LCD_HOZVAL<<8) | (LCD_HFPD<<0);
/*
垂直信号参数
bit[7:0] HSPW=9 HSPW表示HSYNC信号脉冲宽度为(HSPW+1)个VCLK信号周期,即HSPW+1个像素是无效的。
*/
lcd_reg->lcdcon4 = (LCD_HSPW<<0);
/*
引脚的极性设置
bit[11] FRM565=1
bit[10] INVVCLK=1 上升沿取数据
bit[9] INVVLINE=1 水平同步信号反相
bit[8] INVVFRAME=1 垂直同步信号反相
bit[7] INVVD=0 数据信号不需要反相
bit[6] INVVDEN=0 数据使能信号不需要反相
bit[5] INVPWREN=0 电源使能信号不需要反相
bit[3] PWREN=0 先不使用电源使能信号
bit[1:0] BSWP=0 HWSWP=1 设置字节排列的顺序
lcd_reg->lcdcon5 = (LCD_FRM565<<11) | (LCD_INVVCLK<<10) | (LCD_INVLINE<<9) | (LCD_INVVFRAME<<8) | (0<<7) | (0<<6) | (0<<5) | (0<<3) | (LCD_BSWP<<1) | (LCD_HWSWP<<0);
lcd_reg->lcdcon1 &=~(1<<0); // 关闭PWREN信号输出
lcd_reg->lcdcon5 &=~(1<<3); //禁止PWREN信号
/* 3.3 分配显存(framebuffer),把地址告诉LCD控制器和fb_info*/
my_lcd->screen_base=dma_alloc_writecombine(NULL,my_lcd->fix.smem_len, &my_lcd->fix.smem_start, GFP_KERNEL);
/*lcd控制器的地址必须是物理地址*/
lcd_reg->lcdsaddr1 = my_lcd->fix.smem_start >> 1;
lcd_reg->lcdsaddr2 = my_lcd->fix.smem_start;
lcd_reg->lcdsaddr2 += LCD_yres*LCD_xres*2;
lcd_reg->lcdsaddr2 >>= 1;
lcd_reg->lcdsaddr3 = (0<<11)|(LCD_xres<<0); //一行的长度,单位是半字
//PAGEWIDTH [10:0]:保存LCD一行占的宽度(半字数为单位)
printk(KERN_EMERG"ADDRstart = 0x%08lx\n", my_lcd->fix.smem_start);
printk(KERN_EMERG"ADDRend = 0x%08lx\n", my_lcd->fix.smem_start+LCD_yres*LCD_xres*2);
printk(KERN_EMERG"LCDSADDR1 = 0x%08lx\n", lcd_reg->lcdsaddr1);
printk(KERN_EMERG"LCDSADDR2 = 0x%08lx\n", lcd_reg->lcdsaddr2);
printk(KERN_EMERG"LCDSADDR3 = 0x%08lx\n", lcd_reg->lcdsaddr3);
lcd_reg->tconsel &=~(1<<0);
/*4开启LCD,并注册fb_info: register_framebuffer()*/
/*4.1 直接在init函数中开启LCD(后面讲到电源管理,再来优化)*/
lcd_reg->lcdcon1 |=1<<0; //输出PWREN信号
lcd_reg->lcdcon5 |=1<<3; //允许PWREN信号
*GPBdat |=(0X1<<1); //开背光
/*4.2 注册fb_info*/
register_framebuffer(my_lcd);
return 0;
}
static int lcd_exit(void)
{
/* 1卸载内核中的fb_info*/
unregister_framebuffer(my_lcd);
/*2 控制LCDCON1关闭PWREN信号,关背光,iounmap注销地址*/
lcd_reg->lcdcon1 &=~(1<<0); // 关闭PWREN信号输出
lcd_reg->lcdcon5 &=~(1<<3); //禁止PWREN信号
*GPBdat &=~(0X1<<1); //关背光
iounmap(GPBcon);
iounmap(GPCcon);
iounmap(GPDcon);
iounmap(GPGcon);
/*3.释放DMA缓存地址dma_free_writecombine()*/
dma_free_writecombine(0,my_lcd->screen_size,my_lcd->screen_base,my_lcd->fix.smem_start);
/*4.释放注册的fb_info*/
framebuffer_release(my_lcd);
return 0;
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
回到linux2.6.32目录
执行命令,编译内核
make zImage -j4
编译模块
make modules -j4
编译好内核后,到目录 linux2.6.32/arch/arm/boot
找到zImage
复制出来,通过supervivi写入内核到flash
开发板norflash启动
选择 k 命令
到DWN工具上传内核
开机即可看到linux的小企鹅图标出来了
这里可以直接对fb文件进行操作
输入以下命令可以看到屏幕上打印出hello
echo HELLO >> /dev/tty1
输入以下命令是乱码
cat /dev/urandom >> /dev/fb0
遇到的问题:
1.关于内核启动不了
首先linux中的路径linux2.6.32/arch/arm/mach-mini2440.c文件中的分区确保与supervivi一致
static struct mtd_partition friendly_arm_default_nand_part[] = {
[0] = {
.name = "vivi",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "param",
.offset = 0x00040000,
.size = 0x00010000,
},
[2] = {
.name = "kernel",
.offset = 0x00050000,
.size = 0x002b0000,
},
[3] = {
.name = "root",
.offset = 0x00300000,
.size = 0x03d00000, //
},
[4] = {
.name = "nand",
.offset = 0x00000000,
.size = 0x04000000, //
}
};
在supervivi中确保分区的正确性(其中大小根据自己实际调整也行)
查询分区
##### FriendlyARM BIOS for 2440 #####
[x] bon part 0 320k 2368k
[v] Download vivi
[k] Download linux kernel
[y] Download root_yaffs image
[c] Download root_cramfs image
[a] Absolute User Application
[n] Download Nboot
[e] Download Eboot
[i] Download WinCE NK.nb0
[w] Download WinCE NK.bin
[d] Download & Run
[z] Download zImage into RAM
[g] Boot linux from RAM
[f] Format the nand flash
[p] Partition for Linux
[b] Boot the system
[s] Set the boot parameters
[t] Print the TOC struct of wince
[u] Backup NAND Flash to HOST through USB(upload)
[r] Restore NAND Flash from HOST through USB
[q] Goto shell of vivi
Enter your selection: q
Supervivi>
Supervivi>
Supervivi>
Supervivi>
Supervivi> part show
正确的分区
name : offset size flag
------------------------------------------------------------
vivi : 0x00000000 0x00040000 0
param : 0x00040000 0x0010000 0
kernel : 0x00050000 0x00250000 0
root : 0x00300000 0x03d00000 0
启动参数确保正确
##### FriendlyARM BIOS for 2440 #####
[x] bon part 0 320k 2368k
[v] Download vivi
[k] Download linux kernel
[y] Download root_yaffs image
[c] Download root_cramfs image
[a] Absolute User Application
[n] Download Nboot
[e] Download Eboot
[i] Download WinCE NK.nb0
[w] Download WinCE NK.bin
[d] Download & Run
[z] Download zImage into RAM
[g] Boot linux from RAM
[f] Format the nand flash
[p] Partition for Linux
[b] Boot the system
[s] Set the boot parameters
[t] Print the TOC struct of wince
[u] Backup NAND Flash to HOST through USB(upload)
[r] Restore NAND Flash from HOST through USB
[q] Goto shell of vivi
Enter your selection: s
##### Parameter Menu #####
[r] Reset parameter table to default table
[s] Set parameter
[v] View the parameter table
[w] Write the parameter table to flash memeory
[q] Quit
Enter your selection: v
Number of parameters: 9
name : hex integer
-------------------------------------------------------------
mach_type : 000007cf 1999
media_type : 00000003 3
boot_mem_base : 30000000 805306368
baudrate : 0001c200 115200
xmodem : 00000001 1
xmodem_one_nak : 00000000 0
xmodem_initial_timeout : 000493e0 300000
xmodem_timeout : 000f4240 1000000
boot_delay : 01000000 16777216
Linux command line: noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0,115200
这里的root=/dev/mtdblokck3,要看自己的分区实际是几号0/1/2/3,本分区root刚好排在第4个所以是3,如果填错了是找不到linuxrc文件的,就是没法运行控制台
2.LCD图像显示偏移,有黑边,这里是lcd控制器的参数没设置对,如mylcd2.c文件中最开头宏定义注释
所说,按lcd手册值设置即可,也可手动微调下
#define LCD_VBPD (1-1) //[31:24] 这个值越大 显示的图像就越往下偏,根据屏幕的典型值配置即可
#define LCD_HBPD (36-1) //[25:19] 这个值越大 显示的图像就越往右偏
3.图像下半部分显示到上面,左边有部分显示到右边
这个是因为在把显存地址给lcd控制器时,值给错误导致的
lcdsaddr1、lcdsaddr2、lcdsaddr3,检查这三个寄存器所给的值是否正确