/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * Neither the name of The Linux Foundation, Inc. nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include static int fls(uint16_t n) { int i = 0; for (; n; n >>= 1, i++); return i; } static struct qpnp_wled *gwled; static int qpnp_labibb_regulator_set_voltage(struct qpnp_wled *wled); static int qpnp_wled_sec_access(struct qpnp_wled *wled, uint16_t base_addr) { uint8_t reg = QPNP_WLED_SEC_UNLOCK; pm8x41_wled_reg_write(QPNP_WLED_SEC_ACCESS_REG(base_addr), reg); return 0; } /* set wled to a level of brightness */ static int qpnp_wled_set_level(struct qpnp_wled *wled, int level) { int i; uint8_t reg; /* set brightness registers */ for (i = 0; i < wled->num_strings; i++) { reg = level & QPNP_WLED_BRIGHT_LSB_MASK; pm8x41_wled_reg_write(QPNP_WLED_BRIGHT_LSB_REG(wled->sink_base, wled->strings[i]), reg); reg = level >> QPNP_WLED_BRIGHT_MSB_SHIFT; reg = reg & QPNP_WLED_BRIGHT_MSB_MASK; pm8x41_wled_reg_write(QPNP_WLED_BRIGHT_MSB_REG(wled->sink_base, wled->strings[i]), reg); } /* sync */ reg = QPNP_WLED_SYNC; pm8x41_wled_reg_write(QPNP_WLED_SYNC_REG(wled->sink_base), reg); reg = QPNP_WLED_SYNC_RESET; pm8x41_wled_reg_write(QPNP_WLED_SYNC_REG(wled->sink_base), reg); return 0; } static int qpnp_wled_enable(struct qpnp_wled *wled, uint16_t base_addr, bool state) { uint8_t reg; reg = pm8x41_wled_reg_read( QPNP_WLED_MODULE_EN_REG(base_addr)); reg &= QPNP_WLED_MODULE_EN_MASK; reg |= (state << QPNP_WLED_MODULE_EN_SHIFT); pm8x41_wled_reg_write(QPNP_WLED_MODULE_EN_REG(base_addr), reg); return 0; } int qpnp_ibb_enable(bool state) { int rc = 0; uint8_t reg; if (!gwled) { dprintf(CRITICAL, "%s: wled is not initialized yet\n", __func__); return ERROR; } /* enable lab */ if (gwled->ibb_bias_active) { rc = qpnp_wled_enable(gwled, gwled->lab_base, state); udelay(QPNP_WLED_LAB_START_DLY_US + 1); if (rc < 0) return rc; } else { reg = pm8x41_wled_reg_read(QPNP_WLED_LAB_IBB_RDY_REG(gwled->lab_base)); reg &= QPNP_WLED_MODULE_EN_MASK; reg |= (state << QPNP_WLED_MODULE_EN_SHIFT); pm8x41_wled_reg_write(QPNP_WLED_LAB_IBB_RDY_REG(gwled->lab_base), reg); } rc = qpnp_wled_enable(gwled, gwled->ibb_base, state); return rc; } /* enable / disable wled brightness */ void qpnp_wled_enable_backlight(int enable) { int rc; if (!gwled) { dprintf(CRITICAL, "%s: wled is not initialized yet\n", __func__); return; } if (enable) { rc = qpnp_wled_set_level(gwled, QPNP_WLED_MAX_BR_LEVEL); if (rc) { dprintf(CRITICAL,"wled set level failed\n"); return; } } rc = qpnp_wled_enable(gwled, gwled->ctrl_base, enable); if (rc) { dprintf(CRITICAL,"wled %sable failed\n", enable ? "en" : "dis"); return; } } static int qpnp_wled_set_display_type(struct qpnp_wled *wled, uint16_t base_addr) { uint8_t reg = 0; int rc; /* display type */ reg = pm8x41_wled_reg_read(QPNP_WLED_DISP_SEL_REG(base_addr)); reg &= QPNP_WLED_DISP_SEL_MASK; reg |= (wled->disp_type_amoled << QPNP_WLED_DISP_SEL_SHIFT); rc = qpnp_wled_sec_access(wled, base_addr); if (rc) return rc; pm8x41_wled_reg_write(QPNP_WLED_DISP_SEL_REG(base_addr), reg); return 0; } static int qpnp_wled_module_ready(struct qpnp_wled *wled, uint16_t base_addr, bool state) { uint8_t reg; reg = pm8x41_wled_reg_read( QPNP_WLED_MODULE_RDY_REG(base_addr)); reg &= QPNP_WLED_MODULE_RDY_MASK; reg |= (state << QPNP_WLED_MODULE_RDY_SHIFT); pm8x41_wled_reg_write(QPNP_WLED_MODULE_RDY_REG(base_addr), reg); return 0; } /* Configure WLED registers */ static int qpnp_wled_config(struct qpnp_wled *wled) { int rc, i, temp; uint8_t reg = 0; /* Configure display type */ rc = qpnp_wled_set_display_type(wled, wled->ctrl_base); if (rc < 0) return rc; /* Configure the FEEDBACK OUTPUT register */ reg = pm8x41_wled_reg_read( QPNP_WLED_FDBK_OP_REG(wled->ctrl_base)); reg &= QPNP_WLED_FDBK_OP_MASK; reg |= wled->fdbk_op; pm8x41_wled_reg_write(QPNP_WLED_FDBK_OP_REG(wled->ctrl_base), reg); /* Configure the VREF register */ if (wled->vref_mv < QPNP_WLED_VREF_MIN_MV) wled->vref_mv = QPNP_WLED_VREF_MIN_MV; else if (wled->vref_mv > QPNP_WLED_VREF_MAX_MV) wled->vref_mv = QPNP_WLED_VREF_MAX_MV; reg = pm8x41_wled_reg_read( QPNP_WLED_VREF_REG(wled->ctrl_base)); reg &= QPNP_WLED_VREF_MASK; temp = wled->vref_mv - QPNP_WLED_VREF_MIN_MV; reg |= (temp / QPNP_WLED_VREF_STEP_MV); pm8x41_wled_reg_write(QPNP_WLED_VREF_REG(wled->ctrl_base), reg); /* Configure the ILIM register */ if (wled->ilim_ma < QPNP_WLED_ILIM_MIN_MA) wled->ilim_ma = QPNP_WLED_ILIM_MIN_MA; else if (wled->ilim_ma > QPNP_WLED_ILIM_MAX_MA) wled->ilim_ma = QPNP_WLED_ILIM_MAX_MA; reg = pm8x41_wled_reg_read( QPNP_WLED_ILIM_REG(wled->ctrl_base)); temp = (wled->ilim_ma / QPNP_WLED_ILIM_STEP_MA); if (temp != (reg & ~QPNP_WLED_ILIM_MASK)) { reg &= QPNP_WLED_ILIM_MASK; reg |= temp; reg |= QPNP_WLED_ILIM_OVERWRITE; pm8x41_wled_reg_write(QPNP_WLED_ILIM_REG(wled->ctrl_base), reg); } /* Configure the MAX BOOST DUTY register */ if (wled->boost_duty_ns < QPNP_WLED_BOOST_DUTY_MIN_NS) wled->boost_duty_ns = QPNP_WLED_BOOST_DUTY_MIN_NS; else if (wled->boost_duty_ns > QPNP_WLED_BOOST_DUTY_MAX_NS) wled->boost_duty_ns = QPNP_WLED_BOOST_DUTY_MAX_NS; reg = pm8x41_wled_reg_read( QPNP_WLED_BOOST_DUTY_REG(wled->ctrl_base)); reg &= QPNP_WLED_BOOST_DUTY_MASK; reg |= (wled->boost_duty_ns / QPNP_WLED_BOOST_DUTY_STEP_NS); pm8x41_wled_reg_write(QPNP_WLED_BOOST_DUTY_REG(wled->ctrl_base), reg); /* Configure the SWITCHING FREQ register */ if (wled->switch_freq_khz == QPNP_WLED_SWITCH_FREQ_1600_KHZ) temp = QPNP_WLED_SWITCH_FREQ_1600_KHZ_CODE; else temp = QPNP_WLED_SWITCH_FREQ_800_KHZ_CODE; reg = pm8x41_wled_reg_read( QPNP_WLED_SWITCH_FREQ_REG(wled->ctrl_base)); reg &= QPNP_WLED_SWITCH_FREQ_MASK; reg |= temp; pm8x41_wled_reg_write(QPNP_WLED_SWITCH_FREQ_REG(wled->ctrl_base), reg); /* Configure the OVP register */ if (wled->ovp_mv <= QPNP_WLED_OVP_17800_MV) { wled->ovp_mv = QPNP_WLED_OVP_17800_MV; temp = 3; } else if (wled->ovp_mv <= QPNP_WLED_OVP_19400_MV) { wled->ovp_mv = QPNP_WLED_OVP_19400_MV; temp = 2; } else if (wled->ovp_mv <= QPNP_WLED_OVP_29500_MV) { wled->ovp_mv = QPNP_WLED_OVP_29500_MV; temp = 1; } else { wled->ovp_mv = QPNP_WLED_OVP_31000_MV; temp = 0; } reg = pm8x41_wled_reg_read( QPNP_WLED_OVP_REG(wled->ctrl_base)); reg &= QPNP_WLED_OVP_MASK; reg |= temp; pm8x41_wled_reg_write(QPNP_WLED_OVP_REG(wled->ctrl_base), reg); /* Configure the MODULATION register */ if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_1200_KHZ) { wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_1200_KHZ; temp = 3; } else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_2400_KHZ) { wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_2400_KHZ; temp = 2; } else if (wled->mod_freq_khz <= QPNP_WLED_MOD_FREQ_9600_KHZ) { wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_9600_KHZ; temp = 1; } else { wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_19200_KHZ; temp = 0; } reg = pm8x41_wled_reg_read(QPNP_WLED_MOD_REG(wled->sink_base)); reg &= QPNP_WLED_MOD_FREQ_MASK; reg |= (temp << QPNP_WLED_MOD_FREQ_SHIFT); reg &= QPNP_WLED_PHASE_STAG_MASK; reg |= (wled->en_phase_stag << QPNP_WLED_PHASE_STAG_SHIFT); reg &= QPNP_WLED_DIM_RES_MASK; reg |= (wled->en_9b_dim_res << QPNP_WLED_DIM_RES_SHIFT); if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) { reg &= QPNP_WLED_DIM_HYB_MASK; reg |= (1 << QPNP_WLED_DIM_HYB_SHIFT); } else { reg &= QPNP_WLED_DIM_HYB_MASK; reg |= (0 << QPNP_WLED_DIM_HYB_SHIFT); reg &= QPNP_WLED_DIM_ANA_MASK; reg |= wled->dim_mode; } pm8x41_wled_reg_write(QPNP_WLED_MOD_REG(wled->sink_base), reg); /* Configure the HYBRID THRESHOLD register */ if (wled->hyb_thres < QPNP_WLED_HYB_THRES_MIN) wled->hyb_thres = QPNP_WLED_HYB_THRES_MIN; else if (wled->hyb_thres > QPNP_WLED_HYB_THRES_MAX) wled->hyb_thres = QPNP_WLED_HYB_THRES_MAX; reg = pm8x41_wled_reg_read( QPNP_WLED_HYB_THRES_REG(wled->sink_base)); reg &= QPNP_WLED_HYB_THRES_MASK; temp = fls(wled->hyb_thres / QPNP_WLED_HYB_THRES_MIN) - 1; reg |= temp; pm8x41_wled_reg_write(QPNP_WLED_HYB_THRES_REG(wled->sink_base), reg); for (i = 0; i < wled->num_strings; i++) { if (wled->strings[i] >= QPNP_WLED_MAX_STRINGS) { dprintf(CRITICAL,"Invalid string number\n"); return ERR_NOT_VALID; } /* MODULATOR */ reg = pm8x41_wled_reg_read( QPNP_WLED_MOD_EN_REG(wled->sink_base, wled->strings[i])); reg &= QPNP_WLED_MOD_EN_MASK; reg |= (QPNP_WLED_MOD_EN << QPNP_WLED_MOD_EN_SHFT); pm8x41_wled_reg_write(QPNP_WLED_MOD_EN_REG(wled->sink_base, wled->strings[i]), reg); /* SYNC DELAY */ if (wled->sync_dly_us > QPNP_WLED_SYNC_DLY_MAX_US) wled->sync_dly_us = QPNP_WLED_SYNC_DLY_MAX_US; reg = pm8x41_wled_reg_read( QPNP_WLED_SYNC_DLY_REG(wled->sink_base, wled->strings[i])); reg &= QPNP_WLED_SYNC_DLY_MASK; temp = wled->sync_dly_us / QPNP_WLED_SYNC_DLY_STEP_US; reg |= temp; pm8x41_wled_reg_write(QPNP_WLED_SYNC_DLY_REG(wled->sink_base, wled->strings[i]), reg); /* FULL SCALE CURRENT */ if (wled->fs_curr_ua > QPNP_WLED_FS_CURR_MAX_UA) wled->fs_curr_ua = QPNP_WLED_FS_CURR_MAX_UA; reg = pm8x41_wled_reg_read( QPNP_WLED_FS_CURR_REG(wled->sink_base, wled->strings[i])); reg &= QPNP_WLED_FS_CURR_MASK; temp = wled->fs_curr_ua / QPNP_WLED_FS_CURR_STEP_UA; reg |= temp; pm8x41_wled_reg_write(QPNP_WLED_FS_CURR_REG(wled->sink_base, wled->strings[i]), reg); /* CABC */ reg = pm8x41_wled_reg_read( QPNP_WLED_CABC_REG(wled->sink_base, wled->strings[i])); reg &= QPNP_WLED_CABC_MASK; reg |= (wled->en_cabc << QPNP_WLED_CABC_SHIFT); pm8x41_wled_reg_write(QPNP_WLED_CABC_REG(wled->sink_base, wled->strings[i]), reg); /* Enable CURRENT SINK */ reg = pm8x41_wled_reg_read( QPNP_WLED_CURR_SINK_REG(wled->sink_base)); temp = wled->strings[i] + QPNP_WLED_CURR_SINK_SHIFT; reg |= (1 << temp); pm8x41_wled_reg_write(QPNP_WLED_CURR_SINK_REG(wled->sink_base), reg); } /* LAB fast precharge */ reg = pm8x41_wled_reg_read( QPNP_WLED_LAB_FAST_PC_REG(wled->lab_base)); reg &= QPNP_WLED_LAB_FAST_PC_MASK; reg |= (wled->lab_fast_precharge << QPNP_WLED_LAB_FAST_PC_SHIFT); pm8x41_wled_reg_write(QPNP_WLED_LAB_FAST_PC_REG(wled->lab_base), reg); /* Configure lab display type */ rc = qpnp_wled_set_display_type(wled, wled->lab_base); if (rc < 0) return rc; /* make LAB module ready */ rc = qpnp_wled_module_ready(wled, wled->lab_base, true); if (rc < 0) return rc; /* IBB active bias */ if (wled->ibb_pwrup_dly_ms > QPNP_WLED_IBB_PWRUP_DLY_MAX_MS) wled->ibb_pwrup_dly_ms = QPNP_WLED_IBB_PWRUP_DLY_MAX_MS; if (wled->ibb_pwrdn_dly_ms > QPNP_WLED_IBB_PWRDN_DLY_MAX_MS) wled->ibb_pwrdn_dly_ms = QPNP_WLED_IBB_PWRDN_DLY_MAX_MS; reg = pm8x41_wled_reg_read( QPNP_WLED_IBB_BIAS_REG(wled->ibb_base)); reg &= QPNP_WLED_IBB_BIAS_MASK; reg |= (!wled->ibb_bias_active << QPNP_WLED_IBB_BIAS_SHIFT); temp = wled->ibb_pwrup_dly_ms; reg &= QPNP_WLED_IBB_PWRUP_DLY_MASK; reg |= (temp << QPNP_WLED_IBB_PWRUP_DLY_SHIFT); /* Power down delay bits could already be set, clear them before * or'ing new values */ reg &= ~(PWRDN_DLY2_MASK); reg |= wled->ibb_pwrdn_dly_ms; reg |= (wled->ibb_discharge_en << 2); rc = qpnp_wled_sec_access(wled, wled->ibb_base); if (rc) return rc; pm8x41_wled_reg_write(QPNP_WLED_IBB_BIAS_REG(wled->ibb_base), reg); /* Configure ibb display type */ rc = qpnp_wled_set_display_type(wled, wled->ibb_base); if (rc < 0) return rc; /* make IBB module ready */ rc = qpnp_wled_module_ready(wled, wled->ibb_base, true); if (rc < 0) return rc; rc = qpnp_labibb_regulator_set_voltage(wled); if (rc < 0) return rc; return 0; } /* Setup wled default parameters */ static int qpnp_wled_setup(struct qpnp_wled *wled, struct qpnp_wled_config_data *config) { int i; wled->sink_base = QPNP_WLED_SINK_BASE; wled->ctrl_base = QPNP_WLED_CTRL_BASE; wled->ibb_base = QPNP_WLED_IBB_BASE; wled->lab_base = QPNP_WLED_LAB_BASE; wled->fdbk_op = QPNP_WLED_FDBK_AUTO; wled->vref_mv = QPNP_WLED_DFLT_VREF_MV; wled->switch_freq_khz = QPNP_WLED_SWITCH_FREQ_800_KHZ; wled->ovp_mv = QPNP_WLED_OVP_29500_MV; wled->ilim_ma = QPNP_WLED_DFLT_ILIM_MA; wled->boost_duty_ns = QPNP_WLED_BOOST_DUTY_MIN_NS; wled->mod_freq_khz = QPNP_WLED_MOD_FREQ_19200_KHZ; wled->dim_mode = QPNP_WLED_DIM_HYBRID; wled->dim_shape = QPNP_WLED_DIM_SHAPE_LINEAR; if (wled->dim_mode == QPNP_WLED_DIM_HYBRID) { wled->hyb_thres = QPNP_WLED_DFLT_HYB_THRES; } wled->sync_dly_us = 800; wled->fs_curr_ua = 16000; wled->en_9b_dim_res = 0; wled->en_phase_stag = true; wled->en_cabc = 0; wled->num_strings = QPNP_WLED_MAX_STRINGS; for (i = 0; i < wled->num_strings; i++) wled->strings[i] = i; wled->ibb_bias_active = false; wled->lab_fast_precharge = true; wled->ibb_pwrup_dly_ms = config->pwr_up_delay; wled->ibb_pwrdn_dly_ms = config->pwr_down_delay; wled->ibb_discharge_en = config->ibb_discharge_en; wled->disp_type_amoled = config->display_type; wled->lab_min_volt = config->lab_min_volt; wled->lab_max_volt = config->lab_max_volt; wled->ibb_min_volt = config->ibb_min_volt; wled->ibb_max_volt = config->ibb_max_volt; wled->ibb_init_volt = config->ibb_init_volt; wled->lab_init_volt = config->lab_init_volt; return 0; } int qpnp_wled_init(struct qpnp_wled_config_data *config) { int rc; struct qpnp_wled *wled; wled = malloc(sizeof(struct qpnp_wled)); if (!wled) return ERR_NO_MEMORY; memset(wled, 0, sizeof(struct qpnp_wled)); rc = qpnp_wled_setup(wled, config); if (rc) { dprintf(CRITICAL, "Setting WLED parameters failed\n"); return rc; } rc = qpnp_wled_config(wled); if (rc) { dprintf(CRITICAL, "wled config failed\n"); return rc; } gwled = wled; return rc; } static int qpnp_labibb_regulator_set_voltage(struct qpnp_wled *wled) { int rc = -1; uint32_t new_uV; uint8_t val, mask=0; if (wled->lab_min_volt < wled->lab_init_volt) { dprintf(CRITICAL,"qpnp_lab_regulator_set_voltage failed, min_uV %d is less than init volt %d\n", wled->lab_min_volt, wled->lab_init_volt); return rc; } val = (((wled->lab_min_volt - wled->lab_init_volt) + (IBB_LAB_VREG_STEP_SIZE - 1)) / IBB_LAB_VREG_STEP_SIZE); new_uV = val * IBB_LAB_VREG_STEP_SIZE + wled->lab_init_volt; if (new_uV > wled->lab_max_volt) { dprintf(CRITICAL,"qpnp_ibb_regulator_set_voltage unable to set voltage (%d %d)\n", wled->lab_min_volt, wled->lab_max_volt); return rc; } val |= QPNP_LAB_OUTPUT_OVERRIDE_EN; mask = pm8x41_wled_reg_read(wled->lab_base + QPNP_LABIBB_OUTPUT_VOLTAGE); mask &= ~(QPNP_LAB_SET_VOLTAGE_MASK | QPNP_LAB_OUTPUT_OVERRIDE_EN); mask |= val & (QPNP_LAB_SET_VOLTAGE_MASK | QPNP_LAB_OUTPUT_OVERRIDE_EN); pm8x41_wled_reg_write(wled->lab_base + QPNP_LABIBB_OUTPUT_VOLTAGE, mask); udelay(2); /* IBB Set Voltage */ if (wled->ibb_min_volt < wled->ibb_init_volt) { dprintf(CRITICAL, "qpnp_ibb_regulator_set_voltage failed, min_uV %d is less than init volt %d\n", wled->ibb_min_volt, wled->ibb_init_volt); return rc; } val = (((wled->ibb_min_volt - wled->ibb_init_volt) + (IBB_LAB_VREG_STEP_SIZE - 1)) / IBB_LAB_VREG_STEP_SIZE); new_uV = val * IBB_LAB_VREG_STEP_SIZE + wled->ibb_init_volt; if (new_uV > wled->ibb_max_volt) { dprintf(CRITICAL,"qpnp_ibb_regulator_set_voltage unable to set voltage %d %d\n", wled->ibb_min_volt, wled->ibb_max_volt); return rc; } val |= QPNP_LAB_OUTPUT_OVERRIDE_EN; mask = pm8x41_wled_reg_read(wled->ibb_base + QPNP_LABIBB_OUTPUT_VOLTAGE); udelay(2); mask &= ~(QPNP_IBB_SET_VOLTAGE_MASK | QPNP_LAB_OUTPUT_OVERRIDE_EN); mask |= (val & (QPNP_IBB_SET_VOLTAGE_MASK | QPNP_LAB_OUTPUT_OVERRIDE_EN)); pm8x41_wled_reg_write(wled->ibb_base + QPNP_LABIBB_OUTPUT_VOLTAGE,mask); return 0; }