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相关内容做过详细的说明。上面大部分内容为对照代码的分析。如有错误,希望高手予以指正。

猜你喜欢

转载自blog.csdn.net/yx2817/article/details/55258099
今日推荐