From e67e4ad4b161bd60b9da4c1872a9d77e0cac95fa Mon Sep 17 00:00:00 2001
From: Daniel Baumann <daniel.baumann@progress-linux.org>
Date: Mon, 5 Aug 2024 21:21:13 +0200
Subject: Merging upstream version 6.9.7.

Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
---
 drivers/regulator/mp8859.c | 252 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 251 insertions(+), 1 deletion(-)

(limited to 'drivers/regulator/mp8859.c')

diff --git a/drivers/regulator/mp8859.c b/drivers/regulator/mp8859.c
index b820bd6043..ab105ffd6a 100644
--- a/drivers/regulator/mp8859.c
+++ b/drivers/regulator/mp8859.c
@@ -35,6 +35,16 @@
 
 #define MP8859_GO_BIT			0x01
 
+#define MP8859_IOUT_LIM_MASK		0x7f
+
+#define MP8859_ENABLE_MASK		0x80
+#define MP8859_DISCHG_EN_MASK		0x10
+#define MP8859_MODE_MASK		0x08
+
+#define MP8859_PG_MASK			0x80
+#define MP8859_OTP_MASK			0x40
+#define MP8859_OTW_MASK			0x20
+#define MP8859_CC_CV_MASK		0x10
 
 static int mp8859_set_voltage_sel(struct regulator_dev *rdev, unsigned int sel)
 {
@@ -73,21 +83,221 @@ static int mp8859_get_voltage_sel(struct regulator_dev *rdev)
 	return val;
 }
 
+static int mp8859_set_voltage_time_sel(struct regulator_dev *rdev,
+				       unsigned int from, unsigned int to)
+{
+	int change;
+
+	/* The voltage ramps at 1mV/uS, selectors are 10mV */
+	if (from > to)
+		change = from - to;
+	else
+		change = to - from;
+
+	return change * 10 * 1000;
+}
+
+static unsigned int mp8859_get_mode(struct regulator_dev *rdev)
+{
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(rdev->regmap, MP8859_CTL1_REG, &val);
+	if (ret != 0) {
+		dev_err(&rdev->dev, "Failed to read mode: %d\n", ret);
+		return 0;
+	}
+
+	if (val & MP8859_MODE_MASK)
+		return REGULATOR_MODE_FAST;
+	else
+		return REGULATOR_MODE_NORMAL;
+}
+
+static int mp8859_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+	unsigned int val;
+
+	switch (mode) {
+	case REGULATOR_MODE_FAST:
+		val = MP8859_MODE_MASK;
+		break;
+	case REGULATOR_MODE_NORMAL:
+		val = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(rdev->regmap, MP8859_CTL1_REG,
+				  MP8859_MODE_MASK, val);
+}
+
+static int mp8859_set_current_limit(struct regulator_dev *rdev,
+				    int min_uA, int max_uA)
+{
+	unsigned int cur_val, new_val;
+	int ret, i;
+
+	/* Steps of 50mA */
+	new_val = max_uA / 50000;
+	if (new_val > MP8859_IOUT_LIM_MASK)
+		return -EINVAL;
+	if (new_val == 0)
+		return -EINVAL;
+
+	/*
+	 * If the regulator is limiting then ramp gradually as per
+	 * datasheet, otherwise just set the value directly.
+	 */
+	ret = regmap_read(rdev->regmap, MP8859_STATUS_REG, &cur_val);
+	if (ret != 0)
+		return ret;
+	if (!(cur_val & MP8859_CC_CV_MASK)) {
+		return regmap_update_bits(rdev->regmap, MP8859_IOUT_LIM_REG,
+					  MP8859_IOUT_LIM_MASK, new_val);
+	}
+
+	ret = regmap_read(rdev->regmap, MP8859_IOUT_LIM_REG, &cur_val);
+	if (ret != 0)
+		return ret;
+
+	if (cur_val >= new_val) {
+		for (i = cur_val; i >= new_val; i--) {
+			ret = regmap_update_bits(rdev->regmap,
+						 MP8859_IOUT_LIM_REG,
+						 MP8859_IOUT_LIM_MASK,
+						 cur_val - i);
+			if (ret != 0)
+				return ret;
+		}
+	} else {
+		for (i = cur_val; i <= new_val; i++) {
+			ret = regmap_update_bits(rdev->regmap,
+						 MP8859_IOUT_LIM_REG,
+						 MP8859_IOUT_LIM_MASK,
+						 cur_val + i);
+			if (ret != 0)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int mp8859_get_status(struct regulator_dev *rdev)
+{
+	unsigned int val;
+	int ret;
+
+	/* Output status is only meaingful when enabled */
+	ret = regmap_read(rdev->regmap, MP8859_CTL1_REG, &val);
+	if (ret != 0)
+		return ret;
+	if (!(val & MP8859_ENABLE_MASK))
+		return REGULATOR_STATUS_UNDEFINED;
+
+	ret = regmap_read(rdev->regmap, MP8859_STATUS_REG, &val);
+	if (ret != 0)
+		return ret;
+
+	if (val & MP8859_PG_MASK)
+		return REGULATOR_STATUS_ON;
+	else
+		return REGULATOR_STATUS_ERROR;
+}
+
+static int mp8859_get_error_flags(struct regulator_dev *rdev,
+				  unsigned int *flags)
+{
+	unsigned int status, enabled;
+	int ret;
+
+	*flags = 0;
+
+	/* Output status is only meaingful when enabled */
+	ret = regmap_read(rdev->regmap, MP8859_CTL1_REG, &enabled);
+	if (ret != 0)
+		return ret;
+	enabled &= MP8859_ENABLE_MASK;
+
+	ret = regmap_read(rdev->regmap, MP8859_STATUS_REG, &status);
+	if (ret != 0)
+		return ret;
+
+	if (enabled && !(status & MP8859_PG_MASK))
+		status |= REGULATOR_ERROR_FAIL;
+	if (status & MP8859_OTP_MASK)
+		status |= REGULATOR_ERROR_OVER_TEMP;
+	if (status & MP8859_OTW_MASK)
+		status |= REGULATOR_ERROR_OVER_TEMP_WARN;
+	if (status & MP8859_CC_CV_MASK)
+		status |= REGULATOR_ERROR_OVER_CURRENT;
+
+	return 0;
+}
+
 static const struct linear_range mp8859_dcdc_ranges[] = {
 	REGULATOR_LINEAR_RANGE(0, VOL_MIN_IDX, VOL_MAX_IDX, 10000),
 };
 
+static bool mp8859_readable(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MP8859_VOUT_L_REG:
+	case MP8859_VOUT_H_REG:
+	case MP8859_VOUT_GO_REG:
+	case MP8859_IOUT_LIM_REG:
+	case MP8859_CTL1_REG:
+	case MP8859_CTL2_REG:
+	case MP8859_STATUS_REG:
+	case MP8859_INTERRUPT_REG:
+	case MP8859_MASK_REG:
+	case MP8859_ID1_REG:
+	case MP8859_MFR_ID_REG:
+	case MP8859_DEV_ID_REG:
+	case MP8859_IC_REV_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool mp8859_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case MP8859_VOUT_GO_REG:
+	case MP8859_STATUS_REG:
+	case MP8859_INTERRUPT_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
 static const struct regmap_config mp8859_regmap = {
 	.reg_bits = 8,
 	.val_bits = 8,
 	.max_register = MP8859_MAX_REG,
-	.cache_type = REGCACHE_RBTREE,
+	.cache_type = REGCACHE_MAPLE,
+	.readable_reg = mp8859_readable,
+	.volatile_reg = mp8859_volatile,
 };
 
 static const struct regulator_ops mp8859_ops = {
 	.set_voltage_sel = mp8859_set_voltage_sel,
 	.get_voltage_sel = mp8859_get_voltage_sel,
 	.list_voltage = regulator_list_voltage_linear_range,
+	.set_voltage_time_sel = mp8859_set_voltage_time_sel,
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = regulator_is_enabled_regmap,
+	.set_mode = mp8859_set_mode,
+	.get_mode = mp8859_get_mode,
+	.set_active_discharge = regulator_set_active_discharge_regmap,
+	.set_current_limit = mp8859_set_current_limit,
+	.get_status = mp8859_get_status,
+	.get_error_flags = mp8859_get_error_flags,
 };
 
 static const struct regulator_desc mp8859_regulators[] = {
@@ -100,6 +310,12 @@ static const struct regulator_desc mp8859_regulators[] = {
 		.n_voltages = VOL_MAX_IDX + 1,
 		.linear_ranges = mp8859_dcdc_ranges,
 		.n_linear_ranges = 1,
+		.enable_reg = MP8859_CTL1_REG,
+		.enable_mask = MP8859_ENABLE_MASK,
+		.enable_val = MP8859_ENABLE_MASK,
+		.active_discharge_reg = MP8859_CTL1_REG,
+		.active_discharge_on = MP8859_DISCHG_EN_MASK,
+		.active_discharge_mask = MP8859_DISCHG_EN_MASK,
 		.ops = &mp8859_ops,
 		.owner = THIS_MODULE,
 	},
@@ -111,12 +327,46 @@ static int mp8859_i2c_probe(struct i2c_client *i2c)
 	struct regulator_config config = {.dev = &i2c->dev};
 	struct regmap *regmap = devm_regmap_init_i2c(i2c, &mp8859_regmap);
 	struct regulator_dev *rdev;
+	unsigned int val, rev;
 
 	if (IS_ERR(regmap)) {
 		ret = PTR_ERR(regmap);
 		dev_err(&i2c->dev, "regmap init failed: %d\n", ret);
 		return ret;
 	}
+
+	ret = regmap_read(regmap, MP8859_MFR_ID_REG, &val);
+	if (ret != 0) {
+		dev_err(&i2c->dev, "Failed to read manufacturer ID: %d\n", ret);
+		return ret;
+	}
+	if (val != 0x9) {
+		dev_err(&i2c->dev, "Manufacturer ID %x != 9\n", val);
+		return -EINVAL;
+	}
+
+	ret = regmap_read(regmap, MP8859_DEV_ID_REG, &val);
+	if (ret != 0) {
+		dev_err(&i2c->dev, "Failed to read device ID: %d\n", ret);
+		return ret;
+	}
+	if (val != 0x58) {
+		dev_err(&i2c->dev, "Manufacturer ID %x != 0x58\n", val);
+		return -EINVAL;
+	}
+
+	ret = regmap_read(regmap, MP8859_IC_REV_REG, &rev);
+	if (ret != 0) {
+		dev_err(&i2c->dev, "Failed to read device revision: %d\n", ret);
+		return ret;
+	}
+	ret = regmap_read(regmap, MP8859_ID1_REG, &val);
+	if (ret != 0) {
+		dev_err(&i2c->dev, "Failed to read device ID1: %d\n", ret);
+		return ret;
+	}
+	dev_info(&i2c->dev, "MP8859-%04d revision %d\n", val, rev);
+
 	rdev = devm_regulator_register(&i2c->dev, &mp8859_regulators[0],
 					&config);
 
-- 
cgit v1.2.3