From e3116a7023d77f55c3a9d687c5a8aa785fa667c1 Mon Sep 17 00:00:00 2001 From: Umang Agrawal Date: Fri, 12 Apr 2019 15:29:02 +0530 Subject: [PATCH 1/5] regulator: add a regulator driver for the PM8008 PMIC Qualcomm Technologies, Inc. PM8008 is an I2C controlled PMIC containing 7 LDO regulators (two N600 and five P300). Add a PM8008 regulator driver to support PMIC regulator management via the regulator framework. This is a snapshot of the PM8008 regulator driver found in the msm-4.19 kernel branch with a few small changes. The most significant of these was replacing module_platform_driver() with module_init() and module_exit() to avoid a compilation error when compiling the driver as a module. Change-Id: Ie8d96b50558b8dd3084134216dff6393bac16f71 Signed-off-by: Ashay Jaiswal Signed-off-by: Umang Agrawal Signed-off-by: David Collins --- drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 1 + drivers/regulator/qcom_pm8008-regulator.c | 786 ++++++++++++++++++++++ 3 files changed, 796 insertions(+) create mode 100644 drivers/regulator/qcom_pm8008-regulator.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index 41e8897b8137..89609c4b3a29 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -750,6 +750,15 @@ config REGULATOR_PWM This driver supports PWM controlled voltage regulators. PWM duty cycle can increase or decrease the voltage. +config REGULATOR_QCOM_PM8008 + tristate "Qualcomm Technologies, Inc. PM8008 PMIC regulators" + depends on MFD_I2C_PMIC + help + This driver provides support for the voltage regulators and top-level + chip enablement of Qualcomm Technologies, Inc. PM8008 PMIC chips. + When all seven of the PM8008 LDO regulators are disabled, the PM8008 + is put into a low power state. + config REGULATOR_QCOM_RPM tristate "Qualcomm RPM regulator driver" depends on MFD_QCOM_RPM diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 0615584a0277..6ed7bb7eccb5 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -83,6 +83,7 @@ obj-$(CONFIG_REGULATOR_MT6323) += mt6323-regulator.o obj-$(CONFIG_REGULATOR_MT6358) += mt6358-regulator.o obj-$(CONFIG_REGULATOR_MT6380) += mt6380-regulator.o obj-$(CONFIG_REGULATOR_MT6397) += mt6397-regulator.o +obj-$(CONFIG_REGULATOR_QCOM_PM8008) += qcom_pm8008-regulator.o obj-$(CONFIG_REGULATOR_QCOM_RPM) += qcom_rpm-regulator.o obj-$(CONFIG_REGULATOR_QCOM_RPMH) += qcom-rpmh-regulator.o obj-$(CONFIG_REGULATOR_QCOM_SMD_RPM) += qcom_smd-regulator.o diff --git a/drivers/regulator/qcom_pm8008-regulator.c b/drivers/regulator/qcom_pm8008-regulator.c new file mode 100644 index 000000000000..eb4cf1d97cda --- /dev/null +++ b/drivers/regulator/qcom_pm8008-regulator.c @@ -0,0 +1,786 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 MISC_BASE 0x900 + +#define MISC_CHIP_ENABLE_REG (MISC_BASE + 0x50) +#define CHIP_ENABLE_BIT BIT(0) + +#define LDO_ENABLE_REG(base) (base + 0x46) +#define ENABLE_BIT BIT(7) + +#define LDO_STATUS1_REG(base) (base + 0x08) +#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_VSET_VALID_LB_REG(base) (base + 0x42) + +#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_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; + +}; + +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; + u16 base; + int hpm_min_load_ua; + int min_dropout_uv; + int step_rate; +}; + +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_VALID_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, init_mv, delay_us, delay_ms, retry_count = 10; + u8 reg; + + 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_enable(pm8008_reg->parent_supply); + if (rc < 0) { + pm8008_err(pm8008_reg, + "failed to enable parent rc=%d\n", rc); + regulator_disable(pm8008_reg->en_supply); + return rc; + } + } + + 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 VREG_OK + * Read voltage and calculate the delay. + */ + init_mv = pm8008_regulator_get_voltage(rdev) / 1000; + if (init_mv < 0) { + pm8008_err(pm8008_reg, + "failed to get regulator voltage rc=%d\n", rc); + goto out; + } + delay_us = STARTUP_DELAY_USEC + + DIV_ROUND_UP(init_mv * 1000, pm8008_reg->step_rate); + delay_ms = DIV_ROUND_UP(delay_us, 1000); + + /* Retry 10 times for VREG_OK 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 out; + } + 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"); +out: + pm8008_masked_write(pm8008_reg->regmap, + LDO_ENABLE_REG(pm8008_reg->base), ENABLE_BIT, 0); +remove_vote: + rc = regulator_disable(pm8008_reg->en_supply); + if (pm8008_reg->parent_supply) + rc |= regulator_disable(pm8008_reg->parent_supply); + if (rc < 0) + pm8008_err(pm8008_reg, + "failed to disable parent regulator rc=%d\n", rc); + + return -ETIME; +} + +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 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); + } + + /* remove voltage vote from parent regulator */ + if (pm8008_reg->parent_supply) { + 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; + } + rc = regulator_disable(pm8008_reg->parent_supply); + if (rc < 0) { + pm8008_err(pm8008_reg, + "failed to disable parent 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(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; + + if (pm8008_reg->parent_supply) { + /* request on parent regulator with headroom */ + rc = regulator_set_voltage(pm8008_reg->parent_supply, + pm8008_reg->min_dropout_uv + min_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) { + /* remove parent's voltage vote */ + if (pm8008_reg->parent_supply) + regulator_set_voltage(pm8008_reg->parent_supply, + 0, INT_MAX); + } + + 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 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 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_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; + 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_u16(reg_node, "reg", &pm8008_reg->base); + if (rc < 0) { + pr_err("%s: failed to get regulator base rc=%d\n", name, rc); + return rc; + } + + 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; + } + } + + + /* 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.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; + } + + 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; + + /* 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; + + 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_regulator *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_regulator *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_regulator *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.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 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; + } + + 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", + .of_match_table = pm8008_regulator_match_table, + }, + .probe = pm8008_regulator_probe, +}; + +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", + .of_match_table = pm8008_chip_match_table, + }, + .probe = pm8008_chip_probe, + .remove = pm8008_chip_remove, +}; + +static int __init pm8008_regulator_init(void) +{ + int rc; + + rc = platform_driver_register(&pm8008_chip_driver); + if (rc) + return rc; + + return platform_driver_register(&pm8008_regulator_driver); +} +module_init(pm8008_regulator_init); + +static void __exit pm8008_regulator_exit(void) +{ + platform_driver_unregister(&pm8008_regulator_driver); + platform_driver_unregister(&pm8008_chip_driver); +} +module_exit(pm8008_regulator_exit); + +MODULE_DESCRIPTION("QPNP PM8008 PMIC Regulator Driver"); +MODULE_LICENSE("GPL v2"); From 83c5fbd2f29423200d52d69f5e1b3dc35b8836bc Mon Sep 17 00:00:00 2001 From: Kiran Gunda Date: Fri, 15 Nov 2019 18:36:44 +0530 Subject: [PATCH 2/5] regulator: qcom_pm8008: Add LDO OCP interrupt support When the load on an LDO is higher than what it can supply in the current operational mode, the LDO can hit the OCP (Over Current Protection) condition. Add interrupt support to notify the consumers about the OCP condition so that the consumers can take the necessary action. Note that mutex_lock() and mutex_unlock() in the original version of this patch were replaced with regulator_lock() and regulator_unlock() respectively to resolve a compilation error. Change-Id: I118364ea0f5ea28e8749861136301c1c6b8d7f84 Signed-off-by: Kiran Gunda Signed-off-by: David Collins --- drivers/regulator/qcom_pm8008-regulator.c | 127 +++++++++++++++++++++- 1 file changed, 126 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/qcom_pm8008-regulator.c b/drivers/regulator/qcom_pm8008-regulator.c index eb4cf1d97cda..5d7fffe7b8bd 100644 --- a/drivers/regulator/qcom_pm8008-regulator.c +++ b/drivers/regulator/qcom_pm8008-regulator.c @@ -30,10 +30,14 @@ #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 @@ -50,6 +54,10 @@ #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) @@ -64,7 +72,7 @@ struct pm8008_chip { struct regmap *regmap; struct regulator_dev *rdev; struct regulator_desc rdesc; - + int ocp_irq; }; struct regulator_data { @@ -82,10 +90,12 @@ struct pm8008_regulator { 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[] = { @@ -433,6 +443,60 @@ static struct regulator_ops pm8008_regulator_ops = { .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 */ + regulator_lock(pm8008_reg->rdev); + regulator_notifier_call_chain(pm8008_reg->rdev, + REGULATOR_EVENT_OVER_CURRENT, NULL); + regulator_unlock(pm8008_reg->rdev); + +error: + return NOTIFY_OK; +} + static int pm8008_register_ldo(struct pm8008_regulator *pm8008_reg, const char *name) { @@ -481,6 +545,17 @@ static int pm8008_register_ldo(struct pm8008_regulator *pm8008_reg, } } + 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, @@ -557,6 +632,17 @@ static int pm8008_register_ldo(struct pm8008_regulator *pm8008_reg, 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; @@ -569,6 +655,9 @@ static int pm8008_parse_regulator(struct regmap *regmap, struct device *dev) 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) { @@ -579,6 +668,7 @@ static int pm8008_parse_regulator(struct regmap *regmap, struct device *dev) 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) @@ -692,6 +782,18 @@ static int pm8008_init_enable_regulator(struct pm8008_chip *chip) return 0; } +static irqreturn_t pm8008_ocp_irq(int irq, void *_chip) +{ + struct pm8008_chip *chip = _chip; + + regulator_lock(chip->rdev); + regulator_notifier_call_chain(chip->rdev, REGULATOR_EVENT_OVER_CURRENT, + NULL); + regulator_unlock(chip->rdev); + + return IRQ_HANDLED; +} + static int pm8008_chip_probe(struct platform_device *pdev) { int rc = 0; @@ -715,6 +817,29 @@ static int pm8008_chip_probe(struct platform_device *pdev) 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; } From 0a87e5a6418eab92863c6710707156de75150965 Mon Sep 17 00:00:00 2001 From: David Collins Date: Thu, 9 Jan 2020 16:53:39 -0800 Subject: [PATCH 3/5] regulator: qcom_pm8008: change reg property format from u16 to u32 Use of_property_read_u32() instead of of_property_read_u16() to read the "reg" property of each PM8008 regulator device tree node. This allows the "reg" property value to be specified in device tree without the parameter "/bits/ 16". This in turn avoids device tree compilation warnings like this one: arch/arm64/boot/dts/vendor/qcom/lahaina-pmic-overlay.dtsi:68.5-30: Warning (reg_format): /soc/i2c@a94000/pm8008i@8/qcom,pm8008i-regulator/regulator@4000:reg: property has invalid length (2 bytes) (#address-cells == 1, #size-cells == 0) Change-Id: I69d7af28339abf3b7ed1365ccfc9343958f944da Signed-off-by: David Collins --- drivers/regulator/qcom_pm8008-regulator.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/regulator/qcom_pm8008-regulator.c b/drivers/regulator/qcom_pm8008-regulator.c index 5d7fffe7b8bd..919674decf38 100644 --- a/drivers/regulator/qcom_pm8008-regulator.c +++ b/drivers/regulator/qcom_pm8008-regulator.c @@ -506,6 +506,7 @@ static int pm8008_register_ldo(struct pm8008_regulator *pm8008_reg, 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 */ @@ -518,11 +519,12 @@ static int pm8008_register_ldo(struct pm8008_regulator *pm8008_reg, return -EINVAL; } - rc = of_property_read_u16(reg_node, "reg", &pm8008_reg->base); + 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", From cf34f94dd4d2ec031f3fc6c7747c3cc066ceb6fa Mon Sep 17 00:00:00 2001 From: David Collins Date: Thu, 9 Jan 2020 17:50:53 -0800 Subject: [PATCH 4/5] regulator: qcom_pm8008: allow multiple PM8008 instances with unique names Change the qcom_pm8008-regulator "regulator-name" device tree property parsing procedure from an exact match to a substring match. This allows multiple PM8008 chips to be used on a given board which have unique names specified for their respective regulators. Change-Id: I519107431148cdf1882d8e50056f2d2e20ce018f Signed-off-by: David Collins --- drivers/regulator/qcom_pm8008-regulator.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/regulator/qcom_pm8008-regulator.c b/drivers/regulator/qcom_pm8008-regulator.c index 919674decf38..fedcc41c18dc 100644 --- a/drivers/regulator/qcom_pm8008-regulator.c +++ b/drivers/regulator/qcom_pm8008-regulator.c @@ -98,15 +98,15 @@ struct pm8008_regulator { bool enable_ocp_broadcast; }; -static struct regulator_data reg_data[] = { +static struct regulator_data reg_data[PM8008_MAX_LDO] = { /* 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}, + {"l1", "vdd_l1_l2", 10000, 225000}, + {"l2", "vdd_l1_l2", 10000, 225000}, + {"l3", "vdd_l3_l4", 10000, 200000}, + {"l4", "vdd_l3_l4", 10000, 200000}, + {"l5", "vdd_l5", 10000, 300000}, + {"l6", "vdd_l6", 10000, 300000}, + {"l7", "vdd_l7", 10000, 300000}, }; /* common functions */ @@ -511,7 +511,7 @@ static int pm8008_register_ldo(struct pm8008_regulator *pm8008_reg, /* get regulator data */ for (i = 0; i < PM8008_MAX_LDO; i++) - if (!strcmp(reg_data[i].name, name)) + if (strstr(name, reg_data[i].name)) break; if (i == PM8008_MAX_LDO) { From a17fe14c106a10e6249c7ede33ca0ba3397aeeeb Mon Sep 17 00:00:00 2001 From: David Collins Date: Wed, 8 Jan 2020 16:49:35 -0800 Subject: [PATCH 5/5] defconfig: lahaina-gki: enable the qcom_pm8008-regulator driver Enable the PM8008 regulator driver so that the camera driver can configure the camera supply regulators found in the pair of PM8008 PMIC chips on Lahaina boards. Change-Id: Ic07f38865579c5b74ea1c58bc5206194eb3b979f Signed-off-by: David Collins --- arch/arm64/configs/vendor/lahaina_GKI.config | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/vendor/lahaina_GKI.config b/arch/arm64/configs/vendor/lahaina_GKI.config index 3b5e9bba94db..db9f1b8dbb06 100644 --- a/arch/arm64/configs/vendor/lahaina_GKI.config +++ b/arch/arm64/configs/vendor/lahaina_GKI.config @@ -70,6 +70,7 @@ CONFIG_QSEE_IPC_IRQ=m CONFIG_RPMSG_QCOM_GLINK_SPSS=m CONFIG_REGULATOR_RPMH=m CONFIG_REGULATOR_FIXED_VOLTAGE=m +CONFIG_REGULATOR_QCOM_PM8008=m CONFIG_MSM_GCC_LAHAINA=m CONFIG_REGULATOR_REFGEN=m CONFIG_MSM_VIDEOCC_LAHAINA=m