高通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文件中(kernel\msm-3.18\drivers\pwm);

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\n",
			__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\n", __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.\n",
						__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\n",
					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\n");
	}

	pr_debug("pwm_enable_control: %d\n", 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.\n");
		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\n", 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\n", 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\n",
		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地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM