blob: a0eea26c617f3f40365ea3974f4edb0310a2f511 [file] [log] [blame]
/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/qpnp-labibb-regulator.h>
#include <linux/spmi.h>
#include <linux/string.h>
#define QPNP_LABIBB_REGULATOR_DIRVER_NAME "qcom,qpnp-labibb-regulator"
#define REG_PERPH_TYPE 0x04
#define QPNP_LAB_TYPE 0x24
#define QPNP_IBB_TYPE 0x20
/* Common register value for LAB/IBB */
#define REG_LAB_IBB_LCD_MODE 0x0
#define REG_LAB_IBB_AMOLED_MODE BIT(7)
#define REG_LAB_IBB_SEC_UNLOCK_CODE 0xA5
/* LAB register offset definitions */
#define REG_LAB_STATUS1 0x08
#define REG_LAB_VOLTAGE 0x41
#define REG_LAB_RING_SUPPRESSION_CTL 0x42
#define REG_LAB_LCD_AMOLED_SEL 0x44
#define REG_LAB_MODULE_RDY 0x45
#define REG_LAB_ENABLE_CTL 0x46
#define REG_LAB_PD_CTL 0x47
#define REG_LAB_CLK_DIV 0x48
#define REG_LAB_IBB_EN_RDY 0x49
#define REG_LAB_CURRENT_LIMIT 0x4B
#define REG_LAB_PS_CTL 0x50
#define REG_LAB_RDSON_MNGMNT 0x53
#define REG_LAB_PRECHARGE_CTL 0x5E
#define REG_LAB_SOFT_START_CTL 0x5F
#define REG_LAB_SEC_ACCESS 0xD0
/* LAB register bits definitions */
/* REG_LAB_STATUS1 */
#define LAB_STATUS1_VREG_OK_MASK BIT(7)
#define LAB_STATUS1_VREG_OK BIT(7)
/* REG_LAB_VOLTAGE */
#define LAB_VOLTAGE_OVERRIDE_EN BIT(7)
#define LAB_VOLTAGE_SET_BITS 4
#define LAB_VOLTAGE_SET_MASK ((1<<LAB_VOLTAGE_SET_BITS)-1)
/* REG_LAB_RING_SUPPRESSION_CTL */
#define LAB_RING_SUPPRESSION_CTL_EN BIT(7)
/* REG_LAB_MODULE_RDY */
#define LAB_MODULE_RDY_EN BIT(7)
/* REG_LAB_ENABLE_CTL */
#define LAB_ENABLE_CTL_EN BIT(7)
/* REG_LAB_PD_CTL */
#define LAB_PD_CTL_FULL_PD BIT(0)
#define LAB_PD_CTL_STRENGTH_MASK BIT(0)
#define LAB_PD_CTL_DISABLE BIT(1)
#define LAB_PD_CTL_EN_MASK BIT(1)
/* REG_LAB_IBB_EN_RDY */
#define LAB_IBB_EN_RDY_EN BIT(7)
/* REG_LAB_CURRENT_LIMIT */
#define LAB_CURRENT_LIMIT_BITS 3
#define LAB_CURRENT_LIMIT_MASK ((1<<LAB_CURRENT_LIMIT_BITS)-1)
#define LAB_CURRENT_LIMIT_EN BIT(7)
/* REG_LAB_PS_CTL */
#define LAB_PS_CTL_BITS 2
#define LAB_PS_CTL_MASK ((1<<LAB_PS_CTL_BITS)-1)
#define LAB_PS_CTL_EN BIT(7)
/* REG_LAB_RDSON_MNGMNT */
#define LAB_RDSON_MNGMNT_NFET_SLEW_EN BIT(5)
#define LAB_RDSON_MNGMNT_PFET_SLEW_EN BIT(4)
#define LAB_RDSON_MNGMNT_NFET_BITS 2
#define LAB_RDSON_MNGMNT_NFET_MASK ((1<<LAB_RDSON_MNGMNT_NFET_BITS)-1)
#define LAB_RDSON_MNGMNT_NFET_SHIFT 2
#define LAB_RDSON_MNGMNT_PFET_BITS 2
#define LAB_RDSON_MNGMNT_PFET_MASK ((1<<LAB_RDSON_MNGMNT_PFET_BITS)-1)
/* REG_LAB_PRECHARGE_CTL */
#define LAB_PRECHARGE_CTL_EN BIT(2)
#define LAB_PRECHARGE_CTL_EN_BITS 2
#define LAB_PRECHARGE_CTL_EN_MASK ((1<<LAB_PRECHARGE_CTL_EN_BITS)-1)
/* REG_LAB_SOFT_START_CTL */
#define LAB_SOFT_START_CTL_BITS 2
#define LAB_SOFT_START_CTL_MASK ((1<<LAB_SOFT_START_CTL_BITS)-1)
/* IBB register offset definitions */
#define REG_IBB_STATUS1 0x08
#define REG_IBB_VOLTAGE 0x41
#define REG_IBB_RING_SUPPRESSION_CTL 0x42
#define REG_IBB_LCD_AMOLED_SEL 0x44
#define REG_IBB_MODULE_RDY 0x45
#define REG_IBB_ENABLE_CTL 0x46
#define REG_IBB_PD_CTL 0x47
#define REG_IBB_CLK_DIV 0x48
#define REG_IBB_CURRENT_LIMIT 0x4B
#define REG_IBB_PS_CTL 0x50
#define REG_IBB_PWRUP_PWRDN_CTL_1 0x58
#define REG_IBB_SOFT_START_CTL 0x5F
#define REG_IBB_NLIMIT_DAC 0x61
#define REG_IBB_SEC_ACCESS 0xD0
/* IBB register bits definition */
/* REG_IBB_STATUS1 */
#define IBB_STATUS1_VREG_OK_MASK BIT(7)
#define IBB_STATUS1_VREG_OK BIT(7)
/* REG_IBB_VOLTAGE */
#define IBB_VOLTAGE_OVERRIDE_EN BIT(7)
#define IBB_VOLTAGE_SET_BITS 6
#define IBB_VOLTAGE_SET_MASK ((1<<IBB_VOLTAGE_SET_BITS)-1)
/* REG_IBB_RING_SUPPRESSION_CTL */
#define IBB_RING_SUPPRESSION_CTL_EN BIT(7)
/* REG_IBB_MODULE_RDY */
#define REG_IBB_MODULE_RDY_EN BIT(7)
/* REG_IBB_ENABLE_CTL */
#define IBB_ENABLE_CTL_EN BIT(7)
/* REG_IBB_PD_CTL */
#define IBB_PD_CTL_HALF_STRENGTH BIT(0)
#define IBB_PD_CTL_STRENGTH_MASK BIT(0)
#define IBB_PD_CTL_EN BIT(7)
#define IBB_PD_CTL_EN_MASK BIT(7)
/* REG_IBB_CURRENT_LIMIT */
#define IBB_CURRENT_LIMIT_BITS 5
#define IBB_CURRENT_LIMIT_MASK ((1<<IBB_CURRENT_LIMIT_BITS)-1)
#define IBB_CURRENT_LIMIT_DEBOUNCE_SHIFT 5
#define IBB_CURRENT_LIMIT_EN BIT(7)
/* REG_IBB_PS_CTL */
#define IBB_PS_CTL_EN 0x85
#define IBB_PS_CTL_DISABLE 0x5
/* REG_IBB_NLIMIT_DAC */
#define IBB_NLIMIT_DAC_EN 0x0
#define IBB_NLIMIT_DAC_DISABLE 0x5
/* REG_IBB_PWRUP_PWRDN_CTL_1 */
#define IBB_PWRUP_PWRDN_CTL_1_DLY1_BITS 2
#define IBB_PWRUP_PWRDN_CTL_1_DLY1_MASK ((1<<IBB_PWRUP_PWRDN_CTL_1_DLY1_BITS)-1)
#define IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT 3
#define IBB_PWRUP_PWRDN_CTL_1_DLY2_BITS 2
#define IBB_PWRUP_PWRDN_CTL_1_DLY2_MASK ((1<<IBB_PWRUP_PWRDN_CTL_1_DLY2_BITS)-1)
#define IBB_PWRUP_PWRDN_CTL_1_LAB_VREG_OK BIT(7)
#define IBB_PWRUP_PWRDN_CTL_1_EN_DLY1 BIT(6)
#define PWRUP_PWRDN_CTL_1_DISCHARGE_EN BIT(2)
/**
* enum qpnp_labibb_mode - working mode of LAB/IBB regulators
* %QPNP_LABIBB_STANDALONE_MODE: configure LAB/IBB regulator as a
* standalone regulator
* %QPNP_LABIBB_LCD_MODE: configure LAB and IBB regulators
* together to provide power supply for LCD
* %QPNP_LABIBB_AMOLED_MODE: configure LAB and IBB regulators
* together to provide power supply for AMOLED
* %QPNP_LABIBB_MAX_MODE max number of configureable modes
* supported by qpnp_labibb_regulator
*/
enum qpnp_labibb_mode {
QPNP_LABIBB_STANDALONE_MODE = 1,
QPNP_LABIBB_LCD_MODE,
QPNP_LABIBB_AMOLED_MODE,
QPNP_LABIBB_MAX_MODE,
};
static const int ibb_discharge_resistor_plan[] = {
300,
64,
32,
16,
};
static const int ibb_pwrup_dly_plan[] = {
1000,
2000,
4000,
8000,
};
static const int ibb_pwrdn_dly_plan[] = {
1000,
2000,
4000,
8000,
};
static const int lab_clk_div_plan[] = {
3200,
2740,
2400,
2130,
1920,
1750,
1600,
1480,
1370,
1280,
1200,
1130,
1070,
1010,
960,
910,
};
static const int ibb_clk_div_plan[] = {
3200,
2740,
2400,
2130,
1920,
1750,
1600,
1480,
1370,
1280,
1200,
1130,
1070,
1010,
960,
910,
};
static const int lab_current_limit_plan[] = {
200,
400,
600,
800,
};
static const int ibb_current_limit_plan[] = {
0,
50,
100,
150,
200,
250,
300,
350,
400,
450,
500,
550,
600,
650,
700,
750,
800,
850,
900,
950,
1000,
1050,
1100,
1150,
1200,
1250,
1300,
1350,
1400,
1450,
1500,
1550,
};
static const int ibb_debounce_plan[] = {
8,
16,
32,
64,
};
static const int lab_ps_threshold_plan[] = {
20,
30,
40,
50,
};
static const int lab_soft_start_plan[] = {
200,
400,
600,
800,
};
static const int lab_rdson_nfet_plan[] = {
25,
50,
75,
100,
};
static const int lab_rdson_pfet_plan[] = {
25,
50,
75,
100,
};
static const int lab_max_precharge_plan[] = {
200,
300,
400,
500,
};
struct lab_regulator {
struct regulator_desc rdesc;
struct regulator_dev *rdev;
struct mutex lab_mutex;
int curr_volt;
int min_volt;
int step_size;
int slew_rate;
int soft_start;
int vreg_enabled;
};
struct ibb_regulator {
struct regulator_desc rdesc;
struct regulator_dev *rdev;
struct mutex ibb_mutex;
int curr_volt;
int min_volt;
int step_size;
int slew_rate;
int soft_start;
u32 pwrup_dly;
u32 pwrdn_dly;
int vreg_enabled;
};
struct qpnp_labibb {
struct device *dev;
struct spmi_device *spmi;
u16 lab_base;
u16 ibb_base;
struct lab_regulator lab_vreg;
struct ibb_regulator ibb_vreg;
int mode;
};
static int
qpnp_labibb_read(struct qpnp_labibb *labibb, u8 *val,
u16 base, int count)
{
int rc = 0;
struct spmi_device *spmi = labibb->spmi;
if (base == 0) {
pr_err("base cannot be zero base=0x%02x sid=0x%02x rc=%d\n",
base, spmi->sid, rc);
return -EINVAL;
}
rc = spmi_ext_register_readl(spmi->ctrl, spmi->sid, base, val, count);
if (rc) {
pr_err("SPMI read failed base=0x%02x sid=0x%02x rc=%d\n", base,
spmi->sid, rc);
return rc;
}
return 0;
}
static int
qpnp_labibb_write(struct qpnp_labibb *labibb, u16 base,
u8 *val, int count)
{
int rc = 0;
struct spmi_device *spmi = labibb->spmi;
if (base == 0) {
pr_err("base cannot be zero base=0x%02x sid=0x%02x rc=%d\n",
base, spmi->sid, rc);
return -EINVAL;
}
rc = spmi_ext_register_writel(spmi->ctrl, spmi->sid, base, val, count);
if (rc) {
pr_err("write failed base=0x%02x sid=0x%02x rc=%d\n",
base, spmi->sid, rc);
return rc;
}
return 0;
}
static int
qpnp_labibb_masked_write(struct qpnp_labibb *labibb, u16 base,
u8 mask, u8 val, int count)
{
int rc;
u8 reg;
rc = qpnp_labibb_read(labibb, &reg, base, count);
if (rc) {
pr_err("spmi read failed: addr=%03X, rc=%d\n", base, rc);
return rc;
}
pr_debug("addr = 0x%x read 0x%x\n", base, reg);
reg &= ~mask;
reg |= val & mask;
pr_debug("Writing 0x%x\n", reg);
rc = qpnp_labibb_write(labibb, base, &reg, count);
if (rc) {
pr_err("spmi write failed: addr=%03X, rc=%d\n", base, rc);
return rc;
}
return 0;
}
static int qpnp_lab_unlock_sec_access(struct qpnp_labibb *labibb)
{
int rc;
u8 val = REG_LAB_IBB_SEC_UNLOCK_CODE;
rc = qpnp_labibb_write(labibb, labibb->lab_base + REG_LAB_SEC_ACCESS,
&val, 1);
if (rc)
pr_err("qpnp_lab_unlock_sec_access write register %x failed rc = %d\n",
REG_LAB_SEC_ACCESS, rc);
return rc;
}
static int qpnp_ibb_unlock_sec_access(struct qpnp_labibb *labibb)
{
int rc;
u8 val = REG_LAB_IBB_SEC_UNLOCK_CODE;
rc = qpnp_labibb_write(labibb,
labibb->ibb_base + REG_IBB_SEC_ACCESS, &val, 1);
if (rc)
pr_err("qpnp_ibb_unlock_sec_access write register %x failed rc = %d\n",
REG_IBB_SEC_ACCESS, rc);
return rc;
}
static int qpnp_lab_dt_init(struct qpnp_labibb *labibb,
struct device_node *of_node)
{
int rc = 0;
u8 i, val;
u32 tmp;
if (labibb->mode != QPNP_LABIBB_STANDALONE_MODE) {
rc = qpnp_lab_unlock_sec_access(labibb);
if (rc) {
pr_err("unlock lab secure register failed rc = %d\n",
rc);
return rc;
}
if (labibb->mode == QPNP_LABIBB_LCD_MODE)
val = REG_LAB_IBB_LCD_MODE;
else
val = REG_LAB_IBB_AMOLED_MODE;
rc = qpnp_labibb_write(labibb, labibb->lab_base +
REG_LAB_LCD_AMOLED_SEL, &val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_LCD_AMOLED_SEL, rc);
return rc;
}
rc = qpnp_lab_unlock_sec_access(labibb);
if (rc) {
pr_err("unlock lab secure register failed rc = %d\n",
rc);
return rc;
}
val = LAB_IBB_EN_RDY_EN;
rc = qpnp_labibb_write(labibb, labibb->lab_base +
REG_LAB_IBB_EN_RDY, &val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_IBB_EN_RDY, rc);
return rc;
}
}
rc = of_property_read_u32(of_node, "qcom,qpnp-lab-soft-start",
&(labibb->lab_vreg.soft_start));
if (rc < 0) {
pr_err("qcom,qpnp-lab-soft-start is missing, rc = %d\n",
rc);
return rc;
}
for (val = 0; val < sizeof(ARRAY_SIZE(lab_soft_start_plan)); val++)
if (lab_soft_start_plan[val] == labibb->lab_vreg.soft_start)
break;
if (val == ARRAY_SIZE(lab_soft_start_plan))
val = ARRAY_SIZE(lab_soft_start_plan) - 1;
rc = qpnp_labibb_write(labibb, labibb->lab_base +
REG_LAB_SOFT_START_CTL, &val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_SOFT_START_CTL, rc);
return rc;
}
val = 0;
if (of_property_read_bool(of_node, "qcom,qpnp-lab-full-pull-down"))
val |= LAB_PD_CTL_FULL_PD;
if (!of_property_read_bool(of_node, "qcom,qpnp-lab-pull-down-enable"))
val |= LAB_PD_CTL_DISABLE;
rc = qpnp_labibb_write(labibb, labibb->lab_base + REG_LAB_PD_CTL,
&val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_PD_CTL, rc);
return rc;
}
rc = of_property_read_u32(of_node,
"qcom,qpnp-lab-switching-clock-frequency", &tmp);
if (rc) {
pr_err("get qcom,qpnp-lab-switching-clock-frequency failed rc = %d\n",
rc);
return rc;
}
for (val = 0; val < ARRAY_SIZE(lab_clk_div_plan); val++)
if (lab_clk_div_plan[val] == tmp)
break;
if (val == ARRAY_SIZE(lab_clk_div_plan)) {
pr_err("Invalid property in qpnp-lab-switching-clock-frequency\n");
return -EINVAL;
}
rc = qpnp_labibb_write(labibb, labibb->lab_base + REG_LAB_CLK_DIV,
&val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_CLK_DIV, rc);
return rc;
}
rc = of_property_read_u32(of_node,
"qcom,qpnp-lab-limit-maximum-current", &tmp);
if (rc) {
pr_err("get qcom,qpnp-lab-limit-maximum-current failed rc = %d\n",
rc);
return rc;
}
for (val = 0; val < ARRAY_SIZE(lab_current_limit_plan); val++)
if (lab_current_limit_plan[val] == tmp)
break;
if (val == ARRAY_SIZE(lab_current_limit_plan)) {
pr_err("Invalid property in qcom,qpnp-lab-limit-maximum-current\n");
return -EINVAL;
}
if (of_property_read_bool(of_node,
"qcom,qpnp-lab-limit-max-current-enable"))
val |= LAB_CURRENT_LIMIT_EN;
rc = qpnp_labibb_write(labibb, labibb->lab_base +
REG_LAB_CURRENT_LIMIT, &val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_CURRENT_LIMIT, rc);
return rc;
}
if (of_property_read_bool(of_node,
"qcom,qpnp-lab-ring-suppression-enable")) {
val = LAB_RING_SUPPRESSION_CTL_EN;
rc = qpnp_labibb_write(labibb, labibb->lab_base +
REG_LAB_RING_SUPPRESSION_CTL,
&val,
1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_RING_SUPPRESSION_CTL, rc);
return rc;
}
}
rc = of_property_read_u32(of_node, "qcom,qpnp-lab-ps-threshold", &tmp);
if (rc) {
pr_err("get qcom,qpnp-lab-ps-threshold failed rc = %d\n",
rc);
return rc;
}
for (val = 0; val < ARRAY_SIZE(lab_ps_threshold_plan); val++)
if (lab_ps_threshold_plan[val] == tmp)
break;
if (val == ARRAY_SIZE(lab_ps_threshold_plan)) {
pr_err("Invalid property in qcom,qpnp-lab-ps-threshold\n");
return -EINVAL;
}
if (of_property_read_bool(of_node, "qcom,qpnp-lab-ps-enable"))
val |= LAB_PS_CTL_EN;
rc = qpnp_labibb_write(labibb, labibb->lab_base + REG_LAB_PS_CTL,
&val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_PS_CTL, rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-lab-pfet-size", &tmp);
if (rc) {
pr_err("get qcom,qpnp-lab-pfet-size, rc = %d\n", rc);
return rc;
}
for (val = 0; val < ARRAY_SIZE(lab_rdson_pfet_plan); val++)
if (tmp == lab_rdson_pfet_plan[val])
break;
if (val == ARRAY_SIZE(lab_rdson_pfet_plan)) {
pr_err("Invalid property in qcom,qpnp-lab-pfet-size\n");
return -EINVAL;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-lab-nfet-size", &tmp);
if (rc) {
pr_err("get qcom,qpnp-lab-nfet-size, rc = %d\n", rc);
return rc;
}
for (i = 0; i < ARRAY_SIZE(lab_rdson_nfet_plan); i++)
if (tmp == lab_rdson_nfet_plan[i])
break;
if (i == ARRAY_SIZE(lab_rdson_nfet_plan)) {
pr_err("Iniid property in qcom,qpnp-lab-nfet-size\n");
return -EINVAL;
}
val |= i << LAB_RDSON_MNGMNT_NFET_SHIFT;
val |= (LAB_RDSON_MNGMNT_NFET_SLEW_EN | LAB_RDSON_MNGMNT_PFET_SLEW_EN);
rc = qpnp_labibb_write(labibb, labibb->lab_base + REG_LAB_RDSON_MNGMNT,
&val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_RDSON_MNGMNT, rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-lab-max-precharge-time",
&tmp);
if (rc) {
pr_err("get qcom,qpnp-lab-max-precharge-time failed, rc = %d\n",
rc);
return rc;
}
for (val = 0; val < ARRAY_SIZE(lab_max_precharge_plan); val++)
if (lab_max_precharge_plan[val] == tmp)
break;
if (val == ARRAY_SIZE(lab_max_precharge_plan)) {
pr_err("Invalid property in qcom,qpnp-lab-max-precharge-time\n");
return -EINVAL;
}
if (of_property_read_bool(of_node,
"qcom,qpnp-lab-max-precharge-enable"))
val |= LAB_PRECHARGE_CTL_EN;
rc = qpnp_labibb_write(labibb, labibb->lab_base +
REG_LAB_PRECHARGE_CTL, &val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_PRECHARGE_CTL, rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-lab-init-voltage",
&(labibb->lab_vreg.curr_volt));
if (rc) {
pr_err("get qcom,qpnp-lab-init-voltage failed, rc = %d\n", rc);
return rc;
}
if (!of_property_read_bool(of_node,
"qcom,qpnp-lab-use-default-voltage")) {
if (labibb->lab_vreg.curr_volt < labibb->lab_vreg.min_volt) {
pr_err("Invalid qcom,qpnp-lab-init-voltage property, qcom,qpnp-lab-init-voltage %d is less than the the minimum voltage %d",
labibb->lab_vreg.curr_volt,
labibb->lab_vreg.min_volt);
return -EINVAL;
}
val = DIV_ROUND_UP(labibb->lab_vreg.curr_volt -
labibb->lab_vreg.min_volt,
labibb->lab_vreg.step_size);
if (val > LAB_VOLTAGE_SET_MASK) {
pr_err("Invalid qcom,qpnp-lab-init-voltage property, qcom,qpnp-lab-init-voltage %d is larger than the max supported voltage %d",
labibb->lab_vreg.curr_volt,
labibb->lab_vreg.min_volt +
labibb->lab_vreg.step_size *
LAB_VOLTAGE_SET_MASK);
return -EINVAL;
}
labibb->lab_vreg.curr_volt = val * labibb->lab_vreg.step_size +
labibb->lab_vreg.min_volt;
val |= LAB_VOLTAGE_OVERRIDE_EN;
} else {
val = 0;
}
rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
REG_LAB_VOLTAGE,
LAB_VOLTAGE_SET_MASK |
LAB_VOLTAGE_OVERRIDE_EN,
val,
1);
if (rc) {
pr_err("qpnp_lab_regulator_set_voltage write register %x failed rc = %d\n",
REG_LAB_VOLTAGE, rc);
return rc;
}
if (labibb->mode != QPNP_LABIBB_STANDALONE_MODE) {
val = LAB_MODULE_RDY_EN;
rc = qpnp_labibb_write(labibb, labibb->lab_base +
REG_LAB_MODULE_RDY, &val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_LAB_MODULE_RDY, rc);
return rc;
}
}
return rc;
}
static int qpnp_labibb_regulator_enable(struct qpnp_labibb *labibb)
{
int rc;
u8 val = IBB_ENABLE_CTL_EN;
int dly;
int retries;
bool enabled = false;
rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_ENABLE_CTL,
&val, 1);
if (rc) {
pr_err("%s: write register %x failed rc = %d\n",
__func__, REG_IBB_ENABLE_CTL, rc);
return rc;
}
/* total delay time */
dly = labibb->lab_vreg.soft_start + labibb->ibb_vreg.soft_start
+ labibb->ibb_vreg.pwrup_dly;
usleep_range(dly, dly + 100);
/* after this delay, lab should be enabled */
rc = qpnp_labibb_read(labibb, &val,
labibb->lab_base + REG_LAB_STATUS1, 1);
if (rc) {
pr_err("%s: read register %x failed rc = %d\n",
__func__, REG_LAB_STATUS1, rc);
goto err_out;
}
pr_debug("%s: soft=%d %d up=%d dly=%d\n", __func__,
labibb->lab_vreg.soft_start, labibb->ibb_vreg.soft_start,
labibb->ibb_vreg.pwrup_dly, dly);
if (!(val & LAB_STATUS1_VREG_OK)) {
pr_err("%s: failed for LAB %x\n", __func__, val);
goto err_out;
}
/* poll IBB_STATUS to make sure ibb had been enabled */
dly = labibb->ibb_vreg.soft_start + labibb->ibb_vreg.pwrup_dly;
retries = 10;
while (retries--) {
rc = qpnp_labibb_read(labibb, &val,
labibb->ibb_base + REG_IBB_STATUS1, 1);
if (rc) {
pr_err("%s: read register %x failed rc = %d\n",
__func__, REG_IBB_STATUS1, rc);
goto err_out;
}
if (val & IBB_STATUS1_VREG_OK) {
enabled = true;
break;
}
usleep_range(dly, dly + 100);
}
if (!enabled) {
pr_err("%s: failed for IBB %x\n", __func__, val);
goto err_out;
}
labibb->lab_vreg.vreg_enabled = 1;
labibb->ibb_vreg.vreg_enabled = 1;
return 0;
err_out:
val = 0;
rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_ENABLE_CTL,
&val, 1);
if (rc)
pr_err("%s: write register %x failed rc = %d\n",
__func__, REG_IBB_ENABLE_CTL, rc);
return -EINVAL;
}
static int qpnp_labibb_regulator_disable(struct qpnp_labibb *labibb)
{
int rc;
u8 val;
int dly;
int retries;
bool disabled = false;
val = 0;
rc = qpnp_labibb_write(labibb,
labibb->ibb_base + REG_IBB_ENABLE_CTL, &val, 1);
if (rc) {
pr_err("%s: write register %x failed rc = %d\n",
__func__, REG_IBB_ENABLE_CTL, rc);
return rc;
}
/* poll IBB_STATUS to make sure ibb had been disabled */
dly = labibb->ibb_vreg.pwrdn_dly;
retries = 2;
while (retries--) {
usleep_range(dly, dly + 100);
rc = qpnp_labibb_read(labibb, &val,
labibb->ibb_base + REG_IBB_STATUS1, 1);
if (rc) {
pr_err("%s: read register %x failed rc = %d\n",
__func__, REG_IBB_STATUS1, rc);
return rc;
}
if (!(val & IBB_STATUS1_VREG_OK)) {
disabled = true;
break;
}
}
if (!disabled) {
pr_err("%s: failed for IBB %x\n", __func__, val);
return -EINVAL;
}
labibb->lab_vreg.vreg_enabled = 0;
labibb->ibb_vreg.vreg_enabled = 0;
return 0;
}
static int qpnp_lab_regulator_enable(struct regulator_dev *rdev)
{
int rc;
u8 val;
struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
if (!(labibb->lab_vreg.vreg_enabled)) {
if (labibb->mode != QPNP_LABIBB_STANDALONE_MODE)
return qpnp_labibb_regulator_enable(labibb);
val = LAB_ENABLE_CTL_EN;
rc = qpnp_labibb_write(labibb,
labibb->lab_base + REG_LAB_ENABLE_CTL, &val, 1);
if (rc) {
pr_err("qpnp_lab_regulator_enable write register %x failed rc = %d\n",
REG_LAB_ENABLE_CTL, rc);
return rc;
}
udelay(labibb->lab_vreg.soft_start);
rc = qpnp_labibb_read(labibb, &val,
labibb->lab_base + REG_LAB_STATUS1, 1);
if (rc) {
pr_err("qpnp_lab_regulator_enable read register %x failed rc = %d\n",
REG_LAB_STATUS1, rc);
return rc;
}
if ((val & LAB_STATUS1_VREG_OK_MASK) != LAB_STATUS1_VREG_OK) {
pr_err("qpnp_lab_regulator_enable failed\n");
return -EINVAL;
}
labibb->lab_vreg.vreg_enabled = 1;
}
return 0;
}
static int qpnp_lab_regulator_disable(struct regulator_dev *rdev)
{
int rc;
u8 val;
struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
if (labibb->lab_vreg.vreg_enabled) {
if (labibb->mode != QPNP_LABIBB_STANDALONE_MODE)
return qpnp_labibb_regulator_disable(labibb);
val = 0;
rc = qpnp_labibb_write(labibb,
labibb->lab_base + REG_LAB_ENABLE_CTL, &val, 1);
if (rc) {
pr_err("qpnp_lab_regulator_enable write register %x failed rc = %d\n",
REG_LAB_ENABLE_CTL, rc);
return rc;
}
labibb->lab_vreg.vreg_enabled = 0;
}
return 0;
}
static int qpnp_lab_regulator_is_enabled(struct regulator_dev *rdev)
{
struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
return labibb->lab_vreg.vreg_enabled;
}
static int qpnp_lab_regulator_set_voltage(struct regulator_dev *rdev,
int min_uV, int max_uV, unsigned *selector)
{
int rc, new_uV;
u8 val;
struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
if (min_uV < labibb->lab_vreg.min_volt) {
pr_err("qpnp_lab_regulator_set_voltage failed, min_uV %d is less than min_volt %d",
min_uV, labibb->lab_vreg.min_volt);
}
val = DIV_ROUND_UP(min_uV - labibb->lab_vreg.min_volt,
labibb->lab_vreg.step_size);
new_uV = val * labibb->lab_vreg.step_size + labibb->lab_vreg.min_volt;
if (new_uV > max_uV) {
pr_err("qpnp_lab_regulator_set_voltage unable to set voltage (%d %d)\n",
min_uV, max_uV);
return -EINVAL;
}
rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
REG_LAB_VOLTAGE,
LAB_VOLTAGE_SET_MASK |
LAB_VOLTAGE_OVERRIDE_EN,
val | LAB_VOLTAGE_OVERRIDE_EN,
1);
if (rc) {
pr_err("qpnp_lab_regulator_set_voltage write register %x failed rc = %d\n",
REG_LAB_VOLTAGE, rc);
return rc;
}
if (new_uV > labibb->lab_vreg.curr_volt)
udelay(val * labibb->lab_vreg.slew_rate);
labibb->lab_vreg.curr_volt = new_uV;
return 0;
}
static int qpnp_lab_regulator_get_voltage(struct regulator_dev *rdev)
{
struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
return labibb->lab_vreg.curr_volt;
}
static struct regulator_ops qpnp_lab_ops = {
.enable = qpnp_lab_regulator_enable,
.disable = qpnp_lab_regulator_disable,
.is_enabled = qpnp_lab_regulator_is_enabled,
.set_voltage = qpnp_lab_regulator_set_voltage,
.get_voltage = qpnp_lab_regulator_get_voltage,
};
static int register_qpnp_lab_regulator(struct qpnp_labibb *labibb,
struct device_node *of_node)
{
int rc = 0;
struct regulator_init_data *init_data;
struct regulator_desc *rdesc;
struct regulator_config cfg = {};
u8 ibb_en_rdy_val, val;
if (!of_node) {
dev_err(labibb->dev, "qpnp lab regulator device tree node is missing\n");
return -EINVAL;
}
init_data = of_get_regulator_init_data(labibb->dev, of_node);
if (!init_data) {
pr_err("unable to get regulator init data for qpnp lab regulator\n");
return -ENOMEM;
}
rc = qpnp_labibb_read(labibb, &ibb_en_rdy_val,
labibb->lab_base + REG_LAB_IBB_EN_RDY, 1);
if (rc) {
pr_err("qpnp_lab_read register %x failed rc = %d\n",
REG_LAB_IBB_EN_RDY, rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-lab-min-voltage",
&(labibb->lab_vreg.min_volt));
if (rc < 0) {
pr_err("qcom,qpnp-lab-min-voltage is missing, rc = %d\n",
rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-lab-step-size",
&(labibb->lab_vreg.step_size));
if (rc < 0) {
pr_err("qcom,qpnp-lab-step-size is missing, rc = %d\n", rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-lab-slew-rate",
&(labibb->lab_vreg.slew_rate));
if (rc < 0) {
pr_err("qcom,qpnp-lab-slew-rate is missing, rc = %d\n",
rc);
return rc;
}
if (ibb_en_rdy_val == LAB_IBB_EN_RDY_EN) {
rc = qpnp_labibb_read(labibb, &val,
labibb->lab_base + REG_LAB_LCD_AMOLED_SEL, 1);
if (rc) {
pr_err("qpnp_labibb_read register %x failed rc = %d\n",
REG_LAB_LCD_AMOLED_SEL, rc);
return rc;
}
if (val == REG_LAB_IBB_AMOLED_MODE)
labibb->mode = QPNP_LABIBB_AMOLED_MODE;
else
labibb->mode = QPNP_LABIBB_LCD_MODE;
rc = qpnp_labibb_read(labibb, &val, labibb->lab_base +
REG_LAB_VOLTAGE, 1);
if (rc) {
pr_err("qpnp_lab_read read register %x failed rc = %d\n",
REG_LAB_VOLTAGE, rc);
return rc;
}
if (val & LAB_VOLTAGE_OVERRIDE_EN) {
labibb->lab_vreg.curr_volt =
(val &
LAB_VOLTAGE_SET_MASK) *
labibb->lab_vreg.step_size +
labibb->lab_vreg.min_volt;
} else if (labibb->mode == QPNP_LABIBB_LCD_MODE) {
rc = of_property_read_u32(of_node,
"qcom,qpnp-lab-init-lcd-voltage",
&(labibb->lab_vreg.curr_volt));
if (rc) {
pr_err("get qcom,qpnp-lab-init-lcd-voltage failed, rc = %d\n",
rc);
return rc;
}
} else {
rc = of_property_read_u32(of_node,
"qcom,qpnp-lab-init-amoled-voltage",
&(labibb->lab_vreg.curr_volt));
if (rc) {
pr_err("get qcom,qpnp-lab-init-amoled-voltage failed, rc = %d\n",
rc);
return rc;
}
}
rc = qpnp_labibb_read(labibb, &val,
labibb->lab_base + REG_LAB_SOFT_START_CTL, 1);
if (rc) {
pr_err("qpnp_lab_read read register %x failed rc = %d\n",
REG_LAB_SOFT_START_CTL, rc);
return rc;
}
labibb->lab_vreg.soft_start = lab_soft_start_plan
[val & LAB_SOFT_START_CTL_MASK];
labibb->lab_vreg.vreg_enabled = 1;
} else {
rc = qpnp_lab_dt_init(labibb, of_node);
if (rc) {
pr_err("qpnp-lab: wrong DT parameter specified: rc = %d\n",
rc);
return rc;
}
}
if (init_data->constraints.name) {
rdesc = &(labibb->lab_vreg.rdesc);
rdesc->owner = THIS_MODULE;
rdesc->type = REGULATOR_VOLTAGE;
rdesc->ops = &qpnp_lab_ops;
rdesc->name = init_data->constraints.name;
cfg.dev = labibb->dev;
cfg.init_data = init_data;
cfg.driver_data = labibb;
cfg.of_node = of_node;
init_data->constraints.valid_ops_mask
|= REGULATOR_CHANGE_VOLTAGE |
REGULATOR_CHANGE_STATUS;
labibb->lab_vreg.rdev = regulator_register(rdesc, &cfg);
if (IS_ERR(labibb->lab_vreg.rdev)) {
rc = PTR_ERR(labibb->lab_vreg.rdev);
labibb->lab_vreg.rdev = NULL;
pr_err("unable to get regulator init data for qpnp lab regulator, rc = %d\n",
rc);
return rc;
}
} else {
dev_err(labibb->dev, "qpnp lab regulator name missing\n");
return -EINVAL;
}
mutex_init(&(labibb->lab_vreg.lab_mutex));
return 0;
}
/** This API is used to set the pull down strength of LAB regulator
* regulator: the reglator device
* strength: if strength is 0, LAB regulator will be set to half strength.
* otherwise, LAB regulator will be set to full strength
*/
int qpnp_lab_set_pd_strength(struct regulator *regulator, u32 strength)
{
struct qpnp_labibb *labibb;
u8 val;
int rc = 0;
if (strength > 0)
val = LAB_PD_CTL_FULL_PD;
else
val = 0;
labibb = regulator_get_drvdata(regulator);
mutex_lock(&(labibb->lab_vreg.lab_mutex));
rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
REG_LAB_PD_CTL,
LAB_PD_CTL_STRENGTH_MASK,
val,
1);
mutex_unlock(&(labibb->lab_vreg.lab_mutex));
if (rc)
pr_err("qpnp_lab_set_pd_strength write register %x failed rc = %d\n",
REG_LAB_PD_CTL, rc);
return rc;
}
EXPORT_SYMBOL(qpnp_lab_set_pd_strength);
/** This API is used to enable pull down of LAB regulator
* regulator: the reglator device
* enable: if enable is true, this API will enable pull down of LAB regulator.
* otherwise, it will disable pull down for LAB regulator
*/
int qpnp_lab_pd_enable_ctl(struct regulator *regulator, bool enable)
{
struct qpnp_labibb *labibb;
u8 val;
int rc = 0;
if (enable)
val = 0;
else
val = LAB_PD_CTL_DISABLE;
labibb = regulator_get_drvdata(regulator);
mutex_lock(&(labibb->lab_vreg.lab_mutex));
rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
REG_LAB_PD_CTL,
LAB_PD_CTL_EN_MASK,
val,
1);
mutex_unlock(&(labibb->lab_vreg.lab_mutex));
if (rc)
pr_err("qpnp_lab_pd_enable_ctl write register %x failed rc = %d\n",
REG_LAB_PD_CTL, rc);
return rc;
}
EXPORT_SYMBOL(qpnp_lab_pd_enable_ctl);
/** This API is used to set the pull down strength of IBB regulator
* regulator: the reglator device
* strength: if strength is 0, IBB regulator will be set to half strength.
* otherwise, IBB regulator will be set to full strength
*/
int qpnp_ibb_set_pd_strength(struct regulator *regulator, u32 strength)
{
struct qpnp_labibb *labibb;
u8 val;
int rc = 0;
if (strength > 0)
val = 0;
else
val = IBB_PD_CTL_HALF_STRENGTH;
labibb = regulator_get_drvdata(regulator);
mutex_lock(&(labibb->ibb_vreg.ibb_mutex));
rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
REG_IBB_PD_CTL,
IBB_PD_CTL_STRENGTH_MASK,
val,
1);
mutex_unlock(&(labibb->ibb_vreg.ibb_mutex));
if (rc)
pr_err("qpnp_ibb_set_pd_strength write register %x failed rc = %d\n",
REG_IBB_PD_CTL, rc);
return rc;
}
EXPORT_SYMBOL(qpnp_ibb_set_pd_strength);
/** This API is used to enable pull down of IBB regulator
* regulator: the reglator device
* enable: if enable is true, this API will enable pull down of IBB regulator.
* otherwise, it will disable pull down for IBB regulator
*/
int qpnp_ibb_pd_enable_ctl(struct regulator *regulator, bool enable)
{
struct qpnp_labibb *labibb;
u8 val;
int rc = 0;
if (enable)
val = IBB_PD_CTL_EN;
else
val = 0;
labibb = regulator_get_drvdata(regulator);
mutex_lock(&(labibb->ibb_vreg.ibb_mutex));
rc = qpnp_labibb_masked_write(labibb, labibb->lab_base +
REG_IBB_PD_CTL,
IBB_PD_CTL_EN_MASK,
val,
1);
mutex_unlock(&(labibb->ibb_vreg.ibb_mutex));
if (rc)
pr_err("qpnp_ibb_pd_enable_ctl write register %x failed rc = %d\n",
REG_IBB_PD_CTL, rc);
return rc;
}
EXPORT_SYMBOL(qpnp_ibb_pd_enable_ctl);
/** This API is used to set the power up delay for IBB regulator
* regulator: the reglator device
* val: the delay in us for power up of IBB regulator
*/
int qpnp_ibb_set_pwrup_dly(struct regulator *regulator, u32 val)
{
struct qpnp_labibb *labibb;
int rc = 0;
u8 reg;
labibb = regulator_get_drvdata(regulator);
for (reg = 0; reg < ARRAY_SIZE(ibb_pwrup_dly_plan); reg++)
if (val == ibb_pwrup_dly_plan[reg])
break;
if (reg == ARRAY_SIZE(ibb_pwrup_dly_plan))
reg = ARRAY_SIZE(ibb_pwrup_dly_plan) - 1;
mutex_lock(&(labibb->ibb_vreg.ibb_mutex));
if (labibb->ibb_vreg.pwrup_dly == ibb_pwrup_dly_plan[reg]) {
rc = 0;
goto _exit;
}
rc = qpnp_ibb_unlock_sec_access(labibb);
if (rc) {
pr_err("unlock ibb secure register failed rc = %d\n", rc);
goto _exit;
}
rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
REG_IBB_PWRUP_PWRDN_CTL_1,
IBB_PWRUP_PWRDN_CTL_1_DLY1_MASK <<
IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT,
reg << IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT,
1);
if (rc) {
pr_err("qpnp_ibb_set_pwrup write register %x failed rc = %d\n",
REG_IBB_PWRUP_PWRDN_CTL_1, rc);
goto _exit;
}
labibb->ibb_vreg.pwrup_dly = ibb_pwrup_dly_plan[reg];
_exit:
mutex_unlock(&(labibb->ibb_vreg.ibb_mutex));
return rc;
}
EXPORT_SYMBOL(qpnp_ibb_set_pwrup_dly);
/** This API is used to set the power down delay for IBB regulator
* regulator: the reglator device
* val: the delay in us for power down of IBB regulator
*/
int qpnp_ibb_set_pwrdn_dly(struct regulator *regulator, u32 val)
{
struct qpnp_labibb *labibb;
int rc = 0;
u8 reg;
labibb = regulator_get_drvdata(regulator);
for (reg = 0; reg < ARRAY_SIZE(ibb_pwrdn_dly_plan); reg++)
if (val == ibb_pwrdn_dly_plan[reg])
break;
if (reg == ARRAY_SIZE(ibb_pwrdn_dly_plan))
reg = ARRAY_SIZE(ibb_pwrdn_dly_plan) - 1;
mutex_lock(&(labibb->ibb_vreg.ibb_mutex));
if (labibb->ibb_vreg.pwrdn_dly == ibb_pwrdn_dly_plan[reg]) {
rc = 0;
goto _exit;
}
rc = qpnp_ibb_unlock_sec_access(labibb);
if (rc) {
pr_err("unlock ibb secure register failed rc = %d\n", rc);
goto _exit;
}
rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
REG_IBB_PWRUP_PWRDN_CTL_1,
IBB_PWRUP_PWRDN_CTL_1_DLY2_MASK,
reg,
1);
if (rc) {
pr_err("qpnp_ibb_set_pwrdn write register %x failed rc = %d\n",
REG_IBB_PWRUP_PWRDN_CTL_1, rc);
goto _exit;
}
labibb->ibb_vreg.pwrdn_dly = ibb_pwrdn_dly_plan[reg];
_exit:
mutex_unlock(&(labibb->ibb_vreg.ibb_mutex));
return rc;
}
EXPORT_SYMBOL(qpnp_ibb_set_pwrdn_dly);
static int qpnp_ibb_dt_init(struct qpnp_labibb *labibb,
struct device_node *of_node)
{
int rc = 0;
u32 i, tmp;
u8 val;
if (labibb->mode != QPNP_LABIBB_STANDALONE_MODE) {
rc = qpnp_ibb_unlock_sec_access(labibb);
if (rc) {
pr_err("unlock ibb secure register failed rc = %d\n",
rc);
return rc;
}
if (labibb->mode == QPNP_LABIBB_LCD_MODE)
val = REG_LAB_IBB_LCD_MODE;
else
val = REG_LAB_IBB_AMOLED_MODE;
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_LAB_LCD_AMOLED_SEL, &val, 1);
if (rc) {
pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
REG_IBB_LCD_AMOLED_SEL, rc);
return rc;
}
}
rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-discharge-resistor",
&tmp);
if (rc < 0) {
pr_err("qcom,qpnp-ibb-discharge-resistor is missing, rc = %d\n",
rc);
return rc;
}
if (labibb->mode == QPNP_LABIBB_AMOLED_MODE) {
/*
* AMOLED mode needs ibb discharge resistor to be
* configured for 300KOhm
*/
if (tmp < ibb_discharge_resistor_plan[0])
tmp = ibb_discharge_resistor_plan[0];
}
for (val = 0; val < sizeof(ARRAY_SIZE(ibb_discharge_resistor_plan));
val++)
if (ibb_discharge_resistor_plan[val] == tmp)
break;
if (val == ARRAY_SIZE(ibb_discharge_resistor_plan)) {
pr_err("Invalid property in qcom,qpnp-ibb-discharge-resistor\n");
return -EINVAL;
}
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_IBB_SOFT_START_CTL, &val, 1);
if (rc) {
pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
REG_IBB_SOFT_START_CTL, rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-lab-pwrdn-delay",
&tmp);
if (rc < 0) {
pr_err("qcom,qpnp-ibb-lab-pwrdn-delay is missing, rc = %d\n",
rc);
return rc;
}
val = 0;
for (val = 0; val < ARRAY_SIZE(ibb_pwrdn_dly_plan); val++)
if (ibb_pwrdn_dly_plan[val] == tmp)
break;
if (val == ARRAY_SIZE(ibb_pwrdn_dly_plan)) {
pr_err("Invalid property in qcom,qpnp-ibb-lab-pwrdn-delay\n");
return -EINVAL;
}
labibb->ibb_vreg.pwrdn_dly = tmp;
rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-lab-pwrup-delay",
&tmp);
if (rc < 0) {
pr_err("qcom,qpnp-ibb-lab-pwrup-delay is missing, rc = %d\n",
rc);
return rc;
}
for (i = 0; i < ARRAY_SIZE(ibb_pwrup_dly_plan); i++)
if (ibb_pwrup_dly_plan[i] == tmp)
break;
if (i == ARRAY_SIZE(ibb_pwrup_dly_plan)) {
pr_err("Invalid property in qcom,qpnp-ibb-lab-pwrup-delay\n");
return -EINVAL;
}
labibb->ibb_vreg.pwrup_dly = tmp;
val |= (i << IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT);
if (of_property_read_bool(of_node, "qcom,qpnp-ibb-en-discharge"))
val |= PWRUP_PWRDN_CTL_1_DISCHARGE_EN;
if (labibb->mode != QPNP_LABIBB_STANDALONE_MODE)
val |= (IBB_PWRUP_PWRDN_CTL_1_EN_DLY1 | IBB_ENABLE_CTL_EN);
rc = qpnp_ibb_unlock_sec_access(labibb);
if (rc) {
pr_err("unlock ibb secure register failed rc = %d\n", rc);
return rc;
}
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_IBB_PWRUP_PWRDN_CTL_1,
&val,
1);
if (rc) {
pr_err("qpnp_ibb_set_pwrdn write register %x failed rc = %d\n",
REG_IBB_PWRUP_PWRDN_CTL_1, rc);
return rc;
}
val = 0;
if (!of_property_read_bool(of_node, "qcom,qpnp-ibb-full-pull-down"))
val |= IBB_PD_CTL_HALF_STRENGTH;
if (of_property_read_bool(of_node, "qcom,qpnp-ibb-pull-down-enable"))
val |= IBB_PD_CTL_EN;
rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_PD_CTL,
&val, 1);
if (rc) {
pr_err("qpnp_lab_dt_init write register %x failed rc = %d\n",
REG_IBB_PD_CTL, rc);
return rc;
}
rc = of_property_read_u32(of_node,
"qcom,qpnp-ibb-switching-clock-frequency", &tmp);
if (rc) {
pr_err("get qcom,qpnp-ibb-switching-clock-frequency failed rc = %d\n",
rc);
return rc;
}
for (val = 0; val < ARRAY_SIZE(ibb_clk_div_plan); val++)
if (ibb_clk_div_plan[val] == tmp)
break;
if (val == ARRAY_SIZE(ibb_clk_div_plan)) {
pr_err("Invalid property in qpnp-ibb-switching-clock-frequency\n");
return -EINVAL;
}
rc = qpnp_labibb_write(labibb, labibb->ibb_base + REG_IBB_CLK_DIV,
&val, 1);
if (rc) {
pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
REG_IBB_CLK_DIV, rc);
return rc;
}
rc = of_property_read_u32(of_node,
"qcom,qpnp-ibb-limit-maximum-current", &tmp);
if (rc) {
pr_err("get qcom,qpnp-ibb-limit-maximum-current failed rc = %d\n",
rc);
return rc;
}
for (val = 0; val < ARRAY_SIZE(ibb_current_limit_plan); val++)
if (ibb_current_limit_plan[val] == tmp)
break;
if (val == ARRAY_SIZE(ibb_current_limit_plan)) {
pr_err("Invalid property in qcom,qpnp-ibb-limit-maximum-current\n");
return -EINVAL;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-debounce-cycle",
&tmp);
if (rc) {
pr_err("get qcom,qpnp-ibb-debounce-cycle failed rc = %d\n",
rc);
return rc;
}
for (i = 0; i < ARRAY_SIZE(ibb_debounce_plan); i++)
if (ibb_debounce_plan[i] == tmp)
break;
if (i == ARRAY_SIZE(ibb_debounce_plan)) {
pr_err("Invalid property in qcom,qpnp-ibb-debounce-cycle\n");
return -EINVAL;
}
val |= (i << IBB_CURRENT_LIMIT_DEBOUNCE_SHIFT);
if (of_property_read_bool(of_node,
"qcom,qpnp-ibb-limit-max-current-enable"))
val |= IBB_CURRENT_LIMIT_EN;
rc = qpnp_ibb_unlock_sec_access(labibb);
if (rc) {
pr_err("unlock ibb secure register failed rc = %d\n", rc);
return rc;
}
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_IBB_CURRENT_LIMIT,
&val,
1);
if (rc) {
pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
REG_IBB_CURRENT_LIMIT, rc);
return rc;
}
if (of_property_read_bool(of_node,
"qcom,qpnp-ibb-ring-suppression-enable")) {
val = IBB_RING_SUPPRESSION_CTL_EN;
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_IBB_RING_SUPPRESSION_CTL,
&val,
1);
if (rc) {
pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
REG_IBB_RING_SUPPRESSION_CTL, rc);
return rc;
}
}
if (of_property_read_bool(of_node, "qcom,qpnp-ibb-ps-enable")) {
val = IBB_PS_CTL_EN;
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_IBB_PS_CTL,
&val,
1);
if (rc) {
pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
REG_IBB_PS_CTL, rc);
return rc;
}
val = IBB_NLIMIT_DAC_EN;
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_IBB_NLIMIT_DAC,
&val,
1);
if (rc) {
pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
REG_IBB_NLIMIT_DAC, rc);
return rc;
}
} else {
val = IBB_PS_CTL_DISABLE;
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_IBB_PS_CTL,
&val,
1);
if (rc) {
pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
REG_IBB_PS_CTL, rc);
return rc;
}
val = IBB_NLIMIT_DAC_DISABLE;
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_IBB_NLIMIT_DAC,
&val,
1);
if (rc) {
pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
REG_IBB_NLIMIT_DAC, rc);
return rc;
}
}
rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-init-voltage",
&(labibb->ibb_vreg.curr_volt));
if (rc) {
pr_err("get qcom,qpnp-ibb-init-voltage failed, rc = %d\n", rc);
return rc;
}
if (!of_property_read_bool(of_node,
"qcom,qpnp-ibb-use-default-voltage")) {
if (labibb->ibb_vreg.curr_volt < labibb->ibb_vreg.min_volt) {
pr_err("Invalid qcom,qpnp-ibb-init-voltage property, qcom,qpnp-ibb-init-voltage %d is less than the the minimum voltage %d",
labibb->ibb_vreg.curr_volt,
labibb->ibb_vreg.min_volt);
return -EINVAL;
}
val = DIV_ROUND_UP(labibb->ibb_vreg.curr_volt -
labibb->ibb_vreg.min_volt,
labibb->ibb_vreg.step_size);
if (val > IBB_VOLTAGE_SET_MASK) {
pr_err("Invalid qcom,qpnp-ibb-init-voltage property, qcom,qpnp-lab-init-voltage %d is larger than the max supported voltage %d",
labibb->ibb_vreg.curr_volt,
labibb->ibb_vreg.min_volt +
labibb->ibb_vreg.step_size *
IBB_VOLTAGE_SET_MASK);
return -EINVAL;
}
labibb->ibb_vreg.curr_volt = val * labibb->ibb_vreg.step_size +
labibb->ibb_vreg.min_volt;
val |= IBB_VOLTAGE_OVERRIDE_EN;
} else {
val = 0;
}
rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
REG_IBB_VOLTAGE,
IBB_VOLTAGE_SET_MASK |
IBB_VOLTAGE_OVERRIDE_EN,
val,
1);
if (rc)
pr_err("qpnp_ibb_masked_write write register %x failed rc = %d\n",
REG_IBB_VOLTAGE, rc);
if (labibb->mode != QPNP_LABIBB_STANDALONE_MODE) {
val = REG_IBB_MODULE_RDY_EN;
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_IBB_MODULE_RDY, &val, 1);
if (rc) {
pr_err("qpnp_ibb_dt_init write register %x failed rc = %d\n",
REG_IBB_MODULE_RDY, rc);
return rc;
}
}
return rc;
}
static int qpnp_ibb_regulator_enable(struct regulator_dev *rdev)
{
int rc;
u8 val;
struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
if (!(labibb->ibb_vreg.vreg_enabled)) {
if (labibb->mode != QPNP_LABIBB_STANDALONE_MODE)
return qpnp_labibb_regulator_enable(labibb);
val = IBB_ENABLE_CTL_EN;
rc = qpnp_labibb_write(labibb,
labibb->ibb_base + REG_IBB_ENABLE_CTL, &val, 1);
if (rc) {
pr_err("qpnp_ibb_regulator_enable write register %x failed rc = %d\n",
REG_IBB_ENABLE_CTL, rc);
return rc;
}
udelay(labibb->ibb_vreg.soft_start);
rc = qpnp_labibb_read(labibb, &val,
labibb->ibb_base + REG_IBB_STATUS1, 1);
if (rc) {
pr_err("qpnp_ibb_regulator_enable read register %x failed rc = %d\n",
REG_IBB_STATUS1, rc);
return rc;
}
if ((val & IBB_STATUS1_VREG_OK_MASK) != IBB_STATUS1_VREG_OK) {
pr_err("qpnp_ibb_regulator_enable failed\n");
return -EINVAL;
}
labibb->ibb_vreg.vreg_enabled = 1;
}
return 0;
}
static int qpnp_ibb_regulator_disable(struct regulator_dev *rdev)
{
int rc;
u8 val;
struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
if (labibb->ibb_vreg.vreg_enabled) {
if (labibb->mode != QPNP_LABIBB_STANDALONE_MODE)
return qpnp_labibb_regulator_disable(labibb);
val = 0;
rc = qpnp_labibb_write(labibb, labibb->ibb_base +
REG_IBB_ENABLE_CTL, &val, 1);
if (rc) {
pr_err("qpnp_ibb_regulator_enable write register %x failed rc = %d\n",
REG_IBB_ENABLE_CTL, rc);
return rc;
}
labibb->ibb_vreg.vreg_enabled = 0;
}
return 0;
}
static int qpnp_ibb_regulator_is_enabled(struct regulator_dev *rdev)
{
struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
return labibb->ibb_vreg.vreg_enabled;
}
static int qpnp_ibb_regulator_set_voltage(struct regulator_dev *rdev,
int min_uV, int max_uV, unsigned *selector)
{
int rc, new_uV;
u8 val;
struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
if (min_uV < labibb->ibb_vreg.min_volt) {
pr_err("qpnp_ibb_regulator_set_voltage failed, min_uV %d is less than min_volt %d",
min_uV, labibb->ibb_vreg.min_volt);
return -EINVAL;
}
val = DIV_ROUND_UP(min_uV - labibb->ibb_vreg.min_volt,
labibb->ibb_vreg.step_size);
new_uV = val * labibb->ibb_vreg.step_size + labibb->ibb_vreg.min_volt;
if (new_uV > max_uV) {
pr_err("qpnp_ibb_regulator_set_voltage unable to set voltage (%d %d)\n",
min_uV, max_uV);
return -EINVAL;
}
rc = qpnp_labibb_masked_write(labibb, labibb->ibb_base +
REG_IBB_VOLTAGE,
IBB_VOLTAGE_SET_MASK |
IBB_VOLTAGE_OVERRIDE_EN,
val | IBB_VOLTAGE_OVERRIDE_EN,
1);
if (rc) {
pr_err("qpnp_ibb_regulator_set_voltage write register %x failed rc = %d\n",
REG_IBB_VOLTAGE, rc);
return rc;
}
if (new_uV > labibb->ibb_vreg.curr_volt)
udelay(val * labibb->ibb_vreg.slew_rate);
labibb->ibb_vreg.curr_volt = new_uV;
return 0;
}
static int qpnp_ibb_regulator_get_voltage(struct regulator_dev *rdev)
{
struct qpnp_labibb *labibb = rdev_get_drvdata(rdev);
return labibb->ibb_vreg.curr_volt;
}
static struct regulator_ops qpnp_ibb_ops = {
.enable = qpnp_ibb_regulator_enable,
.disable = qpnp_ibb_regulator_disable,
.is_enabled = qpnp_ibb_regulator_is_enabled,
.set_voltage = qpnp_ibb_regulator_set_voltage,
.get_voltage = qpnp_ibb_regulator_get_voltage,
};
static int register_qpnp_ibb_regulator(struct qpnp_labibb *labibb,
struct device_node *of_node)
{
int rc = 0;
struct regulator_init_data *init_data;
struct regulator_desc *rdesc;
struct regulator_config cfg = {};
u8 val;
if (!of_node) {
dev_err(labibb->dev, "qpnp ibb regulator device tree node is missing\n");
return -EINVAL;
}
init_data = of_get_regulator_init_data(labibb->dev, of_node);
if (!init_data) {
pr_err("unable to get regulator init data for qpnp ibb regulator\n");
return -ENOMEM;
}
rc = qpnp_labibb_read(labibb, &val,
labibb->ibb_base + REG_IBB_ENABLE_CTL, 1);
if (rc) {
pr_err("qpnp_ibb_read register %x failed rc = %d\n",
REG_IBB_ENABLE_CTL, rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-min-voltage",
&(labibb->ibb_vreg.min_volt));
if (rc < 0) {
pr_err("qcom,qpnp-ibb-min-voltage is missing, rc = %d\n",
rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-step-size",
&(labibb->ibb_vreg.step_size));
if (rc < 0) {
pr_err("qcom,qpnp-ibb-step-size is missing, rc = %d\n", rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-slew-rate",
&(labibb->ibb_vreg.slew_rate));
if (rc < 0) {
pr_err("qcom,qpnp-ibb-slew-rate is missing, rc = %d\n",
rc);
return rc;
}
rc = of_property_read_u32(of_node, "qcom,qpnp-ibb-soft-start",
&(labibb->ibb_vreg.soft_start));
if (rc < 0) {
pr_err("qcom,qpnp-ibb-soft-start is missing, rc = %d\n",
rc);
return rc;
}
if (val != 0) {
rc = qpnp_labibb_read(labibb, &val,
labibb->ibb_base + REG_IBB_LCD_AMOLED_SEL, 1);
if (rc) {
pr_err("qpnp_labibb_read register %x failed rc = %d\n",
REG_IBB_LCD_AMOLED_SEL, rc);
return rc;
}
if (val == REG_LAB_IBB_AMOLED_MODE)
labibb->mode = QPNP_LABIBB_AMOLED_MODE;
else
labibb->mode = QPNP_LABIBB_LCD_MODE;
rc = qpnp_labibb_read(labibb, &val,
labibb->ibb_base + REG_IBB_VOLTAGE, 1);
if (rc) {
pr_err("qpnp_labibb_read read register %x failed rc = %d\n",
REG_IBB_VOLTAGE, rc);
return rc;
}
if (val & IBB_VOLTAGE_OVERRIDE_EN) {
labibb->ibb_vreg.curr_volt =
(val & IBB_VOLTAGE_SET_MASK) *
labibb->ibb_vreg.step_size +
labibb->ibb_vreg.min_volt;
} else if (labibb->mode == QPNP_LABIBB_LCD_MODE) {
rc = of_property_read_u32(of_node,
"qcom,qpnp-ibb-init-lcd-voltage",
&(labibb->ibb_vreg.curr_volt));
if (rc) {
pr_err("get qcom,qpnp-ibb-init-lcd-voltage failed, rc = %d\n",
rc);
return rc;
}
} else {
rc = of_property_read_u32(of_node,
"qcom,qpnp-ibb-init-amoled-voltage",
&(labibb->ibb_vreg.curr_volt));
if (rc) {
pr_err("get qcom,qpnp-ibb-init-amoled-voltage failed, rc = %d\n",
rc);
return rc;
}
}
rc = qpnp_labibb_read(labibb, &val, labibb->ibb_base +
REG_IBB_PWRUP_PWRDN_CTL_1, 1);
if (rc) {
pr_err("qpnp_labibb_config_init read register %x failed rc = %d\n",
REG_IBB_PWRUP_PWRDN_CTL_1, rc);
return rc;
}
labibb->ibb_vreg.pwrup_dly = ibb_pwrup_dly_plan[
(val >>
IBB_PWRUP_PWRDN_CTL_1_DLY1_SHIFT) &
IBB_PWRUP_PWRDN_CTL_1_DLY1_MASK];
labibb->ibb_vreg.pwrdn_dly = ibb_pwrdn_dly_plan[val &
IBB_PWRUP_PWRDN_CTL_1_DLY2_MASK];
labibb->ibb_vreg.vreg_enabled = 1;
} else {
rc = qpnp_ibb_dt_init(labibb, of_node);
if (rc) {
pr_err("qpnp-ibb: wrong DT parameter specified: rc = %d\n",
rc);
return rc;
}
}
if (init_data->constraints.name) {
rdesc = &(labibb->ibb_vreg.rdesc);
rdesc->owner = THIS_MODULE;
rdesc->type = REGULATOR_VOLTAGE;
rdesc->ops = &qpnp_ibb_ops;
rdesc->name = init_data->constraints.name;
cfg.dev = labibb->dev;
cfg.init_data = init_data;
cfg.driver_data = labibb;
cfg.of_node = of_node;
init_data->constraints.valid_ops_mask
|= REGULATOR_CHANGE_VOLTAGE |
REGULATOR_CHANGE_STATUS;
labibb->ibb_vreg.rdev = regulator_register(rdesc, &cfg);
if (IS_ERR(labibb->ibb_vreg.rdev)) {
rc = PTR_ERR(labibb->ibb_vreg.rdev);
labibb->ibb_vreg.rdev = NULL;
pr_err("unable to get regulator init data for qpnp ibb regulator, rc = %d\n",
rc);
return rc;
}
} else {
dev_err(labibb->dev, "qpnp ibb regulator name missing\n");
return -EINVAL;
}
mutex_init(&(labibb->ibb_vreg.ibb_mutex));
return 0;
}
static int qpnp_labibb_regulator_probe(struct spmi_device *spmi)
{
struct qpnp_labibb *labibb;
struct resource *resource;
struct spmi_resource *spmi_resource;
const char *mode_name;
u8 type;
int rc = 0;
labibb = devm_kzalloc(&spmi->dev,
sizeof(struct qpnp_labibb), GFP_KERNEL);
if (labibb == NULL) {
pr_err("labibb allocation failed.\n");
return -ENOMEM;
}
labibb->dev = &(spmi->dev);
labibb->spmi = spmi;
rc = of_property_read_string(labibb->dev->of_node,
"qpnp,qpnp-labibb-mode", &mode_name);
if (!rc) {
if (strcmp("lcd", mode_name) == 0) {
labibb->mode = QPNP_LABIBB_LCD_MODE;
} else if (strcmp("amoled", mode_name) == 0) {
labibb->mode = QPNP_LABIBB_AMOLED_MODE;
} else if (strcmp("stand-alone", mode_name) == 0) {
labibb->mode = QPNP_LABIBB_STANDALONE_MODE;
} else {
pr_err("Invalid device property in qpnp,qpnp-labibb-mode: %s\n",
mode_name);
return -EINVAL;
}
} else {
pr_err("qpnp_labibb: qpnp,qpnp-labibb-mode is missing.\n");
return rc;
}
spmi_for_each_container_dev(spmi_resource, spmi) {
if (!spmi_resource) {
pr_err("qpnp_labibb: spmi resource absent\n");
return -ENXIO;
}
resource = spmi_get_resource(spmi, spmi_resource,
IORESOURCE_MEM, 0);
if (!(resource && resource->start)) {
pr_err("node %s IO resource absent!\n",
spmi->dev.of_node->full_name);
return -ENXIO;
}
rc = qpnp_labibb_read(labibb, &type,
resource->start + REG_PERPH_TYPE, 1);
if (rc) {
pr_err("Peripheral type read failed rc=%d\n", rc);
goto fail_registration;
}
switch (type) {
case QPNP_LAB_TYPE:
labibb->lab_base = resource->start;
rc = register_qpnp_lab_regulator(labibb,
spmi_resource->of_node);
if (rc)
goto fail_registration;
break;
case QPNP_IBB_TYPE:
labibb->ibb_base = resource->start;
rc = register_qpnp_ibb_regulator(labibb,
spmi_resource->of_node);
if (rc)
goto fail_registration;
break;
default:
pr_err("qpnp_labibb: unknown peripheral type %x\n",
type);
rc = -EINVAL;
goto fail_registration;
}
}
dev_set_drvdata(&spmi->dev, labibb);
return 0;
fail_registration:
if (labibb->lab_vreg.rdev)
regulator_unregister(labibb->lab_vreg.rdev);
if (labibb->ibb_vreg.rdev)
regulator_unregister(labibb->ibb_vreg.rdev);
return rc;
}
static int qpnp_labibb_regulator_remove(struct spmi_device *spmi)
{
struct qpnp_labibb *labibb = dev_get_drvdata(&spmi->dev);
if (labibb) {
if (labibb->lab_vreg.rdev)
regulator_unregister(labibb->lab_vreg.rdev);
if (labibb->ibb_vreg.rdev)
regulator_unregister(labibb->ibb_vreg.rdev);
}
return 0;
}
static struct of_device_id spmi_match_table[] = {
{ .compatible = QPNP_LABIBB_REGULATOR_DIRVER_NAME, },
{ },
};
static struct spmi_driver qpnp_labibb_regulator_driver = {
.driver = {
.name = QPNP_LABIBB_REGULATOR_DIRVER_NAME,
.of_match_table = spmi_match_table,
},
.probe = qpnp_labibb_regulator_probe,
.remove = qpnp_labibb_regulator_remove,
};
static int __init qpnp_labibb_regulator_init(void)
{
return spmi_driver_register(&qpnp_labibb_regulator_driver);
}
arch_initcall(qpnp_labibb_regulator_init);
static void __exit qpnp_labibb_regulator_exit(void)
{
spmi_driver_unregister(&qpnp_labibb_regulator_driver);
}
module_exit(qpnp_labibb_regulator_exit);
MODULE_DESCRIPTION("QPNP labibb driver");
MODULE_LICENSE("GPL v2");