blob: ffdd726c9edab6af963b96908c6e567cdbefb261 [file] [log] [blame]
/* Copyright (c) 2018, 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <err.h>
#include <debug.h>
#include <spmi.h>
#include <qpnp_wled.h>
#include <qpnp_lcdb.h>
#include <qtimer.h>
#include <target.h>
struct qpnp_lcdb *lcdb;
static int qpnp_lcdb_set_voltage(struct qpnp_lcdb *lcdb, uint32_t ldo_v,
uint32_t ncp_v)
{
int rc = -1;
uint32_t new_uV, boost_ref_volt;
uint8_t val;
/* Set LDO voltage */
if (ldo_v < lcdb->ldo_init_volt) {
dprintf(CRITICAL, "qpnp_ldo_set_voltage failed, min_uV %d is "
"less than the minimum supported voltage %d\n",
ldo_v, lcdb->ldo_init_volt);
return rc;
}
val = ((ldo_v - lcdb->ldo_init_volt) +
LDO_VREG_STEP_SIZE_UV - 1) / LDO_VREG_STEP_SIZE_UV;
new_uV = val * LDO_VREG_STEP_SIZE_UV + lcdb->ldo_init_volt;
if (new_uV > lcdb->ldo_max_volt) {
dprintf(CRITICAL, "qpnp_ldo_set_voltage unable to set voltage "
"(%d %d)\n", lcdb->ldo_min_volt, lcdb->ldo_max_volt);
return rc;
}
/* Value adjustment as initial 1V has 100mV step and rest 50mV step */
if ( val > 18 )
val = val - 9;
else
val = val / 2;
pmic_spmi_reg_mask_write(lcdb->lcdb_base +
QPNP_LCDB_LDO_OUTPUT_VOLTAGE_REG,
QPNP_LCDB_LDO_OUTPUT_VOLTAGE_MASK, val);
udelay(2);
/* Set NCP voltage */
if (ncp_v < lcdb->ncp_init_volt) {
dprintf(CRITICAL, "qpnp_ncp_set_voltage failed, min_uV %d is "
"less than the minimum supported voltage %d\n",
ncp_v, lcdb->ncp_init_volt);
return rc;
}
val = ((ncp_v - lcdb->ncp_init_volt) +
NCP_VREG_STEP_SIZE_UV - 1) / NCP_VREG_STEP_SIZE_UV;
new_uV = val * NCP_VREG_STEP_SIZE_UV + lcdb->ncp_init_volt;
if (new_uV > lcdb->ncp_max_volt) {
dprintf(CRITICAL, "qpnp_ncp_set_voltage unable to set voltage "
"(%d %d)\n", lcdb->ncp_min_volt, lcdb->ncp_max_volt);
return rc;
}
/* Value adjustment as initial 1V has 100mV step and rest 50mV step */
if ( val > 18 )
val = val - 9;
else
val = val / 2;
pmic_spmi_reg_mask_write(lcdb->lcdb_base +
QPNP_LCDB_NCP_OUTPUT_VOLTAGE_REG,
QPNP_LCDB_NCP_OUTPUT_VOLTAGE_MASK, val);
udelay(2);
/* Set Boost voltage */
boost_ref_volt = ((lcdb->ldo_max_volt > lcdb->ncp_max_volt) ?
lcdb->ldo_max_volt : lcdb->ncp_max_volt) +
LCDB_BOOST_HEADROOM_VOLT_UV;
if (boost_ref_volt < lcdb->bst_init_volt)
boost_ref_volt = lcdb->bst_init_volt;
val = ((boost_ref_volt - lcdb->bst_init_volt) +
BST_VREG_STEP_SIZE_UV - 1) / BST_VREG_STEP_SIZE_UV;
new_uV = val * BST_VREG_STEP_SIZE_UV + lcdb->bst_init_volt;
pmic_spmi_reg_mask_write(lcdb->lcdb_base +
QPNP_LCDB_BST_OUTPUT_VOLTAGE_REG,
QPNP_LCDB_BST_OUTPUT_VOLTAGE_MASK, val);
return 0;
}
static int qpnp_lcdb_step_voltage(struct qpnp_lcdb *lcdb,
uint32_t step_start_uv)
{
int rc = 0;
uint32_t target_ldo_v, target_ncp_v, i;
for (i = step_start_uv; i <= LCDB_LDO_MAX_VOLTAGE_UV;
i += LCDB_STEP_UV) {
target_ldo_v = min(lcdb->ldo_min_volt, i);
target_ncp_v = min(lcdb->ncp_min_volt, i);
rc = qpnp_lcdb_set_voltage(lcdb, target_ldo_v, target_ncp_v);
if (lcdb->ldo_min_volt <= i && lcdb->ncp_min_volt <= i)
break;
/* 1ms wait */
mdelay(1);
}
return rc;
}
static int qpnp_lcdb_config(struct qpnp_lcdb *lcdb)
{
uint8_t reg = 0;
if (lcdb->lcdb_pwrup_dly_ms > QPNP_LCDB_PWRUP_DLY_MAX_MS)
lcdb->lcdb_pwrup_dly_ms = QPNP_LCDB_PWRUP_DLY_MAX_MS;
if (lcdb->lcdb_pwrdn_dly_ms > QPNP_LCDB_PWRDN_DLY_MAX_MS)
lcdb->lcdb_pwrdn_dly_ms = QPNP_LCDB_PWRDN_DLY_MAX_MS;
reg = pmic_spmi_reg_read(lcdb->lcdb_base +
QPNP_LCDB_PWRUP_PWRDN_CTL_REG);
/* Set power up delay */
reg &= QPNP_LCDB_PWRUP_DLY_MASK;
reg |= (lcdb->lcdb_pwrup_dly_ms << LCDB_PWRUP_DLY_SHIFT);
/* Set power down delay */
reg &= ~(QPNP_LCDB_PWRDN_DLY_MASK);
reg |= (lcdb->lcdb_pwrdn_dly_ms);
pmic_spmi_reg_write(lcdb->lcdb_base + QPNP_LCDB_PWRUP_PWRDN_CTL_REG,
reg);
/* Make LCDB module ready */
pmic_spmi_reg_mask_write(lcdb->lcdb_base + QPNP_LCDB_MODULE_RDY_REG,
QPNP_LCDB_MODULE_RDY_MASK, LCDB_MODULE_RDY);
return 0;
}
static int qpnp_lcdb_setup(struct qpnp_lcdb *lcdb,
struct qpnp_wled_config_data *config)
{
lcdb->lcdb_base = QPNP_LCDB_BASE;
lcdb->lcdb_pwrup_dly_ms = config->pwr_up_delay;
lcdb->lcdb_pwrdn_dly_ms = config->pwr_down_delay;
lcdb->ldo_min_volt = config->lab_min_volt;
lcdb->ldo_max_volt = config->lab_max_volt;
lcdb->ldo_init_volt = LCDB_LDO_INIT_VOLTAGE_UV;
lcdb->ncp_min_volt = config->ibb_min_volt;
lcdb->ncp_max_volt = config->ibb_max_volt;
lcdb->ncp_init_volt = LCDB_NCP_INIT_VOLTAGE_UV;
lcdb->bst_init_volt = LCDB_BOOST_INIT_VOLTAGE_UV;
return 0;
}
int qpnp_lcdb_enable(bool state)
{
int rc = 0;
uint32_t target_ldo_v, target_ncp_v;
if (!lcdb) {
dprintf(CRITICAL, "%s: lcdb is not initialized yet\n", __func__);
return ERROR;
}
if (state) {
/*
* Set LDO/NCP voltage step wise (in steps of 500mV) as a part
* of SW WA to prevent loss in accuracy in ADC measurement of
* VBATT voltage.
* Set minimum of spec voltage or 4.5V.
*/
target_ldo_v = min(lcdb->ldo_min_volt, (lcdb->ldo_init_volt +
LCDB_STEP_UV));
target_ncp_v = min(lcdb->ncp_min_volt, (lcdb->ncp_init_volt +
LCDB_STEP_UV));
rc = qpnp_lcdb_set_voltage(lcdb, target_ldo_v, target_ncp_v);
/* Enable LCDB */
pmic_spmi_reg_mask_write(lcdb->lcdb_base + QPNP_LCDB_ENABLE_CTL_REG,
QPNP_LCDB_ENABLE_CTL_MASK, (state << LCDB_ENABLE_SHIFT));
/* Initial delay of 10ms */
mdelay(10);
/* Start voltage stepping from 5V onwards */
if (lcdb->ldo_min_volt > (lcdb->ldo_init_volt + LCDB_STEP_UV) ||
lcdb->ncp_min_volt > (lcdb->ncp_init_volt + LCDB_STEP_UV))
rc = qpnp_lcdb_step_voltage(lcdb, target_ldo_v +
LCDB_STEP_UV);
} else {
pmic_spmi_reg_mask_write(lcdb->lcdb_base + QPNP_LCDB_ENABLE_CTL_REG,
QPNP_LCDB_ENABLE_CTL_MASK, (state << LCDB_ENABLE_SHIFT));
}
return rc;
}
int qpnp_lcdb_init(struct qpnp_wled_config_data *config)
{
int rc;
if (!target_is_pmi_enabled())
return ERR_NOT_FOUND;
lcdb = malloc(sizeof(struct qpnp_lcdb));
if (!lcdb)
return ERR_NO_MEMORY;
memset(lcdb, 0, sizeof(struct qpnp_lcdb));
rc = qpnp_lcdb_setup(lcdb, config);
if(rc) {
dprintf(CRITICAL, "Setting LCDB parameters failed\n");
return rc;
}
rc = qpnp_lcdb_config(lcdb);
if (rc) {
dprintf(CRITICAL, "lcdb config failed\n");
return rc;
}
return rc;
}