mtk8788 pwm频率及背光亮度调节

=======pwm频率修改

当前ALPS branch上,disp_pwm driver采用的是turnkey code,不同芯片型号,或不同branch,disp_pwm频率设定可能会有差异,因此客户有时会遇到如下问题:

(1)如何修改lk和kernel下disp_pwm频率?

(2)如何修改disp_pwm clock source?

(3)为什么修改dts无效?

首先 介绍下pwm

[SOLUTION]

 对于以上几种问题,归根结底就是:disp_pwm频率如何设定的问题。下面就概括一下,当前遇到的设定disp_pwm频率(fre)的方法。

1、Disp_PWM频率计算方法

fre=source clock/(divider+1)/(period+1)

source clock:时钟源,26MHz/104MHz/125MHz/62.5MHz/15.625/……,与IC design有关。若没有特别说明,一般是26MHz

divider:对source clock除频

period:disp_pwm周期,默认是1023

因此,想达到特定的fre,只需要适当的修改source、divider参数即可(period一般不建议修改)

 

 

2、disp_pwm频率代码位置

lk: ddp_pwm.c/disp_pwm_init()

kernel: ddp_pwm.c/disp_pwm_config_init()

 

3、dts配置方式方式

若lk & kernel都从dts读取disp_pwm参数,则只需针对性修改dts即可

led6:led@6 {

compatible = "mediatek,lcd-backlight";

led_mode = <5>;

data = <1>;

pwm_config = <0 0 0 0 0>;

};

pwm_config参数:clock source、divider、low_duration、high_duration、pmic_pad。与dts配置中pwm_config = <0 0 0 0 0>参数一一对应。只需关注前面2个参数即可

如上设定:clock source=26MHz,divider=0

fre=26MHz/1/1024=25.29KHz

 

3、不使用dts

divider: PWM_DEFAULT_DIV_VALUE   ///设定成你需要的值

CLK_CFG_1 bit[2:0]:0/1/2/……      ///选择不同的source

 

所以遇到频率无法修改成功的问题,先看是使用dts配置,还是disp_pwm driver init时hardcode写死,再相对应的修改设定。

5、kernel下source clock切换问题

【MT8788】ALPS04829940

说明:MT8788(MT8183,Sylvia)芯片上,disp_pwm source有如下5种:

0x100000B0

CLK_CFG_7

bit[2:0]

0

clk26m

26MHz

1

univpll_d3_d4

104MHz

2

osc_d2

125MHz

3

osc_d4

62.5MHz

4

osc_d16

15.625MHz

 

kernel下使用dts设定,ddp_pwm.c/disp_pwm_config_init(),会调用disp_pwm_set_pwmmux()函数对如上的5种clock source进行重新mapping,使用mapping后的clock ID进行配置。8788上我司目前使用的CLK_CFG_7

例如,想设定source=26MHz,则dts中应该这样填写:pwm_config = <4 0 0 0 0>;

寄存器定义

mapping后的clock ID

clock source

预期clk(mhz)

bit[2:0]=0

4

clk26m

26

bit[2:0]=1

3

univpll_d3_d4

104

bit[2:0]=2

2

osc_d2

125

bit[2:0]=3

1

osc_d4

62.5

bit[2:0]=4

0

osc_d16

15.625

 

 

 

=======pwm背光亮度调节

mtk方案并没有使用原生.level = EARLY_SUSPEND_LEVEL_DISABLE_FB去设置亮度,而是通过直接写属性节点brightness的方式,hal层通过读写此节点从而控制亮度

想要知道原生亮度控制方式可以参考此此链接:https://www.cnblogs.com/reality-soul/p/4757728.html

1 hal层控制

节点路径:sys/devices/platform/leds-mt65xx/leds/lcd-backlight/brightness

上层调用流程课参考http://www.52rd.com/Blog/Detail_RD.Blog_apu981_69251.html,此博客已做了解释

2 kernel如何响应亮度

首先看看驱动层brightness设备节点如何创建的

kernel-4.4/drivers/leds/led-class.c模块中会执行static DEVICE_ATTR_RW(brightness);这里要说下这个DEVICE_ATTR_RW宏

#define DEVICE_ATTR_RW(_name) \
    struct device_attribute dev_attr_##_name = __ATTR_RW(_name)

    struct device_attribute dev_attr_(wakealarm) = __ATTR_RW(wakealarm);

    
#define __ATTR_RW(_name) __ATTR(_name, (S_IWUSR | S_IRUGO),    _name##_show, _name##_store)    
             
struct device_attribute dev_attr_(wakealarm) =  __ATTR(wakealarm, (S_IWUSR | S_IRUGO),wakealarm_show, wakealarm_store)         


#define __ATTR(_name, _mode, _show, _store) {                
    .attr = {.name = __stringify(_name),                
         .mode = VERIFY_OCTAL_PERMISSIONS(_mode) },        
    .show    = _show,                        
    .store    = _store,                        
}

struct device_attribute dev_attr_(wakealarm) = {                
    .attr = {.name = __stringify(wakealarm),                
         .mode = VERIFY_OCTAL_PERMISSIONS((S_IWUSR | S_IRUGO)) },        
    .show    = wakealarm_show,                        
    .store    = wakealarm_store,                
}    

 

最终这个宏 static DEVICE_ATTR_RW(wakealarm);  生成了

struct device_attribute dev_attr_(wakealarm) = {                
    .attr = {.name = __stringify(wakealarm),                
         .mode = VERIFY_OCTAL_PERMISSIONS((S_IWUSR | S_IRUGO)) },        
    .show    = wakealarm_show,                        
    .store    = wakealarm_store,                
}

由此可以知道DEVICE_ATTR_RW(brightness)也会去指定一个store函数即brightness_store,在写brightness时会调用brightness_store,一搜果然存在

static ssize_t brightness_store(struct device *dev,
                struct device_attribute *attr, const char *buf, size_t size)
{       
        struct led_classdev *led_cdev = dev_get_drvdata(dev);
        unsigned long state;
        ssize_t ret;
        
        mutex_lock(&led_cdev->led_access);
        
        if (led_sysfs_is_disabled(led_cdev)) {
                ret = -EBUSY;
                goto unlock;
        }
        
        ret = kstrtoul(buf, 10, &state);/*上层写数据时会保存到buf里*/
        if (ret)
                goto unlock;
        
        if (state == LED_OFF)
                led_trigger_remove(led_cdev);
        led_set_brightness(led_cdev, state);/*当上层写节点时会调用led_set_brightness,我们的brightness也会传下去*/
        
        ret = size;
unlock: 
        mutex_unlock(&led_cdev->led_access);
        return ret;
}

 

 接下来led_set_brightness(leds/led-core.c) 会调用led_set_brightness_async(led_cdev, brightness)这里面还涉及到soft-blink可以不管,我们主要分析亮如何传进去的,以及驱动如何把0-255的亮度转化成pwm的占空比。接下来看下led_set_brightness_async干了啥

//led_set_brightness_async定义在leds.h里
static inline void led_set_brightness_async(struct led_classdev *led_cdev,
                                        enum led_brightness value)
{
        value = min(value, led_cdev->max_brightness);
        led_cdev->brightness = value;

        if (!(led_cdev->flags & LED_SUSPENDED))
                led_cdev->brightness_set(led_cdev, value);//接着brightness_set,这个        
                                                          //brightness_set在哪儿定义
}

 

 

 

想要知道brightness_set如何来的就要看kernel-4.4/driver/misc/mediatek/leds/mtk_leds_drv.c中的probe函数

static int mt65xx_leds_probe(struct platform_device *pdev){
    int i;
        int ret;/* rc; */
        struct cust_mt65xx_led *cust_led_list = mt_get_cust_led_list();

        if (cust_led_list == NULL) {
                LEDS_DRV_INFO("%s: get dts fail.\n", __func__);
                return -1;
        }
     ................//省略中间代码
    
     
    g_leds_data[i]->cust.mode = cust_led_list[i].mode;
                g_leds_data[i]->cust.data = cust_led_list[i].data;
                g_leds_data[i]->cust.name = cust_led_list[i].name;

                g_leds_data[i]->cdev.name = cust_led_list[i].name;
                g_leds_data[i]->cust.config_data = cust_led_list[i].config_data;        

                g_leds_data[i]->cdev.brightness_set = mt65xx_led_set;//brightness_set实际上        
                                                                     //是调用的    
                                         //mt65xx_led_set,不清楚的需要去了解下Linux设备与驱动
                g_leds_data[i]->cdev.blink_set = mt65xx_blink_set;

                INIT_WORK(&g_leds_data[i]->work, mt_mt65xx_led_work);

                ret = led_classdev_register(&pdev->dev, &g_leds_data[i]->cdev);

}

mt65xx_led_set接下来会调用mt_mt65xx_led_set 进而调用mt_mt65xx_led_set_cust(misc/mediatek/leds/mt6771/mtk_leds.c)

//这个函数里重点讲下上层0-255级数如何跟驱动pwm级数(0-1024)发生联系的
void mt_mt65xx_led_set(struct led_classdev *led_cdev, enum led_brightness level)
{
	struct mt65xx_led_data *led_data =
	    container_of(led_cdev, struct mt65xx_led_data, cdev);
	/* unsigned long flags; */
	/* spin_lock_irqsave(&leds_lock, flags); */

	if (disp_aal_is_support() == true) {
		if (led_data->level != level) {
			led_data->level = level;
			if (strcmp(led_data->cust.name, "lcd-backlight") != 0) {
				LEDS_DEBUG("Set NLED directly %d at time %lu\n",
					   led_data->level, jiffies);
				schedule_work(&led_data->work);
			} else {
				if (level != 0
				    && level * CONFIG_LIGHTNESS_MAPPING_VALUE < 255) {
					level = 1;
				} else {
					level =
					    (level * CONFIG_LIGHTNESS_MAPPING_VALUE) /
					    255;
				}
				backlight_debug_log(led_data->level, level);
				disp_pq_notify_backlight_changed((((1 <<
								     MT_LED_INTERNAL_LEVEL_BIT_CNT)
								    - 1) * level +
								   127) / 255);
                                //mt_mt65xx_led_set_cust第二个参数就是即将传给驱动的真正值,1《MT_LED_INTERNAL_LEVEL_BIT_CNT是固定的1023,这个计算公式将上层brightness跟pwm(0-1023)对应起来了,至于为什么时驱动中pwm级数为0-1023驱动有涉及,查找一下就知道了 

				disp_aal_notify_backlight_changed((((1 <<
								     MT_LED_INTERNAL_LEVEL_BIT_CNT)
								    - 1) * level +
								   127) / 255);
			}
		}
	} else {

		.............
	
	}
		/* spin_unlock_irqrestore(&leds_lock, flags); */
	}
/* if(0!=aee_kernel_Powerkey_is_press()) */
/* aee_kernel_wdt_kick_Powkey_api("mt_mt65xx_led_set",WDT_SETBY_Backlight); */
}
int mt_mt65xx_led_set_cust(struct cust_mt65xx_led *cust, int level)
{
    #ifdef CONFIG_MTK_PWM
        struct nled_setting led_tmp_setting = { 0, 0, 0 };
        int tmp_level = level;
        unsigned int BacklightLevelSupport =
            Cust_GetBacklightLevelSupport_byPWM();
    #endif
        static bool button_flag;

        switch (cust->mode) {//这些mode实在dts中定义的,8788背光使用的是     
                             //MT65XX_LED_MODE_CUST_BLS_PWM
            case MT65XX_LED_MODE_GPIO:
                LEDS_DEBUG("brightness_set_cust:go GPIO mode!!!!!\n");
                return ((cust_set_brightness) (cust->data)) (level);

            case MT65XX_LED_MODE_PMIC:

        ...................// 省略中间代码
    
        case MT65XX_LED_MODE_CUST_BLS_PWM:
                if (strcmp(cust->name, "lcd-backlight") == 0)
                        bl_brightness_hal = level;
        #ifdef MET_USER_EVENT_SUPPORT
                if (enable_met_backlight_tag())
                        output_met_backlight_tag(level);
        #endif
                return ((cust_set_brightness) (cust->data)) (level);//此函数才是这一些列调用 
                                                                  //中最关键的
    
        ......................
}

((cust_set_brightness) (cust->data)) (level)这个就是一个函数 指针,使用typedef 定义了,那这个函数到底指向那里了?它指向vendor/.../cust_leds.c定义的cust_mt65xx_led结构体中,如下:

static struct cust_mt65xx_led cust_led_list[MT65XX_LED_TYPE_TOTAL] = {
        {"red",               MT65XX_LED_MODE_PMIC, MT65XX_LED_PMIC_NLED_ISINK3,{255}},
        {"green",             MT65XX_LED_MODE_PMIC, MT65XX_LED_PMIC_NLED_ISINK1,{25}},
        {"blue",              MT65XX_LED_MODE_PMIC, MT65XX_LED_PMIC_NLED_ISINK2,{25}},
        {"jogball-backlight", MT65XX_LED_MODE_NONE, -1,{0,0,0,0,0}},
        {"keyboard-backlight",MT65XX_LED_MODE_NONE, -1,{0,0,0,0,0}},
        {"button-backlight",  MT65XX_LED_MODE_NONE, -1,{0,0,0,0,0}},
        {"lcd-backlight",         MT65XX_LED_MODE_CUST_BLS_PWM, (int)disp_bls_set_backlight,{1,0,0,0,0}},
};//MT65XX_LED_MODE_CUST_BLS_PWM模式下设置会调用disp_bls_set_backlight函数


 然后继续调用disp_pwm_set_backlight->disp_pwm_set_backlight_cmdq(/misc/mediatek/video/common/pwm10/ddp_pwm.c)
int disp_pwm_set_backlight_cmdq(enum disp_pwm_id_t id, int level_1024, void *cmdq)
{
#ifndef CONFIG_FPGA_EARLY_PORTING
        /* PWM is excluded from FPGA bitfile */
        unsigned long reg_base;
        int old_pwm;
        int index;
        int abs_diff;
        int max_level_1024;

        if ((DISP_PWM_ALL & id) == 0) {
                PWM_ERR("[ERROR] disp_pwm_set_backlight_cmdq: invalid PWM ID = 0x%x", id);
                return -EFAULT;
        }

        index = index_of_pwm(id);//id为0选用pwm0输出   1 pwm1输出

        /* we have to change backlight after config init or max backlight changed */
        old_pwm = atomic_xchg(&g_pwm_backlight[index], level_1024);
        if (old_pwm != level_1024 || atomic_cmpxchg(&g_pwm_is_change_state[index], 1, 0) == 1) {
                abs_diff = level_1024 - old_pwm;
                if (abs_diff < 0)
                        abs_diff = -abs_diff;

                if (old_pwm == 0 || level_1024 == 0 || abs_diff > 64) {
                        /* To be printed in UART log */
                        disp_pwm_log(level_1024, MSG_LOG);
                        if (old_pwm != level_1024) {
                                /* Print information if backlight is changed */
                                PWM_NOTICE("disp_pwm_set_backlight_cmdq(id = 0x%x, level_1024 = %d), old = %d",
                                        id, level_1024, old_pwm);
                        }
                } else {
                        disp_pwm_log(level_1024, MSG_LOG);
                }

                max_level_1024 = disp_pwm_get_max_backlight(id);

                if (level_1024 > max_level_1024)
                        level_1024 = max_level_1024;
                else if (level_1024 < 0)
                        level_1024 = 0;

                level_1024 = disp_pwm_level_remap(id, level_1024);
                PWM_NOTICE("disp_pwm_set_backlight_cmdq max_level_1024 : %d\n",max_level_1024);
                reg_base = pwm_get_reg_base(id);//读取pwm0的基地址  8788上是1 1 00E000
                PWM_NOTICE("disp_pwm_set_backlight_cmdq(reg_base = 0x%x, DISP_PWM_CON_1_OFF = 0x%x), reg = 0x%x",
                                        reg_base, DISP_PWM_CON_1_OFF, reg_base + DISP_PWM_CON_1_OFF);
                if (level_1024 > 0) {
                        printk("[lcm][kernel] (unsigned int)(INREG32(reg32)&~(mask))|(val)-->%d\n",
                                        (unsigned int)(INREG32(reg_base + DISP_PWM_CON_1_OFF)&~(0x1fff << 16))|(level_1024 << 16));
                        DISP_REG_MASK(cmdq, reg_base + DISP_PWM_CON_1_OFF, level_1024 << 16, 0x1fff << 16);

                        disp_pwm_set_enabled(cmdq, id, 1);
                } else {
                        /* Avoid to set 0 */
                        DISP_REG_MASK(cmdq, reg_base + DISP_PWM_CON_1_OFF, 1 << 16, 0x1fff << 16);
                        //reg_base + DISP_PWM_CON_1_OFF值为1100E01C DISP_PWM_CON_1寄存器

                        disp_pwm_set_enabled(cmdq, id, 0);      /* To save power */
                }

                DISP_REG_MASK(cmdq, reg_base + DISP_PWM_COMMIT_OFF, 1, ~0);
                DISP_REG_MASK(cmdq, reg_base + DISP_PWM_COMMIT_OFF, 0, ~0);
        }

        if (g_pwm_led_mode == MT65XX_LED_MODE_CUST_BLS_PWM &&
                atomic_read(&g_pwm_is_power_on[index]) == 0 && level_1024 > 0) {
                /* print backlight once after device resumed */
                disp_pwm_backlight_status(id, true);
        }
#endif
        return 0;
}


//介绍下如何获取pwm寄存器基地址
#define pwm_get_reg_base(id) ((id == DISP_PWM0) ? DISPSYS_PWM0_BASE : DISPSYS_PWM1_BASE)
传下去的id为0,所以返回DISPSYS_PWM0_BASE,看下这个DISPSYS_PWM0_BASE

#define DISPSYS_PWM0_BASE		    ddp_get_module_va(DISP_MODULE_PWM0)
这个ddp_get_module_va实际上就是从ddp_module结构体中根据module_id获取地址, 贴出来部分ddp_module代码:
static ddp_module  ddp_modules[DISP_MODULE_NUM] = {
        
        ....................
        
	{DISP_MODULE_PWM0,
	 DISP_T_PWM,
	 "pwm0",
	 0,
	 &ddp_driver_pwm,
	 {"mediatek,disp_pwm0",
	  0x1100e000,
	  158,
	  0,
	  0,
	  0}
	},

	{DISP_MODULE_PWM1,
	 DISP_T_PWM,
	 "pwm1",
	 0,
	 NULL,
	 {reg_magic,}
	},

	{DISP_MODULE_CONFIG,
	 DISP_T_UNKNOWN,
	 "config",
	 0,
	 NULL,
	 {"mediatek,mmsys_config",
	  0x14000000,
	  0,
	  0,
	  0,
	  0}
	},
    
    .........................
}


pwm寄存器如下

 

 

DISP_REG_MASK(cmdq, reg_base + DISP_PWM_CON_1_OFF, level_1024 << 16, 0x1fff << 16);如上所说reg_base + DISP_PWM_CON_1_OFF是pwm1,看看这个寄存器说明

 到这里就完全清晰了,DISP_REG_MASK将计算后的pwm级数写进寄存器   转换成占空比(PWM_HIGH_WIDTH/PWM_PERIOD)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值