转自: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核心层的代码了。

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

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

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

  2. Linux下的RTC子系统

    转自:http://blog.csdn.net/weiqing1981127/article/details/8484268 实时时钟的作用主要是为操作系统提供一个可靠的时间,并在断电下,RTC时钟也 ...

  3. backlight 子系统(转载)

    http://blog.csdn.net/weiqing1981127/article/details/8511676   Linux下的Backlight子系统(一) http://blog.csd ...

  4. Linux下触摸屏驱动程序分析

    [摘要: 本文以linux3.5--Exynos4412仄台,剖析触摸屏驱动焦点内容.Linux下触摸屏驱动(以ft5x06_ts为例)须要懂得以下学问: 1. I2C协定 2. Exynos4412 ...

  5. ARM Linux内核Input输入子系统浅解

    --以触摸屏驱动为例 第一章.了解linux input子系统         Linux输入设备总类繁杂,常见的包括有按键.键盘.触摸屏.鼠标.摇杆等等,他们本身就是字符设备,而linux内核将这些 ...

  6. linux下目录简介——/sys

    Linux下/sys目录介绍    1. 概述 ramdisk 文件系统基于磁盘模拟技术,实际文件系统是ex2 ex3等.sysfs是一种基于ram文件系统和proc一样.Sysfs文件系统是一个类似 ...

  7. (转载)linux下各个文件夹的作用

    linux下的文件结构,看看每个文件夹都是干吗用的/bin 二进制可执行命令 /dev 设备特殊文件 /etc 系统管理和配置文件 /etc/rc.d 启动的配置文件和脚本 /home 用户主目录的基 ...

  8. Linux下不同服务器间数据传输--转载

    因为工作原因,需要经常在不同的服务器见进行文件传输,特别是大文件的传输,因此对linux下不同服务器间数据传输命令和工具进行了研究和总结.主要是rcp,scp,rsync,ftp,sftp,lftp, ...

  9. Linux下不同服务器间数据传输

    因为工作原因,需要经常在不同的服务器见进行文件传输,特别是大文件的传输,因此对linux下不同服务器间数据传输命令和工具进行了研究和总结.主要是rcp,scp,rsync,ftp,sftp,lftp, ...

随机推荐

  1. 孤荷凌寒自学python第七十六天开始写Python的第一个爬虫6

    孤荷凌寒自学python第七十六天开始写Python的第一个爬虫6 (完整学习过程屏幕记录视频地址在文末) 今天在上一天的基础上继续完成对我的第一个代码程序的书写. 不过由于对python-docx模 ...

  2. 3.爬虫 urlib库讲解 总结

    urllib库的总结: 用ProcessOn(安利这个软件,够用了)根据前面的几节内容做了个思维导图. urllib库一共有四个模块: request:它是最基本的模块,可以用来模拟发送请求 erro ...

  3. Flask 学习笔记(一)

    一.Web 服务器与 Web 框架 首先明确一下,要运行一个动态网页,我们需要 一个 Web 服务器来监听并响应请求,如果请求的是静态文件它就直接将其返回,如果是动态 url 它就将请求转交给 Web ...

  4. POJ 2162 Document Indexing(模拟)

    Description Andy is fond of old computers. He loves everything about them and he uses emulators of o ...

  5. UVA 437 The Tower of Babylon(DAG上的动态规划)

    题目大意是根据所给的有无限多个的n种立方体,求其所堆砌成的塔最大高度. 方法1,建图求解,可以把问题转化成求DAG上的最长路问题 #include <cstdio> #include &l ...

  6. 最短路径——Dijkstra算法以及二叉堆优化(含证明)

    一般最短路径算法习惯性的分为两种:单源最短路径算法和全顶点之间最短路径.前者是计算出从一个点出发,到达所有其余可到达顶点的距离.后者是计算出图中所有点之间的路径距离. 单源最短路径 Dijkstra算 ...

  7. ios UI自动化测试学习笔记

    一.一些注意事项: 1.做自动化测试时注意如果是真机话首先要设置不锁屏. 2.自动化测试过程中如果程序后台或崩溃了.脚本运行将会暂停,直到程序再次回到前台. 3.必须明确指定关闭自动测试,测试完成或中 ...

  8. 使用 window.getSelection() 方法获取鼠标划取部分的起始位置和结束位置的问题(高亮后不能正确获取)

    如果没有高亮等复杂处理,只需要获取一段文字中选取的字和位置,那么 使用window.getSelection()获取div中选中文字内容及位置 怎么获取textarea中选中文字 则可以满足需求: - ...

  9. P3509 [POI2010]ZAB-Frog

    题目描述 On the bed of one particularly long and straight Byteotian brook there lie  rocks jutting above ...

  10. 国内各运营商(ISP)IP段表

    国内各运营商(ISP)IP段表 来源:http://bbs.hh010.com/forum.php?mod=viewthread&tid=490529&orderby=dateline ...