Linux下的Backlight子系統(一)

版權所有,轉載必須說明轉自 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;  //根據週期求TCNTn=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); //更新TCNTTCMP

       __raw_writel(tcon, S3C2410_TCON);

       local_irq_restore(flags);

       return 0;

}

 

下面說說這個週期是怎麼設計的

我們定時器的輸出頻率fi=PCLK/(prescaler value+1)/(divider value),這個可以獲得確定值

我們需要寫入一個初值nTCNT,這樣就可以獲得一個頻率,爲什麼呢?

根據初值n=fi/fo,那麼n=To/Ti

所以當用戶給pwm_config函數傳遞一個週期period_ns,其實就是To=period_ns

這樣根據前面公式n=To/Ti= period_ns/fi,然後將這個初值n寫入TCNT就可以改變週期了

 

接着我再補充說明下pwm_config函數裏代碼註釋關於自動加載怎麼回事?

定時器工作原理其實是TCNT的值在時鐘到來時,減一計數,每次減一完後,拿當前TCNTTCMP比較,如果TCNT=TCMP,那麼信號電平反向輸出,然後TCNT繼續減一計數,知道TCNT減到零後,如果有自動加載功能那麼此時將由TCNTB把計數初值再次寫給TCNTP,同時TCMPB把比較值給TCMP,這樣就完成一次初值重裝,然後繼續進行計數。我們給這種加載模式起了個名字叫雙緩衝機制,其中TCMPBTCNTB就是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定時器,我們就在mini2440BSP文件mach-mini2440.c中添加如下代碼

static struct platform_device *mini2440_devices[] __initdata = {

       ……

       &s3c_device_timer[0],    //添加

};

這樣我們就分析完pwm核心層的代碼了。

 

 

發佈了57 篇原創文章 · 獲贊 63 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章