Linux下的Backlight子系统
版權(quán)所有,轉(zhuǎn)載必須說明轉(zhuǎn)自?http://my.csdn.net/weiqing1981127?
原創(chuàng)作者:南京郵電大學(xué)? 通信與信息系統(tǒng)專業(yè) 研二 魏清
一.Backlight背光子系統(tǒng)概述
我們的LCD屏常常需要一個背光,調(diào)節(jié)LCD屏背光的亮度,這里所說的背光不是僅僅亮和不亮兩種,而是根據(jù)用戶的需求,背光亮度是可以任意調(diào)節(jié)。Linux內(nèi)核中有一個backlight背光子系統(tǒng),該系統(tǒng)就是為滿足用戶這種需求設(shè)計(jì)的,用戶只要根據(jù)自己的LCD背光電路中PWM輸出引腳,對內(nèi)核backlight子系統(tǒng)代碼進(jìn)行相應(yīng)的配置,就可以實(shí)現(xiàn)LCD的背光。
LCD的背光原理主要是由核心板的一根引腳控制背光電源,一根PWM引腳控制背光亮度組成,應(yīng)用程序可以通過改變PWM的頻率達(dá)到改變背光亮度的目的。
?
我們這里主要講解基于backlight子系統(tǒng)的蜂鳴器驅(qū)動,其實(shí)簡單的使得蜂蜜器發(fā)聲的驅(qū)動很簡單,這里只是把蜂鳴器作為一種設(shè)備,而且這種設(shè)備原理類似背光的原理,都是基于pwm的,而我們的終極目的是使用backlight背光子系統(tǒng)。綜上所述,backlight子系統(tǒng)是基于pwm核心的一種驅(qū)動接口,如果你使用的一種設(shè)備也是基于pwm的,并且需要用戶可以調(diào)節(jié)pwm的頻率以達(dá)到諸如改變背光亮度,改變蜂鳴器頻率的效果,那么你可以使用這個backlight背光子系統(tǒng)。
?
二.PWM核心驅(qū)動
我們先講解下PWM核心
先熟悉下pwm核心代碼在/arch/arm/plat-s3c/pwm.c
查看/arch/arm/plat-s3c/Makefile
obj-$(CONFIG_HAVE_PWM)???????????? += pwm.o
查看/arch/arm/plat-s3c/Konfig,發(fā)現(xiàn)同目錄的Konfig中無對應(yīng)HAVE_PWM選項(xiàng)
查看/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.
所以配置內(nèi)核make menuconfig?時,需要選中這一項(xiàng)。
?
好了,我們看看pwm.c,它是pwm核心驅(qū)動,該驅(qū)動把設(shè)備和驅(qū)動沒有分離開來,都寫在了這個pwm.c中,我們先看看pwm.c中的驅(qū)動部分
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驅(qū)動
?????? 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",? //驅(qū)動名
????????????? .owner??? = THIS_MODULE,
?????? },
?????? .probe??????????? = s3c_pwm_probe,? //探測函數(shù)
?????? .remove????????? = __devexit_p(s3c_pwm_remove),
};
我們看看探測函數(shù)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設(shè)備空間
?????? 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;? //計(jì)算TCON中控制哪個定時器
?????? pwm->clk = clk_get(dev, "pwm-tin");? //獲取預(yù)分頻后的時鐘
?????? 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);?? //信號反轉(zhuǎn)輸出
?????? __raw_writel(tcon, S3C2410_TCON);
?????? local_irq_restore(flags);
?????? ret = pwm_register(pwm);??????? //注冊pwm設(shè)備
?????? 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設(shè)備的函數(shù)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設(shè)備掛到pwm_list鏈表上
?????? mutex_unlock(&pwm_lock);
?????? return 0;
}
剩下來,我們看看這個pwm.c給我們提供了哪些接口函數(shù)
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設(shè)備
EXPORT_SYMBOL(pwm_config);?? //配置PWM設(shè)備,duty_ns為空占比,period_ns為周期
EXPORT_SYMBOL(pwm_enable);?? //啟動Timer定時器
EXPORT_SYMBOL(pwm_disable);?? //關(guān)閉Timer定時器
上面這個函數(shù),只要知道API,會調(diào)用就行了,在此,我分析下最難的一個配置PWM函數(shù),這個函數(shù)主要是根據(jù)周期period_ns,計(jì)算TCNT,根據(jù)空占比duty_ns,計(jì)算TCMP,然后寫入相應(yīng)寄存器。
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; //計(jì)算周期
?????? 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;? //根據(jù)周期求TCNT,n=To/Ti
?????? } else
????????????? tin_ns = NS_IN_HZ / clk_get_rate(pwm->clk);
?????? tcmp = duty_ns / tin_ns;?? //根據(jù)空占比求TCMP
?????? tcmp = tcnt - tcmp;? //根據(jù)占空比求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;
}
?
下面說說這個周期是怎么設(shè)計(jì)的
我們定時器的輸出頻率fi=PCLK/(prescaler value+1)/(divider value),這個可以獲得確定值
我們需要寫入一個初值n給TCNT,這樣就可以獲得一個頻率,為什么呢?
根據(jù)初值n=fi/fo,那么n=To/Ti
所以當(dāng)用戶給pwm_config函數(shù)傳遞一個周期period_ns,其實(shí)就是To=period_ns
這樣根據(jù)前面公式n=To/Ti= period_ns/fi,然后將這個初值n寫入TCNT就可以改變周期了
?
接著我再補(bǔ)充說明下pwm_config函數(shù)里代碼注釋關(guān)于自動加載怎么回事?
定時器工作原理其實(shí)是TCNT的值在時鐘到來時,減一計(jì)數(shù),每次減一完后,拿當(dāng)前TCNT與TCMP比較,如果TCNT=TCMP,那么信號電平反向輸出,然后TCNT繼續(xù)減一計(jì)數(shù),知道TCNT減到零后,如果有自動加載功能那么此時將由TCNTB把計(jì)數(shù)初值再次寫給TCNTP,同時TCMPB把比較值給TCMP,這樣就完成一次初值重裝,然后繼續(xù)進(jìn)行計(jì)數(shù)。我們給這種加載模式起了個名字叫雙緩沖機(jī)制,其中TCMPB和TCNTB就是Buffer緩存。
?
前面說pwm.c集驅(qū)動和設(shè)備于一體,那么下面我們看看設(shè)備相關(guān)的代碼
#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) },
};
上面的代碼就是設(shè)備部分代碼,其實(shí)就是五個定時器的資源,我們把目光放在DEFINE_S3C_TIMER宏上,你會發(fā)現(xiàn)其設(shè)備名是"s3c24xx-pwm",而我們在pwm.c中定義的驅(qū)動名也是"s3c24xx-pwm",這樣如果我們把設(shè)備注冊到內(nèi)核,那么設(shè)備"s3c24xx-pwm"和驅(qū)動"s3c24xx-pwm"就會匹配成功。所以如果你用到定時器0,那么你只要在BSP中添加s3c_device_timer[0]就可以了。我們現(xiàn)在做的是蜂鳴器驅(qū)動,使用的是Timer0定時器,我們就在mini2440的BSP文件mach-mini2440.c中添加如下代碼
static struct platform_device *mini2440_devices[] __initdata = {
?????? ……
?????? &s3c_device_timer[0],??? //添加
};
這樣我們就分析完pwm核心層的代碼了。
三.Backlight核心驅(qū)動
下面我們講講backlight子系統(tǒng)。背光子系統(tǒng)目錄在/driver/video/backlight下,其中背光子系統(tǒng)核心代碼是backlight.c
先查看/driver/video/backlight/Makefile
obj-$(CONFIG_BACKLIGHT_CLASS_DEVICE) += backlight.o
繼續(xù)查看/driver/video/backlight/Kconfig
config BACKLIGHT_CLASS_DEVICE
??????? tristate "Lowlevel Backlight controls"
?????? depends on BACKLIGHT_LCD_SUPPORT
?????? default m
所以配置內(nèi)核make menuconfig時,需要選中這一項(xiàng)。
?
下面看backlight背光的核心代碼backlight.c
static int __init backlight_class_init(void)
{
?????? backlight_class = class_create(THIS_MODULE, "backlight"); //注冊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背光子系統(tǒng)的主要就是靠這個類屬性,當(dāng)我們設(shè)置背光值就是向類屬性中某個成員寫背光值,這個類屬性就是給用戶的一種接口,我們重點(diǎn)看看
#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_NULL,
};
很明顯,在backlight類中我們創(chuàng)建了bl_power,brightness,actural_brightness,max_brightness四個成員,其中brightness是當(dāng)前亮度,max_brightness是最大亮度。當(dāng)用戶層通過cat或者echo命令就會觸發(fā)這些成員。對于這些屬性的讀寫函數(shù),我們先看看讀的函數(shù)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);? //輸出最大亮度
}
這個函數(shù)很簡單,但是重點(diǎn)是引入了幾個backlight背光子系統(tǒng)的幾個重要的數(shù)據(jù)結(jié)構(gòu),我們好好學(xué)習(xí)下。
首先是backlight背光子系統(tǒng)的設(shè)備結(jié)構(gòu)體backlight_device
struct backlight_device {
?????? struct backlight_properties props;? //背光屬性
?????? struct mutex update_lock;
?????? struct mutex ops_lock;
?????? struct backlight_ops *ops;??? //背光操作函數(shù),類似于file_operations
?????? struct notifier_block fb_notif;
?????? struct device dev;? //內(nèi)嵌設(shè)備
};
下面先看看背光屬性結(jié)構(gòu)體backlight_properties
struct backlight_properties {
?????? int brightness;? //當(dāng)前背光值
?????? int max_brightness;? //最大背光值
?????? int power;
?????? int fb_blank;
?????? unsigned int state;
};
再看看背光操作函數(shù)結(jié)構(gòu)體
struct backlight_ops {
?????? unsigned int options;
#define BL_CORE_SUSPENDRESUME?????? (1 << 0)
?????? int (*update_status)(struct backlight_device *);?? //改變背光狀態(tài)
?????? int (*get_brightness)(struct backlight_device *);? //獲取背光值
?????? int (*check_fb)(struct fb_info *);
};
好了,我們繼續(xù)看backlight類屬性中寫的函數(shù),例如設(shè)置當(dāng)前背光值函數(shù)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);? //調(diào)用backlight_update_status設(shè)備背光值
???????????????????? rc = count;
????????????? }
?????? }
?????? mutex_unlock(&bd->ops_lock);
?????? backlight_generate_event(bd, BACKLIGHT_UPDATE_SYSFS);
?????? return rc;
}
跟蹤backlight_update_status
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); //調(diào)用背光操作函數(shù)中改變背光狀態(tài)函數(shù)update_status
?????? mutex_unlock(&bd->update_lock);
}
對于這個backlight背光核心層驅(qū)動backlight.c,剩下的就是這個pwm.c給我們提供了哪些接口函數(shù)了。
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);? //注冊背光設(shè)備
EXPORT_SYMBOL(backlight_device_unregister); //注銷背光設(shè)備
這些接口很簡單,就不細(xì)說了,這樣我們的backlight子系統(tǒng)的核心層就介紹完了。
?
四.基于PWM&Backlight的蜂鳴器驅(qū)動
下面我們結(jié)合上面的PWM核心層和Backlight背光子系統(tǒng)核心層,根據(jù)基于pwm的背光驅(qū)動/driver/video/backlight/pwm_bl.c來修改成基于Mini2440的蜂鳴器驅(qū)動。
先查看/driver/video/backlight/Makefile
obj-$(CONFIG_BACKLIGHT_PWM)?? += pwm_bl.o
繼續(xù)查看/driver/video/backlight/Kconfig
config BACKLIGHT_PWM
?????? tristate "Generic PWM based Backlight Driver"
?????? depends on BACKLIGHT_CLASS_DEVICE && HAVE_PWM
?????? help
?????? ? If you have a LCD backlight adjustable by PWM, say Y to enable
?????? ? this driver.
我們的HAVE_PWM和BACKLIGHT_CLASS_DEVICE分別是在前面講pwm核心和backlight核心時已經(jīng)編譯了,所以配置內(nèi)核make menuconfig時,需要再選中"Generic PWM based Backlight Driver"這項(xiàng)。
?
好了,我們先把我們的蜂鳴器移植進(jìn)去吧,首先我們知道蜂鳴器使用的是GPB0端口,該端口如果工作在TOU0模式,就可以通過設(shè)備定時器的TCNT和TCMP來控制定時器的波形而來。先打開mini2440的BSP文件mach-mini2440.c,如下添加
static struct platform_device s3c_backlight_device = {
?????? .name???????????? = "pwm-backlight",?????????? //設(shè)備名
?????? .dev??????? = {
????????????? .parent??? = &s3c_device_timer[0].dev,? //該設(shè)備基于pwm中的0號定時器
????????????? .platform_data = &s3c_backlight_data,
?????? },
?????? .id=0,???? //對應(yīng)的就是pwm0
};
添加平臺數(shù)據(jù)
static struct platform_pwm_backlight_data s3c_backlight_data = {
?????? .pwm_id???????? = 0,?? //對應(yīng)的就是Timer0
?????? .max_brightness???? = 1000, ?//最大亮度
?????? .dft_brightness?????? = 10 ,????? //當(dāng)前亮度
?????? .pwm_period_ns??? = 800000,? //這就是前面說的T0,即輸出時鐘周期
?????? .init???????? = s3c_bl_init,? //端口初始化
};
注意到平臺數(shù)據(jù)中定義了init函數(shù),由于在蜂鳴器的初始化時,需要對GPB0設(shè)置為TOUT0模式,所以代碼如下編寫
static int s3c_bl_init(struct device *dev)
{
?????? s3c2410_gpio_pullup(S3C2410_GPB(0),0);?? // GPB0不上拉
?????? s3c2410_gpio_cfgpin(S3C2410_GPB(0),S3C2410_GPB0_TOUT0); // GPB0設(shè)置為TOUT0
?????? return 0;
}
然后把這個s3c_backlight_device加入到mini2440_devices數(shù)組
static struct platform_device *mini2440_devices[] __initdata = {
?????? ……
?????? &s3c_device_timer[0],
?????? &s3c_backlight_device, //添加
};
最后添加頭文件
#include <linux/pwm_backlight.h>
這樣配置完后,進(jìn)行make zImage生成zImage內(nèi)核鏡像。
?
好了,下面我們分析下基于pwm的背光驅(qū)動/driver/video/backlight/pwm_bl.c
static struct platform_driver pwm_backlight_driver = {
?????? .driver??????????? = {
????????????? .name????? = "pwm-backlight", //驅(qū)動名
????????????? .owner??? = THIS_MODULE,
?????? },
?????? .probe??????????? = pwm_backlight_probe, //探測函數(shù)
?????? .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);
}
注意上面的pwm_backlight_driver中的驅(qū)動名"pwm-backlight"和我們剛才移植時添加的設(shè)備名"pwm-backlight"是一致的,這樣設(shè)備和驅(qū)動就能匹配成功。下面看探測函數(shù)
static int pwm_backlight_probe(struct platform_device *pdev)
{
?????? struct platform_pwm_backlight_data *data = pdev->dev.platform_data;
?????? struct backlight_device *bl;
?????? struct pwm_bl_data *pb;? //本驅(qū)動的私有結(jié)構(gòu)體
?????? int ret;
?????? if (!data) {
????????????? dev_err(&pdev->dev, "failed to find platform data\n");
????????????? return -EINVAL;
?????? }
?????? if (data->init) {? //初始化端口,這個端口函數(shù)在BSP中定義
????????????? ret = data->init(&pdev->dev);
????????????? if (ret < 0)
???????????????????? return ret;
?????? }
?????? pb = kzalloc(sizeof(*pb), GFP_KERNEL); //分配pwm_bl_data空間
?????? 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->pwm = pwm_request(data->pwm_id, "backlight"); //注冊pwm設(shè)備
?????? if (IS_ERR(pb->pwm)) {
????????????? dev_err(&pdev->dev, "unable to request PWM for backlight\n");
????????????? ret = PTR_ERR(pb->pwm);
????????????? goto err_pwm;
?????? } else
????????????? dev_dbg(&pdev->dev, "got pwm for backlight\n");
?????? bl = backlight_device_register(dev_name(&pdev->dev), &pdev->dev,
???????????????????? pb, &pwm_backlight_ops); //注冊backlight設(shè)備
?????? if (IS_ERR(bl)) {
????????????? dev_err(&pdev->dev, "failed to register backlight\n");
????????????? ret = PTR_ERR(bl);
????????????? goto err_bl;
?????? }
?????? bl->props.max_brightness = data->max_brightness;
?????? bl->props.brightness = data->dft_brightness;
?????? backlight_update_status(bl);? //先點(diǎn)亮背光
?????? platform_set_drvdata(pdev, bl); //設(shè)置bl為私有數(shù)據(jù)
?????? return 0;
err_bl:
?????? pwm_free(pb->pwm);
err_pwm:
?????? kfree(pb);
err_alloc:
?????? if (data->exit)
????????????? data->exit(&pdev->dev);
?????? return ret;
}
對于這個驅(qū)動,我們重點(diǎn)關(guān)注的是注冊backlight設(shè)備時傳入的參數(shù)pwm_backlight_ops,因?yàn)槲覀冎胺治?span style="font-family:'Times New Roman'">backlight背光子系統(tǒng)時說過,背光設(shè)備結(jié)構(gòu)體中有個操作背光的函數(shù)集合,在我們的pwm_bl.c中,就需要定義這個操作背光的函數(shù)集合,也就是pwm_backlight_ops
static struct backlight_ops pwm_backlight_ops = {
?????? .update_status = pwm_backlight_update_status, //更新背光亮度
?????? .get_brightness?????? = pwm_backlight_get_brightness, //獲取背光亮度
};
獲取背光亮度函數(shù)pwm_backlight_get_brightness很簡單,跟蹤得到
static int pwm_backlight_get_brightness(struct backlight_device *bl)
{
?????? return bl->props.brightness;
}
我們重點(diǎn)看更新背光亮度函數(shù)pwm_backlight_update_status
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(brightness);
?????? if (brightness == 0) {? //背光值為0,關(guān)閉背光
????????????? pwm_config(pb->pwm, 0, pb->period);
????????????? pwm_disable(pb->pwm);
?????? } else {?? //調(diào)用pwm中的API設(shè)置背光
????????????? pwm_config(pb->pwm, brightness * pb->period / max, pb->period);
????????????? pwm_enable(pb->pwm);
?????? }
?????? return 0;
}
好了,這樣我們的pwm_bl.c也分析完了。在使用backlight子系統(tǒng)的時候,我們只需要在probe函數(shù)中注冊pwm和backlight設(shè)備,然后定義背光操作函數(shù)集合即可。
?
五.驅(qū)動測試
實(shí)驗(yàn)環(huán)境:內(nèi)核linux2.6.32.2,arm-linux-gcc交叉編譯器,mini2440開發(fā)板
下面我們進(jìn)行對上面的驅(qū)動進(jìn)行測試,按照上面的步驟操作,將上文已經(jīng)編譯好的zImage燒入開發(fā)板,通過超級終端控制,能控制蜂鳴器的發(fā)出的聲音頻率。
?
?
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖
總結(jié)
以上是生活随笔為你收集整理的Linux下的Backlight子系统的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于__init、__initdata和
- 下一篇: Linux下的RTC子系统