[RFC PATCH 1/2] regulator: Add support for software node connections

From: Daniel Scally
Date: Thu Jul 08 2021 - 18:42:46 EST


On some x86 platforms, firmware provided by the OEM does not adequately
describe the constraints of regulator resources nor their connections to
consuming devices via ACPI. To compensate, extend the regulator framework
to allow discovery of constraints and supply-consumer relationships via
software node references and properties. This will allow us to define
those platform specific connections so that the resources can be used in
the usual way.

Signed-off-by: Daniel Scally <djrscally@xxxxxxxxx>
---

swnode_get_regulator_constraints() here is not as extensive as the equivalent
function in of_regulator...I could include all that now, but it didn't seem
worth it so we don't know what any of those values should be.

drivers/regulator/Kconfig | 6 ++
drivers/regulator/Makefile | 1 +
drivers/regulator/core.c | 23 +++++
drivers/regulator/swnode_regulator.c | 111 +++++++++++++++++++++
include/linux/regulator/swnode_regulator.h | 33 ++++++
5 files changed, 174 insertions(+)
create mode 100644 drivers/regulator/swnode_regulator.c
create mode 100644 include/linux/regulator/swnode_regulator.h

diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig
index 9d84d9245490..7e0351f8fe38 100644
--- a/drivers/regulator/Kconfig
+++ b/drivers/regulator/Kconfig
@@ -1171,6 +1171,12 @@ config REGULATOR_SY8827N
help
This driver supports SY8827N single output regulator.

+config REGULATOR_SWNODE
+ bool "Software Node Regulator Support"
+ help
+ This option configures support for providing regulator constraints and
+ mappings to consumers through the use of software nodes.
+
config REGULATOR_TPS51632
tristate "TI TPS51632 Power Regulator"
depends on I2C
diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile
index 580b015296ea..25cb7bbad652 100644
--- a/drivers/regulator/Makefile
+++ b/drivers/regulator/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_OF) += of_regulator.o
obj-$(CONFIG_REGULATOR_FIXED_VOLTAGE) += fixed.o
obj-$(CONFIG_REGULATOR_VIRTUAL_CONSUMER) += virtual.o
obj-$(CONFIG_REGULATOR_USERSPACE_CONSUMER) += userspace-consumer.o
+obj-$(CONFIG_REGULATOR_SWNODE) += swnode_regulator.o

obj-$(CONFIG_REGULATOR_88PG86X) += 88pg86x.o
obj-$(CONFIG_REGULATOR_88PM800) += 88pm800-regulator.o
diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c
index f192bf19492e..6e10f511db80 100644
--- a/drivers/regulator/core.c
+++ b/drivers/regulator/core.c
@@ -20,11 +20,13 @@
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/regmap.h>
+#include <linux/property.h>
#include <linux/regulator/of_regulator.h>
#include <linux/regulator/consumer.h>
#include <linux/regulator/coupler.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
+#include <linux/regulator/swnode_regulator.h>
#include <linux/module.h>

#define CREATE_TRACE_POINTS
@@ -1763,6 +1765,7 @@ static struct regulator_dev *regulator_dev_lookup(struct device *dev,
const char *supply)
{
struct regulator_dev *r = NULL;
+ struct fwnode_handle *swnode;
struct device_node *node;
struct regulator_map *map;
const char *devname = NULL;
@@ -1785,6 +1788,22 @@ static struct regulator_dev *regulator_dev_lookup(struct device *dev,
}
}

+ /* next, try software nodes */
+ if (dev && dev->fwnode && is_software_node(dev->fwnode->secondary)) {
+ swnode = swnode_get_regulator_node(dev, supply);
+ if (!IS_ERR(swnode)) {
+ r = swnode_find_regulator_by_node(swnode);
+ if (r)
+ return r;
+
+ /*
+ * We have a node, but there is no device.
+ * assume it has not registered yet.
+ */
+ return ERR_PTR(-EPROBE_DEFER);
+ }
+ }
+
/* if not found, try doing it non-dt way */
if (dev)
devname = dev_name(dev);
@@ -5242,6 +5261,10 @@ regulator_register(const struct regulator_desc *regulator_desc,
init_data = regulator_of_get_init_data(dev, regulator_desc, config,
&rdev->dev.of_node);

+ if (!init_data)
+ init_data = regulator_swnode_get_init_data(dev, regulator_desc,
+ config, &rdev->dev.fwnode);
+
/*
* Sometimes not all resources are probed already so we need to take
* that into account. This happens most the time if the ena_gpiod comes
diff --git a/drivers/regulator/swnode_regulator.c b/drivers/regulator/swnode_regulator.c
new file mode 100644
index 000000000000..75718de74366
--- /dev/null
+++ b/drivers/regulator/swnode_regulator.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Author: Dan Scally <djrscally@xxxxxxxxx> */
+
+#include <linux/property.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+
+#include "internal.h"
+
+static struct fwnode_handle *
+regulator_swnode_get_init_node(struct fwnode_handle *fwnode,
+ const struct regulator_desc *desc)
+{
+ const struct software_node *parent, *child;
+
+ parent = to_software_node(fwnode->secondary);
+
+ if (desc->regulators_node)
+ child = software_node_find_by_name(parent,
+ desc->regulators_node);
+ else
+ child = software_node_find_by_name(parent, desc->name);
+
+ return software_node_fwnode(child);
+}
+
+static int swnode_get_regulator_constraints(struct fwnode_handle *swnode,
+ struct regulator_init_data *init_data)
+{
+ struct regulation_constraints *constraints = &init_data->constraints;
+ u32 pval;
+ int ret;
+
+ ret = fwnode_property_read_string(swnode, "regulator-name", &constraints->name);
+ if (ret)
+ return ret;
+
+ if (!fwnode_property_read_u32(swnode, "regulator-min-microvolt", &pval))
+ constraints->min_uV = pval;
+
+ if (!fwnode_property_read_u32(swnode, "regulator-max-microvolt", &pval))
+ constraints->max_uV = pval;
+
+ /* Voltage change possible? */
+ if (constraints->min_uV != constraints->max_uV)
+ constraints->valid_ops_mask |= REGULATOR_CHANGE_VOLTAGE;
+
+ /* Do we have a voltage range, if so try to apply it? */
+ if (constraints->min_uV && constraints->max_uV)
+ constraints->apply_uV = true;
+
+ constraints->boot_on = fwnode_property_read_bool(swnode, "regulator-boot-on");
+ constraints->always_on = fwnode_property_read_bool(swnode, "regulator-always-on");
+ if (!constraints->always_on) /* status change should be possible. */
+ constraints->valid_ops_mask |= REGULATOR_CHANGE_STATUS;
+
+ return 0;
+}
+
+struct regulator_init_data *
+regulator_swnode_get_init_data(struct device *dev,
+ const struct regulator_desc *desc,
+ struct regulator_config *config,
+ struct fwnode_handle **regnode)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ struct regulator_init_data *init_data;
+ struct fwnode_handle *regulator;
+ int ret;
+
+ if (!fwnode || !is_software_node(fwnode->secondary))
+ return NULL;
+
+ regulator = regulator_swnode_get_init_node(fwnode, desc);
+ if (!regulator)
+ return NULL;
+
+ init_data = devm_kzalloc(dev, sizeof(*init_data), GFP_KERNEL);
+ if (!init_data)
+ return ERR_PTR(-ENOMEM);
+
+ ret = swnode_get_regulator_constraints(regulator, init_data);
+ if (ret)
+ return ERR_PTR(ret);
+
+ *regnode = regulator;
+
+ return init_data;
+}
+
+struct regulator_dev *
+swnode_find_regulator_by_node(struct fwnode_handle *swnode)
+{
+ struct device *dev;
+
+ dev = class_find_device_by_fwnode(&regulator_class, swnode);
+
+ return dev ? dev_to_rdev(dev) : NULL;
+}
+
+struct fwnode_handle *swnode_get_regulator_node(struct device *dev, const char *supply)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ char *prop_name;
+
+ prop_name = devm_kasprintf(dev, GFP_KERNEL, "%s-supply", supply);
+ if (!prop_name)
+ return ERR_PTR(-ENOMEM);
+
+ return fwnode_find_reference(fwnode->secondary, prop_name, 0);
+}
diff --git a/include/linux/regulator/swnode_regulator.h b/include/linux/regulator/swnode_regulator.h
new file mode 100644
index 000000000000..97527f7dbe80
--- /dev/null
+++ b/include/linux/regulator/swnode_regulator.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Author: Dan Scally <djrscally@xxxxxxxxx> */
+#ifndef __LINUX_SWNODE_REG_H
+#define __LINUX_SWNODE_REG_H
+
+#if defined(CONFIG_REGULATOR_SWNODE)
+struct regulator_init_data *regulator_swnode_get_init_data(struct device *dev,
+ const struct regulator_desc *desc,
+ struct regulator_config *config,
+ struct fwnode_handle **regnode);
+struct regulator_dev *swnode_find_regulator_by_node(struct fwnode_handle *swnode);
+struct fwnode_handle *swnode_get_regulator_node(struct device *dev, const char *supply);
+#else
+struct regulator_init_data *regulator_swnode_get_init_data(struct device *dev,
+ const struct regulator_desc *desc,
+ struct regulator_config *config)
+{
+ return NULL;
+}
+
+struct regulator_dev *swnode_find_regulator_by_node(struct fwnode_handle *swnode)
+{
+ return NULL;
+}
+
+struct fwnode_handle *swnode_get_regulator_node(struct device *dev, const char *supply)
+{
+ return NULL;
+}
+
+#endif
+
+#endif
--
2.25.1