diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2022-03-14 10:10:25 +0100 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2022-03-14 10:10:25 +0100 |
commit | 26dc1bf751724540716a4a17a80f7605ebf61b3a (patch) | |
tree | abdbff429227f48dfd0855dac8ad6381ee8b9ac1 /drivers/regulator | |
parent | d8ef1573dfc3475d4f5dffa37cb4444b5d6f21eb (diff) | |
parent | 79d2a2d4a4608fd0ca9f1eeb06e4aee0fb095f19 (diff) | |
download | barebox-26dc1bf751724540716a4a17a80f7605ebf61b3a.tar.gz barebox-26dc1bf751724540716a4a17a80f7605ebf61b3a.tar.xz |
Merge branch 'for-next/stm32'
Diffstat (limited to 'drivers/regulator')
-rw-r--r-- | drivers/regulator/Kconfig | 9 | ||||
-rw-r--r-- | drivers/regulator/Makefile | 1 | ||||
-rw-r--r-- | drivers/regulator/core.c | 2 | ||||
-rw-r--r-- | drivers/regulator/scmi-regulator.c | 391 |
4 files changed, 403 insertions, 0 deletions
diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index c468e45915..38a47b7bc2 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -55,4 +55,13 @@ config REGULATOR_ANATOP regulators. It is recommended that this option be enabled on i.MX6 platform. +config REGULATOR_ARM_SCMI + tristate "SCMI based regulator driver" + depends on ARM_SCMI_PROTOCOL && OFDEVICE + help + This adds the regulator driver support for ARM platforms using SCMI + protocol for device voltage management. + This driver uses SCMI Message Protocol driver to interact with the + firmware providing the device Voltage functionality. + endif diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index 7b5d527cb1..5eaa657ad1 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_REGULATOR_STPMIC1) += stpmic1_regulator.o obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o obj-$(CONFIG_REGULATOR_STM32_PWR) += stm32-pwr.o obj-$(CONFIG_REGULATOR_STM32_VREFBUF) += stm32-vrefbuf.o +obj-$(CONFIG_REGULATOR_ARM_SCMI) += scmi-regulator.o diff --git a/drivers/regulator/core.c b/drivers/regulator/core.c index a29fe90d8d..af8a0cb4fc 100644 --- a/drivers/regulator/core.c +++ b/drivers/regulator/core.c @@ -166,6 +166,8 @@ int of_regulator_register(struct regulator_dev *rd, struct device_node *node) rd->always_on = of_property_read_bool(node, "regulator-always-on"); name = of_get_property(node, "regulator-name", NULL); + if (!name) + name = node->name; ri = __regulator_register(rd, name); if (IS_ERR(ri)) diff --git a/drivers/regulator/scmi-regulator.c b/drivers/regulator/scmi-regulator.c new file mode 100644 index 0000000000..3e6fd3ec60 --- /dev/null +++ b/drivers/regulator/scmi-regulator.c @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// System Control and Management Interface (SCMI) based regulator driver +// +// Copyright (C) 2020-2021 ARM Ltd. +// +// Implements a regulator driver on top of the SCMI Voltage Protocol. +// +// The ARM SCMI Protocol aims in general to hide as much as possible all the +// underlying operational details while providing an abstracted interface for +// its users to operate upon: as a consequence the resulting operational +// capabilities and configurability of this regulator device are much more +// limited than the ones usually available on a standard physical regulator. +// +// The supported SCMI regulator ops are restricted to the bare minimum: +// +// - 'status_ops': enable/disable/is_enabled +// - 'voltage_ops': get_voltage_sel/set_voltage_sel +// list_voltage/map_voltage +// +// Each SCMI regulator instance is associated, through the means of a proper DT +// entry description, to a specific SCMI Voltage Domain. + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <common.h> +#include <of.h> +#include <regulator.h> +#include <linux/regulator/of_regulator.h> +#include <linux/scmi_protocol.h> +#include <linux/slab.h> +#include <linux/types.h> + +static const struct scmi_voltage_proto_ops *voltage_ops; + +struct scmi_reg_desc { + struct regulator_desc desc; + const char *name; + const char *supply_name; +}; + +struct scmi_regulator { + u32 id; + struct scmi_device *sdev; + struct scmi_protocol_handle *ph; + struct regulator_dev rdev; + struct device_node *device_node; + struct scmi_reg_desc sdesc; +}; + +struct scmi_regulator_info { + int num_doms; + struct scmi_regulator **sregv; +}; + +static inline struct scmi_regulator *to_scmi_regulator(struct regulator_dev *rdev) +{ + return container_of(rdev, struct scmi_regulator, rdev); +} + +static int scmi_reg_enable(struct regulator_dev *rdev) +{ + struct scmi_regulator *sreg = to_scmi_regulator(rdev); + + return voltage_ops->config_set(sreg->ph, sreg->id, + SCMI_VOLTAGE_ARCH_STATE_ON); +} + +static int scmi_reg_disable(struct regulator_dev *rdev) +{ + struct scmi_regulator *sreg = to_scmi_regulator(rdev); + + return voltage_ops->config_set(sreg->ph, sreg->id, + SCMI_VOLTAGE_ARCH_STATE_OFF); +} + +static int scmi_reg_is_enabled(struct regulator_dev *rdev) +{ + int ret; + u32 config; + struct scmi_regulator *sreg = to_scmi_regulator(rdev); + struct scmi_reg_desc *sdesc = &sreg->sdesc; + + ret = voltage_ops->config_get(sreg->ph, sreg->id, &config); + if (ret) { + dev_err(&sreg->sdev->dev, + "Error %d reading regulator %s status.\n", + ret, sdesc->name); + return ret; + } + + return config & SCMI_VOLTAGE_ARCH_STATE_ON; +} + +static int scmi_reg_get_voltage_sel(struct regulator_dev *rdev) +{ + int ret; + s32 volt_uV; + struct scmi_regulator *sreg = to_scmi_regulator(rdev); + + ret = voltage_ops->level_get(sreg->ph, sreg->id, &volt_uV); + if (ret) + return ret; + + return sreg->sdesc.desc.ops->map_voltage(rdev, volt_uV, volt_uV); +} + +static int scmi_reg_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + s32 volt_uV; + struct scmi_regulator *sreg = to_scmi_regulator(rdev); + + volt_uV = sreg->sdesc.desc.ops->list_voltage(rdev, selector); + if (volt_uV <= 0) + return -EINVAL; + + return voltage_ops->level_set(sreg->ph, sreg->id, 0x0, volt_uV); +} + +static const struct regulator_ops scmi_reg_fixed_ops = { + .enable = scmi_reg_enable, + .disable = scmi_reg_disable, + .is_enabled = scmi_reg_is_enabled, +}; + +static const struct regulator_ops scmi_reg_linear_ops = { + .enable = scmi_reg_enable, + .disable = scmi_reg_disable, + .is_enabled = scmi_reg_is_enabled, + .get_voltage_sel = scmi_reg_get_voltage_sel, + .set_voltage_sel = scmi_reg_set_voltage_sel, + .list_voltage = regulator_list_voltage_linear, + .map_voltage = regulator_map_voltage_linear, +}; + +static const struct regulator_ops scmi_reg_discrete_ops = { + .enable = scmi_reg_enable, + .disable = scmi_reg_disable, + .is_enabled = scmi_reg_is_enabled, + .get_voltage_sel = scmi_reg_get_voltage_sel, + .set_voltage_sel = scmi_reg_set_voltage_sel, + .list_voltage = regulator_list_voltage_table, + .map_voltage = regulator_map_voltage_iterate, +}; + +static int +scmi_config_linear_regulator_mappings(struct scmi_regulator *sreg, + const struct scmi_voltage_info *vinfo) +{ + struct regulator_desc *desc = &sreg->sdesc.desc; + s32 delta_uV; + + /* + * Note that SCMI voltage domains describable by linear ranges + * (segments) {low, high, step} are guaranteed to come in one single + * triplet by the SCMI Voltage Domain protocol support itself. + */ + + delta_uV = (vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_HIGH] - + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]); + + /* Rule out buggy negative-intervals answers from fw */ + if (delta_uV < 0) { + dev_err(&sreg->sdev->dev, + "Invalid volt-range %d-%duV for domain %d\n", + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW], + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_HIGH], + sreg->id); + return -EINVAL; + } + + if (!delta_uV) { + /* Just one fixed voltage exposed by SCMI */ + desc->fixed_uV = + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; + desc->n_voltages = 1; + desc->ops = &scmi_reg_fixed_ops; + } else { + /* One simple linear mapping. */ + desc->min_uV = + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; + desc->uV_step = + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_STEP]; + desc->linear_min_sel = 0; + desc->n_voltages = (delta_uV / desc->uV_step) + 1; + desc->ops = &scmi_reg_linear_ops; + } + + return 0; +} + +static int +scmi_config_discrete_regulator_mappings(struct scmi_regulator *sreg, + const struct scmi_voltage_info *vinfo) +{ + struct regulator_desc *desc = &sreg->sdesc.desc; + + /* Discrete non linear levels are mapped to volt_table */ + desc->n_voltages = vinfo->num_levels; + + if (desc->n_voltages > 1) { + desc->volt_table = (const unsigned int *)vinfo->levels_uv; + desc->ops = &scmi_reg_discrete_ops; + } else { + desc->fixed_uV = vinfo->levels_uv[0]; + desc->ops = &scmi_reg_fixed_ops; + } + + return 0; +} + +static int scmi_regulator_common_init(struct scmi_regulator *sreg) +{ + int ret; + struct device_d *dev = &sreg->sdev->dev; + const struct scmi_voltage_info *vinfo; + struct scmi_reg_desc *sdesc = &sreg->sdesc; + + vinfo = voltage_ops->info_get(sreg->ph, sreg->id); + if (!vinfo) { + dev_warn(dev, "Failure to get voltage domain %d\n", + sreg->id); + return -ENODEV; + } + + /* + * Regulator framework does not fully support negative voltages + * so we discard any voltage domain reported as supporting negative + * voltages: as a consequence each levels_uv entry is guaranteed to + * be non-negative from here on. + */ + if (vinfo->negative_volts_allowed) { + dev_warn(dev, "Negative voltages NOT supported...skip %s\n", + sreg->device_node->full_name); + return -EOPNOTSUPP; + } + + sdesc->name = basprintf("%s", vinfo->name); + if (!sdesc->name) + return -ENOMEM; + + if (vinfo->segmented) + ret = scmi_config_linear_regulator_mappings(sreg, vinfo); + else + ret = scmi_config_discrete_regulator_mappings(sreg, vinfo); + + return ret; +} + +static int process_scmi_regulator_of_node(struct scmi_device *sdev, + struct scmi_protocol_handle *ph, + struct device_node *np, + struct scmi_regulator_info *rinfo) +{ + u32 dom, ret; + + ret = of_property_read_u32(np, "reg", &dom); + if (ret) + return ret; + + if (dom >= rinfo->num_doms) + return -ENODEV; + + if (rinfo->sregv[dom]) { + dev_err(&sdev->dev, + "SCMI Voltage Domain %d already in use. Skipping: %s\n", + dom, np->full_name); + return -EINVAL; + } + + rinfo->sregv[dom] = kzalloc(sizeof(struct scmi_regulator), GFP_KERNEL); + if (!rinfo->sregv[dom]) + return -ENOMEM; + + rinfo->sregv[dom]->id = dom; + rinfo->sregv[dom]->sdev = sdev; + rinfo->sregv[dom]->ph = ph; + + /* get hold of good nodes */ + rinfo->sregv[dom]->device_node = np; + + dev_dbg(&sdev->dev, + "Found SCMI Regulator entry -- OF node [%d] -> %s\n", + dom, np->full_name); + + return 0; +} + +static int scmi_regulator_probe(struct scmi_device *sdev) +{ + int d, ret, num_doms; + struct device_node *np, *child; + const struct scmi_handle *handle = sdev->handle; + struct scmi_regulator_info *rinfo; + struct scmi_protocol_handle *ph; + struct scmi_reg_desc *sdesc; + + if (!handle) + return -ENODEV; + + voltage_ops = handle->protocol_get(sdev, SCMI_PROTOCOL_VOLTAGE, &ph); + if (IS_ERR(voltage_ops)) + return PTR_ERR(voltage_ops); + + num_doms = voltage_ops->num_domains_get(ph); + if (num_doms <= 0) { + if (!num_doms) { + dev_err(&sdev->dev, + "number of voltage domains invalid\n"); + num_doms = -EINVAL; + } else { + dev_err(&sdev->dev, + "failed to get voltage domains - err:%d\n", + num_doms); + } + + return num_doms; + } + + rinfo = kzalloc(sizeof(*rinfo), GFP_KERNEL); + if (!rinfo) + return -ENOMEM; + + /* Allocate pointers array for all possible domains */ + rinfo->sregv = kcalloc(num_doms, sizeof(void *), GFP_KERNEL); + if (!rinfo->sregv) + return -ENOMEM; + + rinfo->num_doms = num_doms; + + /* + * Start collecting into rinfo->sregv possibly good SCMI Regulators as + * described by a well-formed DT entry and associated with an existing + * plausible SCMI Voltage Domain number, all belonging to this SCMI + * platform instance node (handle->dev->of_node). + */ + np = of_find_node_by_name(handle->dev->device_node, "regulators"); + for_each_child_of_node(np, child) { + ret = process_scmi_regulator_of_node(sdev, ph, child, rinfo); + /* abort on any mem issue */ + if (ret == -ENOMEM) + return ret; + } + + /* + * Register a regulator for each valid regulator-DT-entry that we + * can successfully reach via SCMI and has a valid associated voltage + * domain. + */ + for (d = 0; d < num_doms; d++) { + struct scmi_regulator *sreg = rinfo->sregv[d]; + + /* Skip empty slots */ + if (!sreg) + continue; + + sdesc = &sreg->sdesc; + + ret = scmi_regulator_common_init(sreg); + /* Skip invalid voltage domains */ + if (ret) + continue; + + ret = of_regulator_register(&sreg->rdev, np); + if (ret) + continue; + + dev_info(&sdev->dev, + "Regulator %s registered for domain [%d]\n", + sreg->sdesc.name, sreg->id); + } + + return 0; +} + +static const struct scmi_device_id scmi_regulator_id_table[] = { + { SCMI_PROTOCOL_VOLTAGE, "regulator" }, + { }, +}; + +static struct scmi_driver scmi_drv = { + .name = "scmi-regulator", + .probe = scmi_regulator_probe, + .id_table = scmi_regulator_id_table, +}; +core_scmi_driver(scmi_drv); + +MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>"); +MODULE_DESCRIPTION("ARM SCMI regulator driver"); +MODULE_LICENSE("GPL v2"); |