第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > LCD(五)Backlight背光子系统

LCD(五)Backlight背光子系统

时间:2019-11-17 06:13:35

相关推荐

LCD(五)Backlight背光子系统

一、Backlight背光子系统概述

LCD的背光原理主要是由核心板的一根引脚控制背光电源,一根PWM引脚控制背光亮度组成,应用程序可以通过改变PWM的频率达到改变背光亮度的目的。

Backlight背光子系统构建过程结构关系图

黑色加粗部分为开发人员需要填充的部分,其中pwm_id:第几个定时器来做pwm;max_brightness背光调节范围的最大值;dft_brightness:默认背光的当前值;pwm_period_ns:定时器周期;update_status()更新背光驱动;get_brightness()获得当前背光值。

二、PWM核心驱动

代码/arch/arm/plat-samsung/pwm.c

内核中需要使能“PWM device support”

System Type -->[*]PWM device support

它是pwm核心驱动,该驱动把设备和驱动没有分离开来,都写在了这个pwm.c中,我们先看看pwm.c中的驱动部分

static int __init pwm_init(void){int ret;clk_scaler[0] = clk_get(NULL, "pwm-scaler0");//获取0号时钟clk_scaler[1] = clk_get(NULL, "pwm-scaler1");//获取1号时钟if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) {printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__);return -EINVAL;}ret = platform_driver_register(&s3c_pwm_driver);//注册pwm驱动if (ret)printk(KERN_ERR "%s: failed to add pwm driver\n", __func__);return ret;}

//s3c_pwm_driver定义static struct platform_driver s3c_pwm_driver = {.driver= {.name= "s3c24xx-pwm",//驱动名.owner= THIS_MODULE,},.probe= s3c_pwm_probe,//探测函数.remove= __devexit_p(s3c_pwm_remove),.suspend= s3c_pwm_suspend,.resume= s3c_pwm_resume,};

探测函数

static int s3c_pwm_probe(struct platform_device *pdev){struct device *dev = &pdev->dev;struct pwm_device *pwm;unsigned long flags;unsigned long tcon;unsigned int id = pdev->id;int ret;if (id == 4) {dev_err(dev, "TIMER4 is currently not supported\n");return -ENXIO;}pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);if (pwm == NULL) {dev_err(dev, "failed to allocate pwm_device\n");return -ENOMEM;}pwm->pdev = pdev;pwm->pwm_id = id;/* calculate base of control bits in TCON */pwm->tcon_base = id == 0 ? 0 : (id * 4) + 4;//计算TCON中控制哪个定时器pwm->clk = clk_get(dev, "pwm-tin");//获取预分频后的时钟if (IS_ERR(pwm->clk)) {dev_err(dev, "failed to get pwm tin clk\n");ret = PTR_ERR(pwm->clk);goto err_alloc;}pwm->clk_div = clk_get(dev, "pwm-tdiv");if (IS_ERR(pwm->clk_div)) {//获取二次分屏后的时钟dev_err(dev, "failed to get pwm tdiv clk\n");ret = PTR_ERR(pwm->clk_div);goto err_clk_tin;}local_irq_save(flags);tcon = __raw_readl(S3C2410_TCON);tcon |= pwm_tcon_invert(pwm);//信号反转输出__raw_writel(tcon, S3C2410_TCON);local_irq_restore(flags);ret = pwm_register(pwm);//注册pwm设备if (ret) {dev_err(dev, "failed to register pwm\n");goto err_clk_tdiv;}pwm_dbg(pwm, "config bits %02x\n",(__raw_readl(S3C2410_TCON) >> pwm->tcon_base) & 0x0f);dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n",clk_get_rate(pwm->clk),clk_get_rate(pwm->clk_div),pwm_is_tdiv(pwm) ? "div" : "ext", pwm->tcon_base);platform_set_drvdata(pdev, pwm);return 0;err_clk_tdiv:clk_put(pwm->clk_div);err_clk_tin:clk_put(pwm->clk);err_alloc:kfree(pwm);return ret;}

pwm设备函数的注册pwm_register

static LIST_HEAD(pwm_list);static DEFINE_MUTEX(pwm_lock);static int pwm_register(struct pwm_device *pwm){pwm->duty_ns = -1;pwm->period_ns = -1;mutex_lock(&pwm_lock);list_add_tail(&pwm->list, &pwm_list);//把pwm设备挂到pwm_list链表上mutex_unlock(&pwm_lock);return 0;}

pwm.c提供的接口函数

struct pwm_device *pwm_request(int pwm_id, const char *label)int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)int pwm_enable(struct pwm_device *pwm)void pwm_free(struct pwm_device *pwm)EXPORT_SYMBOL(pwm_request); //申请PWM设备EXPORT_SYMBOL(pwm_config); //配置PWM设备,duty_ns为空占比,period_ns为周期EXPORT_SYMBOL(pwm_enable); //启动Timer定时器EXPORT_SYMBOL(pwm_disable); //关闭Timer定时器

分析下最难的一个配置PWM函数,这个函数主要是根据周期period_ns,计算TCNT,根据空占比duty_ns,计算TCMP,然后写入相应寄存器。

int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns){unsigned long tin_rate;unsigned long tin_ns;unsigned long period;unsigned long flags;unsigned long tcon;unsigned long tcnt;long tcmp;/* We currently avoid using 64bit arithmetic by using the* fact that anything faster than 1Hz is easily representable* by 32bits. */if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ)return -ERANGE;if (duty_ns > period_ns)return -EINVAL;if (period_ns == pwm->period_ns &&duty_ns == pwm->duty_ns)return 0;/* The TCMP and TCNT can be read without a lock, they're not* shared between the timers. */tcmp = __raw_readl(S3C2410_TCMPB(pwm->pwm_id));tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id));period = NS_IN_HZ / period_ns;//计算周期pwm_dbg(pwm, "duty_ns=%d, period_ns=%d (%lu)\n",duty_ns, period_ns, period);/* Check to see if we are changing the clock rate of the PWM */if (pwm->period_ns != period_ns) {if (pwm_is_tdiv(pwm)) {tin_rate = pwm_calc_tin(pwm, period);clk_set_rate(pwm->clk_div, tin_rate);} elsetin_rate = clk_get_rate(pwm->clk);pwm->period_ns = period_ns;pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate);tin_ns = NS_IN_HZ / tin_rate;tcnt = period_ns / tin_ns;//根据周期求TCNT,n=To/Ti} elsetin_ns = NS_IN_HZ / clk_get_rate(pwm->clk);/* Note, counters count down */tcmp = duty_ns / tin_ns;tcmp = tcnt - tcmp;//根据占空比求TCMP/* the pwm hw only checks the compare register after a decrement,so the pin never toggles if tcmp = tcnt */if (tcmp == tcnt)tcmp--;pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt);if (tcmp < 0)tcmp = 0;/* Update the PWM register block. */local_irq_save(flags);__raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id));//写入TCMp__raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id));//写入TCNTtcon = __raw_readl(S3C2410_TCON);tcon |= pwm_tcon_manulupdate(pwm);tcon |= pwm_tcon_autoreload(pwm);//自动加载__raw_writel(tcon, S3C2410_TCON);tcon &= ~pwm_tcon_manulupdate(pwm);//更新TCNT和TCMP__raw_writel(tcon, S3C2410_TCON);local_irq_restore(flags);return 0;}

下面说说这个周期是怎么设计的

我们定时器的输出频率fi=PCLK/(prescaler value+1)/(divider value),这个可以获得确定值

我们需要写入一个初值n给TCNT,这样就可以获得一个频率,为什么呢?

根据初值n=fi/fo,那么n=To/Ti

所以当用户给pwm_config函数传递一个周期period_ns,其实就是To=period_ns

这样根据前面公式n=To/Ti= period_ns/fi,然后将这个初值n写入TCNT就可以改变周期了

接着我再补充说明下pwm_config函数里代码注释关于自动加载怎么回事?

定时器工作原理其实是TCNT的值在时钟到来时,减一计数,每次减一完后,拿当前TCNT与TCMP比较,如果TCNT=TCMP,那么信号电平反向输出,然后TCNT继续减一计数,直到TCNT减到零后,如果有自动加载功能那么此时将由TCNTB把计数初值再次写给TCNTP,同时TCMPB把比较值给TCMP,这样就完成一次初值重装,然后继续进行计数。我们给这种加载模式起了个名字叫双缓冲机制,其中TCMPB和TCNTB就是Buffer缓存。

前面说pwm.c集驱动和设备于一体,那么下面我们看看设备相关的代码

注:kernel-3.0.8在/arch/plat-samsung/dev-pwm.c文件中

#define TIMER_RESOURCE_SIZE (1)#define TIMER_RESOURCE(_tmr, _irq)\(struct resource [TIMER_RESOURCE_SIZE]) {\[0] = {\.start= _irq,\.end= _irq,\.flags= IORESOURCE_IRQ\}\}#define DEFINE_S3C_TIMER(_tmr_no, _irq)\.name= "s3c24xx-pwm",\.id= _tmr_no,\.num_resources= TIMER_RESOURCE_SIZE,\.resource= TIMER_RESOURCE(_tmr_no, _irq),\/** since we already have an static mapping for the timer,* we do not bother setting any IO resource for the base.*/struct platform_device s3c_device_timer[] = {[0] = { DEFINE_S3C_TIMER(0, IRQ_TIMER0) },[1] = { DEFINE_S3C_TIMER(1, IRQ_TIMER1) },[2] = { DEFINE_S3C_TIMER(2, IRQ_TIMER2) },[3] = { DEFINE_S3C_TIMER(3, IRQ_TIMER3) },[4] = { DEFINE_S3C_TIMER(4, IRQ_TIMER4) },};EXPORT_SYMBOL(s3c_device_timer);

上面的代码就是设备部分代码,其实就是五个定时器的资源,我们把目光放在DEFINE_S3C_TIMER宏上,你会发现其设备名是"s3c24xx-pwm",而我们在pwm.c中定义的驱动名也是"s3c24xx-pwm",这样如果我们把设备注册到内核,那么设备"s3c24xx-pwm"和驱动"s3c24xx-pwm"就会匹配成功。所以如果你用到定时器0,那么你只要在BSP中添加s3c_device_timer[0]就可以了。我们现在做的是Backlight背光驱动,使用的是Timer0定时器,我们就在tq210的BSP文件mach-tq210.c中添加如下代码

static struct platform_device *tq210_devices[] __initdata = {...#ifdef CONFIG_BACKLIGHT_PWM&s3c_device_timer[0],#endif...};

三、Backlight核心驱动

代码/driver/video/backlight/backlight.c

内核中需要使能“Lowlevel Backlight controls”

Device Drivers --->

Graphics support --->[*] Backlight & LCD device support ---><*> Lowlevel Backlight controls

代码分析

static int __init backlight_class_init(void){backlight_class = class_create(THIS_MODULE, "backlight");//在/sys/class下注册backlight类if (IS_ERR(backlight_class)) {printk(KERN_WARNING "Unable to create backlight class; errno = %ld\n",PTR_ERR(backlight_class));return PTR_ERR(backlight_class);}backlight_class->dev_attrs = bl_device_attributes;//添加类属性backlight_class->suspend = backlight_suspend;backlight_class->resume = backlight_resume;return 0;}

backlight背光系统的主要就是靠这个类属性,设置背光值就是向这个类属性中某个成员写入背光值,这个类属性就是给用户的同一接口

#define __ATTR(_name,_mode,_show,_store) { \.attr = {.name = __stringify(_name), .mode = _mode },\.show= _show,\.store= _store,\}

static struct device_attribute bl_device_attributes[] = {__ATTR(bl_power, 0644, backlight_show_power, backlight_store_power),__ATTR(brightness, 0644, backlight_show_brightness,backlight_store_brightness),__ATTR(actual_brightness, 0444, backlight_show_actual_brightness,NULL),__ATTR(max_brightness, 0444, backlight_show_max_brightness, NULL),__ATTR(type, 0444, backlight_show_type, NULL),__ATTR_NULL,};

很明显,在backlight类中我们创建了bl_power,brightness,actural_brightness,max_brightness四个成员,其中brightness是当前亮度,max_brightness是最大亮度。当用户层通过cat或者echo命令就会触发这些成员。对于这些属性的读写函数,我们先看看读的函数backlight_show_max_brightness吧

static ssize_t backlight_show_max_brightness(struct device *dev,struct device_attribute *attr, char *buf){struct backlight_device *bd = to_backlight_device(dev);return sprintf(buf, "%d\n", bd->props.max_brightness);//输出最大亮度}

这个函数很简单,但是重点是引入了几个backlight背光子系统的几个重要的数据结构,我们好好学习下。

首先是backlight背光子系统的设备结构体backlight_device

struct backlight_device {/* Backlight properties */struct backlight_properties props;//背光属性/* Serialise access to update_status method */struct mutex update_lock;/* This protects the 'ops' field. If 'ops' is NULL, the driver thatregistered this device has been unloaded, and if class_get_devdata()points to something in the body of that driver, it is also invalid. */struct mutex ops_lock;const struct backlight_ops *ops;//背光操作函数,类似于file_operation/* The framebuffer notifier block */struct notifier_block fb_notif;struct device dev;//内嵌设备};

下面先看看背光属性结构体backlight_properties

struct backlight_properties {/* Current User requested brightness (0 - max_brightness) */int brightness;//当前背光值/* Maximal value for brightness (read-only) */int max_brightness;//最大背光值/* Current FB Power mode (0: full on, 1..3: power savingmodes; 4: full off), see FB_BLANK_XXX */int power;/* FB Blanking active? (values as for power) *//* Due to be removed, please use (state & BL_CORE_FBBLANK) */int fb_blank;/* Backlight type */enum backlight_type type;/* Flags used to signal drivers of state changes *//* Upper 4 bits are reserved for driver internal use */unsigned int state;#define BL_CORE_SUSPENDED(1 << 0)/* backlight is suspended */#define BL_CORE_FBBLANK(1 << 1)/* backlight is under an fb blank event */#define BL_CORE_DRIVER4(1 << 28)/* reserved for driver specific use */#define BL_CORE_DRIVER3(1 << 29)/* reserved for driver specific use */#define BL_CORE_DRIVER2(1 << 30)/* reserved for driver specific use */#define BL_CORE_DRIVER1(1 << 31)/* reserved for driver specific use */};

在看看背光操作函数

struct backlight_ops {unsigned int options;#define BL_CORE_SUSPENDRESUME(1 << 0)/* Notify the backlight driver some property has changed */int (*update_status)(struct backlight_device *);//更新背光状态/* Return the current backlight brightness (accounting for power,fb_blank etc.) */int (*get_brightness)(struct backlight_device *);//获取背光值/* Check if given framebuffer device is the one bound to this backlight;return 0 if not, !=0 if it is. If NULL, backlight always matches the fb. */int (*check_fb)(struct backlight_device *, struct fb_info *);};

当前背光值函数backlight_store_brightness

static ssize_t backlight_store_brightness(struct device *dev,struct device_attribute *attr, const char *buf, size_t count){int rc;struct backlight_device *bd = to_backlight_device(dev);unsigned long brightness;rc = strict_strtoul(buf, 0, &brightness);if (rc)return rc;rc = -ENXIO;mutex_lock(&bd->ops_lock);if (bd->ops) {if (brightness > bd->props.max_brightness)rc = -EINVAL;else {pr_debug("backlight: set brightness to %lu\n",brightness);bd->props.brightness = brightness;//传入背光值backlight_update_status(bd);//更新背光状态rc = count;}}mutex_unlock(&bd->ops_lock);backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS);return rc;}

static inline void backlight_update_status(struct backlight_device *bd){mutex_lock(&bd->update_lock);if (bd->ops && bd->ops->update_status)bd->ops->update_status(bd);mutex_unlock(&bd->update_lock);}

对于这个backlight背光核心层驱动backlight.c,剩下的就是这个pwm.c给我们提供了哪些接口函数了

struct backlight_device *backlight_device_register(const char *name,struct device *parent, void *devdata, struct backlight_ops *ops)void backlight_device_unregister(struct backlight_device *bd)EXPORT_SYMBOL(backlight_device_register); //注册背光设备EXPORT_SYMBOL(backlight_device_unregister); //注销背光设备

四、基于PWM&Backlight的背光驱动

结合上面的PWM核心层和Backlight背光子系统核心层,根据基于pwm的背光驱动/driver/video/backlight/pwm_bl.c来修改成基于tq210的蜂鸣器驱动

内核中需要使能“Generic PWM based Backlight Driver”

Device Drivers --->

Graphics support --->[*] Backlight & LCD device support ---><*> Generic PWM based Backlight Driver

tq210蜂鸣器使用GPD0_1口,该端口工作在TOU0模式下,就可以通过设备定时器的TCNT和TCMP来控制定时器的波形。

首先,在tq210的BSP文件mach-tq210.c,如下添加

static struct platform_device tq210_backlight_device = {.name= "pwm-backlight",//设备名.dev= {.parent= &s3c_device_timer[0].dev,//该设备基于pwm中的0号定时器.platform_data= &tq_backlight_data,},};

添加平台数据

static struct platform_pwm_backlight_data tq210_backlight_data = {.pwm_id= 0,//对应的就是Timer0.max_brightness= 255,//最大亮度.dft_brightness= 100,//255,//当前亮度.lth_brightness = 50,//咱不知道干啥用的 .pwm_period_ns= 20000,//78770,//T0,即输出时钟周期.init= tq210_backlight_init,//端口初始化.exit= tq210_backlight_exit,};

static int tq210_backlight_init(struct device *dev){int ret;ret = gpio_request(S5PV210_GPD0(0), "Backlight");if (ret) {printk(KERN_ERR "failed to request GPD for PWM-OUT 0\n");return ret;}/* Configure GPIO pin with S5PV210_GPD_0_0_TOUT_0 */s3c_gpio_cfgpin(S5PV210_GPD0(0), S3C_GPIO_SFN(2));return 0;}static void tq210_backlight_exit(struct device *dev){s3c_gpio_cfgpin(S5PV210_GPD0(0), S3C_GPIO_OUTPUT);gpio_free(S5PV210_GPD0(0));}

然后把tq210_backlight_device添加到tq210_devices数组

static struct platform_device *tq210_devices[] __initdata = {...#ifdef CONFIG_BACKLIGHT_PWM&s3c_device_timer[0],&s3c_device_timer[1],&s3c_device_timer[2],&s3c_device_timer[3],&tq210_backlight_device,//同时添加对应的s3c_device_timer[0]#endif...};

最后添加头文件

#include <linux/pwm_backlight.h>

分析pwm_bl.c文件

static struct platform_driver pwm_backlight_driver = {.driver= {.name= "pwm-backlight", //驱动名需要与设备名保持一致.owner= THIS_MODULE,},.probe= pwm_backlight_probe,.remove= pwm_backlight_remove,.suspend= pwm_backlight_suspend,.resume= pwm_backlight_resume,};

static int __init pwm_backlight_init(void){return platform_driver_register(&pwm_backlight_driver);}

探测函数

static int pwm_backlight_probe(struct platform_device *pdev){struct backlight_properties props;struct platform_pwm_backlight_data *data = pdev->dev.platform_data;struct backlight_device *bl;struct pwm_bl_data *pb; //本驱动的私有结构体int ret;if (!data) {dev_err(&pdev->dev, "failed to find platform data\n");return -EINVAL;}if (data->init) {//初始化端口,在BSP文件中定义ret = data->init(&pdev->dev);if (ret < 0)return ret;}pb = kzalloc(sizeof(*pb), GFP_KERNEL);if (!pb) {dev_err(&pdev->dev, "no memory for state\n");ret = -ENOMEM;goto err_alloc;}pb->period = data->pwm_period_ns;//获取周期pb->notify = data->notify;pb->check_fb = data->check_fb;pb->lth_brightness = data->lth_brightness *(data->pwm_period_ns / data->max_brightness);pb->dev = &pdev->dev;pb->pwm = pwm_request(data->pwm_id, "backlight");//注册pwm设备if (IS_ERR(pb->pwm)) {dev_err(&pdev->dev, "unable to request PWM for backlight\n");ret = PTR_ERR(pb->pwm);goto err_pwm;} elsedev_dbg(&pdev->dev, "got pwm for backlight\n");memset(&props, 0, sizeof(struct backlight_properties));props.type = BACKLIGHT_RAW;props.max_brightness = data->max_brightness;bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev, pb,&pwm_backlight_ops, &props);//注册backlight设备,注意pwm_backlight_ops函数if (IS_ERR(bl)) {dev_err(&pdev->dev, "failed to register backlight\n");ret = PTR_ERR(bl);goto err_bl;}bl->props.brightness = data->dft_brightness;backlight_update_status(bl);//先点亮光platform_set_drvdata(pdev, bl);//设置bl私有数据return 0;err_bl:pwm_free(pb->pwm);err_pwm:kfree(pb);err_alloc:if (data->exit)data->exit(&pdev->dev);return ret;}

对于这个驱动,重点关注的是注册backlight设备时传入的参数pwm_backlight_ops,因为我们之前分析backlight背光子系统时说过,背光设备结构体中有个操作背光的函数集合,在我们的pwm_bl.c中,就需要定义这个操作背光的函数集合,也就是pwm_backlight_ops函数

static const struct backlight_ops pwm_backlight_ops = {.update_status= pwm_backlight_update_status,//更新背光亮度.get_brightness= pwm_backlight_get_brightness,//获取背光亮度.check_fb= pwm_backlight_check_fb,};

static int pwm_backlight_update_status(struct backlight_device *bl){struct pwm_bl_data *pb = dev_get_drvdata(&bl->dev);int brightness = bl->props.brightness;int max = bl->props.max_brightness;if (bl->props.power != FB_BLANK_UNBLANK)brightness = 0;if (bl->props.fb_blank != FB_BLANK_UNBLANK)brightness = 0;if (pb->notify)brightness = pb->notify(pb->dev, brightness);if (brightness == 0) {//背光值为0,关闭被背光pwm_config(pb->pwm, 0, pb->period);pwm_disable(pb->pwm);} else {//调用pwm中的API设置背光brightness = pb->lth_brightness +(brightness * (pb->period - pb->lth_brightness) / max);pwm_config(pb->pwm, brightness, pb->period);pwm_enable(pb->pwm);}return 0;}

五、测试

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。