发生异常的现象:

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地址

高通LCD的pwm背光驱动的更多相关文章

  1. 高通LCD驱动调试

    本文转载自:http://www.itgo.me/a/x6305658852004979994/lcd%20qcom 来自 :http://blog.csdn.net/dacaozuo/article ...

  2. 高通ASOC中的machine驱动

    ASoC被分为Machine.Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容:Machin ...

  3. 高通ASOC中的codec驱动

    ASOC的出现是为了让codec独立于CPU,减少和CPU之间的耦合,这样同一个codec驱动就无需修改就可以匹配任何一款平台. 在Machine中已经知道,snd_soc_dai_link结构就指明 ...

  4. imx6背光驱动调试

    1.内核配置pwm背光驱动make menuconfig:Device Driver ---> Graphics support ---> [*] Backlight & LCD ...

  5. 高通adsp架构下sensor

    一.高通sensor架构: linux驱动由浅入深系列:高通sensor架构实例分析之一(整体概览+AP侧代码分析) linux驱动由浅入深系列:高通sensor架构实例分析之二(adsp驱动代码结构 ...

  6. 高通(QCOM)sensor bring up

    高通7150平台 1.添加驱动文件 2.添加编译 3.配置json文件 4.高通默认配置 5.部分sensor外挂电源 6.遇到的问题 1.添加驱动文件 路径:adsp_proc/ssc/sensor ...

  7. 高通 android平台LCD驱动分析

    目前手机芯片厂家提供的源码里包含整个LCD驱动框架,一般厂家会定义一个xxx_fb.c的源文件,注册一个平台设备和平台驱动,在驱动的probe函数中来调用register_framebuffer(), ...

  8. 高通平台 lcd driver 调试小结

    一.概述 1.1 简介 本文档主要包括LCD模块的驱动流程分析.Framebuffer相关知识.Gralloc等相关内容,以及LCD调试的一些经验和相关bug的分析和讲解. 1.2  开发环境 And ...

  9. 高通安卓调试LCD几方面总结

    来公司上班现在已经整整一个月了,蔽人不才,能力有限,学习进度缓慢,不过也是有一点点的收获与心得,在这里写出来与大家分享,养成良好的记录习惯也免得后忘记. 不啰嗦了,开入正题.来公司一个月左右的时间,主 ...

随机推荐

  1. 目标检测技术演进:R-CNN、Fast R-CNN、Faster R-CNN

    看到一篇循序渐进讲R-CNN.Fast R-CNN.Faster R-CNN演进的博文,写得非常好,摘入于此,方便查找和阅读. object detection,就是在给定的图片中精确找到物体所在位置 ...

  2. SpringBoot入门 (八) Cache使用

    本文记录学习在SpringBoot中使用Cache. 一 为什么要使用缓存 缓存是一个数据交换的缓冲区,在一些条件下可以替代数据库.举个例子:我们有一个查询的业务,访问数据的频率特别高,且每次访问时的 ...

  3. Python 工匠:编写条件分支代码的技巧

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由鹅厂优文发表于云+社区专栏 作者:朱雷 | 腾讯IEG高级工程师 『Python 工匠』是什么? 我一直觉得编程某种意义是一门『手艺』 ...

  4. Spring-Task思维导图

    最近在搞一个定时任务的相关东西,为了方便记忆,这里将知识点总结成一个思维导图.后续也会通过思维导图的方式发布博客.

  5. MyBatis原理第四篇——statementHandler对象(sqlSession内部核心实现,插件的基础)

    首先约定文中将的四大对象是指:executor, statementHandler,parameterHandler,resultHandler对象.(为了方便下面的文章说道四大对象就专指它们) 讲到 ...

  6. 并发编程之 Java 内存模型 + volatile 关键字 + Happen-Before 规则

    前言 楼主这个标题其实有一种作死的味道,为什么呢,这三个东西其实可以分开为三篇文章来写,但是,楼主认为这三个东西又都是高度相关的,应当在一个知识点中.在一次学习中去理解这些东西.才能更好的理解 Jav ...

  7. ASP.NET MVC应用程序播放AVI视频

    前面Insus.NET实现一系列在MVC应用程序播放SWF, FLV, WMV, RM, RMVB视频.每篇使用不同的方法方式,大同小异.这篇中,为了MVC应用程序播放AVI视频,用纯M, V, C来 ...

  8. vue项目webpack打包后图片路径错误

    首先项目是vue-cli搭建的,项目结构如下: 然后发现在css里写的图片引用地址在开发时正常显示,但在打包扔上服务器之后报错 报的是404,路径前面多了/static/css,不知道为啥. 在自己慢 ...

  9. [基础篇] 玄机网C#培训课程-初级.

    课程目录 0x01第一课课前准备vs的选择与安装常用工具/网址      http://msdn.itellyou.cn/vs常用设置 0x02第二课C#的语法样式  编程风格VS的常用功能 俩个注意 ...

  10. Codeforces500C(SummerTrainingDay01-G)

    C. New Year Book Reading time limit per test:2 seconds memory limit per test:256 megabytes input:sta ...