Linux移植开发指南学习笔记(六) LCD背光驱动和显示驱动 (Fedora9)
1、添加LCD背光驱动
到目前为止,我们一直都在命令行下移植,查看结果,LCD 屏幕上似乎总是如伸手不见五指的黑夜(
但我的是亮的,成功添加背光驱动后可以来通过echo 0 > /dev/backlight 来开关背光),神秘而又无可奈何,从这一小节开始,我们就要打开这道神秘之门了。
在mini2440/micro2440开发板中,LCD 背光是通过CPU 的LCD_PWR 引脚来控制的,从原理图中可以看出,它对应于GPG4,如图
当LCD_PWR 输出为高电平“1”时,将打开背光;当输出为低电平“0”时,将关闭背光( 注意:这里只是打开和关闭背光,而并没有背光亮度的调节作用)。
现在,我们需要增加一个简单的背光驱动,以便能够通过软件便可简单的控制背光的开关。我们要达到的目的是:在命令终端通过向背光设备发送偶数比如“0”便可关闭背光,发送奇数比如“1”便可打开背光,这样使用起来就方便多了,而不需要专门的应用程序控制它,正如在用户手册中所描述的方法(2.5.10 控制LCD 的背光) :
提示:LCD 背光设备文件:/dev/backlight
在命令行种输入:echo 0 > /dev/backlight 可以关闭 LCD背光。
在命令行种输入:echo 1 > /dev/backlight 可以打开 LCD背光。
为了实现这点,我们在linux-2.6.32.2/drivers/video 目录增加一个mini2440_backlight.c文件,内容如下:
; 以下头文件可能并不是每一个都必须的,但多余的并不会影响驱动程序的内容
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#undef DEBUG
//#define DEBUG
#ifdef DEBUG
#define DPRINTK(x...) {printk(__FUNCTI ON__"(%d): ",__LINE__);printk(##x);}
#else
#define DPRINTK(x...) (void)(0)
#endif
; 定义背光驱动的名称为backligh,将会出现在/dev/backlight
#define DEVICE_NAME "backlight"
; 定义背光变量bl_state,以记录背光的开关状态
static unsigned int bl_state;
; 设置背光开关的函数,主要是翻转背光变量bl_state
static inline void set_bl(int state)
{
bl_state = !!state; // 翻转 bl_state变量
s3c2410_gpio_setpin(S3C2410_GPG(4), bl_state); // 把结果写入背光所用的寄存器 GPG4
}
; 获取背光状态
static inline unsigned int get_bl(void)
{
return bl_state;
}
; 从应用程序读取参数,并传递到内核中
static ssize_t dev_write(struct file *file, const char *buffer, size_t count, loff_t * ppos)
{
unsigned char ch;
int ret;
if (count == 0) {
return count;
}
; 使用 copy_from_user 函数从用户层/ 应用层读取参数
ret = copy_from_user(&ch, buffe r, sizeof ch) ? -EFAULT : 0;
if (ret) {
return ret;
}
ch &= 0x01; // 判断奇数还是偶数
set_bl(ch); // 设置背光状态
return count;
}
; 把内核参数传递给用户层/ 应用层的读函数
static ssize_t dev_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
int ret;
unsigned char str[] = {'0', '1' };
if (count == 0) {
return 0;
}
; 使用 copy_to_user 函数把内核参数传递到用户层/ 应用层
ret = copy_to_user(buffer, str + get_bl(), sizeof(unsigned char) ) ? -EFAULT : 0;
if (ret) {
return ret;
}
return sizeof(unsigned char);
}
; 设备操作集
static struct file_operations dev_fops = {
owner: THIS_MODULE,
read:dev_read,
write: dev_write,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
; 设备初始化,内核启动时就有效
static int __init dev_init(void)
{
int ret;
ret = misc_register(&misc);
printk (DEVICE_NAME"\tinitialized\n");
; 初始化背光所用的端口 GPG4 为输出
s3c2410_gpio_cfgpin(S3C2410_GPG(4), S3C2410_GPIO_OUTPUT);
; 启动内核时打开背光
set_bl(1);
return ret;
}
static void __exit dev_exit(void)
{
misc_deregister(&misc);
}
module_init(dev_init); // 注册背光驱动模块
module_exit(dev_exit); // 卸载背光驱动模块
MODULE_LICENSE("GPL");
MODULE_AUTHOR("FriendlyARM Inc.");
然后把背光配置选项加入内核配置菜单,打开 linux-2.6.32.2/drivers/video/Kconfig ,在如图位置加入:
config FB_S3C2410_DEBUG
bool "S3C2410 lcd debug messages"
depends on FB_S3C2410
help
Turn on debugging messages. Note that you can set/unset at run time
through sysfs
# 在里加入MINI2440的背光驱动配置
config BACKLIGHT_MINI2440
tristate "Backlight support for mini2440 from FriendlyARM"
depends on MACH_MINI2440 && FB_S3C2410
help
backlight driver for MINI2440 from FriendlyARM
config FB_SM501
tristate "Silicon Motion SM501 framebuffer support"
depends on FB && MFD_SM501
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
再打开linux-2.6.32.2/drivers/video/Makefile ,根据配置定义加入驱动目标文件,如图:
# the test framebuffer is last
obj-$(CONFIG_FB_VIRTUAL) += vfb.o
#video output switch sysfs driver
obj-$(CONFIG_VIDEO_OUTPUT_CONTROL) += output.o
obj-$(CONFIG_BACKLIGHT_MINI2440) += mini2440_backlight.o
这样,我们就在内核中移植好了mini2440 的背光驱动,在内核源代码根目录执行:
make menuconfig,依次选择如下子菜单:
Device Drivers --->
Graphics support --->
<*> Support for frame buffer devices --->
在这里,按空格选中我们刚刚加入的mini2440 配置项,然后退出保存内核配置菜单,
在命令行执行:make zImage
将生成arch/arm/boot/zImage ,使用 supervivi 的“k”功能把它烧写到开发板中,通过指令来打开或关闭LCD,这说明我们已经点亮了背光,只不过LCD 驱动还有些问题,接着我们将会详细的介绍如何移植LCD驱动。
同时在ls /dev/back* 可以看到 /dev/backlight
2、移植LCD显示驱动
Linux-2.6.32.2 内核已经支持S3C2440 的LCD 控制器驱动,但在此我们先介绍一下关于2440 LCD控制器以及驱动相关的LCD 的一些基础知识。
注意:在此我们只讨论TFT LCD,也就是真彩屏。
LCD 驱动中最关键的就是时钟频率( Clock frequency) 的设置,时钟频率设置不对,LCD的显示就会闪,或者根本没有显示。一般LCD的Datasheet 上会写有一个推荐的频率,比如mini2440 所用的统宝3.5”LCD ,在它的数据手册第13页,有这样一个表格:
可以看到,这里推荐的时钟频率是 6.39MHz ,近似于 6.4MHz,范围,是 5M-6.85MHz 。S3C2440 之LCD 控制器与此相关的设置为CLKVAL ,通过设置它,就可以在LCD 接口的VCLK引脚上产生LCD 所需要的时钟频率,那么CLKVAL 和VCLK 有何种关系呢?在2440手册(411页) 中,有这样一段描述:
The rate of VCLK signal depends on the CLKVAL field in the LCDCON1 register. Table 15-3 defines the relationship of VCLK and CLKVAL. The minimum value of CLKVAL is 0
接下来,手册中提供了它们的数学关系公式:
VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
那么HCLK 是多少呢?
我们的开发板运行于400Mhz,这个可以在bootloader 的源代码头文件中看到,如图
可见,FCLK:HCLK:PCLK = 1:4:8,因此得出HCLK=100Mhz,再根据上述公式得出
CLKVAL 应为:
CLKVAL=HCLK/(VCLK*2) -1
CLKVAL = 100000000 / (6400000 * 2) - 1 = 6.8
选择最接近的整数值7,并把它写入LCDCON1:17-8(注意:我们实际使用的数值是8) ,由此产生的VCLK 频率实测为5.63Mhz 左右,它也是在5-6.85Mhz 之间的数值,如图:
新内核中的pixclock 参数
在以前较老的Linux 内核中,对于LCD 寄存器的设置都是这样直接填写CLKVAL 的,但Linux-2.6.32.2 内核却不再使用这样简单直观的方式,而是通过一个称为“pixclock”的参数进行调节,它的计算变的复杂和难以理解,我们不清楚Linux 内核中关于2440部分的移植为何改变成这样的方式,这有可能是为了和X86 体系中的设置保持一致的风格,下面我们根据实际的代码进行一些推导和说明,但推导结果和我们的实际设置是并不一致的,会有一些误差。
提示:我们实际提供的pixclock 参数并不是按照以下的方式推导计算出的,而是先确定好CLKVAL 的数值,再反复尝试、猜测得到.
推算见手册
在内核中添加各种LCD类型的支持
打开arch/arm/mach-s3c2440/mach-mini2440.c ,先删除之前的LCD设备平台代码,如下:
/* LCD driver info */
static struct s3c2410fb_display smdk2440_lcd_cfg __initdata = {
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
.type = S3C2410_LC DCON1_TFT,
.width = 240,
.height = 320,
.pixclock = 166667, /* HCLK 60 MHz, divisor 10 */
.xres = 240,
.yres = 320,
.bpp = 16,
.left_margin = 20,
.right_margin = 8,
.hsync_len = 4,
.upper_margin = 8,
.lower_margin = 7,
.vsync_len = 4,
};
static struct s3c2410fb_mach_info smdk2440_fb_info __initdata = {
.displays = &smdk2440_l cd_cfg,
.num_displays = 1,
.default_display = 0,
#if 0
/* currently setup by downloader */
.gpccon = 0xaa940659,
.gpccon_mask = 0xffffffff,
.gpcup = 0x0000ffff,
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaa84aaa0,
.gpdcon_mask = 0xffffffff,
.gpdup = 0x0000faff,
.gpdup_mask = 0xffffffff,
#endif
.lpcsel = ((0xCE6) & ~7) | 1<<4,
};
再把友善之臂已经移植好的代码加入,如下:
/* LCD driver info */
;NEC 3.5”LCD 的配置和参数设置
#if defined(CONFIG_FB_S3C2410_N240320)
#define LCD_WIDTH 240
#define LCD_HEIGHT 320
#define LCD_PIXCLOCK 100000
#define LCD_RIGHT_MARGIN 36
#define LCD_LEFT_MARGIN 19
#define LCD_HSYNC_LEN 5
#define LCD_UPPER_MARGIN 1
#define LCD_LOWER_MARGIN 5
#define LCD_VSYNC_LEN 1
; 夏普8”LCD 的配置和参数设置
#elif defined(CONFIG_FB_S3C2410_TFT640480)
#define LCD_WIDTH 640
#define LCD_HEIGHT 480
#define LCD_PIXCLOCK 80000
#define LCD_RIGHT_MARGIN 67
#define LCD_LEFT_MARGIN 40
#define LCD_HSYNC_LEN 31
#define LCD_UPPER_MARGIN 25
#define LCD_LOWER_MARGIN 5
#define LCD_VSYNC_LEN 1
; 统宝3.5”LCD 的配置和参数设置
#elif defined(CONFIG_FB_S3C2410_T240320)
#define LCD_WIDTH 240
#define LCD_HEIGHT 320
#define LCD_PIXCLOCK 146250//170000
#define LCD_RIGHT_MARGIN 25
#define LCD_LEFT_MARGIN 0
#define LCD_HSYNC_LEN 4
#define LCD_UPPER_MARGIN 1
#define LCD_LOWER_MARGIN 4
#define LCD_VSYNC_LEN 1
; 群创7”LCD 的配置和参数设置
#elif defined(CONFIG_FB_S3C2410_TFT800480)
#define LCD_WIDTH 800
#define LCD_HEIGHT 480
#define LCD_PIXCLOCK 11463//40000
#define LCD_RIGHT_MARGIN 67
#define LCD_LEFT_MARGIN 40
#define LCD_HSYNC_LEN 31
#define LCD_UPPER_MARGIN 25
#define LCD_LOWER_MARGIN 5
#define LCD_VSYNC_LEN 1
;LCD2VGA(分辨率为1024x768)模块的配置和参数设置
#elif defined(CONFIG_FB_S3C2410_VGA1024768)
#define LCD_WIDTH 1024
#define LCD_HEIGHT 768
#define LCD_PIXCLOCK 80000
#define LCD_RIGHT_MARGIN 15
#define LCD_LEFT_MARGIN 199
#define LCD_HSYNC_LEN 15
#define LCD_UPPER_MARGIN 1
#define LCD_LOWER_MARGIN 1
#define LCD_VSYNC_LEN 1
#define LCD_CON5 (S3C2410_LCDCON5_FRM565 | S3C2410_LCDCON5_HWSWP)
#endif
#if defined (LCD_WIDTH)
static struct s3c2410fb_display mini2440_lcd_cfg __initdata = {
#if !defined (LCD_CON5)
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
#else
.lcdcon5 = LCD_CON5,
#endif
.type = S3C2410_LCDCON1_TFT,
.width = LCD_WIDTH,
.height = LCD_HEIGHT,
.pixclock = LCD_PIXCLOCK,
.xres = LCD_WIDTH,
.yres = LCD_HEIGHT,
.bpp = 16,
.left_margin = LCD_LEFT_MARGIN + 1,
.right_margin = LCD_RIGHT_MARGIN + 1,
.hsync_len = LCD_HSYNC_LEN + 1,
.upper_margin = LCD_UPPER_MARGIN + 1,
.lower_margin = LCD_LOWER_MARGIN + 1,
.vsync_len = LCD_VSYNC_LEN + 1,
};
static struct s3c2410fb_mach_info mini2440_fb_info __initdata = {
.displays = &mini2440_lcd_cfg,
.num_displays = 1,
.default_display = 0,
.gpccon = 0xaa955699,
.gpccon_mask = 0xffc003cc,
.gpcup = 0x0000ffff,
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaa95aaa1,
.gpdcon_mask = 0xffc0fff0,
.gpdup = 0x0000faff,
.gpdup_mask = 0xffffffff,
.lpcsel = 0xf82,
};
#endif
然后打开drivers/video/Kconfig ,在大概1935行加入以下配置信息:
config FB_S3C2410_DEBUG
bool "S3C2410 lcd debug messages"
depends on FB_S3C2410
help
Turn on debugging messages. Note that you can set/unset at run time
through sysfs
choice
prompt "LCD select"
depends on FB_S3C2410
help
S3C24x0 LCD size select
config FB_S3C2410_T240320
boolean "3.5 inch 240X320 Toppoly LCD"
depends on FB_S3C2410
help
3.5 inch 240X320 Toppoly LCD
config FB_S3C2410_N240320
boolean "3.5 inch 240X320 NEC LCD"
depends on FB_S3C2410
help
3.5 inch 240x320 NEC LCD
config FB_S3C2410_TFT640480
boolean "8 inch 640X480 L80 LCD"
depends on FB_S3C2410
help
8 inch 640X480 LCD
config FB_S3C2410_TFT800480
boolean "7 inch 800x480 TFT LCD"
depends on FB_S3C2410
help
7 inch 800x480 TFT LCD
config FB_S3C2410_VGA1024768
boolean "VGA 1024x768"
depends on FB_S3C2410
help
VGA 1024x768
endchoice
config BACKLIGHT_MINI2440
tristate "Backlight support for mini2440 from FriendlyARM"
depends on MACH_MINI2440 && FB_S3C2410
help
backlight driver for MINI2440 from FriendlyARM
config FB_SM501
tristate "Silicon Motion SM501 framebuffer support"
depends on FB && MFD_SM501
select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT
这样,我们就完成了LCD 驱动的移植,如果你需要加入其他型号的LCD驱动,也可以参照上面的方式复制即可,一般小尺寸的 pixclock 参数可以参考统宝 3.5” 的,超过 640x480分辨率的参数可以参考8”LCD 的,特别要注意你使用的LCD 的长宽也要修改。
配置内核并下载到开发板测试
现在,我们在命令行输入:make menuconfig 进入内核配置,依次按下面的子菜单项选择:
Device Drivers --->
Graphics support --->
<*> Support for frame buffer devices --->
LCD select (3.5 inch 240X320 Toppoly LCD) --->
会出现如图所示LCD 型号配置选项:
按空格或者回车键选择我们需要的LCD 型号,然后退出保存内核配置。
在命令行执行:
#make zImage
将会生成arch/arm/boot/zImage ,把它烧写到开发板中,一个小企鹅出现,屏幕由两半合为一个。但是触摸时没有反应。
阅读(1111) | 评论(0) | 转发(0) |