All of lore.kernel.org
 help / color / mirror / Atom feed
From: cy_huang <u0084500@gmail.com>
To: robh+dt@kernel.org, krzysztof.kozlowski+dt@linaro.org,
	lee.jones@linaro.org, broonie@kernel.org,
	dmitry.torokhov@gmail.com
Cc: lgirdwood@gmail.com, cy_huang@richtek.com,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-input@vger.kernel.org
Subject: [PATCH 3/4] regulator: rt5120: Add PMIC regulator support
Date: Tue,  7 Jun 2022 13:52:40 +0800	[thread overview]
Message-ID: <1654581161-12349-4-git-send-email-u0084500@gmail.com> (raw)
In-Reply-To: <1654581161-12349-1-git-send-email-u0084500@gmail.com>

From: ChiYuan Huang <cy_huang@richtek.com>

Add RT5120 PMIC regulator support.

It integrates 4 buck convertes, 1 LDO voltage regulator, 1 external
enable signal to control the external power source.

Signed-off-by: ChiYuan Huang <cy_huang@richtek.com>
---
 drivers/regulator/Kconfig            |  10 +
 drivers/regulator/Makefile           |   1 +
 drivers/regulator/rt5120-regulator.c | 417 +++++++++++++++++++++++++++++++++++
 3 files changed, 428 insertions(+)
 create mode 100644 drivers/regulator/rt5120-regulator.c

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 5ef2306..76039d5 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -1047,6 +1047,16 @@ config REGULATOR_RT5033
 	  RT5033 PMIC. The device supports multiple regulators like
 	  current source, LDO and Buck.
 
+config REGULATOR_RT5120
+	tristate "Richtek RT5120 PMIC Regulators"
+	depends on MFD_RT5120
+	help
+	  This adds support for voltage regulator in Richtek RT5120 PMIC.
+	  It integrates 4 channels buck controller, 1 channel LDO, 1 EXTEN
+	  to control external power source. Only BUCK1 is adjustable from
+	  600mV to 1395mV, per step 6.250mV. The others are all fixed voltage
+	  by external hardware circuit.
+
 config REGULATOR_RT5190A
 	tristate "Richtek RT5190A PMIC"
 	depends on I2C
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 1b64ad5..32fce42 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -126,6 +126,7 @@ obj-$(CONFIG_REGULATOR_ROHM)	+= rohm-regulator.o
 obj-$(CONFIG_REGULATOR_RT4801)	+= rt4801-regulator.o
 obj-$(CONFIG_REGULATOR_RT4831)	+= rt4831-regulator.o
 obj-$(CONFIG_REGULATOR_RT5033)	+= rt5033-regulator.o
+obj-$(CONFIG_REGULATOR_RT5120)	+= rt5120-regulator.o
 obj-$(CONFIG_REGULATOR_RT5190A) += rt5190a-regulator.o
 obj-$(CONFIG_REGULATOR_RT6160)	+= rt6160-regulator.o
 obj-$(CONFIG_REGULATOR_RT6245)	+= rt6245-regulator.o
diff --git a/drivers/regulator/rt5120-regulator.c b/drivers/regulator/rt5120-regulator.c
new file mode 100644
index 00000000..c786564
--- /dev/null
+++ b/drivers/regulator/rt5120-regulator.c
@@ -0,0 +1,417 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <linux/bits.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+
+#define RT5120_REG_PGSTAT	0x03
+#define RT5120_REG_CH1VID	0x06
+#define RT5120_REG_CH1SLPVID	0x07
+#define RT5120_REG_ENABLE	0x08
+#define RT5120_REG_MODECTL	0x09
+#define RT5120_REG_UVOVPROT	0x0A
+#define RT5120_REG_SLPCTL	0x0C
+#define RT5120_REG_INTSTAT	0x1E
+#define RT5120_REG_DISCHG	0x1F
+
+#define RT5120_OUTPG_MASK(rid)	BIT(rid + 1)
+#define RT5120_OUTUV_MASK(rid)	BIT(rid + 9)
+#define RT5120_OUTOV_MASK(rid)	BIT(rid + 16)
+#define RT5120_CH1VID_MASK	GENMASK(6, 0)
+#define RT5120_RIDEN_MASK(rid)	BIT(rid + 1)
+#define RT5120_RADEN_MASK(rid)	BIT(rid)
+#define RT5120_FPWM_MASK(rid)	BIT(rid + 1)
+#define RT5120_UVHICCUP_MASK	BIT(1)
+#define RT5120_OVHICCUP_MASK	BIT(0)
+#define RT5120_HOTDIE_MASK	BIT(1)
+
+#define RT5120_BUCK1_MINUV	600000
+#define RT5120_BUCK1_MAXUV	1393750
+#define RT5120_BUCK1_STEPUV	6250
+#define RT5120_BUCK1_NUM_VOLT	0x80
+
+#define RT5120_AUTO_MODE	0
+#define RT5120_FPWM_MODE	1
+
+enum {
+	RT5120_REGULATOR_BUCK1 = 0,
+	RT5120_REGULATOR_BUCK2,
+	RT5120_REGULATOR_BUCK3,
+	RT5120_REGULATOR_BUCK4,
+	RT5120_REGULATOR_LDO,
+	RT5120_REGULATOR_EXTEN,
+	RT5120_MAX_REGULATOR
+};
+
+struct rt5120_priv {
+	struct device *dev;
+	struct regmap *regmap;
+	struct regulator_desc rdesc[RT5120_MAX_REGULATOR];
+};
+
+static int rt5120_buck_set_mode(struct regulator_dev *rdev, unsigned int mode)
+{
+	struct regmap *regmap = rdev_get_regmap(rdev);
+	int rid = rdev_get_id(rdev);
+	unsigned int mask = RT5120_FPWM_MASK(rid), val;
+
+	switch (mode) {
+	case REGULATOR_MODE_NORMAL:
+		val = 0;
+		break;
+	case REGULATOR_MODE_FAST:
+		val = RT5120_FPWM_MASK(rid);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(regmap, RT5120_REG_MODECTL, mask, val);
+}
+
+static unsigned int rt5120_buck_get_mode(struct regulator_dev *rdev)
+{
+	struct regmap *regmap = rdev_get_regmap(rdev);
+	int ret, rid = rdev_get_id(rdev);
+	unsigned int val;
+
+	ret = regmap_read(regmap, RT5120_REG_MODECTL, &val);
+	if (ret)
+		return REGULATOR_MODE_INVALID;
+
+	if (val & RT5120_FPWM_MASK(rid))
+		return REGULATOR_MODE_FAST;
+
+	return REGULATOR_MODE_NORMAL;
+}
+
+static int rt5120_regulator_get_error_flags(struct regulator_dev *rdev,
+					    unsigned int *flags)
+{
+	struct regmap *regmap = rdev_get_regmap(rdev);
+	unsigned int stat, hd_stat, cur_flags = 0;
+	int rid = rdev_get_id(rdev), ret;
+
+	/*
+	 * reg 0x03/0x04/0x05 to indicate PG/UV/OV
+	 * use block read to descrease I/O xfer time
+	 */
+	ret = regmap_raw_read(regmap, RT5120_REG_PGSTAT, &stat, 3);
+	if (ret)
+		return ret;
+
+	ret = regmap_read(regmap, RT5120_REG_INTSTAT, &hd_stat);
+	if (ret)
+		return ret;
+
+	if (!(stat & RT5120_OUTPG_MASK(rid))) {
+		if (stat & RT5120_OUTUV_MASK(rid))
+			cur_flags |= REGULATOR_ERROR_UNDER_VOLTAGE;
+
+		if (stat & RT5120_OUTOV_MASK(rid))
+			cur_flags |= REGULATOR_ERROR_REGULATION_OUT;
+	}
+
+	if (hd_stat & RT5120_HOTDIE_MASK)
+		cur_flags |= REGULATOR_ERROR_OVER_TEMP;
+
+	*flags = cur_flags;
+	return 0;
+}
+
+static int rt5120_buck1_set_suspend_voltage(struct regulator_dev *rdev, int uV)
+{
+	struct regmap *regmap = rdev_get_regmap(rdev);
+	int sel;
+
+	if (uV < RT5120_BUCK1_MINUV || uV > RT5120_BUCK1_MAXUV)
+		return -EINVAL;
+
+	sel = (uV - RT5120_BUCK1_MINUV) / RT5120_BUCK1_STEPUV;
+	return regmap_write(regmap, RT5120_REG_CH1SLPVID, sel);
+}
+
+static int rt5120_regulator_set_suspend_enable(struct regulator_dev *rdev)
+{
+	struct regmap *regmap = rdev_get_regmap(rdev);
+	int rid = rdev_get_id(rdev);
+	unsigned int mask = RT5120_RIDEN_MASK(rid);
+
+	return regmap_update_bits(regmap, RT5120_REG_SLPCTL, mask, mask);
+}
+
+static int rt5120_regulator_set_suspend_disable(struct regulator_dev *rdev)
+{
+	struct regmap *regmap = rdev_get_regmap(rdev);
+	int rid = rdev_get_id(rdev);
+	unsigned int mask = RT5120_RIDEN_MASK(rid);
+
+	return regmap_update_bits(regmap, RT5120_REG_SLPCTL, mask, 0);
+}
+
+static const struct regulator_ops rt5120_buck1_ops = {
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = regulator_is_enabled_regmap,
+	.list_voltage = regulator_list_voltage_linear,
+	.set_voltage_sel = regulator_set_voltage_sel_regmap,
+	.get_voltage_sel = regulator_get_voltage_sel_regmap,
+	.set_active_discharge = regulator_set_active_discharge_regmap,
+	.set_mode = rt5120_buck_set_mode,
+	.get_mode = rt5120_buck_get_mode,
+	.get_error_flags = rt5120_regulator_get_error_flags,
+	.set_suspend_voltage = rt5120_buck1_set_suspend_voltage,
+	.set_suspend_enable = rt5120_regulator_set_suspend_enable,
+	.set_suspend_disable = rt5120_regulator_set_suspend_disable,
+};
+
+static const struct regulator_ops rt5120_buck234_ops = {
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = regulator_is_enabled_regmap,
+	.set_active_discharge = regulator_set_active_discharge_regmap,
+	.set_mode = rt5120_buck_set_mode,
+	.get_mode = rt5120_buck_get_mode,
+	.get_error_flags = rt5120_regulator_get_error_flags,
+	.set_suspend_enable = rt5120_regulator_set_suspend_enable,
+	.set_suspend_disable = rt5120_regulator_set_suspend_disable,
+};
+
+static const struct regulator_ops rt5120_ldo_ops = {
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = regulator_is_enabled_regmap,
+	.set_active_discharge = regulator_set_active_discharge_regmap,
+	.get_error_flags = rt5120_regulator_get_error_flags,
+	.set_suspend_enable = rt5120_regulator_set_suspend_enable,
+	.set_suspend_disable = rt5120_regulator_set_suspend_disable,
+};
+
+static const struct regulator_ops rt5120_exten_ops = {
+	.enable = regulator_enable_regmap,
+	.disable = regulator_disable_regmap,
+	.is_enabled = regulator_is_enabled_regmap,
+	.set_suspend_enable = rt5120_regulator_set_suspend_enable,
+	.set_suspend_disable = rt5120_regulator_set_suspend_disable,
+};
+
+static unsigned int rt5120_buck_of_map_mode(unsigned int mode)
+{
+	switch (mode) {
+	case RT5120_AUTO_MODE:
+		return REGULATOR_MODE_NORMAL;
+	case RT5120_FPWM_MODE:
+		return REGULATOR_MODE_FAST;
+	default:
+		return REGULATOR_MODE_INVALID;
+	}
+}
+
+static void rt5120_fillin_regulator_desc(struct regulator_desc *desc, int rid)
+{
+	static const char * const name[] = { "buck1", "buck2", "buck3", "buck4",
+					     "ldo", "exten" };
+	static const char * const sname[] = { "vin1", "vin2", "vin3", "vin4",
+					      "vinldo", NULL };
+
+	desc->name = name[rid];
+	desc->supply_name = sname[rid];
+	desc->owner = THIS_MODULE;
+	desc->type = REGULATOR_VOLTAGE;
+	desc->id = rid;
+	desc->enable_reg = RT5120_REG_ENABLE;
+	desc->enable_mask = RT5120_RIDEN_MASK(rid);
+	desc->active_discharge_reg = RT5120_REG_DISCHG;
+	desc->active_discharge_mask = RT5120_RADEN_MASK(rid);
+	desc->active_discharge_on = RT5120_RADEN_MASK(rid);
+
+	switch (rid) {
+	case RT5120_REGULATOR_BUCK1:
+		desc->of_map_mode = rt5120_buck_of_map_mode;
+		desc->min_uV = RT5120_BUCK1_MINUV;
+		desc->uV_step = RT5120_BUCK1_STEPUV;
+		desc->n_voltages = RT5120_BUCK1_NUM_VOLT;
+		desc->vsel_reg = RT5120_REG_CH1VID,
+		desc->vsel_mask = RT5120_CH1VID_MASK,
+		desc->ops = &rt5120_buck1_ops;
+		break;
+	case RT5120_REGULATOR_BUCK2 ... RT5120_REGULATOR_BUCK4:
+		desc->of_map_mode = rt5120_buck_of_map_mode;
+		desc->n_voltages = 1;
+		desc->ops = &rt5120_buck234_ops;
+		break;
+	case RT5120_REGULATOR_LDO:
+		desc->n_voltages = 1;
+		desc->ops = &rt5120_ldo_ops;
+		break;
+	default:
+		desc->n_voltages = 1;
+		desc->ops = &rt5120_exten_ops;
+		break;
+	}
+}
+
+static int rt5120_of_parse_cb(struct rt5120_priv *priv, int rid,
+			      struct of_regulator_match *match)
+{
+	struct regulator_desc *desc = priv->rdesc + rid;
+	struct regulator_init_data *init_data = match->init_data;
+
+	if (!init_data || rid == RT5120_REGULATOR_BUCK1)
+		return 0;
+
+	if (init_data->constraints.min_uV != init_data->constraints.max_uV) {
+		dev_err(priv->dev, "Variable voltage for fixed regulator\n");
+		return -EINVAL;
+	}
+
+	desc->fixed_uV = init_data->constraints.min_uV;
+	init_data->constraints.apply_uV = 0;
+	return 0;
+}
+
+static struct of_regulator_match rt5120_regu_match[RT5120_MAX_REGULATOR] = {
+	[RT5120_REGULATOR_BUCK1] = { .name = "buck1", },
+	[RT5120_REGULATOR_BUCK2] = { .name = "buck2", },
+	[RT5120_REGULATOR_BUCK3] = { .name = "buck3", },
+	[RT5120_REGULATOR_BUCK4] = { .name = "buck4", },
+	[RT5120_REGULATOR_LDO] = { .name = "ldo", },
+	[RT5120_REGULATOR_EXTEN] = { .name = "exten", }
+};
+
+static int rt5120_parse_regulator_dt_data(struct rt5120_priv *priv)
+{
+	struct device *dev = priv->dev->parent;
+	struct device_node *reg_node;
+	int i, ret;
+
+	for (i = 0; i < RT5120_MAX_REGULATOR; i++) {
+		rt5120_fillin_regulator_desc(priv->rdesc + i, i);
+
+		rt5120_regu_match[i].desc = priv->rdesc + i;
+	}
+
+	reg_node = of_get_child_by_name(dev->of_node, "regulators");
+	if (!reg_node) {
+		dev_err(priv->dev, "Couldn't find 'regulators' node\n");
+		return -ENODEV;
+	}
+
+	ret = of_regulator_match(priv->dev, reg_node, rt5120_regu_match,
+				 ARRAY_SIZE(rt5120_regu_match));
+
+	of_node_put(reg_node);
+
+	if (ret < 0) {
+		dev_err(priv->dev,
+			"Error parsing regulator init data (%d)\n", ret);
+		return ret;
+	}
+
+	for (i = 0; i < RT5120_MAX_REGULATOR; i++) {
+		ret = rt5120_of_parse_cb(priv, i, rt5120_regu_match + i);
+		if (ret) {
+			dev_err(priv->dev, "Failed in [%d] of_passe_cb\n", i);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int rt5120_device_property_init(struct rt5120_priv *priv)
+{
+	struct device *dev = priv->dev->parent;
+	bool prot_enable;
+	unsigned int prot_enable_val = 0;
+
+	/* Assign UV/OV HW protection behavior */
+	prot_enable = device_property_read_bool(dev,
+					"richtek,enable-undervolt-hiccup");
+	if (prot_enable)
+		prot_enable_val |= RT5120_UVHICCUP_MASK;
+
+	prot_enable = device_property_read_bool(dev,
+					"richtek,enable-overvolt-hiccup");
+	if (prot_enable)
+		prot_enable_val |= RT5120_OVHICCUP_MASK;
+
+	return regmap_update_bits(priv->regmap, RT5120_REG_UVOVPROT,
+				  RT5120_UVHICCUP_MASK | RT5120_OVHICCUP_MASK,
+				  prot_enable_val);
+}
+
+static int rt5120_regulator_probe(struct platform_device *pdev)
+{
+	struct rt5120_priv *priv;
+	struct regulator_dev *rdev;
+	struct regulator_config config = {};
+	int i, ret;
+
+	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->dev = &pdev->dev;
+
+	priv->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!priv->regmap) {
+		dev_err(&pdev->dev, "Failed to init regmap\n");
+		return -ENODEV;
+	}
+
+	ret = rt5120_device_property_init(priv);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to do property init\n");
+		return ret;
+	}
+
+	ret = rt5120_parse_regulator_dt_data(priv);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to parse dt data\n");
+		return ret;
+	}
+
+	config.dev = &pdev->dev;
+	config.regmap = priv->regmap;
+
+	for (i = 0; i < RT5120_MAX_REGULATOR; i++) {
+		config.of_node = rt5120_regu_match[i].of_node;
+		config.init_data = rt5120_regu_match[i].init_data;
+
+		rdev = devm_regulator_register(&pdev->dev, priv->rdesc + i,
+					       &config);
+		if (IS_ERR(rdev)) {
+			dev_err(&pdev->dev,
+				"Failed to register regulator [%d]\n", i);
+			return PTR_ERR(rdev);
+		}
+	}
+
+	return 0;
+}
+
+static const struct platform_device_id rt5120_regulator_dev_table[] = {
+	{ "rt5120-regulator", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(platform, rt5120_regulator_dev_table);
+
+static struct platform_driver rt5120_regulator_driver = {
+	.driver = {
+		.name = "rt5120-regulator",
+	},
+	.id_table = rt5120_regulator_dev_table,
+	.probe = rt5120_regulator_probe,
+};
+module_platform_driver(rt5120_regulator_driver);
+
+MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_DESCRIPTION("Richtek RT5120 regulator driver");
+MODULE_LICENSE("GPL v2");
-- 
2.7.4


  parent reply	other threads:[~2022-06-07  5:53 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-07  5:52 [PATCH 0/4] Add Richtek RT5120 PMIC support cy_huang
2022-06-07  5:52 ` [PATCH 1/4] dt-binding: mfd: " cy_huang
2022-06-07 11:52   ` Krzysztof Kozlowski
2022-06-08  2:52     ` ChiYuan Huang
2022-06-08  7:01       ` Krzysztof Kozlowski
2022-06-08  7:25         ` ChiYuan Huang
2022-06-08  7:44           ` Krzysztof Kozlowski
2022-06-07 19:02   ` Mark Brown
2022-06-08  3:04     ` ChiYuan Huang
2022-06-07  5:52 ` [PATCH 2/4] mfd: rt5120: Add Richtek " cy_huang
2022-06-07  5:52 ` cy_huang [this message]
2022-06-07 19:00   ` [PATCH 3/4] regulator: rt5120: Add PMIC regulator support Mark Brown
2022-06-08  3:15     ` ChiYuan Huang
2022-06-08 10:12       ` Mark Brown
2022-06-09  6:35         ` ChiYuan Huang
2022-06-10 11:03           ` Mark Brown
2022-06-13  1:49             ` ChiYuan Huang
2022-06-13 11:10               ` Mark Brown
2022-06-07  5:52 ` [PATCH 4/4] input: misc: rt5120: Add power key support cy_huang
2022-06-08  7:03   ` Krzysztof Kozlowski
2022-06-08  7:05     ` Krzysztof Kozlowski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1654581161-12349-4-git-send-email-u0084500@gmail.com \
    --to=u0084500@gmail.com \
    --cc=broonie@kernel.org \
    --cc=cy_huang@richtek.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=krzysztof.kozlowski+dt@linaro.org \
    --cc=lee.jones@linaro.org \
    --cc=lgirdwood@gmail.com \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=robh+dt@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.