6fd9dd9e35
Currently the LDO voltage is read from "VSET_VALID_LB" and "VSET_VALID_UB" registers which provides the actual voltage when the LDO is enabled. However, these registers return a value "0" when the LDO is disabled. Due to this the incorrect voltage is set on the parent buck which leads to voltage ripples. Fix it by reading VSET_LB and VSET_UB registers which return a valid voltage even though the LDO is disabled. Change-Id: Id47882802a1d39f7843a40853edfc4f6d17376f5 Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
962 lines
24 KiB
C
962 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. */
|
|
|
|
#define pr_fmt(fmt) "PM8008: %s: " fmt, __func__
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/machine.h>
|
|
#include <linux/regulator/of_regulator.h>
|
|
#include <linux/string.h>
|
|
|
|
#define pm8008_err(reg, message, ...) \
|
|
pr_err("%s: " message, (reg)->rdesc.name, ##__VA_ARGS__)
|
|
#define pm8008_debug(reg, message, ...) \
|
|
pr_debug("%s: " message, (reg)->rdesc.name, ##__VA_ARGS__)
|
|
|
|
#define STARTUP_DELAY_USEC 20
|
|
#define VSET_STEP_SIZE_MV 1
|
|
#define VSET_STEP_MV 8
|
|
#define VSET_STEP_UV (VSET_STEP_MV * 1000)
|
|
|
|
#define MISC_BASE 0x900
|
|
|
|
#define MISC_CHIP_ENABLE_REG (MISC_BASE + 0x50)
|
|
#define CHIP_ENABLE_BIT BIT(0)
|
|
|
|
#define MISC_SHUTDOWN_CTRL_REG (MISC_BASE + 0x59)
|
|
#define IGNORE_LDO_OCP_SHUTDOWN BIT(3)
|
|
|
|
#define LDO_ENABLE_REG(base) (base + 0x46)
|
|
#define ENABLE_BIT BIT(7)
|
|
|
|
#define LDO_STATUS1_REG(base) (base + 0x08)
|
|
#define VREG_OCP_BIT BIT(5)
|
|
#define VREG_READY_BIT BIT(7)
|
|
#define MODE_STATE_MASK GENMASK(1, 0)
|
|
#define MODE_STATE_NPM 3
|
|
#define MODE_STATE_LPM 2
|
|
#define MODE_STATE_BYPASS 0
|
|
|
|
#define LDO_VSET_LB_REG(base) (base + 0x40)
|
|
|
|
#define LDO_MODE_CTL1_REG(base) (base + 0x45)
|
|
#define MODE_PRIMARY_MASK GENMASK(2, 0)
|
|
#define LDO_MODE_NPM 7
|
|
#define LDO_MODE_LPM 4
|
|
#define FORCED_BYPASS 2
|
|
|
|
#define LDO_OCP_CTL1_REG(base) (base + 0x88)
|
|
#define VREG_OCP_STATUS_CLR BIT(1)
|
|
#define LDO_OCP_BROADCAST_EN_BIT BIT(2)
|
|
|
|
#define LDO_STEPPER_CTL_REG(base) (base + 0x3b)
|
|
#define STEP_RATE_MASK GENMASK(1, 0)
|
|
|
|
#define LDO_PD_CTL_REG(base) (base + 0xA0)
|
|
#define STRONG_PD_EN_BIT BIT(7)
|
|
|
|
#define MAX_REG_NAME 20
|
|
#define PM8008_MAX_LDO 7
|
|
|
|
struct pm8008_chip {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct regulator_dev *rdev;
|
|
struct regulator_desc rdesc;
|
|
int ocp_irq;
|
|
};
|
|
|
|
struct regulator_data {
|
|
char *name;
|
|
char *supply_name;
|
|
int hpm_min_load_ua;
|
|
int min_dropout_uv;
|
|
};
|
|
|
|
struct pm8008_regulator {
|
|
struct device *dev;
|
|
struct regmap *regmap;
|
|
struct regulator_desc rdesc;
|
|
struct regulator_dev *rdev;
|
|
struct regulator *parent_supply;
|
|
struct regulator *en_supply;
|
|
struct device_node *of_node;
|
|
struct notifier_block nb;
|
|
u16 base;
|
|
int hpm_min_load_ua;
|
|
int min_dropout_uv;
|
|
int step_rate;
|
|
bool enable_ocp_broadcast;
|
|
};
|
|
|
|
static struct regulator_data reg_data[] = {
|
|
/* name, parent, min load, headroom */
|
|
{"pm8008_l1", "vdd_l1_l2", 10000, 225000},
|
|
{"pm8008_l2", "vdd_l1_l2", 10000, 225000},
|
|
{"pm8008_l3", "vdd_l3_l4", 10000, 200000},
|
|
{"pm8008_l4", "vdd_l3_l4", 10000, 200000},
|
|
{"pm8008_l5", "vdd_l5", 10000, 300000},
|
|
{"pm8008_l6", "vdd_l6", 10000, 300000},
|
|
{"pm8008_l7", "vdd_l7", 10000, 300000},
|
|
};
|
|
|
|
/* common functions */
|
|
static int pm8008_read(struct regmap *regmap, u16 reg, u8 *val, int count)
|
|
{
|
|
int rc;
|
|
|
|
rc = regmap_bulk_read(regmap, reg, val, count);
|
|
if (rc < 0)
|
|
pr_err("failed to read 0x%04x\n", reg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int pm8008_write(struct regmap *regmap, u16 reg, u8 *val, int count)
|
|
{
|
|
int rc;
|
|
|
|
pr_debug("Writing 0x%02x to 0x%04x\n", val, reg);
|
|
rc = regmap_bulk_write(regmap, reg, val, count);
|
|
if (rc < 0)
|
|
pr_err("failed to write 0x%04x\n", reg);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int pm8008_masked_write(struct regmap *regmap, u16 reg, u8 mask,
|
|
u8 val)
|
|
{
|
|
int rc;
|
|
|
|
pr_debug("Writing 0x%02x to 0x%04x with mask 0x%02x\n", val, reg, mask);
|
|
rc = regmap_update_bits(regmap, reg, mask, val);
|
|
if (rc < 0)
|
|
pr_err("failed to write 0x%02x to 0x%04x with mask 0x%02x\n",
|
|
val, reg, mask);
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* PM8008 LDO Regulator callbacks */
|
|
static int pm8008_regulator_get_voltage(struct regulator_dev *rdev)
|
|
{
|
|
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
|
|
u8 vset_raw[2];
|
|
int rc;
|
|
|
|
rc = pm8008_read(pm8008_reg->regmap,
|
|
LDO_VSET_LB_REG(pm8008_reg->base),
|
|
vset_raw, 2);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg,
|
|
"failed to read regulator voltage rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
pm8008_debug(pm8008_reg, "VSET read [%x][%x]\n",
|
|
vset_raw[1], vset_raw[0]);
|
|
return (vset_raw[1] << 8 | vset_raw[0]) * 1000;
|
|
}
|
|
|
|
static int pm8008_regulator_is_enabled(struct regulator_dev *rdev)
|
|
{
|
|
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
u8 reg;
|
|
|
|
rc = pm8008_read(pm8008_reg->regmap,
|
|
LDO_ENABLE_REG(pm8008_reg->base), ®, 1);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to read enable reg rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return !!(reg & ENABLE_BIT);
|
|
}
|
|
|
|
static int pm8008_regulator_enable(struct regulator_dev *rdev)
|
|
{
|
|
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
|
|
int rc, rc2, current_uv, delay_us, delay_ms, retry_count = 10;
|
|
u8 reg;
|
|
|
|
current_uv = pm8008_regulator_get_voltage(rdev);
|
|
if (current_uv < 0) {
|
|
pm8008_err(pm8008_reg, "failed to get current voltage rc=%d\n",
|
|
current_uv);
|
|
return current_uv;
|
|
}
|
|
|
|
rc = regulator_enable(pm8008_reg->en_supply);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg,
|
|
"failed to enable en_supply rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (pm8008_reg->parent_supply) {
|
|
rc = regulator_set_voltage(pm8008_reg->parent_supply,
|
|
current_uv + pm8008_reg->min_dropout_uv,
|
|
INT_MAX);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to request parent supply voltage rc=%d\n",
|
|
rc);
|
|
goto remove_en;
|
|
}
|
|
|
|
rc = regulator_enable(pm8008_reg->parent_supply);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg,
|
|
"failed to enable parent rc=%d\n", rc);
|
|
regulator_set_voltage(pm8008_reg->parent_supply, 0,
|
|
INT_MAX);
|
|
goto remove_en;
|
|
}
|
|
}
|
|
|
|
rc = pm8008_masked_write(pm8008_reg->regmap,
|
|
LDO_ENABLE_REG(pm8008_reg->base),
|
|
ENABLE_BIT, ENABLE_BIT);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg,
|
|
"failed to enable regulator rc=%d\n", rc);
|
|
goto remove_vote;
|
|
}
|
|
|
|
/*
|
|
* Wait for the VREG_READY status bit to be set using a timeout delay
|
|
* calculated from the current commanded voltage.
|
|
*/
|
|
delay_us = STARTUP_DELAY_USEC
|
|
+ DIV_ROUND_UP(current_uv, pm8008_reg->step_rate);
|
|
delay_ms = DIV_ROUND_UP(delay_us, 1000);
|
|
|
|
/* Retry 10 times for VREG_READY before bailing out */
|
|
while (retry_count--) {
|
|
if (delay_ms > 20)
|
|
msleep(delay_ms);
|
|
else
|
|
usleep_range(delay_us, delay_us + 100);
|
|
|
|
rc = pm8008_read(pm8008_reg->regmap,
|
|
LDO_STATUS1_REG(pm8008_reg->base), ®, 1);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg,
|
|
"failed to read regulator status rc=%d\n", rc);
|
|
goto disable_ldo;
|
|
}
|
|
if (reg & VREG_READY_BIT) {
|
|
pm8008_debug(pm8008_reg, "regulator enabled\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
pm8008_err(pm8008_reg, "failed to enable regulator, VREG_READY not set\n");
|
|
rc = -ETIME;
|
|
|
|
disable_ldo:
|
|
pm8008_masked_write(pm8008_reg->regmap,
|
|
LDO_ENABLE_REG(pm8008_reg->base), ENABLE_BIT, 0);
|
|
|
|
remove_vote:
|
|
if (pm8008_reg->parent_supply) {
|
|
rc2 = regulator_disable(pm8008_reg->parent_supply);
|
|
if (rc2 < 0)
|
|
pm8008_err(pm8008_reg, "failed to disable parent supply rc=%d\n",
|
|
rc2);
|
|
rc2 = regulator_set_voltage(pm8008_reg->parent_supply, 0,
|
|
INT_MAX);
|
|
if (rc2 < 0)
|
|
pm8008_err(pm8008_reg, "failed to remove voltage vote for parent supply rc=%d\n",
|
|
rc2);
|
|
}
|
|
|
|
remove_en:
|
|
rc2 = regulator_disable(pm8008_reg->en_supply);
|
|
if (rc2 < 0)
|
|
pm8008_err(pm8008_reg, "failed to disable en_supply rc=%d\n",
|
|
rc2);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int pm8008_regulator_disable(struct regulator_dev *rdev)
|
|
{
|
|
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
|
|
rc = pm8008_masked_write(pm8008_reg->regmap,
|
|
LDO_ENABLE_REG(pm8008_reg->base),
|
|
ENABLE_BIT, 0);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg,
|
|
"failed to disable regulator rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
/* remove voltage vote from parent regulator */
|
|
if (pm8008_reg->parent_supply) {
|
|
rc = regulator_disable(pm8008_reg->parent_supply);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to disable parent rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
rc = regulator_set_voltage(pm8008_reg->parent_supply,
|
|
0, INT_MAX);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to remove parent voltage rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* remove vote from chip enable regulator */
|
|
rc = regulator_disable(pm8008_reg->en_supply);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to disable en_supply rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
pm8008_debug(pm8008_reg, "regulator disabled\n");
|
|
return 0;
|
|
}
|
|
|
|
static int pm8008_write_voltage(struct pm8008_regulator *pm8008_reg, int min_uv,
|
|
int max_uv)
|
|
{
|
|
int rc = 0, mv;
|
|
u8 vset_raw[2];
|
|
|
|
mv = DIV_ROUND_UP(min_uv, 1000);
|
|
if (mv * 1000 > max_uv) {
|
|
pm8008_err(pm8008_reg,
|
|
"requested voltage above maximum limit\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Each LSB of regulator is 1mV and the voltage setpoint
|
|
* should be multiple of 8mV(step).
|
|
*/
|
|
mv = DIV_ROUND_UP(DIV_ROUND_UP(mv, VSET_STEP_MV) * VSET_STEP_MV,
|
|
VSET_STEP_SIZE_MV);
|
|
|
|
vset_raw[0] = mv & 0xff;
|
|
vset_raw[1] = (mv & 0xff00) >> 8;
|
|
rc = pm8008_write(pm8008_reg->regmap, LDO_VSET_LB_REG(pm8008_reg->base),
|
|
vset_raw, 2);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to write voltage rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
pm8008_debug(pm8008_reg, "VSET=[%x][%x]\n", vset_raw[1], vset_raw[0]);
|
|
return 0;
|
|
}
|
|
|
|
static int pm8008_regulator_set_voltage_time(struct regulator_dev *rdev,
|
|
int old_uV, int new_uv)
|
|
{
|
|
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
|
|
|
|
return DIV_ROUND_UP(abs(new_uv - old_uV), pm8008_reg->step_rate);
|
|
}
|
|
|
|
static int pm8008_regulator_set_voltage(struct regulator_dev *rdev,
|
|
int min_uv, int max_uv, unsigned int *selector)
|
|
{
|
|
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
|
|
int rc = 0, current_uv = 0, rounded_uv = 0, enabled = 0;
|
|
|
|
if (pm8008_reg->parent_supply) {
|
|
enabled = pm8008_regulator_is_enabled(rdev);
|
|
if (enabled < 0) {
|
|
return enabled;
|
|
} else if (enabled) {
|
|
current_uv = pm8008_regulator_get_voltage(rdev);
|
|
if (current_uv < 0)
|
|
return current_uv;
|
|
rounded_uv = roundup(min_uv, VSET_STEP_UV);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the parent_supply voltage before changing the LDO voltage when
|
|
* the LDO voltage is being increased.
|
|
*/
|
|
if (pm8008_reg->parent_supply && enabled && rounded_uv >= current_uv) {
|
|
/* Request parent voltage with headroom */
|
|
rc = regulator_set_voltage(pm8008_reg->parent_supply,
|
|
rounded_uv + pm8008_reg->min_dropout_uv,
|
|
INT_MAX);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to request parent supply voltage rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
rc = pm8008_write_voltage(pm8008_reg, min_uv, max_uv);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
/*
|
|
* Set the parent_supply voltage after changing the LDO voltage when
|
|
* the LDO voltage is being reduced.
|
|
*/
|
|
if (pm8008_reg->parent_supply && enabled && rounded_uv < current_uv) {
|
|
/*
|
|
* Ensure sufficient time for the LDO voltage to slew down
|
|
* before reducing the parent supply voltage. The regulator
|
|
* framework will add the same delay after this function returns
|
|
* in all cases (i.e. enabled/disabled and increasing/decreasing
|
|
* voltage).
|
|
*/
|
|
udelay(pm8008_regulator_set_voltage_time(rdev, rounded_uv,
|
|
current_uv));
|
|
|
|
/* Request parent voltage with headroom */
|
|
rc = regulator_set_voltage(pm8008_reg->parent_supply,
|
|
rounded_uv + pm8008_reg->min_dropout_uv,
|
|
INT_MAX);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to request parent supply voltage rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
pm8008_debug(pm8008_reg, "voltage set to %d\n", min_uv);
|
|
return rc;
|
|
}
|
|
|
|
static int pm8008_regulator_set_mode(struct regulator_dev *rdev,
|
|
unsigned int mode)
|
|
{
|
|
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
u8 val = LDO_MODE_LPM;
|
|
|
|
if (mode == REGULATOR_MODE_NORMAL)
|
|
val = LDO_MODE_NPM;
|
|
else if (mode == REGULATOR_MODE_IDLE)
|
|
val = LDO_MODE_LPM;
|
|
|
|
rc = pm8008_masked_write(pm8008_reg->regmap,
|
|
LDO_MODE_CTL1_REG(pm8008_reg->base),
|
|
MODE_PRIMARY_MASK, val);
|
|
if (!rc)
|
|
pm8008_debug(pm8008_reg, "mode set to %d\n", val);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static unsigned int pm8008_regulator_get_mode(struct regulator_dev *rdev)
|
|
{
|
|
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
u8 reg;
|
|
|
|
rc = pm8008_read(pm8008_reg->regmap,
|
|
LDO_STATUS1_REG(pm8008_reg->base), ®, 1);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to get mode rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return ((reg & MODE_STATE_MASK) == MODE_STATE_NPM)
|
|
? REGULATOR_MODE_NORMAL : REGULATOR_MODE_IDLE;
|
|
}
|
|
|
|
static int pm8008_regulator_set_load(struct regulator_dev *rdev, int load_uA)
|
|
{
|
|
struct pm8008_regulator *pm8008_reg = rdev_get_drvdata(rdev);
|
|
int mode;
|
|
|
|
if (load_uA >= pm8008_reg->hpm_min_load_ua)
|
|
mode = REGULATOR_MODE_NORMAL;
|
|
else
|
|
mode = REGULATOR_MODE_IDLE;
|
|
|
|
return pm8008_regulator_set_mode(rdev, mode);
|
|
}
|
|
|
|
static struct regulator_ops pm8008_regulator_ops = {
|
|
.enable = pm8008_regulator_enable,
|
|
.disable = pm8008_regulator_disable,
|
|
.is_enabled = pm8008_regulator_is_enabled,
|
|
.set_voltage = pm8008_regulator_set_voltage,
|
|
.get_voltage = pm8008_regulator_get_voltage,
|
|
.set_mode = pm8008_regulator_set_mode,
|
|
.get_mode = pm8008_regulator_get_mode,
|
|
.set_load = pm8008_regulator_set_load,
|
|
.set_voltage_time = pm8008_regulator_set_voltage_time,
|
|
};
|
|
|
|
static int pm8008_ldo_cb(struct notifier_block *nb, ulong event, void *data)
|
|
{
|
|
struct pm8008_regulator *pm8008_reg = container_of(nb,
|
|
struct pm8008_regulator, nb);
|
|
u8 val;
|
|
int rc;
|
|
|
|
if (event != REGULATOR_EVENT_OVER_CURRENT)
|
|
return NOTIFY_OK;
|
|
|
|
rc = pm8008_read(pm8008_reg->regmap,
|
|
LDO_STATUS1_REG(pm8008_reg->base), &val, 1);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg,
|
|
"failed to read regulator status rc=%d\n", rc);
|
|
goto error;
|
|
}
|
|
|
|
if (!(val & VREG_OCP_BIT))
|
|
return NOTIFY_OK;
|
|
|
|
pr_err("OCP triggered on %s\n", pm8008_reg->rdesc.name);
|
|
/*
|
|
* Toggle the OCP_STATUS_CLR bit to re-arm the OCP status for
|
|
* the next OCP event
|
|
*/
|
|
rc = pm8008_masked_write(pm8008_reg->regmap,
|
|
LDO_OCP_CTL1_REG(pm8008_reg->base),
|
|
VREG_OCP_STATUS_CLR, VREG_OCP_STATUS_CLR);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to write OCP_STATUS_CLR rc=%d\n",
|
|
rc);
|
|
goto error;
|
|
}
|
|
|
|
rc = pm8008_masked_write(pm8008_reg->regmap,
|
|
LDO_OCP_CTL1_REG(pm8008_reg->base),
|
|
VREG_OCP_STATUS_CLR, 0);
|
|
if (rc < 0) {
|
|
pm8008_err(pm8008_reg, "failed to write OCP_STATUS_CLR rc=%d\n",
|
|
rc);
|
|
goto error;
|
|
}
|
|
|
|
/* Notify the consumers about the OCP event */
|
|
mutex_lock(&pm8008_reg->rdev->mutex);
|
|
regulator_notifier_call_chain(pm8008_reg->rdev,
|
|
REGULATOR_EVENT_OVER_CURRENT, NULL);
|
|
mutex_unlock(&pm8008_reg->rdev->mutex);
|
|
|
|
error:
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static int pm8008_register_ldo(struct pm8008_regulator *pm8008_reg,
|
|
const char *name)
|
|
{
|
|
struct regulator_config reg_config = {};
|
|
struct regulator_init_data *init_data;
|
|
struct device *dev = pm8008_reg->dev;
|
|
struct device_node *reg_node = pm8008_reg->of_node;
|
|
char buff[MAX_REG_NAME];
|
|
int rc, i, init_voltage;
|
|
u32 base = 0;
|
|
u8 reg;
|
|
|
|
/* get regulator data */
|
|
for (i = 0; i < PM8008_MAX_LDO; i++)
|
|
if (!strcmp(reg_data[i].name, name))
|
|
break;
|
|
|
|
if (i == PM8008_MAX_LDO) {
|
|
pr_err("Invalid regulator name %s\n", name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = of_property_read_u32(reg_node, "reg", &base);
|
|
if (rc < 0) {
|
|
pr_err("%s: failed to get regulator base rc=%d\n", name, rc);
|
|
return rc;
|
|
}
|
|
pm8008_reg->base = base;
|
|
|
|
pm8008_reg->min_dropout_uv = reg_data[i].min_dropout_uv;
|
|
of_property_read_u32(reg_node, "qcom,min-dropout-voltage",
|
|
&pm8008_reg->min_dropout_uv);
|
|
|
|
pm8008_reg->hpm_min_load_ua = reg_data[i].hpm_min_load_ua;
|
|
of_property_read_u32(reg_node, "qcom,hpm-min-load",
|
|
&pm8008_reg->hpm_min_load_ua);
|
|
init_voltage = -EINVAL;
|
|
of_property_read_u32(reg_node, "qcom,init-voltage", &init_voltage);
|
|
|
|
if (of_property_read_bool(reg_node, "qcom,strong-pd")) {
|
|
rc = pm8008_masked_write(pm8008_reg->regmap,
|
|
LDO_PD_CTL_REG(pm8008_reg->base),
|
|
STRONG_PD_EN_BIT, STRONG_PD_EN_BIT);
|
|
if (rc < 0) {
|
|
pr_err("%s: failed to configure pull down rc=%d\n",
|
|
name, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (pm8008_reg->enable_ocp_broadcast) {
|
|
rc = pm8008_masked_write(pm8008_reg->regmap,
|
|
LDO_OCP_CTL1_REG(pm8008_reg->base),
|
|
LDO_OCP_BROADCAST_EN_BIT,
|
|
LDO_OCP_BROADCAST_EN_BIT);
|
|
if (rc < 0) {
|
|
pr_err("%s: failed to configure ocp broadcast rc=%d\n",
|
|
name, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* get slew rate */
|
|
rc = pm8008_read(pm8008_reg->regmap,
|
|
LDO_STEPPER_CTL_REG(pm8008_reg->base), ®, 1);
|
|
if (rc < 0) {
|
|
pr_err("%s: failed to read step rate configuration rc=%d\n",
|
|
name, rc);
|
|
return rc;
|
|
}
|
|
pm8008_reg->step_rate = 38400 >> (reg & STEP_RATE_MASK);
|
|
|
|
scnprintf(buff, MAX_REG_NAME, "%s-supply", reg_data[i].supply_name);
|
|
if (of_find_property(dev->of_node, buff, NULL)) {
|
|
pm8008_reg->parent_supply = devm_regulator_get(dev,
|
|
reg_data[i].supply_name);
|
|
if (IS_ERR(pm8008_reg->parent_supply)) {
|
|
rc = PTR_ERR(pm8008_reg->parent_supply);
|
|
if (rc != -EPROBE_DEFER)
|
|
pr_err("%s: failed to get parent regulator rc=%d\n",
|
|
name, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
/* pm8008_en should be present otherwise fail the regulator probe */
|
|
pm8008_reg->en_supply = devm_regulator_get(dev, "pm8008_en");
|
|
if (IS_ERR(pm8008_reg->en_supply)) {
|
|
rc = PTR_ERR(pm8008_reg->en_supply);
|
|
pr_err("%s: failed to get chip_en supply\n", name);
|
|
return rc;
|
|
}
|
|
|
|
init_data = of_get_regulator_init_data(dev, reg_node,
|
|
&pm8008_reg->rdesc);
|
|
if (init_data == NULL) {
|
|
pr_err("%s: failed to get regulator data\n", name);
|
|
return -ENODATA;
|
|
}
|
|
if (!init_data->constraints.name) {
|
|
pr_err("%s: regulator name missing\n", name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* configure the initial voltage for the regulator */
|
|
if (init_voltage > 0) {
|
|
rc = pm8008_write_voltage(pm8008_reg, init_voltage,
|
|
init_data->constraints.max_uV);
|
|
if (rc < 0)
|
|
pr_err("%s: failed to set initial voltage rc=%d\n",
|
|
name, rc);
|
|
}
|
|
|
|
init_data->constraints.input_uV = init_data->constraints.max_uV;
|
|
init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_STATUS
|
|
| REGULATOR_CHANGE_VOLTAGE
|
|
| REGULATOR_CHANGE_MODE
|
|
| REGULATOR_CHANGE_DRMS;
|
|
reg_config.dev = dev;
|
|
reg_config.init_data = init_data;
|
|
reg_config.driver_data = pm8008_reg;
|
|
reg_config.of_node = reg_node;
|
|
|
|
pm8008_reg->rdesc.owner = THIS_MODULE;
|
|
pm8008_reg->rdesc.type = REGULATOR_VOLTAGE;
|
|
pm8008_reg->rdesc.ops = &pm8008_regulator_ops;
|
|
pm8008_reg->rdesc.name = init_data->constraints.name;
|
|
pm8008_reg->rdesc.n_voltages = 1;
|
|
|
|
pm8008_reg->rdev = devm_regulator_register(dev, &pm8008_reg->rdesc,
|
|
®_config);
|
|
if (IS_ERR(pm8008_reg->rdev)) {
|
|
rc = PTR_ERR(pm8008_reg->rdev);
|
|
pr_err("%s: failed to register regulator rc=%d\n",
|
|
pm8008_reg->rdesc.name, rc);
|
|
return rc;
|
|
}
|
|
|
|
if (pm8008_reg->enable_ocp_broadcast) {
|
|
pm8008_reg->nb.notifier_call = pm8008_ldo_cb;
|
|
rc = devm_regulator_register_notifier(pm8008_reg->en_supply,
|
|
&pm8008_reg->nb);
|
|
if (rc < 0) {
|
|
pr_err("Failed to register a regulator notifier rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
pr_debug("%s regulator registered\n", name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* PMIC probe and helper function */
|
|
static int pm8008_parse_regulator(struct regmap *regmap, struct device *dev)
|
|
{
|
|
int rc = 0;
|
|
const char *name;
|
|
struct device_node *child;
|
|
struct pm8008_regulator *pm8008_reg;
|
|
bool ocp;
|
|
|
|
ocp = of_property_read_bool(dev->of_node, "qcom,enable-ocp-broadcast");
|
|
|
|
/* parse each subnode and register regulator for regulator child */
|
|
for_each_available_child_of_node(dev->of_node, child) {
|
|
pm8008_reg = devm_kzalloc(dev, sizeof(*pm8008_reg), GFP_KERNEL);
|
|
if (!pm8008_reg)
|
|
return -ENOMEM;
|
|
|
|
pm8008_reg->regmap = regmap;
|
|
pm8008_reg->of_node = child;
|
|
pm8008_reg->dev = dev;
|
|
pm8008_reg->enable_ocp_broadcast = ocp;
|
|
|
|
rc = of_property_read_string(child, "regulator-name", &name);
|
|
if (rc)
|
|
continue;
|
|
|
|
rc = pm8008_register_ldo(pm8008_reg, name);
|
|
if (rc < 0) {
|
|
pr_err("failed to register regulator %s rc=%d\n",
|
|
name, rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pm8008_regulator_probe(struct platform_device *pdev)
|
|
{
|
|
int rc = 0;
|
|
struct regmap *regmap;
|
|
|
|
regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
|
if (!regmap) {
|
|
pr_err("parent regmap is missing\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
rc = pm8008_parse_regulator(regmap, &pdev->dev);
|
|
if (rc < 0) {
|
|
pr_err("failed to parse device tree rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* PM8008 chip enable regulator callbacks */
|
|
static int pm8008_enable_regulator_enable(struct regulator_dev *rdev)
|
|
{
|
|
struct pm8008_chip *chip = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
|
|
rc = pm8008_masked_write(chip->regmap, MISC_CHIP_ENABLE_REG,
|
|
CHIP_ENABLE_BIT, CHIP_ENABLE_BIT);
|
|
if (rc < 0) {
|
|
pm8008_err(chip, "failed to enable chip rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
pm8008_debug(chip, "regulator enabled\n");
|
|
return 0;
|
|
}
|
|
|
|
static int pm8008_enable_regulator_disable(struct regulator_dev *rdev)
|
|
{
|
|
struct pm8008_chip *chip = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
|
|
rc = pm8008_masked_write(chip->regmap, MISC_CHIP_ENABLE_REG,
|
|
CHIP_ENABLE_BIT, 0);
|
|
if (rc < 0) {
|
|
pm8008_err(chip, "failed to disable chip rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
pm8008_debug(chip, "regulator disabled\n");
|
|
return 0;
|
|
}
|
|
|
|
static int pm8008_enable_regulator_is_enabled(struct regulator_dev *rdev)
|
|
{
|
|
struct pm8008_chip *chip = rdev_get_drvdata(rdev);
|
|
int rc;
|
|
u8 reg;
|
|
|
|
rc = pm8008_read(chip->regmap, MISC_CHIP_ENABLE_REG, ®, 1);
|
|
if (rc < 0) {
|
|
pm8008_err(chip, "failed to get chip state rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
return !!(reg & CHIP_ENABLE_BIT);
|
|
}
|
|
|
|
static struct regulator_ops pm8008_enable_reg_ops = {
|
|
.enable = pm8008_enable_regulator_enable,
|
|
.disable = pm8008_enable_regulator_disable,
|
|
.is_enabled = pm8008_enable_regulator_is_enabled,
|
|
};
|
|
|
|
static int pm8008_init_enable_regulator(struct pm8008_chip *chip)
|
|
{
|
|
struct regulator_config cfg = {};
|
|
int rc = 0;
|
|
|
|
cfg.dev = chip->dev;
|
|
cfg.driver_data = chip;
|
|
|
|
chip->rdesc.owner = THIS_MODULE;
|
|
chip->rdesc.type = REGULATOR_VOLTAGE;
|
|
chip->rdesc.ops = &pm8008_enable_reg_ops;
|
|
chip->rdesc.of_match = "qcom,pm8008-chip-en";
|
|
chip->rdesc.name = "qcom,pm8008-chip-en";
|
|
|
|
chip->rdev = devm_regulator_register(chip->dev, &chip->rdesc, &cfg);
|
|
if (IS_ERR(chip->rdev)) {
|
|
rc = PTR_ERR(chip->rdev);
|
|
chip->rdev = NULL;
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t pm8008_ocp_irq(int irq, void *_chip)
|
|
{
|
|
struct pm8008_chip *chip = _chip;
|
|
|
|
mutex_lock(&chip->rdev->mutex);
|
|
regulator_notifier_call_chain(chip->rdev, REGULATOR_EVENT_OVER_CURRENT,
|
|
NULL);
|
|
mutex_unlock(&chip->rdev->mutex);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int pm8008_chip_probe(struct platform_device *pdev)
|
|
{
|
|
int rc = 0;
|
|
struct pm8008_chip *chip;
|
|
|
|
chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
|
|
if (!chip)
|
|
return -ENOMEM;
|
|
|
|
chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
|
|
if (!chip->regmap) {
|
|
pr_err("parent regmap is missing\n");
|
|
return -EINVAL;
|
|
}
|
|
chip->dev = &pdev->dev;
|
|
|
|
/* Register chip enable regulator */
|
|
rc = pm8008_init_enable_regulator(chip);
|
|
if (rc < 0) {
|
|
pr_err("Failed to register chip enable regulator rc=%d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
chip->ocp_irq = of_irq_get_byname(chip->dev->of_node, "ocp");
|
|
if (chip->ocp_irq < 0) {
|
|
pr_debug("Failed to get pm8008-ocp-irq\n");
|
|
} else {
|
|
rc = devm_request_threaded_irq(chip->dev, chip->ocp_irq, NULL,
|
|
pm8008_ocp_irq, IRQF_ONESHOT,
|
|
"ocp", chip);
|
|
if (rc < 0) {
|
|
pr_err("Failed to request 'pm8008-ocp-irq' rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
|
|
/* Ignore PMIC shutdown for LDO OCP event */
|
|
rc = pm8008_masked_write(chip->regmap, MISC_SHUTDOWN_CTRL_REG,
|
|
IGNORE_LDO_OCP_SHUTDOWN, IGNORE_LDO_OCP_SHUTDOWN);
|
|
if (rc < 0) {
|
|
pr_err("Failed to write MISC_SHUTDOWN register rc=%d\n",
|
|
rc);
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
pr_debug("PM8008 chip registered\n");
|
|
return 0;
|
|
}
|
|
|
|
static int pm8008_chip_remove(struct platform_device *pdev)
|
|
{
|
|
struct pm8008_chip *chip = platform_get_drvdata(pdev);
|
|
int rc;
|
|
|
|
rc = pm8008_masked_write(chip->regmap, MISC_CHIP_ENABLE_REG,
|
|
CHIP_ENABLE_BIT, 0);
|
|
if (rc < 0)
|
|
pr_err("failed to disable chip rc=%d\n", rc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id pm8008_regulator_match_table[] = {
|
|
{
|
|
.compatible = "qcom,pm8008-regulator",
|
|
},
|
|
{ },
|
|
};
|
|
|
|
static struct platform_driver pm8008_regulator_driver = {
|
|
.driver = {
|
|
.name = "qcom,pm8008-regulator",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = pm8008_regulator_match_table,
|
|
},
|
|
.probe = pm8008_regulator_probe,
|
|
};
|
|
module_platform_driver(pm8008_regulator_driver);
|
|
|
|
static const struct of_device_id pm8008_chip_match_table[] = {
|
|
{
|
|
.compatible = "qcom,pm8008-chip",
|
|
},
|
|
{ },
|
|
};
|
|
|
|
static struct platform_driver pm8008_chip_driver = {
|
|
.driver = {
|
|
.name = "qcom,pm8008-chip",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = pm8008_chip_match_table,
|
|
},
|
|
.probe = pm8008_chip_probe,
|
|
.remove = pm8008_chip_remove,
|
|
};
|
|
module_platform_driver(pm8008_chip_driver);
|
|
|
|
MODULE_DESCRIPTION("QPNP PM8008 PMIC Regulator Driver");
|
|
MODULE_LICENSE("GPL v2");
|