1. 程式人生 > >LCD(五)Backlight背光子系統

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

    /* 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));//寫入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集驅動和裝置於一體,那麼下面我們看看裝置相關的程式碼

注: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 that
	   registered 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 saving
	   modes; 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;
	} else
		dev_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;
}

五、測試