Linux下的Backlight子系统(一)【转】

转自:http://blog.csdn.net/weiqing1981127/article/details/8511676

版权所有,转载必须说明转自 http://my.csdn.net/weiqing1981127 

原创作者:南京邮电大学  通信与信息系统专业 研二 魏清

一.Backlight背光子系统概述

我们的LCD屏常常需要一个背光,调节LCD屏背光的亮度,这里所说的背光不是仅仅亮和不亮两种,而是根据用户的需求,背光亮度是可以任意调节。Linux内核中有一个backlight背光子系统,该系统就是为满足用户这种需求设计的,用户只要根据自己的LCD背光电路中PWM输出引脚,对内核backlight子系统代码进行相应的配置,就可以实现LCD的背光。

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

 

我们这里主要讲解基于backlight子系统的蜂鸣器驱动,其实简单的使得蜂蜜器发声的驱动很简单,这里只是把蜂鸣器作为一种设备,而且这种设备原理类似背光的原理,都是基于pwm的,而我们的终极目的是使用backlight背光子系统。综上所述,backlight子系统是基于pwm核心的一种驱动接口,如果你使用的一种设备也是基于pwm的,并且需要用户可以调节pwm的频率以达到诸如改变背光亮度,改变蜂鸣器频率的效果,那么你可以使用这个backlight背光子系统。

 

二.PWM核心驱动

我们先讲解下PWM核心

先熟悉下pwm核心代码在/arch/arm/plat-s3c/pwm.c

查看/arch/arm/plat-s3c/Makefile

obj-$(CONFIG_HAVE_PWM)             += pwm.o

查看/arch/arm/plat-s3c/Konfig,发现同目录的Konfig中无对应HAVE_PWM选项

查看/arch/arm/plat-s3c24xx/Konfig

config S3C24XX_PWM

       bool "PWM device support"

       select HAVE_PWM

       help

         Support for exporting the PWM timer blocks via the pwm device

         system.

所以配置内核make menuconfig时,需要选中这一项。

 

好了,我们看看pwm.c,它是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),

};

我们看看探测函数s3c_pwm_probe

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); //分配pwm设备空间

       if (pwm == NULL) {

              dev_err(dev, "failed to allocate pwm_device\n");

              return -ENOMEM;

       }

       pwm->pdev = pdev;

       pwm->pwm_id = id;

       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 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定时器

上面这个函数,只要知道API,会调用就行了,在此,我分析下最难的一个配置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;

       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;

       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);

       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);

              } else

                     tin_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

       } else

              tin_ns = NS_IN_HZ / clk_get_rate(pwm->clk);

       tcmp = duty_ns / tin_ns;   //根据空占比求TCMP

       tcmp = tcnt - tcmp;  //根据占空比求TCMP

       if (tcmp == tcnt)

              tcmp--;

       pwm_dbg(pwm, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt);

       if (tcmp < 0)

              tcmp = 0;

       local_irq_save(flags);

       __raw_writel(tcmp, S3C2410_TCMPB(pwm->pwm_id)); //写入TCMP

       __raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id)); //写入TCNT

       tcon = __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集驱动和设备于一体,那么下面我们看看设备相关的代码

#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),       \

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) },

};

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

static struct platform_device *mini2440_devices[] __initdata = {

       ……

       &s3c_device_timer[0],    //添加

};

这样我们就分析完pwm核心层的代码了。

时间: 2024-10-14 06:55:58

Linux下的Backlight子系统(一)【转】的相关文章

Linux下的Backlight子系统(二)【转】

转自:http://blog.csdn.net/weiqing1981127/article/details/8515847 版权所有,转载必须说明转自 http://my.csdn.net/weiqing1981127  原创作者:南京邮电大学  通信与信息系统专业 研二 魏清     三.Backlight核心驱动 下面我们讲讲backlight子系统.背光子系统目录在/driver/video/backlight下,其中背光子系统核心代码是backlight.c 先查看/driver/vi

linux下如何安装STREAMS子系统

问题描述 linux下如何安装STREAMS子系统 求助!!在apue上看到的, 不知道如何在linux下安装STREAMS子系统

Linux下的邮件系统概述

在Linux下有许多软件包可以实现标准的邮件服务.UNIX/Linux的电子邮件系统模型基本可分为邮件传递代理MTA,邮件存储和获取代理MSA,以及邮件客户代理MUA三大模块.虽然也有一些特殊的邮件应用程序无法确切归入上述分类.下面我们就Linux邮件服务做一概述. 1.邮件传递代理-MTA 在Linux下的邮件传递代理包括sendmail,该系统几乎在任何UNIX平台上都有相应的版本:还有D.J. Bernstein的qmail以及Wietse Venema的Postfix系统.它们负责接收并

Linux下PCI设备驱动程序开发

PCI是一种广泛采用的总线标准,它提供了许多优于其它总线标准(如EISA)的新特性,目前已经成为 计算机系统中应用最为广泛,并且最为通用的总线标准.Linux的内核能较好地支持PCI总线,本文以 Intel 386体系结构为主,探讨了在Linux下开发PCI设备驱动程序的基本框架. 一.PCI总线系统体系结构 PCI是外围设备互连(Peripheral Component Interconnect)的简称,作为一种通用的总线接口标准 ,它在目前的计算机系统中得到了非常广泛的应用.PCI提供了一组

LINUX下的文件结构介绍

/bin 二进制可执行命令 /dev 设备特殊文件 /etc 系统管理和配置文件 /etc/rc.d 启动的配置文件和脚本 /home 用户主目录的基点,比如用户user的主目录就是/home/user,可以用~user表示 /lib 标准程序设计库,又叫动态链接共享库,作用类似windows里的.dll文件 /sbin 系统管理命令,这里存放的是系统管理员使用的管理程序 /tmp 公用的临时文件存储点 /root 系统管理员的主目录(呵呵,特权阶级) /mnt 系统提供这个目录是让用户临时挂载

如何在linux下检测内存泄漏

1.开发背景 在 windows 下使用 VC 编程时,我们通常需要 DEBUG 模式下运行程序,而后调试器将在退出程序时,打印出程序运行过程中在堆上分配而没有释放的内存信息,其中包括代码文件名.行号以及内存大小.该功能是 MFC Framework 提供的内置机制,封装在其类结构体系内部. 在 linux 或者 unix 下,我们的 C++ 程序缺乏相应的手段来检测内存信息,而只能使用 top 指令观察进程的动态内存总额.而且程序退出时,我们无法获知任何内存泄漏信息.为了更好的辅助在 linu

浅谈Linux下的syslog守护进程

syslog是Linux下默认的日志守护进程.任何希望生成日志信息的程序都可以通过syslog 接口实现该目的. 同时,几乎所有的网络设备都可以通过syslog协议,将日志信息以UDP的形式传送给远端服务器,远端接收日志服务器必须通过syslogd监听UDP端口514,并根据 syslog.conf 配置文件张的配置处理.配置文件 /etc/rsyslog.conf 中规定了syslogd如何根据设备和信息的重要级别来报告信息. 这里只需要有个大致了解即可,下面都会提到这些东西. 关于配置文件:

嵌入式Linux下高速USB主控制器的设计与实现

在嵌入式系统的发展历程中,Linux操作系统的源码公开,结构清晰,功能强大,可移植性强等特点使其在嵌入式领域应用越来越广泛.USB接口的热插拔,即插即用,数据传输可靠,扩展方便,成本低等优点使其逐渐成为嵌入式系统的必备接口之一.随着嵌入式系统应用领域的不断扩展,人们对其性能的要求不断提高,特别是USB设备的读写速度受到越来越多的关注.然而.目前多数嵌入式系统仅支持基础的USB低速或全速外设,不能满足人们对高速数据传输的要求.为此,基于AT91RM9200平台完成了高速USB的硬件设计和Linux

LINUX下的tty,console与串口分析

1.LINUX下TTY.CONSOLE.串口之间是怎样的层次关系?具体的函数接口是怎样的?串口是如何被调用的?  2.printk函数是把信息发送到控制台上吧?如何让PRINTK把信息通过串口送出?或者说系统在什么地方来决定是将信息送到显示器还是串口?  3.start_kernel中一开始就用到了printk函数(好象是printk(linux_banner什么的),在 这个时候整个内核还没跑起来呢那这时候的printk是如何被调用的?在我们的系统中,系统启动是用的现代公司的BOOTLOADE