zoukankan      html  css  js  c++  java
  • 高通LCD的pwm背光驱动

    发生异常的现象:

    msm8953 lcd在快速亮灭的情况下背光概率性休眠不灭;测量高通pwm,发现正常的时候pwm的管脚LCM_BL_PWM为低电平,失败的时候为高电平;

    根据原理图:

    mpp是什么?
    mpp是基于电源pmic的管脚,也叫做多功能管脚;MPP的全称是Multi Purpose Pin;可以做电源、gpio、ADC、PWM、SINK等功能。

    背光的控制方式:

    1. LCD控制IC支持动态背光控制功能(CABC)通过解析图像的直方图动态改变输出PWM的占空比从而动态调节LCD的背光,在不改变图像显示效果的情况下降低功耗,PMIC根据CABC的占空比控制背光输出电压;
    2. 背光控制部分不经过PMIC而是通过一颗单独的带有boost转换功能的LED驱动器如LM3630A,该芯片通过PWM调节亮度。

    我们使用的就是第一种方式;

    通过soc->pmi8950(内部pwm)->mpp3的方式去控制。

    lcd背光控制调用流程:

    首先,我们用的是mipi接口,所以lcd显示驱动是在mdss_dsi.c中,pwm驱动控制是在pwm-qpnp.c文件中(kernelmsm-3.18driverspwm);

    mdss_dsi.c文件中,具体在哪里调用到背光函数呢?

    根据打印log,可以知道背光控制函数mdss_dsi_panel_bl_ctrl

    mdss_dsi_panel_bl_ctrl这个函数是在mdss_dsi_panel.c文件中;

    调用顺序如下:
    mdss_dsi_ctrl_probe -- >
      mdss_dsi_config_panel -- >
        mdss_dsi_panel_init -- >
         ctrl_pdata->panel_data.set_backlight = mdss_dsi_panel_bl_ctrl;

    根据mdss_dsi_panel_bl_ctrl函数:

    static void mdss_dsi_panel_bl_ctrl(struct mdss_panel_data *pdata,
    							u32 bl_level)
    {
        ......
    
    	/*
    	 * Some backlight controllers specify a minimum duty cycle
    	 * for the backlight brightness. If the brightness is less
    	 * than it, the controller can malfunction.
    	 */
    
    	if ((bl_level < pdata->panel_info.bl_min) && (bl_level != 0))
    		bl_level = pdata->panel_info.bl_min;
    
    	switch (ctrl_pdata->bklt_ctrl) {
    	case BL_WLED:
    		led_trigger_event(bl_led_trigger, bl_level);
    		break;
    	case BL_PWM:
    		mdss_dsi_panel_bklt_pwm(ctrl_pdata, bl_level);
    		break;
    	case BL_DCS_CMD:
    		if (!mdss_dsi_sync_wait_enable(ctrl_pdata)) {
    			mdss_dsi_panel_bklt_dcs(ctrl_pdata, bl_level);
    			break;
    		}
    		/*
    		 * DCS commands to update backlight are usually sent at
    		 * the same time to both the controllers. However, if
    		 * sync_wait is enabled, we need to ensure that the
    		 * dcs commands are first sent to the non-trigger
    		 * controller so that when the commands are triggered,
    		 * both controllers receive it at the same time.
    		 */
    		sctrl = mdss_dsi_get_other_ctrl(ctrl_pdata);
    		if (mdss_dsi_sync_wait_trigger(ctrl_pdata)) {
    			if (sctrl)
    				mdss_dsi_panel_bklt_dcs(sctrl, bl_level);
    			mdss_dsi_panel_bklt_dcs(ctrl_pdata, bl_level);
    		} else {
    			mdss_dsi_panel_bklt_dcs(ctrl_pdata, bl_level);
    			if (sctrl)
    				mdss_dsi_panel_bklt_dcs(sctrl, bl_level);
    		}
    		break;
    	default:
    		pr_err("%s: Unknown bl_ctrl configuration
    ",
    			__func__);
    		break;
    	}
    }
    

    我们进入mdss_dsi_panel_bklt_pwm函数来看看:

    static void mdss_dsi_panel_bklt_pwm(struct mdss_dsi_ctrl_pdata *ctrl, int level)
    {
    	int ret;
    	u32 duty;
    	u32 period_ns;
    
    	if (ctrl->pwm_bl == NULL) {
    		pr_err("%s: no PWM
    ", __func__);
    		return;
    	}
    
    	if (level == 0) {
    		if (ctrl->pwm_enabled) {
    			ret = pwm_config_us(ctrl->pwm_bl, level,
    					ctrl->pwm_period);
    			if (ret)
    				pr_err("%s: pwm_config_us() failed err=%d.
    ",
    						__func__, ret);
    			pwm_disable(ctrl->pwm_bl);
    		}
    		ctrl->pwm_enabled = 0;
    		return;
    	}
    
        ....
    }
    

    进入pwm_disable函数,这里有调用了一个回调函数:

    /**
     * pwm_disable() - stop a PWM output toggling
     * @pwm: PWM device
     */
    void pwm_disable(struct pwm_device *pwm)
    {
    	if (pwm && test_and_clear_bit(PWMF_ENABLED, &pwm->flags)) {
    		pwm->chip->ops->disable(pwm->chip, pwm);
    	}
    }
    

    搜索之后,可以在qpnp_pwm_disablepwm-qpnp.c文件中找到相应的函数和函数集):

    static struct pwm_ops qpnp_pwm_ops = {
    	.enable = qpnp_pwm_enable,
    	.disable = qpnp_pwm_disable,
    	.config = qpnp_pwm_config,
    	.free = qpnp_pwm_free,
    	.owner = THIS_MODULE,
    };
    
    /**
     * qpnp_pwm_disable - stop a PWM output toggling
     * @pwm_chip: the PWM chip
     * @pwm: the PWM device
     */
    static void qpnp_pwm_disable(struct pwm_chip *pwm_chip,
    		struct pwm_device *pwm)
    {
    
    	struct qpnp_pwm_chip	*chip = qpnp_pwm_from_pwm_chip(pwm_chip);
    	unsigned long		flags;
    	int rc = 0;
    
    	spin_lock_irqsave(&chip->lpg_lock, flags);
    
    	if (QPNP_IS_PWM_CONFIG_SELECTED(
    		chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL]) ||
    			chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED)
    		rc = qpnp_lpg_configure_pwm_state(chip,
    					QPNP_PWM_DISABLE);
    	else if (!(chip->flags & QPNP_PWM_LUT_NOT_SUPPORTED))
    		rc = qpnp_lpg_configure_lut_state(chip,
    					QPNP_LUT_DISABLE);
    
    	if (!rc)
    		chip->enabled = false;
    
    	spin_unlock_irqrestore(&chip->lpg_lock, flags);
    
    	if (rc)
    		pr_err("Failed to disable PWM channel: %d
    ",
    					chip->channel_id);
    }
    

    来到qpnp_lpg_configure_pwm_state(chip, QPNP_PWM_DISABLE);这个函数中来:

    static int qpnp_lpg_configure_pwm_state(struct qpnp_pwm_chip *chip,
    					enum qpnp_pwm_state state)
    {
    	struct qpnp_lpg_config	*lpg_config = &chip->lpg_config;
    	u8			value, mask;
    	int			rc;
    	bool			test_enable;
    
    	if (chip->sub_type == QPNP_PWM_MODE_ONLY_SUB_TYPE) {
    		if (state == QPNP_PWM_ENABLE)
    			value = QPNP_ENABLE_PWM_MODE_ONLY_SUB_TYPE;
    		else
    			value = QPNP_DISABLE_PWM_MODE_ONLY_SUB_TYPE;
    
    		mask = QPNP_PWM_MODE_ONLY_ENABLE_DISABLE_MASK_SUB_TYPE;
    	} else {
    		if (state == QPNP_PWM_ENABLE)
    			value = qpnp_enable_pwm_mode(chip);
    		else
    			value = QPNP_DISABLE_PWM_MODE(chip);
    
    		mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
    			QPNP_PWM_SRC_SELECT_MASK | QPNP_PWM_EN_RAMP_GEN_MASK;
    		if (chip->sub_type != QPNP_LPG_S_CHAN_SUB_TYPE)
    			mask |= QPNP_EN_PWM_OUTPUT_MASK;
    	}
    
    	if (chip->in_test_mode) {
    		test_enable = (state == QPNP_PWM_ENABLE) ? 1 : 0;
    		rc = qpnp_dtest_config(chip, test_enable);
    		if (rc)
    			pr_err("Failed to configure TEST mode
    ");
    	}
    
    	pr_debug("pwm_enable_control: %d
    ", value);
    	rc = qpnp_lpg_save_and_write(value, mask,
    		&chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
    		SPMI_LPG_REG_ADDR(lpg_config->base_addr,
    		QPNP_ENABLE_CONTROL), 1, chip);
    	if (rc)
    		goto out;
    
    	/*
    	 * Due to LPG hardware bug, in the PWM mode, having enabled PWM,
    	 * We have to write PWM values one more time.
    	 */
    	if (state == QPNP_PWM_ENABLE)
    		return qpnp_lpg_save_pwm_value(chip);
    out:
    	return rc;
    }
    

    qpnp_lpg_save_pwm_value会保存着上一次pwm的高低电平的值,

    rc = qpnp_lpg_save_and_write(value, mask,
    			&chip->qpnp_lpg_registers[QPNP_PWM_VALUE_LSB],
    			SPMI_LPG_REG_ADDR(lpg_config->base_addr,
    			QPNP_PWM_VALUE_LSB), 1, chip);
    

    保存了上一次亮屏的时候的电平值;所以只要把这段语句去掉,在快速闪灭屏的时候,灭屏就不会出现背光不灭的情况,这是因为寄存器没有写好前,就保存亮屏的高电平值;

    LCD背光驱动

    qpnp_lpg_init进入probe函数中,spmi驱动是什么呢?参考这篇文章:
    SPMI理解
    其实简单理解spmi就是一个通讯协议;

    static int qpnp_pwm_probe(struct spmi_device *spmi)
    {
    	struct qpnp_pwm_chip	*pwm_chip;
    	int			rc;
    
    	pwm_chip = kzalloc(sizeof(*pwm_chip), GFP_KERNEL);
    	if (pwm_chip == NULL) {
    		pr_err("kzalloc() failed.
    ");
    		return -ENOMEM;
    	}
    
    	spin_lock_init(&pwm_chip->lpg_lock);
    
    	pwm_chip->spmi_dev = spmi;
    	dev_set_drvdata(&spmi->dev, pwm_chip);
    
    	rc = qpnp_parse_dt_config(spmi, pwm_chip);
    
    	if (rc) {
    		pr_err("Failed parsing DT parameters, rc=%d
    ", rc);
    		goto failed_config;
    	}
    
    	pwm_chip->chip.dev = &spmi->dev;
    	pwm_chip->chip.ops = &qpnp_pwm_ops;    
    	pwm_chip->chip.base = -1;
    	pwm_chip->chip.npwm = 1;
    
    	rc = pwmchip_add(&pwm_chip->chip);
    	if (rc < 0) {
    		pr_err("pwmchip_add() failed: %d
    ", rc);
    		goto failed_insert;
    	}
    
    	if (pwm_chip->channel_owner)
    		pwm_chip->chip.pwms[0].label = pwm_chip->channel_owner;
    
    	pr_debug("PWM device sid:%d channel:%d probed successfully
    ",
    		spmi->sid, pwm_chip->channel_id);
    	return 0;
    
    failed_insert:
    	kfree(pwm_chip->lpg_config.lut_config.duty_pct_list);
    failed_config:
    	dev_set_drvdata(&spmi->dev, NULL);
    	kfree(pwm_chip);
    	return rc;
    }
    

    pwm_chip->chip.ops = &qpnp_pwm_ops;注册相应的回调函数;

    patch地址

    patch地址

  • 相关阅读:
    [转载]Shell十三问(入门与提高)
    [转载]FPGA学习步骤
    matlab设计切比雪夫低通滤波器
    累加器A与ACC区别
    [转载]3分钟设计滤波器
    [转载]卷积运算的实际意义
    [转载]CRC校验原理
    一个怂女婿的成长笔记【三】
    一个怂女婿的成长笔记【一】
    一个怂女婿的成长笔记【二】
  • 原文地址:https://www.cnblogs.com/linhaostudy/p/9173117.html
Copyright © 2011-2022 走看看