显示屏按其显示原理大致可分为CRT(显像管)、LCD(液晶)及OLED三类,从市场应用看,手机中使用的显示屏主流是LCD,OLED只在翻盖机的小屏中占有少量份额,而CRT在手机中没有用到。
LCD本身不会发光,要想让其显示所要数据和图像,就必需使用白光背光源,手机中的白光背光源一般由数个侧发光白色LED灯组成,LED灯的个数由屏的大小尺寸决定,一般由2~6个不等。
【1】LCD 背光控制原理
在 fl2440开发板中,LCD 背光是通过CPU 的LCD_PWR 引脚来控制的,从原理图中可以看出,它对应于GPG4,如图
当LCD_PWR 输出为高电平“1”时,将打开背光;当输出为低电平“0”时,将关闭背光(注意:这里只是打开和关闭背光,而并没有背光亮度的调节作用)。
【2】 在内核中添加背光驱动程序
现在,我们需要增加一个简单的背光驱动,以便能够通过软件便可简单的控制背光的开关。我们要达到的目的是:在命令终端通过向背光设备发送偶数比如“0”便可关闭背光,发送奇数比如“1”便可打开背光,这样使用起来就方便多了,而不需要专门的应用程序控制它,正如在用户手册中所描述的方法(2.5.10 控制LCD 的背光):
提示:LCD 背光设备文件:/dev/backlight
在命令行种输入:echo 0 > /dev/backlight 可以关闭LCD 背光。
在命令行种输入:echo 1 > /dev/backlight 可以打开LCD 背光。
为了实现这点,我们在drivers/video/backlight 目录增加一个fl2440_backlight.c文件,内容如下:
#include <linux/errno.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/init.h> #include <linux/serio.h> #include <linux/delay.h> #include <linux/clk.h> #include <linux/miscdevice.h>
#include <asm/io.h> #include <asm/irq.h> #include <asm/uaccess.h> #include <mach/regs-clock.h> #include <plat/regs-timer.h> #include <mach/regs-gpio.h> #include <linux/cdev.h> #include <mach/gpio.h> #define DEVICE_NAME "backlight" //设备名称 #define DEVICE_MINOR 255 //次设备号自动申请,这里我们将设备注册为misc设备,这种设备的主设备号都为10
#defineS3C2410_GPG4S3C2410_GPG(4) #defineS3C2410_GPG4_OUTP(0x01 << 8)
static long fl2440_backlight_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch(cmd) { case 0:
//当接收的命令为0时,就将GPG4引脚设为低电平,关闭背光 s3c2410_gpio_setpin(S3C2410_GPG4, 0); printk(DEVICE_NAME " turn off!\n"); return 0; case 1:
//当接收的命令为1时,就将GPG4引脚设为高电平,开启背光 s3c2410_gpio_setpin(S3C2410_GPG4, 1); printk(DEVICE_NAME " turn on!\n"); return 0; default: return -EINVAL; } }
static struct file_operations dev_fops = { .owner = THIS_MODULE, .unlocked_ioctl = fl2440_backlight_ioctl, //这里只使用控制IO口的方式来控制背光 };
static struct miscdevice misc = { .minor = DEVICE_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, };
static int __init dev_init(void) { int ret; ret = misc_register(&misc); //注册成misc设备 if(ret < 0) { printk("Register misc device fiald!"); return ret; }
//将GPG4口配置成输出口,默认为点亮 s3c2410_gpio_cfgpin(S3C2410_GPG4, S3C2410_GPG4_OUTP); s3c2410_gpio_setpin(S3C2410_GPG4, 1); return ret; }
static void __exit dev_exit(void) { misc_deregister(&misc); //注销该misc设备 }
module_init(dev_init); module_exit(dev_exit);
MODULE_LICENSE("GPL"); MODULE_AUTHOR("smart.zhao"); MODULE_DESCRIPTION("Backlight control for fl2440");
【3】把背光配置选项加入内核配置菜单
打开drivers/video/backlight/Kconfig,定位到1929行附近,加入如下代码:
#
# Backlight & LCD drivers configuration
#
menuconfig BACKLIGHT_LCD_SUPPORT
bool "Backlight & LCD device support"
help
Enable this to be able to choose the drivers for controlling the
backlight and the LCD panel on some platforms, for example on PDAs.
if BACKLIGHT_LCD_SUPPORT
config BACKLIGHT_FL2440 tristate "Backlight support for FL2440 from FriendlyARM" depends on BACKLIGHT_LCD_SUPPORT help backlight driver for FL2440 from FriendlyARM
#
# LCD
#
config LCD_CLASS_DEVICE
再打开 drivers/video/backlight/Makefile,定位到文件末尾,根据配置定义加入驱动目标文件,如下:
obj-$(CONFIG_BACKLIGHT_ADP5520) += adp5520_bl.o
obj-$(CONFIG_BACKLIGHT_FL2440) += fl2440_backlight.o
【4】在内核配置菜单中确认"Backlight support for mini2440 from FriendlyARM",被选中。
在内核源代码根目录执行:
make menuconfig,依次选择如下子菜单:
Device Drivers --->
Graphics support --->
[*] Backlight & LCD device support --->
就可以找到该配置选项,如图
在这里,如果没有被选中,按空格选中我们刚刚加入的mini2440 配置项,然后退出时保存内核配置菜单,在命令行执行:make uImage
将生成的arch/arm/boot/uImage复制到/nfsboot目录下,然后启动开发板,可以在启动时看到LCD屏显示的企鹅图像,这说明我们已经点亮了背光。
【5】编写测试程序linux-test/codetest/backlight_test.c,代码如下:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/ioctl.h>
int main(int argc, char **argv) { int turn; int fd; //检测命令后面带的参数 if(argc == 1 || argc > 2) { printf("Usage: backlight_test on|off!\n"); exit(1); } //打开背光设备 fd = open("/dev/backlight", O_RDWR); if(fd < 0) { printf("Open Backlight Device Faild!\n"); exit(1); } //判断输入的参数 if(strcmp(argv[1], "on") == 0) { turn = 1; } else if(strcmp(argv[1], "off") == 0) { turn = 0; } else { printf("Usage: backlight_test on|off!\n"); exit(1); } //进行IO控制 ioctl(fd, turn);
//关闭背光设备 close(fd);
return 0; } 编译程序,测试结果如下:
ANDROID里,自带的背光亮度调节,会调用哪个接口?
会调用
/sys/class/leds/lcd-backlight/brightness
而这个brightness节点,是如何声明的呢
请看LED驱动里的这么一句
static DEVICE_ATTR(brightness, /*NAME*/
0644, /*权限*/
led_brightness_show, /*Read Point*/
led_brightness_store); /*Write Point*/
这就是声明brightness的。
这里调用了led_brightness_store做为写背光函数,也就是调节时用到,它最终会调用到显示驱动里的PWM操作函数,实现背光调节。
分析:
在LED驱动中,led_classdev_register ()函数,调用device_create()创建了操作结构体空间leds_class,
led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,"%s", led_cdev->name);
关键是填充led_cdev结构体。
在led_brightness_store函数里,调用led_set_brightness设置背光亮度,最终是调用了
led_cdev->brightness_set(led_cdev, value);实现操作,这个操作,会调用哪里的函数呢?它是显示驱动里的brightness_set()函数。
请注意看,在显示驱动里(S3cfb_lte480wv.c),有这么一段代码,
static struct led_classdev smdk_backlight_led = {
.name = "lcd-backlight",
.brightness = SMDK_DEFAULT_BACKLIGHT_BRIGHTNESS,
.brightness_set = smdk_brightness_set,
};
把brightness_set指向了smdk_brightness_set()函数。这就有戏看了,终于找到真正操作函数了。
当然,LED驱动是如何与显示驱动关联起来的,这一套机制,得看一下
static int smdk_bl_probe(struct platform_device *pdev)
{
led_classdev_register(&pdev->dev, &smdk_backlight_led);
return 0;
}
原来使用了led_classdev_register(),它是由LEDS驱动提供的函数,
#include 传过来的。
这就把smdk_backlight_led 结构体指针传给了LEDS驱动,里面就有smdk_brightness_set函数的指针,所以,LEDS里的led_cdev->brightness_set能调用到这边来,呵呵。原来是这样。
注册背光的控制节点
/sys/class/leds/lcd-backlight/brightness
是在这个函数 platform_driver_register() à
smdk_bl_probe() à led_classdev_register(&pdev->dev, &smdk_backlight_led);
在smdk_backlight_led结构体里,设置了节点名称lcd-backlight和控制接口等。
而led_classdev_register()是led-class里的函数。最终调用了device_createp 这样的函数实现创建节点。