Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3f163f75 authored by Ashay Jaiswal's avatar Ashay Jaiswal
Browse files

regulator: qcom_pm8008: Regulator driver for the PM8008 PMIC



PM8008 is a I2C based regulator PMIC supporting 7 LDOs(2 N600
and 5 P300). Add the PM8008 regulator driver to support PMIC
regulator management via regulator framework.

Change-Id: Ie8d96b50558b8dd3084134216dff6393bac16f71
Signed-off-by: default avatarAshay Jaiswal <ashayj@codeaurora.org>
parent 9f512d30
Loading
Loading
Loading
Loading
+124 −0
Original line number Diff line number Diff line
Qualcomm Technologies, Inc. PM8008 Regulator

PM8008 is an I2C based PMIC regulator chip.

=======================
Required Node Structure
=======================

==============================================
PM8008 chip specific device
==============================================
PM8008 chip specific properties:

- compatible:
  Usage: required
  Value type: <string>
  Definition: must be "qcom,pm8008-chip"

- pinctrl-names:
  Usage: required
  Value type: <string>
  Definition: must be "default"

- pinctrl-0:
  Usage: required
  Value type: <phandle>
  Definition: pinctrol handle for chip enable GPIO.

- regulator sub-node:
  Usage: required
  Value type: <child sub node>
  Definition: Chip enable regulator device to control chip enable
		functionality. Must be "qcom,pm8008-chip-en".
Example:
	qcom,pm8008-chip@900 {
		compatible = "qcom,pm8008-chip";
		pinctrl-names = "default";
		pinctrl-0 = <&pincontrol handle>; // chip enable GPIO

		PM8008_EN: qcom,pm8008-chip-en {
			regulator-name = "pm8008-chip-en";
		};
	};


========================================================
PM8008 regulator device
========================================================
PM8008 chip regulator specific properties:

- compatible:
  Usage: required
  Value type: <string>
  Definition: must be "qcom,pm8008-regulator"

- <pin>-supply:
  Usage: optional
  Value type: <phandle>
  Definition: Reference to parent regulator supplying the input pin, as
		described in the data sheet.
		Must be one of the following:
		vdd_l1_l2-supply: supply for LDO1/LDO2 of PM8008
		vdd_l3_l4-supply: supply for LDO3/LDO4 of PM8008
		vdd_l5-supply: supply for LDO5 of PM8008
		vdd_l6-supply: supply for LDO6 of PM8008
		vdd_l7-supply: supply for LDO7 of PM8008

- pm8008_en-supply:
  Usage: required
  Value type: <phandle>
  Definition: Reference to PM8008 chip enable regulator, which manages
		chip enable functionlity of PM8008.

============================================================================
Second Level Nodes - PM8008 regulator peripherals of PM8008 regulator device
============================================================================

- qcom,hpm-min-load:
  Usage: optional
  Value type: <u32>
  Definition: Load current in uA which corresponds to the minimum load
		which requires the regulator to be in high power mode.

- qcom,min-dropout-voltage:
  Usage: optional
  Value type: <u32>
  Definition: Specifies the minimum voltage in microvolts that the parent
		supply regulator must output above the output of this
		regulator.  It is only meaningful if the corresponding parent
		supply property has been specified in the first level node.

- qcom,init-voltage
  Usage:      optional
  Value type: <u32>
  Definition: Specifies the initial voltage in microvolts to for a regulator.

- qcom,strong-pd
  Usage:      optional
  Value type: <bool>
  Definition: Property if present enables strong pull-down.

The content of each sub-node is defined by the standard binding for regulators -
see regulator.txt - with additional custom properties described below:

Example:

	qcom,pm8008-regulator {
		compatible = "qcom,pm8008-regulator";

		pm8008_en-supply = <&PM8008_EN>;
		vdd_l1_l2-supply = <&parent-supply>;
		...

		L1: qcom,pm8008-l1@4000 {
			reg = /bits/ 16 <0x4000>;
			regulator-name = "pm8008_l1";
			regulator-min-microvolt = <2900000>;
			regulator-max-microvolt = <3100000>;
			qcom,min-dropout-voltage = <100000>;
			qcom,hpm-min-load = <10000>;
		}

		.....
	};
+9 −0
Original line number Diff line number Diff line
@@ -337,6 +337,15 @@ config REGULATOR_ISL6271A
	help
	  This driver supports ISL6271A voltage regulator chip.

config REGULATOR_PM8008
	bool "Qualcomm Technologies Inc. PM8008 regulator driver"
	depends on MFD_I2C_PMIC
	help
	  Say Y here to support the PM8008 PMIC chip.
	  This driver controls PM8008 PMIC chip and the voltage
	  regulators found in Qualcomm Technologies Inc. PM8008
	  PMIC.

config REGULATOR_LM363X
	tristate "TI LM363X voltage regulators"
	depends on MFD_TI_LMU
+1 −0
Original line number Diff line number Diff line
@@ -85,6 +85,7 @@ obj-$(CONFIG_REGULATOR_QPNP_AMOLED) += qpnp-amoled-regulator.o
obj-$(CONFIG_REGULATOR_QPNP_OLEDB) += qpnp-oledb-regulator.o
obj-$(CONFIG_REGULATOR_PALMAS) += palmas-regulator.o
obj-$(CONFIG_REGULATOR_PFUZE100) += pfuze100-regulator.o
obj-$(CONFIG_REGULATOR_PM8008) += qcom_pm8008-regulator.o
obj-$(CONFIG_REGULATOR_PV88060) += pv88060-regulator.o
obj-$(CONFIG_REGULATOR_PV88080) += pv88080-regulator.o
obj-$(CONFIG_REGULATOR_PV88090) += pv88090-regulator.o
+781 −0
Original line number Diff line number Diff line
/* Copyright (c) 2019 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.
 */

#define pr_fmt(fmt) "PM8008: %s: " fmt, __func__

#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 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), &reg, 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), &reg, 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), &reg, 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), &reg, 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->rdev = devm_regulator_register(dev, &pm8008_reg->rdesc,
						&reg_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, &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 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",
		.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");