RK292X-PWM背光碟機動分析及修改
原文連結[http://blog.chinaunix.net/uid-28623414-id-3618965.html]
相關檔案
kernel/arch/arm/mach-rk2928/board-rk2926-sdk.c
kernel/drivers/video/backlight/rk2818_backlight.h
kernel/drivers/video/backlight/rk29_backlight.c
一、驅動的裝置模型
和大多數我們常用的驅動一樣,PWM也是做成了Platform型別的裝置驅動。而與之相關的,會需要我們提供相應的Platform Device。而Platform Device是在板檔案中宣告和註冊的。當platform匯流排匹配Device和Driver時候,會在Driver裡獲取到我們預先註冊在系統裡的Platform Device,進而對其進行初始化和控制。
二、Backlight裝置的註冊
/***********************************************************
* rk30 backlight
************************************************************/
#ifdef CONFIG_BACKLIGHT_RK29_BL
//用來描述pwm裝置的ID,通過這個巨集來切換pwm的通道(2926支援3路pwm,且共享一路clock)
#define PWM_ID 0
#define PWM_MUX_NAME GPIO0D2_PWM_0_NAME
#define PWM_MUX_MODE GPIO0D_PWM_0
#define PWM_MUX_MODE_GPIO GPIO0D_GPIO0D2
#define PWM_GPIO RK2928_PIN0_PD2
#define PWM_EFFECT_VALUE 0
#if defined(V86_VERSION_1_1)
#define LCD_DISP_ON_PIN
#endif
#ifdef LCD_DISP_ON_PIN
#if defined(V86_VERSION_1_1)
#define BL_EN_PIN RK2928_PIN3_PC1
#define BL_EN_VALUE GPIO_HIGH
#define BL_EN_MUX_NAME GPIO3C1_OTG_DRVVBUS_NAME
#define BL_EN_MUX_MODE GPIO3C_GPIO3C1
#else
#define BL_EN_PIN RK2928_PIN1_PB0
#define BL_EN_VALUE GPIO_HIGH
#endif
#endif
static int rk29_backlight_io_init(void)
{
int ret = 0;
/* 設定GPIO口為PWM功能 */
rk30_mux_api_set(PWM_MUX_NAME, PWM_MUX_MODE);
#ifdef LCD_DISP_ON_PIN
#if defined(V86_VERSION_1_1)
rk30_mux_api_set(BL_EN_MUX_NAME, BL_EN_MUX_MODE);
#endif
/* 請求GPIO口 */
ret = gpio_request(BL_EN_PIN, NULL);
if (ret != 0) {
gpio_free(BL_EN_PIN);
}
/* 設定GPIO口為輸出 */
gpio_direction_output(BL_EN_PIN, 0);
/* 設定輸出為高點平 */
gpio_set_value(BL_EN_PIN, BL_EN_VALUE);
#endif
return ret;
}
static int rk29_backlight_io_deinit(void)
{
int ret = 0;
#ifdef LCD_DISP_ON_PIN
/* 管腳狀態取反 */
gpio_set_value(BL_EN_PIN, !BL_EN_VALUE);
/* 釋放GPIO */
gpio_free(BL_EN_PIN);
#endif
/* 設定管腳功能為GPIO */
rk30_mux_api_set(PWM_MUX_NAME, PWM_MUX_MODE_GPIO);
#if defined(V86_VERSION_1_0) || defined(V86_VERSION_1_1)
#if defined(CONFIG_MFD_TPS65910)
if(g_pmic_type == PMIC_TYPE_TPS65910)
{
gpio_direction_output(PWM_GPIO, GPIO_HIGH);
}
#endif
#endif
return ret;
}
static int rk29_backlight_pwm_suspend(void)
{
int ret = 0;
rk30_mux_api_set(PWM_MUX_NAME, PWM_MUX_MODE_GPIO);
if (gpio_request(PWM_GPIO, NULL)) {
printk("func %s, line %d: request gpio fail\n", __FUNCTION__, __LINE__);
return -1;
}
#if defined(CONFIG_MFD_TPS65910)
if (pmic_is_tps65910() )
#if defined(V86_VERSION_1_0) || defined(V86_VERSION_1_1)
gpio_direction_output(PWM_GPIO, GPIO_HIGH);
#else
gpio_direction_output(PWM_GPIO, GPIO_LOW);
#endif
#endif
#if defined(CONFIG_REGULATOR_ACT8931)
if (pmic_is_act8931() )
gpio_direction_output(PWM_GPIO, GPIO_HIGH);
#endif
#ifdef LCD_DISP_ON_PIN
gpio_direction_output(BL_EN_PIN, 0);
gpio_set_value(BL_EN_PIN, !BL_EN_VALUE);
#endif
return ret;
}
static int rk29_backlight_pwm_resume(void)
{
gpio_free(PWM_GPIO);
rk30_mux_api_set(PWM_MUX_NAME, PWM_MUX_MODE);
#ifdef LCD_DISP_ON_PIN
msleep(30);
gpio_direction_output(BL_EN_PIN, 1);
gpio_set_value(BL_EN_PIN, BL_EN_VALUE);
#endif
return 0;
}
/* 設定背光資訊結構體 */
static struct rk29_bl_info rk29_bl_info = {
.pwm_id = PWM_ID,
.min_brightness = 40,//modefy by logan for make backlight lower
.bl_ref = PWM_EFFECT_VALUE,
.io_init = rk29_backlight_io_init,
.io_deinit = rk29_backlight_io_deinit,
.pwm_suspend = rk29_backlight_pwm_suspend,
.pwm_resume = rk29_backlight_pwm_resume,
};
/* 設定背光裝置(Platform型別)結構體 */
static struct platform_device rk29_device_backlight = {
.name = "rk29_backlight",
.id = -1,
.dev = {
.platform_data = &rk29_bl_info,
}
};
#if defined(V86_VERSION_1_0) || defined(V86_VERSION_1_1) //for v86 to modify flash lcd when startup
static int __init set_pwm_gpio_high(void)
{
printk("%s, xhc", __func__);
rk30_mux_api_set(PWM_MUX_NAME, PWM_MUX_MODE_GPIO);
if (gpio_request(PWM_GPIO, NULL)) {
printk("func %s, line %d: request gpio fail\n", __FUNCTION__, __LINE__);
return -1;
}
gpio_direction_output(PWM_GPIO, GPIO_HIGH);
gpio_free(PWM_GPIO);
return 0;
}
core_initcall(set_pwm_gpio_high);
#endif
#endif
從上面程式碼可以看出,實際上,核心內容只是填充了一個Platform device的結構體。而背光資訊是以Platform_data(void *型別)形式儲存的。而在背光資訊結構體的初始化中,也無非是設定了預設的PWM通道和初始化、反初始化、休眠和恢復等回撥函式。而在其中,最主要的是初始化函式。在裡面完成了PWM功能的選擇、IO口的申請等內容。這是我們完成PWM功能所必須的。
static struct platform_device *devices[] __initdata = {
#ifdef CONFIG_FB_ROCKCHIP
&device_fb,
#endif
#ifdef CONFIG_LCDC_RK2928
&device_lcdc,
#endif
#ifdef CONFIG_BACKLIGHT_RK29_BL
&rk29_device_backlight,
#endif
#ifdef CONFIG_ION
&device_ion,
#endif
#ifdef CONFIG_SND_SOC_RK2928
&device_acodec,
#endif
#ifdef CONFIG_BATTERY_RK30_ADC_FAC
&rk30_device_adc_battery,
#endif
};
static void __init rk2928_board_init(void)
{
store_boot_source();
gpio_request(POWER_ON_PIN, "poweronpin");
gpio_direction_output(POWER_ON_PIN, GPIO_HIGH);
pm_power_off = rk2928_pm_power_off;
rk30_i2c_register_board_info();
spi_register_board_info(board_spi_devices, ARRAY_SIZE(board_spi_devices));
platform_add_devices(devices, ARRAY_SIZE(devices));
#ifdef CONFIG_WIFI_CONTROL_FUNC
rk29sdk_wifi_bt_gpio_control_init();
#endif
}
接著,我們可以看到,我們將我們的背光設備註冊進了名為devices的Platform_device的數組裡,並在rk2928_board_init函式裡最終通過platform_add_devices新增進了平臺裝置列表裡,以便在對應的驅動註冊時進行匹配。而匹配的依據就是我們的裝置名稱(.name = “rk29_backlight”)。
在系統啟動後,會載入各種裝置驅動,當有與我們剛剛註冊的裝置名稱相同的驅動時,就會將兩者關聯,並執行驅動中的probe函式。在背光碟機動中,我們可以看到驅動的型別是platform_driver,而對應的驅動名稱同樣為”rk29_backlight”:
static struct platform_driver rk29_backlight_driver = {
.probe = rk29_backlight_probe,
.remove = rk29_backlight_remove,
.driver = {
.name = "rk29_backlight",
.owner = THIS_MODULE,
},
.shutdown = rk29_backlight_shutdown,
};
當名稱匹配之後,會執行驅動中的probe回撥函式:
static int rk29_backlight_probe(struct platform_device *pdev)
{
int ret = 0;
/* 獲取註冊的背光資訊 */
struct rk29_bl_info *rk29_bl_info = pdev->dev.platform_data;
u32 id = rk29_bl_info->pwm_id;
u32 divh, div_total;
unsigned long pwm_clk_rate;
背光屬性
struct backlight_properties props;
int pre_div = PWM_APB_PRE_DIV;
if (rk29_bl) {
printk(KERN_CRIT "%s: backlight device register has existed \n",
__func__);
return -EEXIST;
}
if (!rk29_bl_info->delay_ms)
rk29_bl_info->delay_ms = 100;
if (rk29_bl_info->min_brightness < 0 || rk29_bl_info->min_brightness > BL_STEP)
rk29_bl_info->min_brightness = 52;
if (rk29_bl_info && rk29_bl_info->io_init) {
rk29_bl_info->io_init();
}
if(rk29_bl_info->pre_div > 0)
pre_div = rk29_bl_info->pre_div;
memset(&props, 0, sizeof(struct backlight_properties));
props.type = BACKLIGHT_RAW;
props.max_brightness = BL_STEP;
/* 註冊背光裝置 */
rk29_bl = backlight_device_register("rk28_bl", &pdev->dev, rk29_bl_info, &rk29_bl_ops, &props);
if (!rk29_bl) {
printk(KERN_CRIT "%s: backlight device register error\n",
__func__);
return -ENODEV;
}
#if defined(CONFIG_ARCH_RK29)
pwm_clk = clk_get(NULL, "pwm"); /* 獲取pwm時鐘 */
#elif defined(CONFIG_ARCH_RK30) || defined(CONFIG_ARCH_RK2928)
if (id == 0 || id == 1)
pwm_clk = clk_get(NULL, "pwm01");
else if (id == 2 || id == 3)
pwm_clk = clk_get(NULL, "pwm23");
#endif
if (IS_ERR(pwm_clk) || !pwm_clk) {
printk(KERN_ERR "failed to get pwm clock source\n");
return -ENODEV;
}
/* 獲取時鐘頻率(RK2926上為1485000000) */
pwm_clk_rate = clk_get_rate(pwm_clk);
/* 計算APB預分頻後(RK2926上,分配係數pre_div為1000)一週期的時鐘數 */
div_total = pwm_clk_rate / pre_div;
/* 根據PWM分頻因子計算分頻後的一週期時鐘數,分頻係數見下圖 */
div_total >>= (1 + (PWM_DIV >> 9));
/* 分頻因子過小,有可能出現為0的情況,則給預設值1 */
div_total = (div_total) ? div_total : 1;
if(rk29_bl_info->bl_ref) {
divh = 0;
} else {
divh = div_total;
}
/* 使能pwm時鐘 */
clk_enable(pwm_clk);
/* 復位並設定分頻值 */
write_pwm_reg(id, PWM_REG_CTRL, PWM_DIV|PWM_RESET);
/* 設定LRC暫存器為總時鐘週期數 */
write_pwm_reg(id, PWM_REG_LRC, div_total);
/* 設定HRC暫存器,佔空比為HRC/LRC */
write_pwm_reg(id, PWM_REG_HRC, divh);
/* 設定CNTR暫存器初值為0 */
write_pwm_reg(id, PWM_REG_CNTR, 0x0);
/* 使能PWM及TIMER使能 */
write_pwm_reg(id, PWM_REG_CTRL, PWM_DIV|PWM_ENABLE|PWM_TIME_EN);
rk29_bl->props.power = FB_BLANK_UNBLANK;
rk29_bl->props.fb_blank = FB_BLANK_UNBLANK;
/* 設定預設亮度為50% */
rk29_bl->props.brightness = BL_STEP / 2;
rk29_bl->props.state = BL_CORE_DRIVER1;
/* 執行背光延時更新動作 */
schedule_delayed_work(&rk29_backlight_work, msecs_to_jiffies(rk29_bl_info->delay_ms));
/* 建立屬性檔案 */
ret = device_create_file(&pdev->dev,&dev_attr_rk29backlight);
if(ret)
{
dev_err(&pdev->dev, "failed to create sysfs file\n");
}
/* 註冊android掛起和喚醒相關函式 */
register_early_suspend(&bl_early_suspend);
#ifdef CONFIG_LOGO_LOWERPOWER_WARNING
if( 1 == system_lowerpower ){
rk29_bl->props.brightness =5;
mdelay (1500);
}
#endif
printk("RK29 Backlight Driver Initialized.\n");
return ret;
}
由上面的PWM_DIV(0<<9)可知,分頻係數為1/2,所以我們在div_total >>= (1 + (PWM_DIV >> 9))計算後,實際效果相當於乘了1/2。其他分頻係數以此類推。
在開啟pwm後,設定了預設亮度為50%,並交由延時工作佇列來進行亮度的更新工作。實際更新亮度的函式為:
static int rk29_bl_update_status(struct backlight_device *bl)
{
u32 divh,div_total;
struct rk29_bl_info *rk29_bl_info = bl_get_data(bl);
u32 id = rk29_bl_info->pwm_id;
u32 ref = rk29_bl_info->bl_ref;
int brightness = bl->props.brightness;
if (suspend_flag)
return 0;
if (bl->props.brightness < rk29_bl_info->min_brightness && bl->props.brightness != 0) /*avoid can't view screen when close backlight*/
brightness = rk29_bl_info->min_brightness;
if (bl->props.power != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.fb_blank != FB_BLANK_UNBLANK)
brightness = 0;
if (bl->props.state & BL_CORE_DRIVER3)
brightness = 0;
/* 讀取一個pwm週期中的時鐘週期數 */
div_total = read_pwm_reg(id, PWM_REG_LRC);
/* 通過亮度值計算設定高點平時鐘週期數 */
if (ref) {
divh = div_total*brightness/BL_STEP;
} else {
divh = div_total*(BL_STEP-brightness)/BL_STEP;
}
/* 更新HRC,以更新新佔空比的pwm輸出 */
write_pwm_reg(id, PWM_REG_HRC, divh);
if ((bl->props.state & BL_CORE_DRIVER1) && brightness ==0 ){ //BL_CORE_DRIVER1 is the flag if backlight is closed.
bl->props.state &= ~BL_CORE_DRIVER1;
clk_disable(pwm_clk);
if (rk29_bl_info->pwm_suspend)
rk29_bl_info->pwm_suspend();
}else if(!(bl->props.state & BL_CORE_DRIVER1) && brightness != 0){
bl->props.state |= BL_CORE_DRIVER1;
if (rk29_bl_info->pwm_resume)
rk29_bl_info->pwm_resume();
clk_enable(pwm_clk);
}
DBG("%s:line=%d,brightness = %d, div_total = %d, divh = %d state=%x \n",__FUNCTION__,__LINE__,brightness, div_total, divh,bl->props.state);
return 0;
}
在實際情況下,我們的螢幕是在pwm為低電平輸出時為亮狀態,也就是說我們的佔空比是滅屏狀態所佔的比例。所以我們需要將亮狀態的輸出調整為 1-佔空比,而我們每一個pwm週期為LRC個時鐘週期,高電平週期為HRC,所以佔空比為HRC/LRC,對應到實際情況下,我們的有效亮度佔空比應為1-HRC/LRC = (LRC-HRC)/LRC。而對映到BL_STEP上,就是(BL_STEP-brightness)/BL_STEP。所以,我們如果想調整一個pwm週期的時間,那麼我們可以調整LRC的數值,而如果想要設定佔空比,那麼我們就調整HRC的值。在我們實際情況下,當HRC為0時,佔空比為1,而有效亮度佔空比為100%,當HRC為LRC時,佔空比為100%,實際有效亮度佔空比為0。上面的ref變數就是用來標識我們當前狀態下的佔空比是否與真正的有效亮度對應,並根據它來進行分類計算。
事實上,瑞芯微文件和手冊並沒有對pwm相關內容做過詳細的說明。上面大部分內容為對照程式碼的分析。如有錯誤,希望高手予以指正。