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");