aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/regulator
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/regulator')
-rw-r--r--drivers/regulator/Makefile2
-rw-r--r--drivers/regulator/bd9576-regulator.c1054
-rw-r--r--drivers/regulator/core.c163
-rw-r--r--drivers/regulator/devres.c52
-rw-r--r--drivers/regulator/internal.h11
-rw-r--r--drivers/regulator/irq_helpers.c397
-rw-r--r--drivers/regulator/of_regulator.c58
-rw-r--r--drivers/regulator/qcom-labibb-regulator.c10
-rw-r--r--drivers/regulator/qcom_spmi-regulator.c6
-rw-r--r--drivers/regulator/stpmic1_regulator.c20
10 files changed, 1623 insertions, 150 deletions
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 2f072544285a..8c2f82206b94 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -4,7 +4,7 @@
#
-obj-$(CONFIG_REGULATOR) += core.o dummy.o fixed-helper.o helpers.o devres.o
+obj-$(CONFIG_REGULATOR) += core.o dummy.o fixed-helper.o helpers.o devres.o irq_helpers.o
obj-$(CONFIG_OF) += of_regulator.o
obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o
obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o
diff --git a/drivers/regulator/bd9576-regulator.c b/drivers/regulator/bd9576-regulator.c
index 8e63169eebae..8b54d88827be 100644
--- a/drivers/regulator/bd9576-regulator.c
+++ b/drivers/regulator/bd9576-regulator.c
@@ -2,10 +2,10 @@
// Copyright (C) 2020 ROHM Semiconductors
// ROHM BD9576MUF/BD9573MUF regulator driver
-#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
+#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/mfd/rohm-bd957x.h>
#include <linux/mfd/rohm-generic.h>
@@ -16,11 +16,18 @@
#include <linux/regulator/machine.h>
#include <linux/regulator/of_regulator.h>
#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
#define BD957X_VOUTS1_VOLT 3300000
#define BD957X_VOUTS4_BASE_VOLT 1030000
#define BD957X_VOUTS34_NUM_VOLT 32
+#define BD9576_THERM_IRQ_MASK_TW BIT(5)
+#define BD9576_xVD_IRQ_MASK_VOUTL1 BIT(5)
+#define BD9576_UVD_IRQ_MASK_VOUTS1_OCW BIT(6)
+#define BD9576_xVD_IRQ_MASK_VOUT1TO4 0x0F
+
static const unsigned int vout1_volt_table[] = {
5000000, 4900000, 4800000, 4700000, 4600000,
4500000, 4500000, 4500000, 5000000, 5100000,
@@ -42,9 +49,85 @@ static const unsigned int voutl1_volt_table[] = {
2220000
};
+static const struct linear_range vout1_xvd_ranges[] = {
+ REGULATOR_LINEAR_RANGE(225000, 0x01, 0x2b, 0),
+ REGULATOR_LINEAR_RANGE(225000, 0x2c, 0x54, 5000),
+ REGULATOR_LINEAR_RANGE(425000, 0x55, 0x7f, 0),
+};
+
+static const struct linear_range vout234_xvd_ranges[] = {
+ REGULATOR_LINEAR_RANGE(17000, 0x01, 0x0f, 0),
+ REGULATOR_LINEAR_RANGE(17000, 0x10, 0x6d, 1000),
+ REGULATOR_LINEAR_RANGE(110000, 0x6e, 0x7f, 0),
+};
+
+static const struct linear_range voutL1_xvd_ranges[] = {
+ REGULATOR_LINEAR_RANGE(34000, 0x01, 0x0f, 0),
+ REGULATOR_LINEAR_RANGE(34000, 0x10, 0x6d, 2000),
+ REGULATOR_LINEAR_RANGE(220000, 0x6e, 0x7f, 0),
+};
+
+static struct linear_range voutS1_ocw_ranges_internal[] = {
+ REGULATOR_LINEAR_RANGE(200000, 0x01, 0x04, 0),
+ REGULATOR_LINEAR_RANGE(250000, 0x05, 0x18, 50000),
+ REGULATOR_LINEAR_RANGE(1200000, 0x19, 0x3f, 0),
+};
+
+static struct linear_range voutS1_ocw_ranges[] = {
+ REGULATOR_LINEAR_RANGE(50000, 0x01, 0x04, 0),
+ REGULATOR_LINEAR_RANGE(60000, 0x05, 0x18, 10000),
+ REGULATOR_LINEAR_RANGE(250000, 0x19, 0x3f, 0),
+};
+
+static struct linear_range voutS1_ocp_ranges_internal[] = {
+ REGULATOR_LINEAR_RANGE(300000, 0x01, 0x06, 0),
+ REGULATOR_LINEAR_RANGE(350000, 0x7, 0x1b, 50000),
+ REGULATOR_LINEAR_RANGE(1350000, 0x1c, 0x3f, 0),
+};
+
+static struct linear_range voutS1_ocp_ranges[] = {
+ REGULATOR_LINEAR_RANGE(70000, 0x01, 0x06, 0),
+ REGULATOR_LINEAR_RANGE(80000, 0x7, 0x1b, 10000),
+ REGULATOR_LINEAR_RANGE(280000, 0x1c, 0x3f, 0),
+};
+
struct bd957x_regulator_data {
struct regulator_desc desc;
int base_voltage;
+ struct regulator_dev *rdev;
+ int ovd_notif;
+ int uvd_notif;
+ int temp_notif;
+ int ovd_err;
+ int uvd_err;
+ int temp_err;
+ const struct linear_range *xvd_ranges;
+ int num_xvd_ranges;
+ bool oc_supported;
+ unsigned int ovd_reg;
+ unsigned int uvd_reg;
+ unsigned int xvd_mask;
+ unsigned int ocp_reg;
+ unsigned int ocp_mask;
+ unsigned int ocw_reg;
+ unsigned int ocw_mask;
+ unsigned int ocw_rfet;
+};
+
+#define BD9576_NUM_REGULATORS 6
+#define BD9576_NUM_OVD_REGULATORS 5
+
+struct bd957x_data {
+ struct bd957x_regulator_data regulator_data[BD9576_NUM_REGULATORS];
+ struct regmap *regmap;
+ struct delayed_work therm_irq_suppress;
+ struct delayed_work ovd_irq_suppress;
+ struct delayed_work uvd_irq_suppress;
+ unsigned int therm_irq;
+ unsigned int ovd_irq;
+ unsigned int uvd_irq;
+ spinlock_t err_lock;
+ int regulator_global_err;
};
static int bd957x_vout34_list_voltage(struct regulator_dev *rdev,
@@ -78,151 +161,784 @@ static int bd957x_list_voltage(struct regulator_dev *rdev,
return desc->volt_table[index];
}
-static const struct regulator_ops bd957x_vout34_ops = {
+static void bd9576_fill_ovd_flags(struct bd957x_regulator_data *data,
+ bool warn)
+{
+ if (warn) {
+ data->ovd_notif = REGULATOR_EVENT_OVER_VOLTAGE_WARN;
+ data->ovd_err = REGULATOR_ERROR_OVER_VOLTAGE_WARN;
+ } else {
+ data->ovd_notif = REGULATOR_EVENT_REGULATION_OUT;
+ data->ovd_err = REGULATOR_ERROR_REGULATION_OUT;
+ }
+}
+
+static void bd9576_fill_ocp_flags(struct bd957x_regulator_data *data,
+ bool warn)
+{
+ if (warn) {
+ data->uvd_notif = REGULATOR_EVENT_OVER_CURRENT_WARN;
+ data->uvd_err = REGULATOR_ERROR_OVER_CURRENT_WARN;
+ } else {
+ data->uvd_notif = REGULATOR_EVENT_OVER_CURRENT;
+ data->uvd_err = REGULATOR_ERROR_OVER_CURRENT;
+ }
+}
+
+static void bd9576_fill_uvd_flags(struct bd957x_regulator_data *data,
+ bool warn)
+{
+ if (warn) {
+ data->uvd_notif = REGULATOR_EVENT_UNDER_VOLTAGE_WARN;
+ data->uvd_err = REGULATOR_ERROR_UNDER_VOLTAGE_WARN;
+ } else {
+ data->uvd_notif = REGULATOR_EVENT_UNDER_VOLTAGE;
+ data->uvd_err = REGULATOR_ERROR_UNDER_VOLTAGE;
+ }
+}
+
+static void bd9576_fill_temp_flags(struct bd957x_regulator_data *data,
+ bool enable, bool warn)
+{
+ if (!enable) {
+ data->temp_notif = 0;
+ data->temp_err = 0;
+ } else if (warn) {
+ data->temp_notif = REGULATOR_EVENT_OVER_TEMP_WARN;
+ data->temp_err = REGULATOR_ERROR_OVER_TEMP_WARN;
+ } else {
+ data->temp_notif = REGULATOR_EVENT_OVER_TEMP;
+ data->temp_err = REGULATOR_ERROR_OVER_TEMP;
+ }
+}
+
+static int bd9576_set_limit(const struct linear_range *r, int num_ranges,
+ struct regmap *regmap, int reg, int mask, int lim)
+{
+ int ret;
+ bool found;
+ int sel = 0;
+
+ if (lim) {
+
+ ret = linear_range_get_selector_low_array(r, num_ranges,
+ lim, &sel, &found);
+ if (ret)
+ return ret;
+
+ if (!found)
+ dev_warn(regmap_get_device(regmap),
+ "limit %d out of range. Setting lower\n",
+ lim);
+ }
+
+ return regmap_update_bits(regmap, reg, mask, sel);
+}
+
+static bool check_ocp_flag_mismatch(struct regulator_dev *rdev, int severity,
+ struct bd957x_regulator_data *r)
+{
+ if ((severity == REGULATOR_SEVERITY_ERR &&
+ r->uvd_notif != REGULATOR_EVENT_OVER_CURRENT) ||
+ (severity == REGULATOR_SEVERITY_WARN &&
+ r->uvd_notif != REGULATOR_EVENT_OVER_CURRENT_WARN)) {
+ dev_warn(rdev_get_dev(rdev),
+ "Can't support both OCP WARN and ERR\n");
+ /* Do not overwrite ERR config with WARN */
+ if (severity == REGULATOR_SEVERITY_WARN)
+ return true;
+
+ bd9576_fill_ocp_flags(r, 0);
+ }
+
+ return false;
+}
+
+static bool check_uvd_flag_mismatch(struct regulator_dev *rdev, int severity,
+ struct bd957x_regulator_data *r)
+{
+ if ((severity == REGULATOR_SEVERITY_ERR &&
+ r->uvd_notif != REGULATOR_EVENT_UNDER_VOLTAGE) ||
+ (severity == REGULATOR_SEVERITY_WARN &&
+ r->uvd_notif != REGULATOR_EVENT_UNDER_VOLTAGE_WARN)) {
+ dev_warn(rdev_get_dev(rdev),
+ "Can't support both UVD WARN and ERR\n");
+ if (severity == REGULATOR_SEVERITY_WARN)
+ return true;
+
+ bd9576_fill_uvd_flags(r, 0);
+ }
+
+ return false;
+}
+
+static bool check_ovd_flag_mismatch(struct regulator_dev *rdev, int severity,
+ struct bd957x_regulator_data *r)
+{
+ if ((severity == REGULATOR_SEVERITY_ERR &&
+ r->ovd_notif != REGULATOR_EVENT_REGULATION_OUT) ||
+ (severity == REGULATOR_SEVERITY_WARN &&
+ r->ovd_notif != REGULATOR_EVENT_OVER_VOLTAGE_WARN)) {
+ dev_warn(rdev_get_dev(rdev),
+ "Can't support both OVD WARN and ERR\n");
+ if (severity == REGULATOR_SEVERITY_WARN)
+ return true;
+
+ bd9576_fill_ovd_flags(r, 0);
+ }
+
+ return false;
+}
+
+static bool check_temp_flag_mismatch(struct regulator_dev *rdev, int severity,
+ struct bd957x_regulator_data *r)
+{
+ if ((severity == REGULATOR_SEVERITY_ERR &&
+ r->ovd_notif != REGULATOR_EVENT_OVER_TEMP) ||
+ (severity == REGULATOR_SEVERITY_WARN &&
+ r->ovd_notif != REGULATOR_EVENT_OVER_TEMP_WARN)) {
+ dev_warn(rdev_get_dev(rdev),
+ "Can't support both thermal WARN and ERR\n");
+ if (severity == REGULATOR_SEVERITY_WARN)
+ return true;
+ }
+
+ return false;
+}
+
+static int bd9576_set_ocp(struct regulator_dev *rdev, int lim_uA, int severity,
+ bool enable)
+{
+ struct bd957x_data *d;
+ struct bd957x_regulator_data *r;
+ int reg, mask;
+ int Vfet, rfet;
+ const struct linear_range *range;
+ int num_ranges;
+
+ if ((lim_uA && !enable) || (!lim_uA && enable))
+ return -EINVAL;
+
+ r = container_of(rdev->desc, struct bd957x_regulator_data, desc);
+ if (!r->oc_supported)
+ return -EINVAL;
+
+ d = rdev_get_drvdata(rdev);
+
+ if (severity == REGULATOR_SEVERITY_PROT) {
+ reg = r->ocp_reg;
+ mask = r->ocp_mask;
+ if (r->ocw_rfet) {
+ range = voutS1_ocp_ranges;
+ num_ranges = ARRAY_SIZE(voutS1_ocp_ranges);
+ rfet = r->ocw_rfet / 1000;
+ } else {
+ range = voutS1_ocp_ranges_internal;
+ num_ranges = ARRAY_SIZE(voutS1_ocp_ranges_internal);
+ /* Internal values are already micro-amperes */
+ rfet = 1000;
+ }
+ } else {
+ reg = r->ocw_reg;
+ mask = r->ocw_mask;
+
+ if (r->ocw_rfet) {
+ range = voutS1_ocw_ranges;
+ num_ranges = ARRAY_SIZE(voutS1_ocw_ranges);
+ rfet = r->ocw_rfet / 1000;
+ } else {
+ range = voutS1_ocw_ranges_internal;
+ num_ranges = ARRAY_SIZE(voutS1_ocw_ranges_internal);
+ /* Internal values are already micro-amperes */
+ rfet = 1000;
+ }
+
+ /* We abuse uvd fields for OCW on VoutS1 */
+ if (r->uvd_notif) {
+ /*
+ * If both warning and error are requested, prioritize
+ * ERROR configuration
+ */
+ if (check_ocp_flag_mismatch(rdev, severity, r))
+ return 0;
+ } else {
+ bool warn = severity == REGULATOR_SEVERITY_WARN;
+
+ bd9576_fill_ocp_flags(r, warn);
+ }
+ }
+
+ /*
+ * limits are given in uA, rfet is mOhm
+ * Divide lim_uA by 1000 to get Vfet in uV.
+ * (We expect both Rfet and limit uA to be magnitude of hundreds of
+ * milli Amperes & milli Ohms => we should still have decent accuracy)
+ */
+ Vfet = lim_uA/1000 * rfet;
+
+ return bd9576_set_limit(range, num_ranges, d->regmap,
+ reg, mask, Vfet);
+}
+
+static int bd9576_set_uvp(struct regulator_dev *rdev, int lim_uV, int severity,
+ bool enable)
+{
+ struct bd957x_data *d;
+ struct bd957x_regulator_data *r;
+ int mask, reg;
+
+ if (severity == REGULATOR_SEVERITY_PROT) {
+ if (!enable || lim_uV)
+ return -EINVAL;
+ return 0;
+ }
+
+ /*
+ * BD9576 has enable control as a special value in limit reg. Can't
+ * set limit but keep feature disabled or enable W/O given limit.
+ */
+ if ((lim_uV && !enable) || (!lim_uV && enable))
+ return -EINVAL;
+
+ r = container_of(rdev->desc, struct bd957x_regulator_data, desc);
+ d = rdev_get_drvdata(rdev);
+
+ mask = r->xvd_mask;
+ reg = r->uvd_reg;
+ /*
+ * Check that there is no mismatch for what the detection IRQs are to
+ * be used.
+ */
+ if (r->uvd_notif) {
+ if (check_uvd_flag_mismatch(rdev, severity, r))
+ return 0;
+ } else {
+ bd9576_fill_uvd_flags(r, severity == REGULATOR_SEVERITY_WARN);
+ }
+
+ return bd9576_set_limit(r->xvd_ranges, r->num_xvd_ranges, d->regmap,
+ reg, mask, lim_uV);
+}
+
+static int bd9576_set_ovp(struct regulator_dev *rdev, int lim_uV, int severity,
+ bool enable)
+{
+ struct bd957x_data *d;
+ struct bd957x_regulator_data *r;
+ int mask, reg;
+
+ if (severity == REGULATOR_SEVERITY_PROT) {
+ if (!enable || lim_uV)
+ return -EINVAL;
+ return 0;
+ }
+
+ /*
+ * BD9576 has enable control as a special value in limit reg. Can't
+ * set limit but keep feature disabled or enable W/O given limit.
+ */
+ if ((lim_uV && !enable) || (!lim_uV && enable))
+ return -EINVAL;
+
+ r = container_of(rdev->desc, struct bd957x_regulator_data, desc);
+ d = rdev_get_drvdata(rdev);
+
+ mask = r->xvd_mask;
+ reg = r->ovd_reg;
+ /*
+ * Check that there is no mismatch for what the detection IRQs are to
+ * be used.
+ */
+ if (r->ovd_notif) {
+ if (check_ovd_flag_mismatch(rdev, severity, r))
+ return 0;
+ } else {
+ bd9576_fill_ovd_flags(r, severity == REGULATOR_SEVERITY_WARN);
+ }
+
+ return bd9576_set_limit(r->xvd_ranges, r->num_xvd_ranges, d->regmap,
+ reg, mask, lim_uV);
+}
+
+
+static int bd9576_set_tw(struct regulator_dev *rdev, int lim, int severity,
+ bool enable)
+{
+ struct bd957x_data *d;
+ struct bd957x_regulator_data *r;
+ int i;
+
+ /*
+ * BD9576MUF has fixed temperature limits
+ * The detection can only be enabled/disabled
+ */
+ if (lim)
+ return -EINVAL;
+
+ /* Protection can't be disabled */
+ if (severity == REGULATOR_SEVERITY_PROT) {
+ if (!enable)
+ return -EINVAL;
+ else
+ return 0;
+ }
+
+ r = container_of(rdev->desc, struct bd957x_regulator_data, desc);
+ d = rdev_get_drvdata(rdev);
+
+ /*
+ * Check that there is no mismatch for what the detection IRQs are to
+ * be used.
+ */
+ if (r->temp_notif)
+ if (check_temp_flag_mismatch(rdev, severity, r))
+ return 0;
+
+ bd9576_fill_temp_flags(r, enable, severity == REGULATOR_SEVERITY_WARN);
+
+ if (enable)
+ return regmap_update_bits(d->regmap, BD957X_REG_INT_THERM_MASK,
+ BD9576_THERM_IRQ_MASK_TW, 0);
+
+ /*
+ * If any of the regulators is interested in thermal warning we keep IRQ
+ * enabled.
+ */
+ for (i = 0; i < BD9576_NUM_REGULATORS; i++)
+ if (d->regulator_data[i].temp_notif)
+ return 0;
+
+ return regmap_update_bits(d->regmap, BD957X_REG_INT_THERM_MASK,
+ BD9576_THERM_IRQ_MASK_TW,
+ BD9576_THERM_IRQ_MASK_TW);
+}
+
+static const struct regulator_ops bd9573_vout34_ops = {
.is_enabled = regulator_is_enabled_regmap,
.list_voltage = bd957x_vout34_list_voltage,
.get_voltage_sel = regulator_get_voltage_sel_regmap,
};
-static const struct regulator_ops bd957X_vouts1_regulator_ops = {
+static const struct regulator_ops bd9576_vout34_ops = {
+ .is_enabled = regulator_is_enabled_regmap,
+ .list_voltage = bd957x_vout34_list_voltage,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_over_voltage_protection = bd9576_set_ovp,
+ .set_under_voltage_protection = bd9576_set_uvp,
+ .set_thermal_protection = bd9576_set_tw,
+};
+
+static const struct regulator_ops bd9573_vouts1_regulator_ops = {
+ .is_enabled = regulator_is_enabled_regmap,
+};
+
+static const struct regulator_ops bd9576_vouts1_regulator_ops = {
+ .is_enabled = regulator_is_enabled_regmap,
+ .set_over_current_protection = bd9576_set_ocp,
+};
+
+static const struct regulator_ops bd9573_ops = {
.is_enabled = regulator_is_enabled_regmap,
+ .list_voltage = bd957x_list_voltage,
+ .get_voltage_sel = regulator_get_voltage_sel_regmap,
};
-static const struct regulator_ops bd957x_ops = {
+static const struct regulator_ops bd9576_ops = {
.is_enabled = regulator_is_enabled_regmap,
.list_voltage = bd957x_list_voltage,
.get_voltage_sel = regulator_get_voltage_sel_regmap,
+ .set_over_voltage_protection = bd9576_set_ovp,
+ .set_under_voltage_protection = bd9576_set_uvp,
+ .set_thermal_protection = bd9576_set_tw,
+};
+
+static const struct regulator_ops *bd9573_ops_arr[] = {
+ [BD957X_VD50] = &bd9573_ops,
+ [BD957X_VD18] = &bd9573_ops,
+ [BD957X_VDDDR] = &bd9573_vout34_ops,
+ [BD957X_VD10] = &bd9573_vout34_ops,
+ [BD957X_VOUTL1] = &bd9573_ops,
+ [BD957X_VOUTS1] = &bd9573_vouts1_regulator_ops,
};
-static struct bd957x_regulator_data bd9576_regulators[] = {
- {
- .desc = {
- .name = "VD50",
- .of_match = of_match_ptr("regulator-vd50"),
- .regulators_node = of_match_ptr("regulators"),
- .id = BD957X_VD50,
- .type = REGULATOR_VOLTAGE,
- .ops = &bd957x_ops,
- .volt_table = &vout1_volt_table[0],
- .n_voltages = ARRAY_SIZE(vout1_volt_table),
- .vsel_reg = BD957X_REG_VOUT1_TUNE,
- .vsel_mask = BD957X_MASK_VOUT1_TUNE,
- .enable_reg = BD957X_REG_POW_TRIGGER1,
- .enable_mask = BD957X_REGULATOR_EN_MASK,
- .enable_val = BD957X_REGULATOR_DIS_VAL,
- .enable_is_inverted = true,
- .owner = THIS_MODULE,
+static const struct regulator_ops *bd9576_ops_arr[] = {
+ [BD957X_VD50] = &bd9576_ops,
+ [BD957X_VD18] = &bd9576_ops,
+ [BD957X_VDDDR] = &bd9576_vout34_ops,
+ [BD957X_VD10] = &bd9576_vout34_ops,
+ [BD957X_VOUTL1] = &bd9576_ops,
+ [BD957X_VOUTS1] = &bd9576_vouts1_regulator_ops,
+};
+
+static int vouts1_get_fet_res(struct device_node *np,
+ const struct regulator_desc *desc,
+ struct regulator_config *cfg)
+{
+ struct bd957x_regulator_data *data;
+ int ret;
+ u32 uohms;
+
+ data = container_of(desc, struct bd957x_regulator_data, desc);
+
+ ret = of_property_read_u32(np, "rohm,ocw-fet-ron-micro-ohms", &uohms);
+ if (ret) {
+ if (ret != -EINVAL)
+ return ret;
+
+ return 0;
+ }
+ data->ocw_rfet = uohms;
+ return 0;
+}
+
+static struct bd957x_data bd957x_regulators = {
+ .regulator_data = {
+ {
+ .desc = {
+ .name = "VD50",
+ .of_match = of_match_ptr("regulator-vd50"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD957X_VD50,
+ .type = REGULATOR_VOLTAGE,
+ .volt_table = &vout1_volt_table[0],
+ .n_voltages = ARRAY_SIZE(vout1_volt_table),
+ .vsel_reg = BD957X_REG_VOUT1_TUNE,
+ .vsel_mask = BD957X_MASK_VOUT1_TUNE,
+ .enable_reg = BD957X_REG_POW_TRIGGER1,
+ .enable_mask = BD957X_REGULATOR_EN_MASK,
+ .enable_val = BD957X_REGULATOR_DIS_VAL,
+ .enable_is_inverted = true,
+ .owner = THIS_MODULE,
+ },
+ .xvd_ranges = vout1_xvd_ranges,
+ .num_xvd_ranges = ARRAY_SIZE(vout1_xvd_ranges),
+ .ovd_reg = BD9576_REG_VOUT1_OVD,
+ .uvd_reg = BD9576_REG_VOUT1_UVD,
+ .xvd_mask = BD9576_MASK_XVD,
},
- },
- {
- .desc = {
- .name = "VD18",
- .of_match = of_match_ptr("regulator-vd18"),
- .regulators_node = of_match_ptr("regulators"),
- .id = BD957X_VD18,
- .type = REGULATOR_VOLTAGE,
- .ops = &bd957x_ops,
- .volt_table = &vout2_volt_table[0],
- .n_voltages = ARRAY_SIZE(vout2_volt_table),
- .vsel_reg = BD957X_REG_VOUT2_TUNE,
- .vsel_mask = BD957X_MASK_VOUT2_TUNE,
- .enable_reg = BD957X_REG_POW_TRIGGER2,
- .enable_mask = BD957X_REGULATOR_EN_MASK,
- .enable_val = BD957X_REGULATOR_DIS_VAL,
- .enable_is_inverted = true,
- .owner = THIS_MODULE,
+ {
+ .desc = {
+ .name = "VD18",
+ .of_match = of_match_ptr("regulator-vd18"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD957X_VD18,
+ .type = REGULATOR_VOLTAGE,
+ .volt_table = &vout2_volt_table[0],
+ .n_voltages = ARRAY_SIZE(vout2_volt_table),
+ .vsel_reg = BD957X_REG_VOUT2_TUNE,
+ .vsel_mask = BD957X_MASK_VOUT2_TUNE,
+ .enable_reg = BD957X_REG_POW_TRIGGER2,
+ .enable_mask = BD957X_REGULATOR_EN_MASK,
+ .enable_val = BD957X_REGULATOR_DIS_VAL,
+ .enable_is_inverted = true,
+ .owner = THIS_MODULE,
+ },
+ .xvd_ranges = vout234_xvd_ranges,
+ .num_xvd_ranges = ARRAY_SIZE(vout234_xvd_ranges),
+ .ovd_reg = BD9576_REG_VOUT2_OVD,
+ .uvd_reg = BD9576_REG_VOUT2_UVD,
+ .xvd_mask = BD9576_MASK_XVD,
},
- },
- {
- .desc = {
- .name = "VDDDR",
- .of_match = of_match_ptr("regulator-vdddr"),
- .regulators_node = of_match_ptr("regulators"),
- .id = BD957X_VDDDR,
- .ops = &bd957x_vout34_ops,
- .type = REGULATOR_VOLTAGE,
- .n_voltages = BD957X_VOUTS34_NUM_VOLT,
- .vsel_reg = BD957X_REG_VOUT3_TUNE,
- .vsel_mask = BD957X_MASK_VOUT3_TUNE,
- .enable_reg = BD957X_REG_POW_TRIGGER3,
- .enable_mask = BD957X_REGULATOR_EN_MASK,
- .enable_val = BD957X_REGULATOR_DIS_VAL,
- .enable_is_inverted = true,
- .owner = THIS_MODULE,
+ {
+ .desc = {
+ .name = "VDDDR",
+ .of_match = of_match_ptr("regulator-vdddr"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD957X_VDDDR,
+ .type = REGULATOR_VOLTAGE,
+ .n_voltages = BD957X_VOUTS34_NUM_VOLT,
+ .vsel_reg = BD957X_REG_VOUT3_TUNE,
+ .vsel_mask = BD957X_MASK_VOUT3_TUNE,
+ .enable_reg = BD957X_REG_POW_TRIGGER3,
+ .enable_mask = BD957X_REGULATOR_EN_MASK,
+ .enable_val = BD957X_REGULATOR_DIS_VAL,
+ .enable_is_inverted = true,
+ .owner = THIS_MODULE,
+ },
+ .ovd_reg = BD9576_REG_VOUT3_OVD,
+ .uvd_reg = BD9576_REG_VOUT3_UVD,
+ .xvd_mask = BD9576_MASK_XVD,
+ .xvd_ranges = vout234_xvd_ranges,
+ .num_xvd_ranges = ARRAY_SIZE(vout234_xvd_ranges),
},
- },
- {
- .desc = {
- .name = "VD10",
- .of_match = of_match_ptr("regulator-vd10"),
- .regulators_node = of_match_ptr("regulators"),
- .id = BD957X_VD10,
- .ops = &bd957x_vout34_ops,
- .type = REGULATOR_VOLTAGE,
- .fixed_uV = BD957X_VOUTS4_BASE_VOLT,
- .n_voltages = BD957X_VOUTS34_NUM_VOLT,
- .vsel_reg = BD957X_REG_VOUT4_TUNE,
- .vsel_mask = BD957X_MASK_VOUT4_TUNE,
- .enable_reg = BD957X_REG_POW_TRIGGER4,
- .enable_mask = BD957X_REGULATOR_EN_MASK,
- .enable_val = BD957X_REGULATOR_DIS_VAL,
- .enable_is_inverted = true,
- .owner = THIS_MODULE,
+ {
+ .desc = {
+ .name = "VD10",
+ .of_match = of_match_ptr("regulator-vd10"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD957X_VD10,
+ .type = REGULATOR_VOLTAGE,
+ .fixed_uV = BD957X_VOUTS4_BASE_VOLT,
+ .n_voltages = BD957X_VOUTS34_NUM_VOLT,
+ .vsel_reg = BD957X_REG_VOUT4_TUNE,
+ .vsel_mask = BD957X_MASK_VOUT4_TUNE,
+ .enable_reg = BD957X_REG_POW_TRIGGER4,
+ .enable_mask = BD957X_REGULATOR_EN_MASK,
+ .enable_val = BD957X_REGULATOR_DIS_VAL,
+ .enable_is_inverted = true,
+ .owner = THIS_MODULE,
+ },
+ .xvd_ranges = vout234_xvd_ranges,
+ .num_xvd_ranges = ARRAY_SIZE(vout234_xvd_ranges),
+ .ovd_reg = BD9576_REG_VOUT4_OVD,
+ .uvd_reg = BD9576_REG_VOUT4_UVD,
+ .xvd_mask = BD9576_MASK_XVD,
},
- },
- {
- .desc = {
- .name = "VOUTL1",
- .of_match = of_match_ptr("regulator-voutl1"),
- .regulators_node = of_match_ptr("regulators"),
- .id = BD957X_VOUTL1,
- .ops = &bd957x_ops,
- .type = REGULATOR_VOLTAGE,
- .volt_table = &voutl1_volt_table[0],
- .n_voltages = ARRAY_SIZE(voutl1_volt_table),
- .vsel_reg = BD957X_REG_VOUTL1_TUNE,
- .vsel_mask = BD957X_MASK_VOUTL1_TUNE,
- .enable_reg = BD957X_REG_POW_TRIGGERL1,
- .enable_mask = BD957X_REGULATOR_EN_MASK,
- .enable_val = BD957X_REGULATOR_DIS_VAL,
- .enable_is_inverted = true,
- .owner = THIS_MODULE,
+ {
+ .desc = {
+ .name = "VOUTL1",
+ .of_match = of_match_ptr("regulator-voutl1"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD957X_VOUTL1,
+ .type = REGULATOR_VOLTAGE,
+ .volt_table = &voutl1_volt_table[0],
+ .n_voltages = ARRAY_SIZE(voutl1_volt_table),
+ .vsel_reg = BD957X_REG_VOUTL1_TUNE,
+ .vsel_mask = BD957X_MASK_VOUTL1_TUNE,
+ .enable_reg = BD957X_REG_POW_TRIGGERL1,
+ .enable_mask = BD957X_REGULATOR_EN_MASK,
+ .enable_val = BD957X_REGULATOR_DIS_VAL,
+ .enable_is_inverted = true,
+ .owner = THIS_MODULE,
+ },
+ .xvd_ranges = voutL1_xvd_ranges,
+ .num_xvd_ranges = ARRAY_SIZE(voutL1_xvd_ranges),
+ .ovd_reg = BD9576_REG_VOUTL1_OVD,
+ .uvd_reg = BD9576_REG_VOUTL1_UVD,
+ .xvd_mask = BD9576_MASK_XVD,
},
- },
- {
- .desc = {
- .name = "VOUTS1",
- .of_match = of_match_ptr("regulator-vouts1"),
- .regulators_node = of_match_ptr("regulators"),
- .id = BD957X_VOUTS1,
- .ops = &bd957X_vouts1_regulator_ops,
- .type = REGULATOR_VOLTAGE,
- .n_voltages = 1,
- .fixed_uV = BD957X_VOUTS1_VOLT,
- .enable_reg = BD957X_REG_POW_TRIGGERS1,
- .enable_mask = BD957X_REGULATOR_EN_MASK,
- .enable_val = BD957X_REGULATOR_DIS_VAL,
- .enable_is_inverted = true,
- .owner = THIS_MODULE,
+ {
+ .desc = {
+ .name = "VOUTS1",
+ .of_match = of_match_ptr("regulator-vouts1"),
+ .regulators_node = of_match_ptr("regulators"),
+ .id = BD957X_VOUTS1,
+ .type = REGULATOR_VOLTAGE,
+ .n_voltages = 1,
+ .fixed_uV = BD957X_VOUTS1_VOLT,
+ .enable_reg = BD957X_REG_POW_TRIGGERS1,
+ .enable_mask = BD957X_REGULATOR_EN_MASK,
+ .enable_val = BD957X_REGULATOR_DIS_VAL,
+ .enable_is_inverted = true,
+ .owner = THIS_MODULE,
+ .of_parse_cb = vouts1_get_fet_res,
+ },
+ .oc_supported = true,
+ .ocw_reg = BD9576_REG_VOUT1S_OCW,
+ .ocw_mask = BD9576_MASK_VOUT1S_OCW,
+ .ocp_reg = BD9576_REG_VOUT1S_OCP,
+ .ocp_mask = BD9576_MASK_VOUT1S_OCP,
},
},
};
+static int bd9576_renable(struct regulator_irq_data *rid, int reg, int mask)
+{
+ int val, ret;
+ struct bd957x_data *d = (struct bd957x_data *)rid->data;
+
+ ret = regmap_read(d->regmap, reg, &val);
+ if (ret)
+ return REGULATOR_FAILED_RETRY;
+
+ if (rid->opaque && rid->opaque == (val & mask)) {
+ /*
+ * It seems we stil have same status. Ack and return
+ * information that we are still out of limits and core
+ * should not enable IRQ
+ */
+ regmap_write(d->regmap, reg, mask & val);
+ return REGULATOR_ERROR_ON;
+ }
+ rid->opaque = 0;
+ /*
+ * Status was changed. Either prolem was solved or we have new issues.
+ * Let's re-enable IRQs and be prepared to report problems again
+ */
+ return REGULATOR_ERROR_CLEARED;
+}
+
+static int bd9576_uvd_renable(struct regulator_irq_data *rid)
+{
+ return bd9576_renable(rid, BD957X_REG_INT_UVD_STAT, UVD_IRQ_VALID_MASK);
+}
+
+static int bd9576_ovd_renable(struct regulator_irq_data *rid)
+{
+ return bd9576_renable(rid, BD957X_REG_INT_OVD_STAT, OVD_IRQ_VALID_MASK);
+}
+
+static int bd9576_temp_renable(struct regulator_irq_data *rid)
+{
+ return bd9576_renable(rid, BD957X_REG_INT_THERM_STAT,
+ BD9576_THERM_IRQ_MASK_TW);
+}
+
+static int bd9576_uvd_handler(int irq, struct regulator_irq_data *rid,
+ unsigned long *dev_mask)
+{
+ int val, ret, i;
+ struct bd957x_data *d = (struct bd957x_data *)rid->data;
+
+ ret = regmap_read(d->regmap, BD957X_REG_INT_UVD_STAT, &val);
+ if (ret)
+ return REGULATOR_FAILED_RETRY;
+
+ *dev_mask = 0;
+
+ rid->opaque = val & UVD_IRQ_VALID_MASK;
+
+ /*
+ * Go through the set status bits and report either error or warning
+ * to the notifier depending on what was flagged in DT
+ */
+ *dev_mask = val & BD9576_xVD_IRQ_MASK_VOUT1TO4;
+ /* There is 1 bit gap in register after Vout1 .. Vout4 statuses */
+ *dev_mask |= ((val & BD9576_xVD_IRQ_MASK_VOUTL1) >> 1);
+ /*
+ * We (ab)use the uvd for OCW notification. DT parsing should
+ * have added correct OCW flag to uvd_notif and uvd_err for S1
+ */
+ *dev_mask |= ((val & BD9576_UVD_IRQ_MASK_VOUTS1_OCW) >> 1);
+
+ for_each_set_bit(i, dev_mask, 6) {
+ struct bd957x_regulator_data *rdata;
+ struct regulator_err_state *stat;
+
+ rdata = &d->regulator_data[i];
+ stat = &rid->states[i];
+
+ stat->notifs = rdata->uvd_notif;
+ stat->errors = rdata->uvd_err;
+ }
+
+ ret = regmap_write(d->regmap, BD957X_REG_INT_UVD_STAT,
+ UVD_IRQ_VALID_MASK & val);
+
+ return 0;
+}
+
+static int bd9576_ovd_handler(int irq, struct regulator_irq_data *rid,
+ unsigned long *dev_mask)
+{
+ int val, ret, i;
+ struct bd957x_data *d = (struct bd957x_data *)rid->data;
+
+ ret = regmap_read(d->regmap, BD957X_REG_INT_OVD_STAT, &val);
+ if (ret)
+ return REGULATOR_FAILED_RETRY;
+
+ rid->opaque = val & OVD_IRQ_VALID_MASK;
+ *dev_mask = 0;
+
+ if (!(val & OVD_IRQ_VALID_MASK))
+ return 0;
+
+ *dev_mask = val & BD9576_xVD_IRQ_MASK_VOUT1TO4;
+ /* There is 1 bit gap in register after Vout1 .. Vout4 statuses */
+ *dev_mask |= ((val & BD9576_xVD_IRQ_MASK_VOUTL1) >> 1);
+
+ for_each_set_bit(i, dev_mask, 5) {
+ struct bd957x_regulator_data *rdata;
+ struct regulator_err_state *stat;
+
+ rdata = &d->regulator_data[i];
+ stat = &rid->states[i];
+
+ stat->notifs = rdata->ovd_notif;
+ stat->errors = rdata->ovd_err;
+ }
+
+ /* Clear the sub-IRQ status */
+ regmap_write(d->regmap, BD957X_REG_INT_OVD_STAT,
+ OVD_IRQ_VALID_MASK & val);
+
+ return 0;
+}
+
+#define BD9576_DEV_MASK_ALL_REGULATORS 0x3F
+
+static int bd9576_thermal_handler(int irq, struct regulator_irq_data *rid,
+ unsigned long *dev_mask)
+{
+ int val, ret, i;
+ struct bd957x_data *d = (struct bd957x_data *)rid->data;
+
+ ret = regmap_read(d->regmap, BD957X_REG_INT_THERM_STAT, &val);
+ if (ret)
+ return REGULATOR_FAILED_RETRY;
+
+ if (!(val & BD9576_THERM_IRQ_MASK_TW)) {
+ *dev_mask = 0;
+ return 0;
+ }
+
+ *dev_mask = BD9576_DEV_MASK_ALL_REGULATORS;
+
+ for (i = 0; i < BD9576_NUM_REGULATORS; i++) {
+ struct bd957x_regulator_data *rdata;
+ struct regulator_err_state *stat;
+
+ rdata = &d->regulator_data[i];
+ stat = &rid->states[i];
+
+ stat->notifs = rdata->temp_notif;
+ stat->errors = rdata->temp_err;
+ }
+
+ /* Clear the sub-IRQ status */
+ regmap_write(d->regmap, BD957X_REG_INT_THERM_STAT,
+ BD9576_THERM_IRQ_MASK_TW);
+
+ return 0;
+}
+
static int bd957x_probe(struct platform_device *pdev)
{
+ int i;
+ unsigned int num_reg_data;
+ bool vout_mode, ddr_sel, may_have_irqs;
struct regmap *regmap;
+ struct bd957x_data *ic_data;
struct regulator_config config = { 0 };
- int i;
- bool vout_mode, ddr_sel;
- const struct bd957x_regulator_data *reg_data = &bd9576_regulators[0];
- unsigned int num_reg_data = ARRAY_SIZE(bd9576_regulators);
+ /* All regulators are related to UVD and thermal IRQs... */
+ struct regulator_dev *rdevs[BD9576_NUM_REGULATORS];
+ /* ...But VoutS1 is not flagged by OVD IRQ */
+ struct regulator_dev *ovd_devs[BD9576_NUM_OVD_REGULATORS];
+ static const struct regulator_irq_desc bd9576_notif_uvd = {
+ .name = "bd9576-uvd",
+ .irq_off_ms = 1000,
+ .map_event = bd9576_uvd_handler,
+ .renable = bd9576_uvd_renable,
+ .data = &bd957x_regulators,
+ };
+ static const struct regulator_irq_desc bd9576_notif_ovd = {
+ .name = "bd9576-ovd",
+ .irq_off_ms = 1000,
+ .map_event = bd9576_ovd_handler,
+ .renable = bd9576_ovd_renable,
+ .data = &bd957x_regulators,
+ };
+ static const struct regulator_irq_desc bd9576_notif_temp = {
+ .name = "bd9576-temp",
+ .irq_off_ms = 1000,
+ .map_event = bd9576_thermal_handler,
+ .renable = bd9576_temp_renable,
+ .data = &bd957x_regulators,
+ };
enum rohm_chip_type chip = platform_get_device_id(pdev)->driver_data;
+ num_reg_data = ARRAY_SIZE(bd957x_regulators.regulator_data);
+
+ ic_data = &bd957x_regulators;
+
regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!regmap) {
dev_err(&pdev->dev, "No regmap\n");
return -EINVAL;
}
+
+ ic_data->regmap = regmap;
vout_mode = of_property_read_bool(pdev->dev.parent->of_node,
"rohm,vout1-en-low");
if (vout_mode) {
@@ -269,15 +985,17 @@ static int bd957x_probe(struct platform_device *pdev)
* bytes and use bd9576_regulators directly for non-constant configs
* like DDR voltage selection.
*/
+ platform_set_drvdata(pdev, ic_data);
ddr_sel = of_property_read_bool(pdev->dev.parent->of_node,
"rohm,ddr-sel-low");
if (ddr_sel)
- bd9576_regulators[2].desc.fixed_uV = 1350000;
+ ic_data->regulator_data[2].desc.fixed_uV = 1350000;
else
- bd9576_regulators[2].desc.fixed_uV = 1500000;
+ ic_data->regulator_data[2].desc.fixed_uV = 1500000;
switch (chip) {
case ROHM_CHIP_TYPE_BD9576:
+ may_have_irqs = true;
dev_dbg(&pdev->dev, "Found BD9576MUF\n");
break;
case ROHM_CHIP_TYPE_BD9573:
@@ -288,38 +1006,122 @@ static int bd957x_probe(struct platform_device *pdev)
return -EINVAL;
}
+ for (i = 0; i < num_reg_data; i++) {
+ struct regulator_desc *d;
+
+ d = &ic_data->regulator_data[i].desc;
+
+
+ if (may_have_irqs) {
+ if (d->id >= ARRAY_SIZE(bd9576_ops_arr))
+ return -EINVAL;
+
+ d->ops = bd9576_ops_arr[d->id];
+ } else {
+ if (d->id >= ARRAY_SIZE(bd9573_ops_arr))
+ return -EINVAL;
+
+ d->ops = bd9573_ops_arr[d->id];
+ }
+ }
+
config.dev = pdev->dev.parent;
config.regmap = regmap;
+ config.driver_data = ic_data;
for (i = 0; i < num_reg_data; i++) {
- const struct regulator_desc *desc;
- struct regulator_dev *rdev;
- const struct bd957x_regulator_data *r;
+ struct bd957x_regulator_data *r = &ic_data->regulator_data[i];
+ const struct regulator_desc *desc = &r->desc;
- r = &reg_data[i];
- desc = &r->desc;
-
- rdev = devm_regulator_register(&pdev->dev, desc, &config);
- if (IS_ERR(rdev)) {
+ r->rdev = devm_regulator_register(&pdev->dev, desc,
+ &config);
+ if (IS_ERR(r->rdev)) {
dev_err(&pdev->dev,
"failed to register %s regulator\n",
desc->name);
- return PTR_ERR(rdev);
+ return PTR_ERR(r->rdev);
}
/*
* Clear the VOUT1 GPIO setting - rest of the regulators do not
* support GPIO control
*/
config.ena_gpiod = NULL;
+
+ if (!may_have_irqs)
+ continue;
+
+ rdevs[i] = r->rdev;
+ if (i < BD957X_VOUTS1)
+ ovd_devs[i] = r->rdev;
}
+ if (may_have_irqs) {
+ void *ret;
+ /*
+ * We can add both the possible error and warning flags here
+ * because the core uses these only for status clearing and
+ * if we use warnings - errors are always clear and the other
+ * way around. We can also add CURRENT flag for all regulators
+ * because it is never set if it is not supported. Same applies
+ * to setting UVD for VoutS1 - it is not accidentally cleared
+ * as it is never set.
+ */
+ int uvd_errs = REGULATOR_ERROR_UNDER_VOLTAGE |
+ REGULATOR_ERROR_UNDER_VOLTAGE_WARN |
+ REGULATOR_ERROR_OVER_CURRENT |
+ REGULATOR_ERROR_OVER_CURRENT_WARN;
+ int ovd_errs = REGULATOR_ERROR_OVER_VOLTAGE_WARN |
+ REGULATOR_ERROR_REGULATION_OUT;
+ int temp_errs = REGULATOR_ERROR_OVER_TEMP |
+ REGULATOR_ERROR_OVER_TEMP_WARN;
+ int irq;
+
+ irq = platform_get_irq_byname(pdev, "bd9576-uvd");
+
+ /* Register notifiers - can fail if IRQ is not given */
+ ret = devm_regulator_irq_helper(&pdev->dev, &bd9576_notif_uvd,
+ irq, 0, uvd_errs, NULL,
+ &rdevs[0],
+ BD9576_NUM_REGULATORS);
+ if (IS_ERR(ret)) {
+ if (PTR_ERR(ret) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ dev_warn(&pdev->dev, "UVD disabled %pe\n", ret);
+ }
+
+ irq = platform_get_irq_byname(pdev, "bd9576-ovd");
+
+ ret = devm_regulator_irq_helper(&pdev->dev, &bd9576_notif_ovd,
+ irq, 0, ovd_errs, NULL,
+ &ovd_devs[0],
+ BD9576_NUM_OVD_REGULATORS);
+ if (IS_ERR(ret)) {
+ if (PTR_ERR(ret) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ dev_warn(&pdev->dev, "OVD disabled %pe\n", ret);
+ }
+ irq = platform_get_irq_byname(pdev, "bd9576-temp");
+
+ ret = devm_regulator_irq_helper(&pdev->dev, &bd9576_notif_temp,
+ irq, 0, temp_errs, NULL,
+ &rdevs[0],
+ BD9576_NUM_REGULATORS);
+ if (IS_ERR(ret)) {
+ if (PTR_ERR(ret) == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+ dev_warn(&pdev->dev, "Thermal warning disabled %pe\n",
+ ret);
+ }
+ }
return 0;
}
static const struct platform_device_id bd957x_pmic_id[] = {
- { "bd9573-pmic", ROHM_CHIP_TYPE_BD9573 },
- { "bd9576-pmic", ROHM_CHIP_TYPE_BD9576 },
+ { "bd9573-regulator", ROHM_CHIP_TYPE_BD9573 },
+ { "bd9576-regulator", ROHM_CHIP_TYPE_BD9576 },
{ },
};
MODULE_DEVICE_TABLE(platform, bd957x_pmic_id);
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index 4d1463d2361a..750b538858d6 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -33,17 +33,6 @@
#include "dummy.h"
#include "internal.h"
-#define rdev_crit(rdev, fmt, ...) \
- pr_crit("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__)
-#define rdev_err(rdev, fmt, ...) \
- pr_err("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__)
-#define rdev_warn(rdev, fmt, ...) \
- pr_warn("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__)
-#define rdev_info(rdev, fmt, ...) \
- pr_info("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__)
-#define rdev_dbg(rdev, fmt, ...) \
- pr_debug("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__)
-
static DEFINE_WW_CLASS(regulator_ww_class);
static DEFINE_MUTEX(regulator_nesting_mutex);
static DEFINE_MUTEX(regulator_list_mutex);
@@ -117,6 +106,7 @@ const char *rdev_get_name(struct regulator_dev *rdev)
else
return "";
}
+EXPORT_SYMBOL_GPL(rdev_get_name);
static bool have_full_constraints(void)
{
@@ -1305,6 +1295,52 @@ static int machine_constraints_current(struct regulator_dev *rdev,
static int _regulator_do_enable(struct regulator_dev *rdev);
+static int notif_set_limit(struct regulator_dev *rdev,
+ int (*set)(struct regulator_dev *, int, int, bool),
+ int limit, int severity)
+{
+ bool enable;
+
+ if (limit == REGULATOR_NOTIF_LIMIT_DISABLE) {
+ enable = false;
+ limit = 0;
+ } else {
+ enable = true;
+ }
+
+ if (limit == REGULATOR_NOTIF_LIMIT_ENABLE)
+ limit = 0;
+
+ return set(rdev, limit, severity, enable);
+}
+
+static int handle_notify_limits(struct regulator_dev *rdev,
+ int (*set)(struct regulator_dev *, int, int, bool),
+ struct notification_limit *limits)
+{
+ int ret = 0;
+
+ if (!set)
+ return -EOPNOTSUPP;
+
+ if (limits->prot)
+ ret = notif_set_limit(rdev, set, limits->prot,
+ REGULATOR_SEVERITY_PROT);
+ if (ret)
+ return ret;
+
+ if (limits->err)
+ ret = notif_set_limit(rdev, set, limits->err,
+ REGULATOR_SEVERITY_ERR);
+ if (ret)
+ return ret;
+
+ if (limits->warn)
+ ret = notif_set_limit(rdev, set, limits->warn,
+ REGULATOR_SEVERITY_WARN);
+
+ return ret;
+}
/**
* set_machine_constraints - sets regulator constraints
* @rdev: regulator source
@@ -1390,9 +1426,27 @@ static int set_machine_constraints(struct regulator_dev *rdev)
}
}
+ /*
+ * Existing logic does not warn if over_current_protection is given as
+ * a constraint but driver does not support that. I think we should
+ * warn about this type of issues as it is possible someone changes
+ * PMIC on board to another type - and the another PMIC's driver does
+ * not support setting protection. Board composer may happily believe
+ * the DT limits are respected - especially if the new PMIC HW also
+ * supports protection but the driver does not. I won't change the logic
+ * without hearing more experienced opinion on this though.
+ *
+ * If warning is seen as a good idea then we can merge handling the
+ * over-curret protection and detection and get rid of this special
+ * handling.
+ */
if (rdev->constraints->over_current_protection
&& ops->set_over_current_protection) {
- ret = ops->set_over_current_protection(rdev);
+ int lim = rdev->constraints->over_curr_limits.prot;
+
+ ret = ops->set_over_current_protection(rdev, lim,
+ REGULATOR_SEVERITY_PROT,
+ true);
if (ret < 0) {
rdev_err(rdev, "failed to set over current protection: %pe\n",
ERR_PTR(ret));
@@ -1400,6 +1454,62 @@ static int set_machine_constraints(struct regulator_dev *rdev)
}
}
+ if (rdev->constraints->over_current_detection)
+ ret = handle_notify_limits(rdev,
+ ops->set_over_current_protection,
+ &rdev->constraints->over_curr_limits);
+ if (ret) {
+ if (ret != -EOPNOTSUPP) {
+ rdev_err(rdev, "failed to set over current limits: %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ rdev_warn(rdev,
+ "IC does not support requested over-current limits\n");
+ }
+
+ if (rdev->constraints->over_voltage_detection)
+ ret = handle_notify_limits(rdev,
+ ops->set_over_voltage_protection,
+ &rdev->constraints->over_voltage_limits);
+ if (ret) {
+ if (ret != -EOPNOTSUPP) {
+ rdev_err(rdev, "failed to set over voltage limits %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ rdev_warn(rdev,
+ "IC does not support requested over voltage limits\n");
+ }
+
+ if (rdev->constraints->under_voltage_detection)
+ ret = handle_notify_limits(rdev,
+ ops->set_under_voltage_protection,
+ &rdev->constraints->under_voltage_limits);
+ if (ret) {
+ if (ret != -EOPNOTSUPP) {
+ rdev_err(rdev, "failed to set under voltage limits %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ rdev_warn(rdev,
+ "IC does not support requested under voltage limits\n");
+ }
+
+ if (rdev->constraints->over_temp_detection)
+ ret = handle_notify_limits(rdev,
+ ops->set_thermal_protection,
+ &rdev->constraints->temp_limits);
+ if (ret) {
+ if (ret != -EOPNOTSUPP) {
+ rdev_err(rdev, "failed to set temperature limits %pe\n",
+ ERR_PTR(ret));
+ return ret;
+ }
+ rdev_warn(rdev,
+ "IC does not support requested temperature limits\n");
+ }
+
if (rdev->constraints->active_discharge && ops->set_active_discharge) {
bool ad_state = (rdev->constraints->active_discharge ==
REGULATOR_ACTIVE_DISCHARGE_ENABLE) ? true : false;
@@ -4393,22 +4503,36 @@ unsigned int regulator_get_mode(struct regulator *regulator)
}
EXPORT_SYMBOL_GPL(regulator_get_mode);
+static int rdev_get_cached_err_flags(struct regulator_dev *rdev)
+{
+ int ret = 0;
+
+ if (rdev->use_cached_err) {
+ spin_lock(&rdev->err_lock);
+ ret = rdev->cached_err;
+ spin_unlock(&rdev->err_lock);
+ }
+ return ret;
+}
+
static int _regulator_get_error_flags(struct regulator_dev *rdev,
unsigned int *flags)
{
- int ret;
+ int cached_flags, ret = 0;
regulator_lock(rdev);
- /* sanity check */
- if (!rdev->desc->ops->get_error_flags) {
+ cached_flags = rdev_get_cached_err_flags(rdev);
+
+ if (rdev->desc->ops->get_error_flags)
+ ret = rdev->desc->ops->get_error_flags(rdev, flags);
+ else if (!rdev->use_cached_err)
ret = -EINVAL;
- goto out;
- }
- ret = rdev->desc->ops->get_error_flags(rdev, flags);
-out:
+ *flags |= cached_flags;
+
regulator_unlock(rdev);
+
return ret;
}
@@ -5241,6 +5365,7 @@ regulator_register(const struct regulator_desc *regulator_desc,
goto rinse;
}
device_initialize(&rdev->dev);
+ spin_lock_init(&rdev->err_lock);
/*
* Duplicate the config so the driver could override it after
diff --git a/drivers/regulator/devres.c b/drivers/regulator/devres.c
index 3091210889e3..a8de0aa88bad 100644
--- a/drivers/regulator/devres.c
+++ b/drivers/regulator/devres.c
@@ -481,3 +481,55 @@ void devm_regulator_unregister_notifier(struct regulator *regulator,
WARN_ON(rc);
}
EXPORT_SYMBOL_GPL(devm_regulator_unregister_notifier);
+
+static void regulator_irq_helper_drop(void *res)
+{
+ regulator_irq_helper_cancel(&res);
+}
+
+/**
+ * devm_regulator_irq_helper - resource managed registration of IRQ based
+ * regulator event/error notifier
+ *
+ * @dev: device to which lifetime the helper's lifetime is
+ * bound.
+ * @d: IRQ helper descriptor.
+ * @irq: IRQ used to inform events/errors to be notified.
+ * @irq_flags: Extra IRQ flags to be OR'ed with the default
+ * IRQF_ONESHOT when requesting the (threaded) irq.
+ * @common_errs: Errors which can be flagged by this IRQ for all rdevs.
+ * When IRQ is re-enabled these errors will be cleared
+ * from all associated regulators
+ * @per_rdev_errs: Optional error flag array describing errors specific
+ * for only some of the regulators. These errors will be
+ * or'ed with common errors. If this is given the array
+ * should contain rdev_amount flags. Can be set to NULL
+ * if there is no regulator specific error flags for this
+ * IRQ.
+ * @rdev: Array of pointers to regulators associated with this
+ * IRQ.
+ * @rdev_amount: Amount of regulators associated with this IRQ.
+ *
+ * Return: handle to irq_helper or an ERR_PTR() encoded error code.
+ */
+void *devm_regulator_irq_helper(struct device *dev,
+ const struct regulator_irq_desc *d, int irq,
+ int irq_flags, int common_errs,
+ int *per_rdev_errs,
+ struct regulator_dev **rdev, int rdev_amount)
+{
+ void *ptr;
+ int ret;
+
+ ptr = regulator_irq_helper(dev, d, irq, irq_flags, common_errs,
+ per_rdev_errs, rdev, rdev_amount);
+ if (IS_ERR(ptr))
+ return ptr;
+
+ ret = devm_add_action_or_reset(dev, regulator_irq_helper_drop, ptr);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return ptr;
+}
+EXPORT_SYMBOL_GPL(devm_regulator_irq_helper);
diff --git a/drivers/regulator/internal.h b/drivers/regulator/internal.h
index 2391b565ef11..1e9c71642143 100644
--- a/drivers/regulator/internal.h
+++ b/drivers/regulator/internal.h
@@ -15,6 +15,17 @@
#define REGULATOR_STATES_NUM (PM_SUSPEND_MAX + 1)
+#define rdev_crit(rdev, fmt, ...) \
+ pr_crit("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__)
+#define rdev_err(rdev, fmt, ...) \
+ pr_err("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__)
+#define rdev_warn(rdev, fmt, ...) \
+ pr_warn("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__)
+#define rdev_info(rdev, fmt, ...) \
+ pr_info("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__)
+#define rdev_dbg(rdev, fmt, ...) \
+ pr_debug("%s: " fmt, rdev_get_name(rdev), ##__VA_ARGS__)
+
struct regulator_voltage {
int min_uV;
int max_uV;
diff --git a/drivers/regulator/irq_helpers.c b/drivers/regulator/irq_helpers.c
new file mode 100644
index 000000000000..fabe2e53093e
--- /dev/null
+++ b/drivers/regulator/irq_helpers.c
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2021 ROHM Semiconductors
+// regulator IRQ based event notification helpers
+//
+// Logic has been partially adapted from qcom-labibb driver.
+//
+// Author: Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/regulator/driver.h>
+
+#include "internal.h"
+
+#define REGULATOR_FORCED_SAFETY_SHUTDOWN_WAIT_MS 10000
+
+struct regulator_irq {
+ struct regulator_irq_data rdata;
+ struct regulator_irq_desc desc;
+ int irq;
+ int retry_cnt;
+ struct delayed_work isr_work;
+};
+
+/*
+ * Should only be called from threaded handler to prevent potential deadlock
+ */
+static void rdev_flag_err(struct regulator_dev *rdev, int err)
+{
+ spin_lock(&rdev->err_lock);
+ rdev->cached_err |= err;
+ spin_unlock(&rdev->err_lock);
+}
+
+static void rdev_clear_err(struct regulator_dev *rdev, int err)
+{
+ spin_lock(&rdev->err_lock);
+ rdev->cached_err &= ~err;
+ spin_unlock(&rdev->err_lock);
+}
+
+static void regulator_notifier_isr_work(struct work_struct *work)
+{
+ struct regulator_irq *h;
+ struct regulator_irq_desc *d;
+ struct regulator_irq_data *rid;
+ int ret = 0;
+ int tmo, i;
+ int num_rdevs;
+
+ h = container_of(work, struct regulator_irq,
+ isr_work.work);
+ d = &h->desc;
+ rid = &h->rdata;
+ num_rdevs = rid->num_states;
+
+reread:
+ if (d->fatal_cnt && h->retry_cnt > d->fatal_cnt) {
+ if (!d->die)
+ return hw_protection_shutdown("Regulator HW failure? - no IC recovery",
+ REGULATOR_FORCED_SAFETY_SHUTDOWN_WAIT_MS);
+ ret = d->die(rid);
+ /*
+ * If the 'last resort' IC recovery failed we will have
+ * nothing else left to do...
+ */
+ if (ret)
+ return hw_protection_shutdown("Regulator HW failure. IC recovery failed",
+ REGULATOR_FORCED_SAFETY_SHUTDOWN_WAIT_MS);
+
+ /*
+ * If h->die() was implemented we assume recovery has been
+ * attempted (probably regulator was shut down) and we
+ * just enable IRQ and bail-out.
+ */
+ goto enable_out;
+ }
+ if (d->renable) {
+ ret = d->renable(rid);
+
+ if (ret == REGULATOR_FAILED_RETRY) {
+ /* Driver could not get current status */
+ h->retry_cnt++;
+ if (!d->reread_ms)
+ goto reread;
+
+ tmo = d->reread_ms;
+ goto reschedule;
+ }
+
+ if (ret) {
+ /*
+ * IC status reading succeeded. update error info
+ * just in case the renable changed it.
+ */
+ for (i = 0; i < num_rdevs; i++) {
+ struct regulator_err_state *stat;
+ struct regulator_dev *rdev;
+
+ stat = &rid->states[i];
+ rdev = stat->rdev;
+ rdev_clear_err(rdev, (~stat->errors) &
+ stat->possible_errs);
+ }
+ h->retry_cnt++;
+ /*
+ * The IC indicated problem is still ON - no point in
+ * re-enabling the IRQ. Retry later.
+ */
+ tmo = d->irq_off_ms;
+ goto reschedule;
+ }
+ }
+
+ /*
+ * Either IC reported problem cleared or no status checker was provided.
+ * If problems are gone - good. If not - then the IRQ will fire again
+ * and we'll have a new nice loop. In any case we should clear error
+ * flags here and re-enable IRQs.
+ */
+ for (i = 0; i < num_rdevs; i++) {
+ struct regulator_err_state *stat;
+ struct regulator_dev *rdev;
+
+ stat = &rid->states[i];
+ rdev = stat->rdev;
+ rdev_clear_err(rdev, stat->possible_errs);
+ }
+
+ /*
+ * Things have been seemingly successful => zero retry-counter.
+ */
+ h->retry_cnt = 0;
+
+enable_out:
+ enable_irq(h->irq);
+
+ return;
+
+reschedule:
+ if (!d->high_prio)
+ mod_delayed_work(system_wq, &h->isr_work,
+ msecs_to_jiffies(tmo));
+ else
+ mod_delayed_work(system_highpri_wq, &h->isr_work,
+ msecs_to_jiffies(tmo));
+}
+
+static irqreturn_t regulator_notifier_isr(int irq, void *data)
+{
+ struct regulator_irq *h = data;
+ struct regulator_irq_desc *d;
+ struct regulator_irq_data *rid;
+ unsigned long rdev_map = 0;
+ int num_rdevs;
+ int ret, i;
+
+ d = &h->desc;
+ rid = &h->rdata;
+ num_rdevs = rid->num_states;
+
+ if (d->fatal_cnt)
+ h->retry_cnt++;
+
+ /*
+ * we spare a few cycles by not clearing statuses prior to this call.
+ * The IC driver must initialize the status buffers for rdevs
+ * which it indicates having active events via rdev_map.
+ *
+ * Maybe we should just to be on a safer side(?)
+ */
+ ret = d->map_event(irq, rid, &rdev_map);
+
+ /*
+ * If status reading fails (which is unlikely) we don't ack/disable
+ * IRQ but just increase fail count and retry when IRQ fires again.
+ * If retry_count exceeds the given safety limit we call IC specific die
+ * handler which can try disabling regulator(s).
+ *
+ * If no die handler is given we will just bug() as a last resort.
+ *
+ * We could try disabling all associated rdevs - but we might shoot
+ * ourselves in the head and leave the problematic regulator enabled. So
+ * if IC has no die-handler populated we just assume the regulator
+ * can't be disabled.
+ */
+ if (unlikely(ret == REGULATOR_FAILED_RETRY))
+ goto fail_out;
+
+ h->retry_cnt = 0;
+ /*
+ * Let's not disable IRQ if there were no status bits for us. We'd
+ * better leave spurious IRQ handling to genirq
+ */
+ if (ret || !rdev_map)
+ return IRQ_NONE;
+
+ /*
+ * Some events are bogus if the regulator is disabled. Skip such events
+ * if all relevant regulators are disabled
+ */
+ if (d->skip_off) {
+ for_each_set_bit(i, &rdev_map, num_rdevs) {
+ struct regulator_dev *rdev;
+ const struct regulator_ops *ops;
+
+ rdev = rid->states[i].rdev;
+ ops = rdev->desc->ops;
+
+ /*
+ * If any of the flagged regulators is enabled we do
+ * handle this
+ */
+ if (ops->is_enabled(rdev))
+ break;
+ }
+ if (i == num_rdevs)
+ return IRQ_NONE;
+ }
+
+ /* Disable IRQ if HW keeps line asserted */
+ if (d->irq_off_ms)
+ disable_irq_nosync(irq);
+
+ /*
+ * IRQ seems to be for us. Let's fire correct notifiers / store error
+ * flags
+ */
+ for_each_set_bit(i, &rdev_map, num_rdevs) {
+ struct regulator_err_state *stat;
+ struct regulator_dev *rdev;
+
+ stat = &rid->states[i];
+ rdev = stat->rdev;
+
+ rdev_dbg(rdev, "Sending regulator notification EVT 0x%lx\n",
+ stat->notifs);
+
+ regulator_notifier_call_chain(rdev, stat->notifs, NULL);
+ rdev_flag_err(rdev, stat->errors);
+ }
+
+ if (d->irq_off_ms) {
+ if (!d->high_prio)
+ schedule_delayed_work(&h->isr_work,
+ msecs_to_jiffies(d->irq_off_ms));
+ else
+ mod_delayed_work(system_highpri_wq,
+ &h->isr_work,
+ msecs_to_jiffies(d->irq_off_ms));
+ }
+
+ return IRQ_HANDLED;
+
+fail_out:
+ if (d->fatal_cnt && h->retry_cnt > d->fatal_cnt) {
+ /* If we have no recovery, just try shut down straight away */
+ if (!d->die) {
+ hw_protection_shutdown("Regulator failure. Retry count exceeded",
+ REGULATOR_FORCED_SAFETY_SHUTDOWN_WAIT_MS);
+ } else {
+ ret = d->die(rid);
+ /* If die() failed shut down as a last attempt to save the HW */
+ if (ret)
+ hw_protection_shutdown("Regulator failure. Recovery failed",
+ REGULATOR_FORCED_SAFETY_SHUTDOWN_WAIT_MS);
+ }
+ }
+
+ return IRQ_NONE;
+}
+
+static int init_rdev_state(struct device *dev, struct regulator_irq *h,
+ struct regulator_dev **rdev, int common_err,
+ int *rdev_err, int rdev_amount)
+{
+ int i;
+
+ h->rdata.states = devm_kzalloc(dev, sizeof(*h->rdata.states) *
+ rdev_amount, GFP_KERNEL);
+ if (!h->rdata.states)
+ return -ENOMEM;
+
+ h->rdata.num_states = rdev_amount;
+ h->rdata.data = h->desc.data;
+
+ for (i = 0; i < rdev_amount; i++) {
+ h->rdata.states[i].possible_errs = common_err;
+ if (rdev_err)
+ h->rdata.states[i].possible_errs |= *rdev_err++;
+ h->rdata.states[i].rdev = *rdev++;
+ }
+
+ return 0;
+}
+
+static void init_rdev_errors(struct regulator_irq *h)
+{
+ int i;
+
+ for (i = 0; i < h->rdata.num_states; i++)
+ if (h->rdata.states[i].possible_errs)
+ h->rdata.states[i].rdev->use_cached_err = true;
+}
+
+/**
+ * regulator_irq_helper - register IRQ based regulator event/error notifier
+ *
+ * @dev: device providing the IRQs
+ * @d: IRQ helper descriptor.
+ * @irq: IRQ used to inform events/errors to be notified.
+ * @irq_flags: Extra IRQ flags to be OR'ed with the default
+ * IRQF_ONESHOT when requesting the (threaded) irq.
+ * @common_errs: Errors which can be flagged by this IRQ for all rdevs.
+ * When IRQ is re-enabled these errors will be cleared
+ * from all associated regulators
+ * @per_rdev_errs: Optional error flag array describing errors specific
+ * for only some of the regulators. These errors will be
+ * or'ed with common errors. If this is given the array
+ * should contain rdev_amount flags. Can be set to NULL
+ * if there is no regulator specific error flags for this
+ * IRQ.
+ * @rdev: Array of pointers to regulators associated with this
+ * IRQ.
+ * @rdev_amount: Amount of regulators associated with this IRQ.
+ *
+ * Return: handle to irq_helper or an ERR_PTR() encoded error code.
+ */
+void *regulator_irq_helper(struct device *dev,
+ const struct regulator_irq_desc *d, int irq,
+ int irq_flags, int common_errs, int *per_rdev_errs,
+ struct regulator_dev **rdev, int rdev_amount)
+{
+ struct regulator_irq *h;
+ int ret;
+
+ if (!rdev_amount || !d || !d->map_event || !d->name)
+ return ERR_PTR(-EINVAL);
+
+ h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL);
+ if (!h)
+ return ERR_PTR(-ENOMEM);
+
+ h->irq = irq;
+ h->desc = *d;
+
+ ret = init_rdev_state(dev, h, rdev, common_errs, per_rdev_errs,
+ rdev_amount);
+ if (ret)
+ return ERR_PTR(ret);
+
+ init_rdev_errors(h);
+
+ if (h->desc.irq_off_ms)
+ INIT_DELAYED_WORK(&h->isr_work, regulator_notifier_isr_work);
+
+ ret = request_threaded_irq(h->irq, NULL, regulator_notifier_isr,
+ IRQF_ONESHOT | irq_flags, h->desc.name, h);
+ if (ret) {
+ dev_err(dev, "Failed to request IRQ %d\n", irq);
+
+ return ERR_PTR(ret);
+ }
+
+ return h;
+}
+EXPORT_SYMBOL_GPL(regulator_irq_helper);
+
+/**
+ * regulator_irq_helper_cancel - drop IRQ based regulator event/error notifier
+ *
+ * @handle: Pointer to handle returned by a successful call to
+ * regulator_irq_helper(). Will be NULLed upon return.
+ *
+ * The associated IRQ is released and work is cancelled when the function
+ * returns.
+ */
+void regulator_irq_helper_cancel(void **handle)
+{
+ if (handle && *handle) {
+ struct regulator_irq *h = *handle;
+
+ free_irq(h->irq, h);
+ if (h->desc.irq_off_ms)
+ cancel_delayed_work_sync(&h->isr_work);
+
+ h = NULL;
+ }
+}
+EXPORT_SYMBOL_GPL(regulator_irq_helper_cancel);
diff --git a/drivers/regulator/of_regulator.c b/drivers/regulator/of_regulator.c
index 49f6c05fee34..f54d4f176882 100644
--- a/drivers/regulator/of_regulator.c
+++ b/drivers/regulator/of_regulator.c
@@ -21,6 +21,62 @@ static const char *const regulator_states[PM_SUSPEND_MAX + 1] = {
[PM_SUSPEND_MAX] = "regulator-state-disk",
};
+static void fill_limit(int *limit, int val)
+{
+ if (val)
+ if (val == 1)
+ *limit = REGULATOR_NOTIF_LIMIT_ENABLE;
+ else
+ *limit = val;
+ else
+ *limit = REGULATOR_NOTIF_LIMIT_DISABLE;
+}
+
+static void of_get_regulator_prot_limits(struct device_node *np,
+ struct regulation_constraints *constraints)
+{
+ u32 pval;
+ int i;
+ static const char *const props[] = {
+ "regulator-oc-%s-microamp",
+ "regulator-ov-%s-microvolt",
+ "regulator-temp-%s-kelvin",
+ "regulator-uv-%s-microvolt",
+ };
+ struct notification_limit *limits[] = {
+ &constraints->over_curr_limits,
+ &constraints->over_voltage_limits,
+ &constraints->temp_limits,
+ &constraints->under_voltage_limits,
+ };
+ bool set[4] = {0};
+
+ /* Protection limits: */
+ for (i = 0; i < ARRAY_SIZE(props); i++) {
+ char prop[255];
+ bool found;
+ int j;
+ static const char *const lvl[] = {
+ "protection", "error", "warn"
+ };
+ int *l[] = {
+ &limits[i]->prot, &limits[i]->err, &limits[i]->warn,
+ };
+
+ for (j = 0; j < ARRAY_SIZE(lvl); j++) {
+ snprintf(prop, 255, props[i], lvl[j]);
+ found = !of_property_read_u32(np, prop, &pval);
+ if (found)
+ fill_limit(l[j], pval);
+ set[i] |= found;
+ }
+ }
+ constraints->over_current_detection = set[0];
+ constraints->over_voltage_detection = set[1];
+ constraints->over_temp_detection = set[2];
+ constraints->under_voltage_detection = set[3];
+}
+
static int of_get_regulation_constraints(struct device *dev,
struct device_node *np,
struct regulator_init_data **init_data,
@@ -188,6 +244,8 @@ static int of_get_regulation_constraints(struct device *dev,
constraints->over_current_protection = of_property_read_bool(np,
"regulator-over-current-protection");
+ of_get_regulator_prot_limits(np, constraints);
+
for (i = 0; i < ARRAY_SIZE(regulator_states); i++) {
switch (i) {
case PM_SUSPEND_MEM:
diff --git a/drivers/regulator/qcom-labibb-regulator.c b/drivers/regulator/qcom-labibb-regulator.c
index de25e3279b4b..b3da0dc58782 100644
--- a/drivers/regulator/qcom-labibb-regulator.c
+++ b/drivers/regulator/qcom-labibb-regulator.c
@@ -307,13 +307,21 @@ end:
return IRQ_HANDLED;
}
-static int qcom_labibb_set_ocp(struct regulator_dev *rdev)
+static int qcom_labibb_set_ocp(struct regulator_dev *rdev, int lim,
+ int severity, bool enable)
{
struct labibb_regulator *vreg = rdev_get_drvdata(rdev);
char *ocp_irq_name;
u32 irq_flags = IRQF_ONESHOT;
int irq_trig_low, ret;
+ /*
+ * labibb supports only protection - and does not support setting
+ * limit. Furthermore, we don't support disabling protection.
+ */
+ if (lim || severity != REGULATOR_SEVERITY_PROT || !enable)
+ return -EINVAL;
+
/* If there is no OCP interrupt, there's nothing to set */
if (vreg->ocp_irq <= 0)
return -EINVAL;
diff --git a/drivers/regulator/qcom_spmi-regulator.c b/drivers/regulator/qcom_spmi-regulator.c
index 95677c51c1fa..41424a3366d0 100644
--- a/drivers/regulator/qcom_spmi-regulator.c
+++ b/drivers/regulator/qcom_spmi-regulator.c
@@ -595,11 +595,15 @@ static int spmi_regulator_vs_enable(struct regulator_dev *rdev)
return regulator_enable_regmap(rdev);
}
-static int spmi_regulator_vs_ocp(struct regulator_dev *rdev)
+static int spmi_regulator_vs_ocp(struct regulator_dev *rdev, int lim_uA,
+ int severity, bool enable)
{
struct spmi_regulator *vreg = rdev_get_drvdata(rdev);
u8 reg = SPMI_VS_OCP_OVERRIDE;
+ if (lim_uA || !enable || severity != REGULATOR_SEVERITY_PROT)
+ return -EINVAL;
+
return spmi_vreg_write(vreg, SPMI_VS_REG_OCP, &reg, 1);
}
diff --git a/drivers/regulator/stpmic1_regulator.c b/drivers/regulator/stpmic1_regulator.c
index cf10fdb72e32..2d7597c76e4a 100644
--- a/drivers/regulator/stpmic1_regulator.c
+++ b/drivers/regulator/stpmic1_regulator.c
@@ -32,7 +32,8 @@ struct stpmic1_regulator_cfg {
static int stpmic1_set_mode(struct regulator_dev *rdev, unsigned int mode);
static unsigned int stpmic1_get_mode(struct regulator_dev *rdev);
-static int stpmic1_set_icc(struct regulator_dev *rdev);
+static int stpmic1_set_icc(struct regulator_dev *rdev, int lim, int severity,
+ bool enable);
static unsigned int stpmic1_map_mode(unsigned int mode);
enum {
@@ -491,11 +492,26 @@ static int stpmic1_set_mode(struct regulator_dev *rdev, unsigned int mode)
STPMIC1_BUCK_MODE_LP, value);
}
-static int stpmic1_set_icc(struct regulator_dev *rdev)
+static int stpmic1_set_icc(struct regulator_dev *rdev, int lim, int severity,
+ bool enable)
{
struct stpmic1_regulator_cfg *cfg = rdev_get_drvdata(rdev);
struct regmap *regmap = rdev_get_regmap(rdev);
+ /*
+ * The code seems like one bit in a register controls whether OCP is
+ * enabled. So we might be able to turn it off here is if that
+ * was requested. I won't support this because I don't have the HW.
+ * Feel free to try and implement if you have the HW and need kernel
+ * to disable this.
+ *
+ * Also, I don't know if limit can be configured or if we support
+ * error/warning instead of protect. So I just keep existing logic
+ * and assume no.
+ */
+ if (lim || severity != REGULATOR_SEVERITY_PROT || !enable)
+ return -EINVAL;
+
/* enable switch off in case of over current */
return regmap_update_bits(regmap, cfg->icc_reg, cfg->icc_mask,
cfg->icc_mask);