* [PATCH v2 0/4] Add support for SCMIv3.0 Voltage Domain Protocol and SCMI-Regulator @ 2020-10-15 15:39 Cristian Marussi 2020-10-15 15:39 ` [PATCH v2 1/4] firmware: arm_scmi: Add Voltage Domain Support Cristian Marussi ` (3 more replies) 0 siblings, 4 replies; 9+ messages in thread From: Cristian Marussi @ 2020-10-15 15:39 UTC (permalink / raw) To: linux-kernel, linux-arm-kernel Cc: robh, satyakim, f.fainelli, vincent.guittot, sudeep.holla, broonie, cristian.marussi, james.quinlan, Jonathan.Cameron, souvik.chakravarty, etienne.carriere, lukasz.luba Hi, this series introduces the support for the new SCMI Voltage Domain Protocol defined by the upcoming SCMIv3.0 specification, whose BETA release is available at [1]. Afterwards, a new generic SCMI Regulator driver is developed on top of the new SCMI VD Protocol. The series is currently based on for-next/scmi [2] on top of: commit fd7c58ee3026 ("firmware: arm_scmi: Fix locking in notifications") Any feedback welcome, Thanks, Cristian --- v1 --> v2 - rebased on for-next/scmi v5.10 - DT bindings - removed any reference to negative voltages - SCMI Regulator - removed duplicate regulator naming - removed redundant .get/set_voltage ops: only _sel variants implemented - removed condexpr on fail path to increase readability - VD Protocol - fix voltage levels query loop to reload full cmd description between iterations as reported by Etienne Carriere - ensure transport rx buffer is properly sized calli scmi_reset_rx_to_maxsz between transfers [1]:https://developer.arm.com/documentation/den0056/c/ [2]:https://git.kernel.org/pub/scm/linux/kernel/git/sudeep.holla/linux.git/log/?h=for-next/scmi Cristian Marussi (4): firmware: arm_scmi: Add Voltage Domain Support firmware: arm_scmi: add SCMI Voltage Domain devname regulator: add SCMI driver dt-bindings: arm: add support for SCMI Regulators .../devicetree/bindings/arm/arm,scmi.txt | 42 ++ drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/common.h | 1 + drivers/firmware/arm_scmi/driver.c | 3 + drivers/firmware/arm_scmi/voltage.c | 382 +++++++++++++++ drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 1 + drivers/regulator/scmi-regulator.c | 453 ++++++++++++++++++ include/linux/scmi_protocol.h | 64 +++ 9 files changed, 956 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/arm_scmi/voltage.c create mode 100644 drivers/regulator/scmi-regulator.c -- 2.17.1 _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 1/4] firmware: arm_scmi: Add Voltage Domain Support 2020-10-15 15:39 [PATCH v2 0/4] Add support for SCMIv3.0 Voltage Domain Protocol and SCMI-Regulator Cristian Marussi @ 2020-10-15 15:39 ` Cristian Marussi 2020-10-16 13:36 ` Etienne Carriere 2020-10-15 15:39 ` [PATCH v2 2/4] firmware: arm_scmi: add SCMI Voltage Domain devname Cristian Marussi ` (2 subsequent siblings) 3 siblings, 1 reply; 9+ messages in thread From: Cristian Marussi @ 2020-10-15 15:39 UTC (permalink / raw) To: linux-kernel, linux-arm-kernel Cc: robh, satyakim, f.fainelli, vincent.guittot, sudeep.holla, broonie, cristian.marussi, james.quinlan, Jonathan.Cameron, souvik.chakravarty, etienne.carriere, lukasz.luba Add SCMI Voltage Domain protocol support. Signed-off-by: Cristian Marussi <cristian.marussi@arm.com> --- v1 --> v2 - fix voltage levels query loop to reload full cmd description between iterations as reported by Etienne Carriere - ensure rx buffer is properly sized calli scmi_reset_rx_to_maxsz between transfers --- drivers/firmware/arm_scmi/Makefile | 2 +- drivers/firmware/arm_scmi/common.h | 1 + drivers/firmware/arm_scmi/driver.c | 2 + drivers/firmware/arm_scmi/voltage.c | 382 ++++++++++++++++++++++++++++ include/linux/scmi_protocol.h | 64 +++++ 5 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/arm_scmi/voltage.c diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile index bc0d54f8e861..6a2ef63306d6 100644 --- a/drivers/firmware/arm_scmi/Makefile +++ b/drivers/firmware/arm_scmi/Makefile @@ -4,7 +4,7 @@ scmi-driver-y = driver.o notify.o scmi-transport-y = shmem.o scmi-transport-$(CONFIG_MAILBOX) += mailbox.o scmi-transport-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smc.o -scmi-protocols-y = base.o clock.o perf.o power.o reset.o sensors.o system.o +scmi-protocols-y = base.o clock.o perf.o power.o reset.o sensors.o system.o voltage.o scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \ $(scmi-transport-y) obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h index 65063fa948d4..c0fb45e7c3e8 100644 --- a/drivers/firmware/arm_scmi/common.h +++ b/drivers/firmware/arm_scmi/common.h @@ -169,6 +169,7 @@ DECLARE_SCMI_REGISTER_UNREGISTER(perf); DECLARE_SCMI_REGISTER_UNREGISTER(power); DECLARE_SCMI_REGISTER_UNREGISTER(reset); DECLARE_SCMI_REGISTER_UNREGISTER(sensors); +DECLARE_SCMI_REGISTER_UNREGISTER(voltage); DECLARE_SCMI_REGISTER_UNREGISTER(system); #define DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(id, name) \ diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index 3dfd8b6a0ebf..ada35e63feae 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -946,6 +946,7 @@ static int __init scmi_driver_init(void) scmi_power_register(); scmi_reset_register(); scmi_sensors_register(); + scmi_voltage_register(); scmi_system_register(); return platform_driver_register(&scmi_driver); @@ -961,6 +962,7 @@ static void __exit scmi_driver_exit(void) scmi_power_unregister(); scmi_reset_unregister(); scmi_sensors_unregister(); + scmi_voltage_unregister(); scmi_system_unregister(); platform_driver_unregister(&scmi_driver); diff --git a/drivers/firmware/arm_scmi/voltage.c b/drivers/firmware/arm_scmi/voltage.c new file mode 100644 index 000000000000..a20da5128de1 --- /dev/null +++ b/drivers/firmware/arm_scmi/voltage.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * System Control and Management Interface (SCMI) Voltage Protocol + * + * Copyright (C) 2020 ARM Ltd. + */ + +#include <linux/scmi_protocol.h> + +#include "common.h" + +#define VOLTAGE_DOMS_NUM_MASK GENMASK(15, 0) +#define REMAINING_LEVELS_MASK GENMASK(31, 16) +#define RETURNED_LEVELS_MASK GENMASK(11, 0) + +enum scmi_voltage_protocol_cmd { + VOLTAGE_DOMAIN_ATTRIBUTES = 0x3, + VOLTAGE_DESCRIBE_LEVELS = 0x4, + VOLTAGE_CONFIG_SET = 0x5, + VOLTAGE_CONFIG_GET = 0x6, + VOLTAGE_LEVEL_SET = 0x7, + VOLTAGE_LEVEL_GET = 0x8, +}; + +struct scmi_msg_resp_protocol_attributes { + __le32 attr; +#define NUM_VOLTAGE_DOMAINS(x) (u16)(FIELD_GET(VOLTAGE_DOMS_NUM_MASK, (x))) +}; + +struct scmi_msg_resp_domain_attributes { + __le32 attr; + u8 name[SCMI_MAX_STR_SIZE]; +}; + +struct scmi_msg_cmd_describe_levels { + __le32 domain_id; + __le32 level_index; +}; + +struct scmi_msg_resp_describe_levels { + __le32 flags; +#define NUM_REMAINING_LEVELS(f) (u16)(FIELD_GET(REMAINING_LEVELS_MASK, (f))) +#define NUM_RETURNED_LEVELS(f) (u16)(FIELD_GET(RETURNED_LEVELS_MASK, (f))) +#define SUPPORTS_SEGMENTED_LEVELS(f) ((f) & BIT(12)) + __le32 voltage[]; +}; + +struct scmi_msg_cmd_config_set { + __le32 domain_id; + __le32 config; +}; + +struct scmi_msg_cmd_level_set { + __le32 domain_id; + __le32 flags; + __le32 voltage_level; +}; + +struct voltage_info { + u32 version; + u16 num_domains; + struct scmi_voltage_info **domains; +}; + +static int scmi_protocol_attributes_get(const struct scmi_handle *handle, + struct voltage_info *vinfo) +{ + int ret; + struct scmi_xfer *t; + struct scmi_msg_resp_protocol_attributes *resp; + + ret = scmi_xfer_get_init(handle, PROTOCOL_ATTRIBUTES, + SCMI_PROTOCOL_VOLTAGE, 0, sizeof(*resp), &t); + if (ret) + return ret; + + resp = t->rx.buf; + ret = scmi_do_xfer(handle, t); + if (!ret) + vinfo->num_domains = + NUM_VOLTAGE_DOMAINS(le32_to_cpu(resp->attr)); + + scmi_xfer_put(handle, t); + return ret; +} + +static inline int scmi_init_voltage_levels(struct device *dev, + struct scmi_voltage_info *v, + u32 flags, u32 num_levels) +{ + bool segmented; + + segmented = SUPPORTS_SEGMENTED_LEVELS(flags); + /* segmented levels array's entries must be multiple-of-3 */ + if (!num_levels || (segmented && num_levels % 3)) + return -EINVAL; + + v->levels_uv = devm_kcalloc(dev, num_levels, sizeof(u32), GFP_KERNEL); + if (!v->levels_uv) + return -ENOMEM; + + v->num_levels = num_levels; + v->segmented = segmented; + + return 0; +} + +static int scmi_voltage_descriptors_get(const struct scmi_handle *handle, + struct voltage_info *vinfo) +{ + int ret, dom; + struct scmi_xfer *td, *tl; + struct device *dev = handle->dev; + struct scmi_msg_resp_domain_attributes *resp_dom; + struct scmi_msg_resp_describe_levels *resp_levels; + + ret = scmi_xfer_get_init(handle, VOLTAGE_DOMAIN_ATTRIBUTES, + SCMI_PROTOCOL_VOLTAGE, sizeof(__le32), + sizeof(*resp_dom), &td); + if (ret) + return ret; + resp_dom = td->rx.buf; + + ret = scmi_xfer_get_init(handle, VOLTAGE_DESCRIBE_LEVELS, + SCMI_PROTOCOL_VOLTAGE, sizeof(__le64), 0, &tl); + if (ret) + goto outd; + resp_levels = tl->rx.buf; + + for (dom = 0; dom < vinfo->num_domains; dom++) { + u32 desc_index = 0; + u16 num_returned = 0, num_remaining = 0; + struct scmi_msg_cmd_describe_levels *cmd; + struct scmi_voltage_info *v; + + /* Retrieve domain attributes at first ... */ + put_unaligned_le32(dom, td->tx.buf); + ret = scmi_do_xfer(handle, td); + if (ret) + continue; + + v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL); + if (!v) { + ret = -ENOMEM; + break; + } + + v->id = dom; + v->attributes = le32_to_cpu(resp_dom->attr); + strlcpy(v->name, resp_dom->name, SCMI_MAX_STR_SIZE); + + cmd = tl->tx.buf; + /* ...then retrieve domain levels descriptions */ + do { + u32 flags; + int cnt; + + cmd->domain_id = cpu_to_le32(v->id); + cmd->level_index = desc_index; + ret = scmi_do_xfer(handle, tl); + if (ret) + break; + + flags = le32_to_cpu(resp_levels->flags); + num_returned = NUM_RETURNED_LEVELS(flags); + num_remaining = NUM_REMAINING_LEVELS(flags); + + /* Allocate space for num_levels if not already done */ + if (!v->num_levels) { + ret = scmi_init_voltage_levels(dev, v, flags, + num_returned + + num_remaining); + if (ret) + break; + } + + if (desc_index + num_returned > v->num_levels) { + dev_err(handle->dev, + "No. of voltage levels can't exceed %d", + v->num_levels); + ret = -EINVAL; + break; + } + + for (cnt = 0; cnt < num_returned; cnt++) { + s32 val; + + val = + (s32)le32_to_cpu(resp_levels->voltage[cnt]); + v->levels_uv[desc_index + cnt] = val; + if (!v->negative_volts_allowed && val < 0) + v->negative_volts_allowed = true; + } + + desc_index += num_returned; + + scmi_reset_rx_to_maxsz(handle, tl); + /* check both to avoid infinite loop due to buggy fw */ + } while (num_returned && num_remaining); + + /* + * Bail out on memory errors, just skip domain on any + * other error. + */ + if (!ret) + vinfo->domains[dom] = v; + else if (ret == -ENOMEM) + break; + + scmi_reset_rx_to_maxsz(handle, td); + } + + scmi_xfer_put(handle, tl); +outd: + scmi_xfer_put(handle, td); + + return ret; +} + +static inline int __scmi_voltage_get_u32(const struct scmi_handle *handle, + u8 cmd_id, u32 domain_id, u32 *value) +{ + int ret; + struct scmi_xfer *t; + struct voltage_info *vinfo = handle->voltage_priv; + + if (domain_id >= vinfo->num_domains) + return -EINVAL; + + ret = scmi_xfer_get_init(handle, cmd_id, + SCMI_PROTOCOL_VOLTAGE, + sizeof(__le32), 0, &t); + if (ret) + return ret; + + put_unaligned_le32(domain_id, t->tx.buf); + ret = scmi_do_xfer(handle, t); + if (!ret) + *value = get_unaligned_le32(t->rx.buf); + + scmi_xfer_put(handle, t); + return ret; +} + +static int scmi_voltage_config_set(const struct scmi_handle *handle, + u32 domain_id, u32 config) +{ + int ret; + struct scmi_xfer *t; + struct voltage_info *vinfo = handle->voltage_priv; + struct scmi_msg_cmd_config_set *cmd; + + if (domain_id >= vinfo->num_domains) + return -EINVAL; + + ret = scmi_xfer_get_init(handle, VOLTAGE_CONFIG_SET, + SCMI_PROTOCOL_VOLTAGE, + sizeof(*cmd), 0, &t); + if (ret) + return ret; + + cmd = t->tx.buf; + cmd->domain_id = cpu_to_le32(domain_id); + cmd->config = cpu_to_le32(config & GENMASK(3, 0)); + + ret = scmi_do_xfer(handle, t); + + scmi_xfer_put(handle, t); + return ret; +} + +static int scmi_voltage_config_get(const struct scmi_handle *handle, + u32 domain_id, u32 *config) +{ + return __scmi_voltage_get_u32(handle, VOLTAGE_CONFIG_GET, + domain_id, config); +} + +static int scmi_voltage_level_set(const struct scmi_handle *handle, + u32 domain_id, u32 flags, s32 volt_uV) +{ + int ret; + struct scmi_xfer *t; + struct voltage_info *vinfo = handle->voltage_priv; + struct scmi_msg_cmd_level_set *cmd; + + if (domain_id >= vinfo->num_domains) + return -EINVAL; + + ret = scmi_xfer_get_init(handle, VOLTAGE_LEVEL_SET, + SCMI_PROTOCOL_VOLTAGE, + sizeof(*cmd), 0, &t); + if (ret) + return ret; + + cmd = t->tx.buf; + cmd->domain_id = cpu_to_le32(domain_id); + cmd->flags = cpu_to_le32(flags); + cmd->voltage_level = cpu_to_le32(volt_uV); + + ret = scmi_do_xfer(handle, t); + + scmi_xfer_put(handle, t); + return ret; +} + +static int scmi_voltage_level_get(const struct scmi_handle *handle, + u32 domain_id, s32 *volt_uV) +{ + return __scmi_voltage_get_u32(handle, VOLTAGE_LEVEL_GET, + domain_id, (u32 *)volt_uV); +} + +static const struct scmi_voltage_info * +scmi_voltage_info_get(const struct scmi_handle *handle, u32 domain_id) +{ + struct voltage_info *vinfo = handle->voltage_priv; + + if (domain_id >= vinfo->num_domains) + return NULL; + + return vinfo->domains[domain_id]; +} + +static int scmi_voltage_domains_num_get(const struct scmi_handle *handle) +{ + struct voltage_info *vinfo = handle->voltage_priv; + + return vinfo->num_domains; +} + +static struct scmi_voltage_ops voltage_ops = { + .num_domains_get = scmi_voltage_domains_num_get, + .info_get = scmi_voltage_info_get, + .config_set = scmi_voltage_config_set, + .config_get = scmi_voltage_config_get, + .level_set = scmi_voltage_level_set, + .level_get = scmi_voltage_level_get, +}; + +static int scmi_voltage_protocol_init(struct scmi_handle *handle) +{ + int ret; + u32 version; + struct voltage_info *vinfo; + + ret = scmi_version_get(handle, SCMI_PROTOCOL_VOLTAGE, &version); + if (ret) + return ret; + + dev_dbg(handle->dev, "Voltage Version %d.%d\n", + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); + + vinfo = devm_kzalloc(handle->dev, sizeof(*vinfo), GFP_KERNEL); + if (!vinfo) + return -ENOMEM; + vinfo->version = version; + + ret = scmi_protocol_attributes_get(handle, vinfo); + if (ret) + return ret; + + if (vinfo->num_domains) { + vinfo->domains = devm_kcalloc(handle->dev, vinfo->num_domains, + sizeof(vinfo->domains), + GFP_KERNEL); + if (!vinfo->domains) + return -ENOMEM; + ret = scmi_voltage_descriptors_get(handle, vinfo); + if (ret) + return ret; + } else { + dev_warn(handle->dev, "No Voltage domains found.\n"); + } + + handle->voltage_ops = &voltage_ops; + handle->voltage_priv = vinfo; + + return 0; +} + +DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(SCMI_PROTOCOL_VOLTAGE, voltage) diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h index 9cd312a1ff92..032ad9bb2a53 100644 --- a/include/linux/scmi_protocol.h +++ b/include/linux/scmi_protocol.h @@ -209,6 +209,64 @@ struct scmi_reset_ops { int (*deassert)(const struct scmi_handle *handle, u32 domain); }; +/** + * struct scmi_voltage_info - describe one available SCMI Voltage Domain + * + * @id: the domain ID as advertised by the platform + * @segmented: defines the layout of the entries of array @levels_uv. + * - when True the entries are to be interpreted as triplets, + * each defining a segment representing a range of equally + * space voltages: <lowest_volts>, <highest_volt>, <step_uV> + * - when False the entries simply represent a single discrete + * supported voltage level + * @negative_volts_allowed: True if any of the entries of @levels_uv represent + * a negative voltage. + * @attributes: represents Voltage Domain advertised attributes + * @name: name assigned to the Voltage Domain by platform + * @num_levels: number of total entries in @levels_uv. + * @levels_uv: array of entries describing the available voltage levels for + * this domain. + */ +struct scmi_voltage_info { + u32 id; + bool segmented; +#define SCMI_VOLTAGE_SEGMENT_LOW 0 +#define SCMI_VOLTAGE_SEGMENT_HIGH 1 +#define SCMI_VOLTAGE_SEGMENT_STEP 2 + bool negative_volts_allowed; + u32 attributes; + char name[SCMI_MAX_STR_SIZE]; + u16 num_levels; + s32 *levels_uv; +}; + +/** + * struct scmi_voltage_ops - represents the various operations provided + * by SCMI Voltage Protocol + * + * @num_domains_get: get the count of voltage domains provided by SCMI + * @info_get: get the information of the specified domain + * @config_set: set the config for the specified domain + * @config_get: get the config of the specified domain + * @level_set: set the voltage level for the specified domain + * @level_get: get the voltage level of the specified domain + */ +struct scmi_voltage_ops { + int (*num_domains_get)(const struct scmi_handle *handle); + const struct scmi_voltage_info *(*info_get) + (const struct scmi_handle *handle, u32 domain_id); + int (*config_set)(const struct scmi_handle *handle, u32 domain_id, + u32 config); +#define SCMI_VOLTAGE_ARCH_STATE_OFF 0x0 +#define SCMI_VOLTAGE_ARCH_STATE_ON 0x7 + int (*config_get)(const struct scmi_handle *handle, u32 domain_id, + u32 *config); + int (*level_set)(const struct scmi_handle *handle, u32 domain_id, + u32 flags, s32 volt_uV); + int (*level_get)(const struct scmi_handle *handle, u32 domain_id, + s32 *volt_uV); +}; + /** * struct scmi_notify_ops - represents notifications' operations provided by * SCMI core @@ -262,6 +320,7 @@ struct scmi_notify_ops { * @clk_ops: pointer to set of clock protocol operations * @sensor_ops: pointer to set of sensor protocol operations * @reset_ops: pointer to set of reset protocol operations + * @voltage_ops: pointer to set of voltage protocol operations * @notify_ops: pointer to set of notifications related operations * @perf_priv: pointer to private data structure specific to performance * protocol(for internal use only) @@ -273,6 +332,8 @@ struct scmi_notify_ops { * protocol(for internal use only) * @reset_priv: pointer to private data structure specific to reset * protocol(for internal use only) + * @voltage_priv: pointer to private data structure specific to voltage + * protocol(for internal use only) * @notify_priv: pointer to private data structure specific to notifications * (for internal use only) */ @@ -284,6 +345,7 @@ struct scmi_handle { const struct scmi_power_ops *power_ops; const struct scmi_sensor_ops *sensor_ops; const struct scmi_reset_ops *reset_ops; + const struct scmi_voltage_ops *voltage_ops; const struct scmi_notify_ops *notify_ops; /* for protocol internal use */ void *perf_priv; @@ -291,6 +353,7 @@ struct scmi_handle { void *power_priv; void *sensor_priv; void *reset_priv; + void *voltage_priv; void *notify_priv; void *system_priv; }; @@ -303,6 +366,7 @@ enum scmi_std_protocol { SCMI_PROTOCOL_CLOCK = 0x14, SCMI_PROTOCOL_SENSOR = 0x15, SCMI_PROTOCOL_RESET = 0x16, + SCMI_PROTOCOL_VOLTAGE = 0x17, }; enum scmi_system_events { -- 2.17.1 _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2 1/4] firmware: arm_scmi: Add Voltage Domain Support 2020-10-15 15:39 ` [PATCH v2 1/4] firmware: arm_scmi: Add Voltage Domain Support Cristian Marussi @ 2020-10-16 13:36 ` Etienne Carriere 2020-10-16 16:51 ` Cristian Marussi 0 siblings, 1 reply; 9+ messages in thread From: Etienne Carriere @ 2020-10-16 13:36 UTC (permalink / raw) To: Cristian Marussi Cc: Rob Herring, satyakim, f.fainelli, Vincent Guittot, Sudeep Holla, linux-kernel, broonie, moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE, Jim Quinlan, Jonathan.Cameron, Souvik Chakravarty, lukasz.luba Hello Cristian, Thanks for the update. Some minor comments below. FYI I successfully tested this series. Regards, Etienne On Thu, 15 Oct 2020 at 17:40, Cristian Marussi <cristian.marussi@arm.com> wrote: > > Add SCMI Voltage Domain protocol support. > > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com> > --- > v1 --> v2 > - fix voltage levels query loop to reload full cmd description > between iterations as reported by Etienne Carriere > - ensure rx buffer is properly sized calli scmi_reset_rx_to_maxsz > between transfers > --- > drivers/firmware/arm_scmi/Makefile | 2 +- > drivers/firmware/arm_scmi/common.h | 1 + > drivers/firmware/arm_scmi/driver.c | 2 + > drivers/firmware/arm_scmi/voltage.c | 382 ++++++++++++++++++++++++++++ > include/linux/scmi_protocol.h | 64 +++++ > 5 files changed, 450 insertions(+), 1 deletion(-) > create mode 100644 drivers/firmware/arm_scmi/voltage.c > > diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile > index bc0d54f8e861..6a2ef63306d6 100644 > --- a/drivers/firmware/arm_scmi/Makefile > +++ b/drivers/firmware/arm_scmi/Makefile > @@ -4,7 +4,7 @@ scmi-driver-y = driver.o notify.o > scmi-transport-y = shmem.o > scmi-transport-$(CONFIG_MAILBOX) += mailbox.o > scmi-transport-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smc.o > -scmi-protocols-y = base.o clock.o perf.o power.o reset.o sensors.o system.o > +scmi-protocols-y = base.o clock.o perf.o power.o reset.o sensors.o system.o voltage.o > scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \ > $(scmi-transport-y) > obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o > diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h > index 65063fa948d4..c0fb45e7c3e8 100644 > --- a/drivers/firmware/arm_scmi/common.h > +++ b/drivers/firmware/arm_scmi/common.h > @@ -169,6 +169,7 @@ DECLARE_SCMI_REGISTER_UNREGISTER(perf); > DECLARE_SCMI_REGISTER_UNREGISTER(power); > DECLARE_SCMI_REGISTER_UNREGISTER(reset); > DECLARE_SCMI_REGISTER_UNREGISTER(sensors); > +DECLARE_SCMI_REGISTER_UNREGISTER(voltage); > DECLARE_SCMI_REGISTER_UNREGISTER(system); > > #define DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(id, name) \ > diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c > index 3dfd8b6a0ebf..ada35e63feae 100644 > --- a/drivers/firmware/arm_scmi/driver.c > +++ b/drivers/firmware/arm_scmi/driver.c > @@ -946,6 +946,7 @@ static int __init scmi_driver_init(void) > scmi_power_register(); > scmi_reset_register(); > scmi_sensors_register(); > + scmi_voltage_register(); > scmi_system_register(); > > return platform_driver_register(&scmi_driver); > @@ -961,6 +962,7 @@ static void __exit scmi_driver_exit(void) > scmi_power_unregister(); > scmi_reset_unregister(); > scmi_sensors_unregister(); > + scmi_voltage_unregister(); > scmi_system_unregister(); > > platform_driver_unregister(&scmi_driver); > diff --git a/drivers/firmware/arm_scmi/voltage.c b/drivers/firmware/arm_scmi/voltage.c > new file mode 100644 > index 000000000000..a20da5128de1 > --- /dev/null > +++ b/drivers/firmware/arm_scmi/voltage.c > @@ -0,0 +1,382 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * System Control and Management Interface (SCMI) Voltage Protocol > + * > + * Copyright (C) 2020 ARM Ltd. > + */ > + > +#include <linux/scmi_protocol.h> > + > +#include "common.h" > + > +#define VOLTAGE_DOMS_NUM_MASK GENMASK(15, 0) > +#define REMAINING_LEVELS_MASK GENMASK(31, 16) > +#define RETURNED_LEVELS_MASK GENMASK(11, 0) > + > +enum scmi_voltage_protocol_cmd { > + VOLTAGE_DOMAIN_ATTRIBUTES = 0x3, > + VOLTAGE_DESCRIBE_LEVELS = 0x4, > + VOLTAGE_CONFIG_SET = 0x5, > + VOLTAGE_CONFIG_GET = 0x6, > + VOLTAGE_LEVEL_SET = 0x7, > + VOLTAGE_LEVEL_GET = 0x8, > +}; > + > +struct scmi_msg_resp_protocol_attributes { > + __le32 attr; > +#define NUM_VOLTAGE_DOMAINS(x) (u16)(FIELD_GET(VOLTAGE_DOMS_NUM_MASK, (x))) > +}; > + > +struct scmi_msg_resp_domain_attributes { > + __le32 attr; > + u8 name[SCMI_MAX_STR_SIZE]; > +}; > + > +struct scmi_msg_cmd_describe_levels { > + __le32 domain_id; > + __le32 level_index; > +}; > + > +struct scmi_msg_resp_describe_levels { > + __le32 flags; > +#define NUM_REMAINING_LEVELS(f) (u16)(FIELD_GET(REMAINING_LEVELS_MASK, (f))) > +#define NUM_RETURNED_LEVELS(f) (u16)(FIELD_GET(RETURNED_LEVELS_MASK, (f))) > +#define SUPPORTS_SEGMENTED_LEVELS(f) ((f) & BIT(12)) > + __le32 voltage[]; > +}; > + > +struct scmi_msg_cmd_config_set { > + __le32 domain_id; > + __le32 config; > +}; > + > +struct scmi_msg_cmd_level_set { > + __le32 domain_id; > + __le32 flags; > + __le32 voltage_level; > +}; > + > +struct voltage_info { > + u32 version; > + u16 num_domains; > + struct scmi_voltage_info **domains; > +}; > + > +static int scmi_protocol_attributes_get(const struct scmi_handle *handle, > + struct voltage_info *vinfo) > +{ > + int ret; > + struct scmi_xfer *t; > + struct scmi_msg_resp_protocol_attributes *resp; > + > + ret = scmi_xfer_get_init(handle, PROTOCOL_ATTRIBUTES, > + SCMI_PROTOCOL_VOLTAGE, 0, sizeof(*resp), &t); > + if (ret) > + return ret; > + > + resp = t->rx.buf; > + ret = scmi_do_xfer(handle, t); > + if (!ret) > + vinfo->num_domains = > + NUM_VOLTAGE_DOMAINS(le32_to_cpu(resp->attr)); > + > + scmi_xfer_put(handle, t); > + return ret; > +} > + > +static inline int scmi_init_voltage_levels(struct device *dev, > + struct scmi_voltage_info *v, > + u32 flags, u32 num_levels) > +{ > + bool segmented; > + > + segmented = SUPPORTS_SEGMENTED_LEVELS(flags); > + /* segmented levels array's entries must be multiple-of-3 */ > + if (!num_levels || (segmented && num_levels % 3)) > + return -EINVAL; I think segment levels are described by a single triplet. If right, should test (!num_levels || (segmented && num_levels == 3)). > + > + v->levels_uv = devm_kcalloc(dev, num_levels, sizeof(u32), GFP_KERNEL); > + if (!v->levels_uv) > + return -ENOMEM; > + > + v->num_levels = num_levels; > + v->segmented = segmented; > + > + return 0; > +} > + > +static int scmi_voltage_descriptors_get(const struct scmi_handle *handle, > + struct voltage_info *vinfo) > +{ > + int ret, dom; > + struct scmi_xfer *td, *tl; > + struct device *dev = handle->dev; > + struct scmi_msg_resp_domain_attributes *resp_dom; > + struct scmi_msg_resp_describe_levels *resp_levels; > + > + ret = scmi_xfer_get_init(handle, VOLTAGE_DOMAIN_ATTRIBUTES, > + SCMI_PROTOCOL_VOLTAGE, sizeof(__le32), > + sizeof(*resp_dom), &td); > + if (ret) > + return ret; > + resp_dom = td->rx.buf; > + > + ret = scmi_xfer_get_init(handle, VOLTAGE_DESCRIBE_LEVELS, > + SCMI_PROTOCOL_VOLTAGE, sizeof(__le64), 0, &tl); > + if (ret) > + goto outd; > + resp_levels = tl->rx.buf; > + > + for (dom = 0; dom < vinfo->num_domains; dom++) { > + u32 desc_index = 0; > + u16 num_returned = 0, num_remaining = 0; > + struct scmi_msg_cmd_describe_levels *cmd; > + struct scmi_voltage_info *v; > + > + /* Retrieve domain attributes at first ... */ > + put_unaligned_le32(dom, td->tx.buf); > + ret = scmi_do_xfer(handle, td); > + if (ret) > + continue; Continue and break and report the error code? Seems you prefer to abort only on local error (ENOMEM), not comm error. Maybe state with nice inline comment as done below (/* Skip domain on error */). > + > + v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL); > + if (!v) { > + ret = -ENOMEM; > + break; > + } > + > + v->id = dom; > + v->attributes = le32_to_cpu(resp_dom->attr); > + strlcpy(v->name, resp_dom->name, SCMI_MAX_STR_SIZE); > + > + cmd = tl->tx.buf; > + /* ...then retrieve domain levels descriptions */ > + do { > + u32 flags; > + int cnt; > + > + cmd->domain_id = cpu_to_le32(v->id); > + cmd->level_index = desc_index; > + ret = scmi_do_xfer(handle, tl); > + if (ret) > + break; > + > + flags = le32_to_cpu(resp_levels->flags); > + num_returned = NUM_RETURNED_LEVELS(flags); > + num_remaining = NUM_REMAINING_LEVELS(flags); > + > + /* Allocate space for num_levels if not already done */ > + if (!v->num_levels) { > + ret = scmi_init_voltage_levels(dev, v, flags, > + num_returned + > + num_remaining); > + if (ret) > + break; > + } > + > + if (desc_index + num_returned > v->num_levels) { > + dev_err(handle->dev, > + "No. of voltage levels can't exceed %d", Missing '\n'. > + v->num_levels); > + ret = -EINVAL; > + break; > + } > + > + for (cnt = 0; cnt < num_returned; cnt++) { > + s32 val; > + > + val = > + (s32)le32_to_cpu(resp_levels->voltage[cnt]); > + v->levels_uv[desc_index + cnt] = val; > + if (!v->negative_volts_allowed && val < 0) could skip testing v->negative_volts_allowed. if (val < 0) v->negative_volts_allowed = true; > + v->negative_volts_allowed = true; > + } > + > + desc_index += num_returned; > + > + scmi_reset_rx_to_maxsz(handle, tl); > + /* check both to avoid infinite loop due to buggy fw */ > + } while (num_returned && num_remaining); > + > + /* > + * Bail out on memory errors, just skip domain on any > + * other error. > + */ > + if (!ret) > + vinfo->domains[dom] = v; > + else if (ret == -ENOMEM) > + break; > + > + scmi_reset_rx_to_maxsz(handle, td); > + } > + > + scmi_xfer_put(handle, tl); > +outd: > + scmi_xfer_put(handle, td); > + > + return ret; > +} > + > +static inline int __scmi_voltage_get_u32(const struct scmi_handle *handle, inline needed ? > + u8 cmd_id, u32 domain_id, u32 *value) > +{ > + int ret; > + struct scmi_xfer *t; > + struct voltage_info *vinfo = handle->voltage_priv; > + > + if (domain_id >= vinfo->num_domains) > + return -EINVAL; > + > + ret = scmi_xfer_get_init(handle, cmd_id, > + SCMI_PROTOCOL_VOLTAGE, > + sizeof(__le32), 0, &t); > + if (ret) > + return ret; > + > + put_unaligned_le32(domain_id, t->tx.buf); > + ret = scmi_do_xfer(handle, t); > + if (!ret) > + *value = get_unaligned_le32(t->rx.buf); > + > + scmi_xfer_put(handle, t); > + return ret; > +} > + > +static int scmi_voltage_config_set(const struct scmi_handle *handle, > + u32 domain_id, u32 config) > +{ > + int ret; > + struct scmi_xfer *t; > + struct voltage_info *vinfo = handle->voltage_priv; > + struct scmi_msg_cmd_config_set *cmd; > + > + if (domain_id >= vinfo->num_domains) > + return -EINVAL; > + > + ret = scmi_xfer_get_init(handle, VOLTAGE_CONFIG_SET, > + SCMI_PROTOCOL_VOLTAGE, > + sizeof(*cmd), 0, &t); > + if (ret) > + return ret; > + > + cmd = t->tx.buf; > + cmd->domain_id = cpu_to_le32(domain_id); > + cmd->config = cpu_to_le32(config & GENMASK(3, 0)); > + > + ret = scmi_do_xfer(handle, t); > + > + scmi_xfer_put(handle, t); > + return ret; > +} > + > +static int scmi_voltage_config_get(const struct scmi_handle *handle, > + u32 domain_id, u32 *config) > +{ > + return __scmi_voltage_get_u32(handle, VOLTAGE_CONFIG_GET, > + domain_id, config); > +} > + > +static int scmi_voltage_level_set(const struct scmi_handle *handle, > + u32 domain_id, u32 flags, s32 volt_uV) > +{ > + int ret; > + struct scmi_xfer *t; > + struct voltage_info *vinfo = handle->voltage_priv; > + struct scmi_msg_cmd_level_set *cmd; > + > + if (domain_id >= vinfo->num_domains) > + return -EINVAL; > + > + ret = scmi_xfer_get_init(handle, VOLTAGE_LEVEL_SET, > + SCMI_PROTOCOL_VOLTAGE, > + sizeof(*cmd), 0, &t); > + if (ret) > + return ret; > + > + cmd = t->tx.buf; > + cmd->domain_id = cpu_to_le32(domain_id); > + cmd->flags = cpu_to_le32(flags); > + cmd->voltage_level = cpu_to_le32(volt_uV); > + > + ret = scmi_do_xfer(handle, t); > + > + scmi_xfer_put(handle, t); > + return ret; > +} > + > +static int scmi_voltage_level_get(const struct scmi_handle *handle, > + u32 domain_id, s32 *volt_uV) > +{ > + return __scmi_voltage_get_u32(handle, VOLTAGE_LEVEL_GET, > + domain_id, (u32 *)volt_uV); > +} > + > +static const struct scmi_voltage_info * > +scmi_voltage_info_get(const struct scmi_handle *handle, u32 domain_id) > +{ > + struct voltage_info *vinfo = handle->voltage_priv; > + > + if (domain_id >= vinfo->num_domains) > + return NULL; > + > + return vinfo->domains[domain_id]; > +} > + > +static int scmi_voltage_domains_num_get(const struct scmi_handle *handle) > +{ > + struct voltage_info *vinfo = handle->voltage_priv; > + > + return vinfo->num_domains; > +} > + > +static struct scmi_voltage_ops voltage_ops = { > + .num_domains_get = scmi_voltage_domains_num_get, > + .info_get = scmi_voltage_info_get, > + .config_set = scmi_voltage_config_set, > + .config_get = scmi_voltage_config_get, > + .level_set = scmi_voltage_level_set, > + .level_get = scmi_voltage_level_get, > +}; > + > +static int scmi_voltage_protocol_init(struct scmi_handle *handle) > +{ > + int ret; > + u32 version; > + struct voltage_info *vinfo; > + > + ret = scmi_version_get(handle, SCMI_PROTOCOL_VOLTAGE, &version); > + if (ret) > + return ret; > + > + dev_dbg(handle->dev, "Voltage Version %d.%d\n", > + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); > + > + vinfo = devm_kzalloc(handle->dev, sizeof(*vinfo), GFP_KERNEL); > + if (!vinfo) > + return -ENOMEM; > + vinfo->version = version; > + > + ret = scmi_protocol_attributes_get(handle, vinfo); > + if (ret) > + return ret; > + > + if (vinfo->num_domains) { > + vinfo->domains = devm_kcalloc(handle->dev, vinfo->num_domains, > + sizeof(vinfo->domains), > + GFP_KERNEL); > + if (!vinfo->domains) > + return -ENOMEM; > + ret = scmi_voltage_descriptors_get(handle, vinfo); > + if (ret) > + return ret; > + } else { > + dev_warn(handle->dev, "No Voltage domains found.\n"); > + } > + > + handle->voltage_ops = &voltage_ops; > + handle->voltage_priv = vinfo; > + > + return 0; > +} > + > +DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(SCMI_PROTOCOL_VOLTAGE, voltage) > diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h > index 9cd312a1ff92..032ad9bb2a53 100644 > --- a/include/linux/scmi_protocol.h > +++ b/include/linux/scmi_protocol.h > @@ -209,6 +209,64 @@ struct scmi_reset_ops { > int (*deassert)(const struct scmi_handle *handle, u32 domain); > }; > > +/** > + * struct scmi_voltage_info - describe one available SCMI Voltage Domain > + * > + * @id: the domain ID as advertised by the platform > + * @segmented: defines the layout of the entries of array @levels_uv. > + * - when True the entries are to be interpreted as triplets, > + * each defining a segment representing a range of equally > + * space voltages: <lowest_volts>, <highest_volt>, <step_uV> > + * - when False the entries simply represent a single discrete > + * supported voltage level > + * @negative_volts_allowed: True if any of the entries of @levels_uv represent > + * a negative voltage. > + * @attributes: represents Voltage Domain advertised attributes > + * @name: name assigned to the Voltage Domain by platform > + * @num_levels: number of total entries in @levels_uv. > + * @levels_uv: array of entries describing the available voltage levels for > + * this domain. > + */ > +struct scmi_voltage_info { > + u32 id; > + bool segmented; > +#define SCMI_VOLTAGE_SEGMENT_LOW 0 > +#define SCMI_VOLTAGE_SEGMENT_HIGH 1 > +#define SCMI_VOLTAGE_SEGMENT_STEP 2 > + bool negative_volts_allowed; > + u32 attributes; > + char name[SCMI_MAX_STR_SIZE]; > + u16 num_levels; > + s32 *levels_uv; > +}; > + > +/** > + * struct scmi_voltage_ops - represents the various operations provided > + * by SCMI Voltage Protocol > + * > + * @num_domains_get: get the count of voltage domains provided by SCMI > + * @info_get: get the information of the specified domain > + * @config_set: set the config for the specified domain > + * @config_get: get the config of the specified domain > + * @level_set: set the voltage level for the specified domain > + * @level_get: get the voltage level of the specified domain > + */ > +struct scmi_voltage_ops { > + int (*num_domains_get)(const struct scmi_handle *handle); > + const struct scmi_voltage_info *(*info_get) > + (const struct scmi_handle *handle, u32 domain_id); > + int (*config_set)(const struct scmi_handle *handle, u32 domain_id, > + u32 config); > +#define SCMI_VOLTAGE_ARCH_STATE_OFF 0x0 > +#define SCMI_VOLTAGE_ARCH_STATE_ON 0x7 > + int (*config_get)(const struct scmi_handle *handle, u32 domain_id, > + u32 *config); > + int (*level_set)(const struct scmi_handle *handle, u32 domain_id, > + u32 flags, s32 volt_uV); > + int (*level_get)(const struct scmi_handle *handle, u32 domain_id, > + s32 *volt_uV); > +}; > + > /** > * struct scmi_notify_ops - represents notifications' operations provided by > * SCMI core > @@ -262,6 +320,7 @@ struct scmi_notify_ops { > * @clk_ops: pointer to set of clock protocol operations > * @sensor_ops: pointer to set of sensor protocol operations > * @reset_ops: pointer to set of reset protocol operations > + * @voltage_ops: pointer to set of voltage protocol operations > * @notify_ops: pointer to set of notifications related operations > * @perf_priv: pointer to private data structure specific to performance > * protocol(for internal use only) > @@ -273,6 +332,8 @@ struct scmi_notify_ops { > * protocol(for internal use only) > * @reset_priv: pointer to private data structure specific to reset > * protocol(for internal use only) > + * @voltage_priv: pointer to private data structure specific to voltage > + * protocol(for internal use only) > * @notify_priv: pointer to private data structure specific to notifications > * (for internal use only) > */ > @@ -284,6 +345,7 @@ struct scmi_handle { > const struct scmi_power_ops *power_ops; > const struct scmi_sensor_ops *sensor_ops; > const struct scmi_reset_ops *reset_ops; > + const struct scmi_voltage_ops *voltage_ops; > const struct scmi_notify_ops *notify_ops; > /* for protocol internal use */ > void *perf_priv; > @@ -291,6 +353,7 @@ struct scmi_handle { > void *power_priv; > void *sensor_priv; > void *reset_priv; > + void *voltage_priv; > void *notify_priv; > void *system_priv; > }; > @@ -303,6 +366,7 @@ enum scmi_std_protocol { > SCMI_PROTOCOL_CLOCK = 0x14, > SCMI_PROTOCOL_SENSOR = 0x15, > SCMI_PROTOCOL_RESET = 0x16, > + SCMI_PROTOCOL_VOLTAGE = 0x17, > }; > > enum scmi_system_events { > -- > 2.17.1 > _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 1/4] firmware: arm_scmi: Add Voltage Domain Support 2020-10-16 13:36 ` Etienne Carriere @ 2020-10-16 16:51 ` Cristian Marussi 0 siblings, 0 replies; 9+ messages in thread From: Cristian Marussi @ 2020-10-16 16:51 UTC (permalink / raw) To: Etienne Carriere Cc: Rob Herring, satyakim, f.fainelli, Vincent Guittot, Sudeep Holla, linux-kernel, broonie, moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE, Jim Quinlan, Jonathan.Cameron, Souvik Chakravarty, lukasz.luba On Fri, Oct 16, 2020 at 03:36:51PM +0200, Etienne Carriere wrote: > Hello Cristian, > > Thanks for the update. > Some minor comments below. > FYI I successfully tested this series. > Hi Etienne thanks for the review and for testing ! > Regards, > Etienne > > On Thu, 15 Oct 2020 at 17:40, Cristian Marussi <cristian.marussi@arm.com> wrote: > > > > Add SCMI Voltage Domain protocol support. > > > > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com> > > --- > > v1 --> v2 > > - fix voltage levels query loop to reload full cmd description > > between iterations as reported by Etienne Carriere > > - ensure rx buffer is properly sized calli scmi_reset_rx_to_maxsz > > between transfers > > --- > > drivers/firmware/arm_scmi/Makefile | 2 +- > > drivers/firmware/arm_scmi/common.h | 1 + > > drivers/firmware/arm_scmi/driver.c | 2 + > > drivers/firmware/arm_scmi/voltage.c | 382 ++++++++++++++++++++++++++++ > > include/linux/scmi_protocol.h | 64 +++++ > > 5 files changed, 450 insertions(+), 1 deletion(-) > > create mode 100644 drivers/firmware/arm_scmi/voltage.c > > > > diff --git a/drivers/firmware/arm_scmi/Makefile b/drivers/firmware/arm_scmi/Makefile > > index bc0d54f8e861..6a2ef63306d6 100644 > > --- a/drivers/firmware/arm_scmi/Makefile > > +++ b/drivers/firmware/arm_scmi/Makefile > > @@ -4,7 +4,7 @@ scmi-driver-y = driver.o notify.o > > scmi-transport-y = shmem.o > > scmi-transport-$(CONFIG_MAILBOX) += mailbox.o > > scmi-transport-$(CONFIG_HAVE_ARM_SMCCC_DISCOVERY) += smc.o > > -scmi-protocols-y = base.o clock.o perf.o power.o reset.o sensors.o system.o > > +scmi-protocols-y = base.o clock.o perf.o power.o reset.o sensors.o system.o voltage.o > > scmi-module-objs := $(scmi-bus-y) $(scmi-driver-y) $(scmi-protocols-y) \ > > $(scmi-transport-y) > > obj-$(CONFIG_ARM_SCMI_PROTOCOL) += scmi-module.o > > diff --git a/drivers/firmware/arm_scmi/common.h b/drivers/firmware/arm_scmi/common.h > > index 65063fa948d4..c0fb45e7c3e8 100644 > > --- a/drivers/firmware/arm_scmi/common.h > > +++ b/drivers/firmware/arm_scmi/common.h > > @@ -169,6 +169,7 @@ DECLARE_SCMI_REGISTER_UNREGISTER(perf); > > DECLARE_SCMI_REGISTER_UNREGISTER(power); > > DECLARE_SCMI_REGISTER_UNREGISTER(reset); > > DECLARE_SCMI_REGISTER_UNREGISTER(sensors); > > +DECLARE_SCMI_REGISTER_UNREGISTER(voltage); > > DECLARE_SCMI_REGISTER_UNREGISTER(system); > > > > #define DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(id, name) \ > > diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c > > index 3dfd8b6a0ebf..ada35e63feae 100644 > > --- a/drivers/firmware/arm_scmi/driver.c > > +++ b/drivers/firmware/arm_scmi/driver.c > > @@ -946,6 +946,7 @@ static int __init scmi_driver_init(void) > > scmi_power_register(); > > scmi_reset_register(); > > scmi_sensors_register(); > > + scmi_voltage_register(); > > scmi_system_register(); > > > > return platform_driver_register(&scmi_driver); > > @@ -961,6 +962,7 @@ static void __exit scmi_driver_exit(void) > > scmi_power_unregister(); > > scmi_reset_unregister(); > > scmi_sensors_unregister(); > > + scmi_voltage_unregister(); > > scmi_system_unregister(); > > > > platform_driver_unregister(&scmi_driver); > > diff --git a/drivers/firmware/arm_scmi/voltage.c b/drivers/firmware/arm_scmi/voltage.c > > new file mode 100644 > > index 000000000000..a20da5128de1 > > --- /dev/null > > +++ b/drivers/firmware/arm_scmi/voltage.c > > @@ -0,0 +1,382 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * System Control and Management Interface (SCMI) Voltage Protocol > > + * > > + * Copyright (C) 2020 ARM Ltd. > > + */ > > + > > +#include <linux/scmi_protocol.h> > > + > > +#include "common.h" > > + > > +#define VOLTAGE_DOMS_NUM_MASK GENMASK(15, 0) > > +#define REMAINING_LEVELS_MASK GENMASK(31, 16) > > +#define RETURNED_LEVELS_MASK GENMASK(11, 0) > > + > > +enum scmi_voltage_protocol_cmd { > > + VOLTAGE_DOMAIN_ATTRIBUTES = 0x3, > > + VOLTAGE_DESCRIBE_LEVELS = 0x4, > > + VOLTAGE_CONFIG_SET = 0x5, > > + VOLTAGE_CONFIG_GET = 0x6, > > + VOLTAGE_LEVEL_SET = 0x7, > > + VOLTAGE_LEVEL_GET = 0x8, > > +}; > > + > > +struct scmi_msg_resp_protocol_attributes { > > + __le32 attr; > > +#define NUM_VOLTAGE_DOMAINS(x) (u16)(FIELD_GET(VOLTAGE_DOMS_NUM_MASK, (x))) > > +}; > > + > > +struct scmi_msg_resp_domain_attributes { > > + __le32 attr; > > + u8 name[SCMI_MAX_STR_SIZE]; > > +}; > > + > > +struct scmi_msg_cmd_describe_levels { > > + __le32 domain_id; > > + __le32 level_index; > > +}; > > + > > +struct scmi_msg_resp_describe_levels { > > + __le32 flags; > > +#define NUM_REMAINING_LEVELS(f) (u16)(FIELD_GET(REMAINING_LEVELS_MASK, (f))) > > +#define NUM_RETURNED_LEVELS(f) (u16)(FIELD_GET(RETURNED_LEVELS_MASK, (f))) > > +#define SUPPORTS_SEGMENTED_LEVELS(f) ((f) & BIT(12)) > > + __le32 voltage[]; > > +}; > > + > > +struct scmi_msg_cmd_config_set { > > + __le32 domain_id; > > + __le32 config; > > +}; > > + > > +struct scmi_msg_cmd_level_set { > > + __le32 domain_id; > > + __le32 flags; > > + __le32 voltage_level; > > +}; > > + > > +struct voltage_info { > > + u32 version; > > + u16 num_domains; > > + struct scmi_voltage_info **domains; > > +}; > > + > > +static int scmi_protocol_attributes_get(const struct scmi_handle *handle, > > + struct voltage_info *vinfo) > > +{ > > + int ret; > > + struct scmi_xfer *t; > > + struct scmi_msg_resp_protocol_attributes *resp; > > + > > + ret = scmi_xfer_get_init(handle, PROTOCOL_ATTRIBUTES, > > + SCMI_PROTOCOL_VOLTAGE, 0, sizeof(*resp), &t); > > + if (ret) > > + return ret; > > + > > + resp = t->rx.buf; > > + ret = scmi_do_xfer(handle, t); > > + if (!ret) > > + vinfo->num_domains = > > + NUM_VOLTAGE_DOMAINS(le32_to_cpu(resp->attr)); > > + > > + scmi_xfer_put(handle, t); > > + return ret; > > +} > > + > > +static inline int scmi_init_voltage_levels(struct device *dev, > > + struct scmi_voltage_info *v, > > + u32 flags, u32 num_levels) > > +{ > > + bool segmented; > > + > > + segmented = SUPPORTS_SEGMENTED_LEVELS(flags); > > + /* segmented levels array's entries must be multiple-of-3 */ > > + if (!num_levels || (segmented && num_levels % 3)) > > + return -EINVAL; > > I think segment levels are described by a single triplet. > If right, should test (!num_levels || (segmented && num_levels == 3)). > > You're right, multiple triplets were initially described but dropped from the final spec, so my bad I forgot to update this. (same goes with the correspondent regulator support for multiple linear mappings as you spotted there). I'll fix in V3 like if (!num_levels || (segmented && num_levels != 3)) return -EINVAL; > > + > > + v->levels_uv = devm_kcalloc(dev, num_levels, sizeof(u32), GFP_KERNEL); > > + if (!v->levels_uv) > > + return -ENOMEM; > > + > > + v->num_levels = num_levels; > > + v->segmented = segmented; > > + > > + return 0; > > +} > > + > > +static int scmi_voltage_descriptors_get(const struct scmi_handle *handle, > > + struct voltage_info *vinfo) > > +{ > > + int ret, dom; > > + struct scmi_xfer *td, *tl; > > + struct device *dev = handle->dev; > > + struct scmi_msg_resp_domain_attributes *resp_dom; > > + struct scmi_msg_resp_describe_levels *resp_levels; > > + > > + ret = scmi_xfer_get_init(handle, VOLTAGE_DOMAIN_ATTRIBUTES, > > + SCMI_PROTOCOL_VOLTAGE, sizeof(__le32), > > + sizeof(*resp_dom), &td); > > + if (ret) > > + return ret; > > + resp_dom = td->rx.buf; > > + > > + ret = scmi_xfer_get_init(handle, VOLTAGE_DESCRIBE_LEVELS, > > + SCMI_PROTOCOL_VOLTAGE, sizeof(__le64), 0, &tl); > > + if (ret) > > + goto outd; > > + resp_levels = tl->rx.buf; > > + > > + for (dom = 0; dom < vinfo->num_domains; dom++) { > > + u32 desc_index = 0; > > + u16 num_returned = 0, num_remaining = 0; > > + struct scmi_msg_cmd_describe_levels *cmd; > > + struct scmi_voltage_info *v; > > + > > + /* Retrieve domain attributes at first ... */ > > + put_unaligned_le32(dom, td->tx.buf); > > + ret = scmi_do_xfer(handle, td); > > + if (ret) > > + continue; > > Continue and break and report the error code? > Seems you prefer to abort only on local error (ENOMEM), not comm error. > Maybe state with nice inline comment as done below (/* Skip domain on error */). > Ok I'll do. > > + > > + v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL); > > + if (!v) { > > + ret = -ENOMEM; > > + break; > > + } > > + > > + v->id = dom; > > + v->attributes = le32_to_cpu(resp_dom->attr); > > + strlcpy(v->name, resp_dom->name, SCMI_MAX_STR_SIZE); > > + > > + cmd = tl->tx.buf; > > + /* ...then retrieve domain levels descriptions */ > > + do { > > + u32 flags; > > + int cnt; > > + > > + cmd->domain_id = cpu_to_le32(v->id); > > + cmd->level_index = desc_index; > > + ret = scmi_do_xfer(handle, tl); > > + if (ret) > > + break; > > + > > + flags = le32_to_cpu(resp_levels->flags); > > + num_returned = NUM_RETURNED_LEVELS(flags); > > + num_remaining = NUM_REMAINING_LEVELS(flags); > > + > > + /* Allocate space for num_levels if not already done */ > > + if (!v->num_levels) { > > + ret = scmi_init_voltage_levels(dev, v, flags, > > + num_returned + > > + num_remaining); > > + if (ret) > > + break; > > + } > > + > > + if (desc_index + num_returned > v->num_levels) { > > + dev_err(handle->dev, > > + "No. of voltage levels can't exceed %d", > > Missing '\n'. > Right. > > + v->num_levels); > > + ret = -EINVAL; > > + break; > > + } > > + > > + for (cnt = 0; cnt < num_returned; cnt++) { > > + s32 val; > > + > > + val = > > + (s32)le32_to_cpu(resp_levels->voltage[cnt]); > > + v->levels_uv[desc_index + cnt] = val; > > + if (!v->negative_volts_allowed && val < 0) > > could skip testing v->negative_volts_allowed. > if (val < 0) > v->negative_volts_allowed = true; > Ok. > > > + v->negative_volts_allowed = true; > > + } > > + > > + desc_index += num_returned; > > + > > + scmi_reset_rx_to_maxsz(handle, tl); > > + /* check both to avoid infinite loop due to buggy fw */ > > + } while (num_returned && num_remaining); > > + > > + /* > > + * Bail out on memory errors, just skip domain on any > > + * other error. > > + */ > > + if (!ret) > > + vinfo->domains[dom] = v; > > + else if (ret == -ENOMEM) > > + break; > > + > > + scmi_reset_rx_to_maxsz(handle, td); > > + } > > + > > + scmi_xfer_put(handle, tl); > > +outd: > > + scmi_xfer_put(handle, td); > > + > > + return ret; > > +} > > + > > +static inline int __scmi_voltage_get_u32(const struct scmi_handle *handle, > > inline needed ? > Mmm, probably not. Thanks Cristian > > + u8 cmd_id, u32 domain_id, u32 *value) > > +{ > > + int ret; > > + struct scmi_xfer *t; > > + struct voltage_info *vinfo = handle->voltage_priv; > > + > > + if (domain_id >= vinfo->num_domains) > > + return -EINVAL; > > + > > + ret = scmi_xfer_get_init(handle, cmd_id, > > + SCMI_PROTOCOL_VOLTAGE, > > + sizeof(__le32), 0, &t); > > + if (ret) > > + return ret; > > + > > + put_unaligned_le32(domain_id, t->tx.buf); > > + ret = scmi_do_xfer(handle, t); > > + if (!ret) > > + *value = get_unaligned_le32(t->rx.buf); > > + > > + scmi_xfer_put(handle, t); > > + return ret; > > +} > > + > > +static int scmi_voltage_config_set(const struct scmi_handle *handle, > > + u32 domain_id, u32 config) > > +{ > > + int ret; > > + struct scmi_xfer *t; > > + struct voltage_info *vinfo = handle->voltage_priv; > > + struct scmi_msg_cmd_config_set *cmd; > > + > > + if (domain_id >= vinfo->num_domains) > > + return -EINVAL; > > + > > + ret = scmi_xfer_get_init(handle, VOLTAGE_CONFIG_SET, > > + SCMI_PROTOCOL_VOLTAGE, > > + sizeof(*cmd), 0, &t); > > + if (ret) > > + return ret; > > + > > + cmd = t->tx.buf; > > + cmd->domain_id = cpu_to_le32(domain_id); > > + cmd->config = cpu_to_le32(config & GENMASK(3, 0)); > > + > > + ret = scmi_do_xfer(handle, t); > > + > > + scmi_xfer_put(handle, t); > > + return ret; > > +} > > + > > +static int scmi_voltage_config_get(const struct scmi_handle *handle, > > + u32 domain_id, u32 *config) > > +{ > > + return __scmi_voltage_get_u32(handle, VOLTAGE_CONFIG_GET, > > + domain_id, config); > > +} > > + > > +static int scmi_voltage_level_set(const struct scmi_handle *handle, > > + u32 domain_id, u32 flags, s32 volt_uV) > > +{ > > + int ret; > > + struct scmi_xfer *t; > > + struct voltage_info *vinfo = handle->voltage_priv; > > + struct scmi_msg_cmd_level_set *cmd; > > + > > + if (domain_id >= vinfo->num_domains) > > + return -EINVAL; > > + > > + ret = scmi_xfer_get_init(handle, VOLTAGE_LEVEL_SET, > > + SCMI_PROTOCOL_VOLTAGE, > > + sizeof(*cmd), 0, &t); > > + if (ret) > > + return ret; > > + > > + cmd = t->tx.buf; > > + cmd->domain_id = cpu_to_le32(domain_id); > > + cmd->flags = cpu_to_le32(flags); > > + cmd->voltage_level = cpu_to_le32(volt_uV); > > + > > + ret = scmi_do_xfer(handle, t); > > + > > + scmi_xfer_put(handle, t); > > + return ret; > > +} > > + > > +static int scmi_voltage_level_get(const struct scmi_handle *handle, > > + u32 domain_id, s32 *volt_uV) > > +{ > > + return __scmi_voltage_get_u32(handle, VOLTAGE_LEVEL_GET, > > + domain_id, (u32 *)volt_uV); > > +} > > + > > +static const struct scmi_voltage_info * > > +scmi_voltage_info_get(const struct scmi_handle *handle, u32 domain_id) > > +{ > > + struct voltage_info *vinfo = handle->voltage_priv; > > + > > + if (domain_id >= vinfo->num_domains) > > + return NULL; > > + > > + return vinfo->domains[domain_id]; > > +} > > + > > +static int scmi_voltage_domains_num_get(const struct scmi_handle *handle) > > +{ > > + struct voltage_info *vinfo = handle->voltage_priv; > > + > > + return vinfo->num_domains; > > +} > > + > > +static struct scmi_voltage_ops voltage_ops = { > > + .num_domains_get = scmi_voltage_domains_num_get, > > + .info_get = scmi_voltage_info_get, > > + .config_set = scmi_voltage_config_set, > > + .config_get = scmi_voltage_config_get, > > + .level_set = scmi_voltage_level_set, > > + .level_get = scmi_voltage_level_get, > > +}; > > + > > +static int scmi_voltage_protocol_init(struct scmi_handle *handle) > > +{ > > + int ret; > > + u32 version; > > + struct voltage_info *vinfo; > > + > > + ret = scmi_version_get(handle, SCMI_PROTOCOL_VOLTAGE, &version); > > + if (ret) > > + return ret; > > + > > + dev_dbg(handle->dev, "Voltage Version %d.%d\n", > > + PROTOCOL_REV_MAJOR(version), PROTOCOL_REV_MINOR(version)); > > + > > + vinfo = devm_kzalloc(handle->dev, sizeof(*vinfo), GFP_KERNEL); > > + if (!vinfo) > > + return -ENOMEM; > > + vinfo->version = version; > > + > > + ret = scmi_protocol_attributes_get(handle, vinfo); > > + if (ret) > > + return ret; > > + > > + if (vinfo->num_domains) { > > + vinfo->domains = devm_kcalloc(handle->dev, vinfo->num_domains, > > + sizeof(vinfo->domains), > > + GFP_KERNEL); > > + if (!vinfo->domains) > > + return -ENOMEM; > > + ret = scmi_voltage_descriptors_get(handle, vinfo); > > + if (ret) > > + return ret; > > + } else { > > + dev_warn(handle->dev, "No Voltage domains found.\n"); > > + } > > + > > + handle->voltage_ops = &voltage_ops; > > + handle->voltage_priv = vinfo; > > + > > + return 0; > > +} > > + > > +DEFINE_SCMI_PROTOCOL_REGISTER_UNREGISTER(SCMI_PROTOCOL_VOLTAGE, voltage) > > diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h > > index 9cd312a1ff92..032ad9bb2a53 100644 > > --- a/include/linux/scmi_protocol.h > > +++ b/include/linux/scmi_protocol.h > > @@ -209,6 +209,64 @@ struct scmi_reset_ops { > > int (*deassert)(const struct scmi_handle *handle, u32 domain); > > }; > > > > +/** > > + * struct scmi_voltage_info - describe one available SCMI Voltage Domain > > + * > > + * @id: the domain ID as advertised by the platform > > + * @segmented: defines the layout of the entries of array @levels_uv. > > + * - when True the entries are to be interpreted as triplets, > > + * each defining a segment representing a range of equally > > + * space voltages: <lowest_volts>, <highest_volt>, <step_uV> > > + * - when False the entries simply represent a single discrete > > + * supported voltage level > > + * @negative_volts_allowed: True if any of the entries of @levels_uv represent > > + * a negative voltage. > > + * @attributes: represents Voltage Domain advertised attributes > > + * @name: name assigned to the Voltage Domain by platform > > + * @num_levels: number of total entries in @levels_uv. > > + * @levels_uv: array of entries describing the available voltage levels for > > + * this domain. > > + */ > > +struct scmi_voltage_info { > > + u32 id; > > + bool segmented; > > +#define SCMI_VOLTAGE_SEGMENT_LOW 0 > > +#define SCMI_VOLTAGE_SEGMENT_HIGH 1 > > +#define SCMI_VOLTAGE_SEGMENT_STEP 2 > > + bool negative_volts_allowed; > > + u32 attributes; > > + char name[SCMI_MAX_STR_SIZE]; > > + u16 num_levels; > > + s32 *levels_uv; > > +}; > > + > > +/** > > + * struct scmi_voltage_ops - represents the various operations provided > > + * by SCMI Voltage Protocol > > + * > > + * @num_domains_get: get the count of voltage domains provided by SCMI > > + * @info_get: get the information of the specified domain > > + * @config_set: set the config for the specified domain > > + * @config_get: get the config of the specified domain > > + * @level_set: set the voltage level for the specified domain > > + * @level_get: get the voltage level of the specified domain > > + */ > > +struct scmi_voltage_ops { > > + int (*num_domains_get)(const struct scmi_handle *handle); > > + const struct scmi_voltage_info *(*info_get) > > + (const struct scmi_handle *handle, u32 domain_id); > > + int (*config_set)(const struct scmi_handle *handle, u32 domain_id, > > + u32 config); > > +#define SCMI_VOLTAGE_ARCH_STATE_OFF 0x0 > > +#define SCMI_VOLTAGE_ARCH_STATE_ON 0x7 > > + int (*config_get)(const struct scmi_handle *handle, u32 domain_id, > > + u32 *config); > > + int (*level_set)(const struct scmi_handle *handle, u32 domain_id, > > + u32 flags, s32 volt_uV); > > + int (*level_get)(const struct scmi_handle *handle, u32 domain_id, > > + s32 *volt_uV); > > +}; > > + > > /** > > * struct scmi_notify_ops - represents notifications' operations provided by > > * SCMI core > > @@ -262,6 +320,7 @@ struct scmi_notify_ops { > > * @clk_ops: pointer to set of clock protocol operations > > * @sensor_ops: pointer to set of sensor protocol operations > > * @reset_ops: pointer to set of reset protocol operations > > + * @voltage_ops: pointer to set of voltage protocol operations > > * @notify_ops: pointer to set of notifications related operations > > * @perf_priv: pointer to private data structure specific to performance > > * protocol(for internal use only) > > @@ -273,6 +332,8 @@ struct scmi_notify_ops { > > * protocol(for internal use only) > > * @reset_priv: pointer to private data structure specific to reset > > * protocol(for internal use only) > > + * @voltage_priv: pointer to private data structure specific to voltage > > + * protocol(for internal use only) > > * @notify_priv: pointer to private data structure specific to notifications > > * (for internal use only) > > */ > > @@ -284,6 +345,7 @@ struct scmi_handle { > > const struct scmi_power_ops *power_ops; > > const struct scmi_sensor_ops *sensor_ops; > > const struct scmi_reset_ops *reset_ops; > > + const struct scmi_voltage_ops *voltage_ops; > > const struct scmi_notify_ops *notify_ops; > > /* for protocol internal use */ > > void *perf_priv; > > @@ -291,6 +353,7 @@ struct scmi_handle { > > void *power_priv; > > void *sensor_priv; > > void *reset_priv; > > + void *voltage_priv; > > void *notify_priv; > > void *system_priv; > > }; > > @@ -303,6 +366,7 @@ enum scmi_std_protocol { > > SCMI_PROTOCOL_CLOCK = 0x14, > > SCMI_PROTOCOL_SENSOR = 0x15, > > SCMI_PROTOCOL_RESET = 0x16, > > + SCMI_PROTOCOL_VOLTAGE = 0x17, > > }; > > > > enum scmi_system_events { > > -- > > 2.17.1 > > _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 2/4] firmware: arm_scmi: add SCMI Voltage Domain devname 2020-10-15 15:39 [PATCH v2 0/4] Add support for SCMIv3.0 Voltage Domain Protocol and SCMI-Regulator Cristian Marussi 2020-10-15 15:39 ` [PATCH v2 1/4] firmware: arm_scmi: Add Voltage Domain Support Cristian Marussi @ 2020-10-15 15:39 ` Cristian Marussi 2020-10-15 15:40 ` [PATCH v2 3/4] regulator: add SCMI driver Cristian Marussi 2020-10-15 15:40 ` [PATCH v2 4/4] dt-bindings: arm: add support for SCMI Regulators Cristian Marussi 3 siblings, 0 replies; 9+ messages in thread From: Cristian Marussi @ 2020-10-15 15:39 UTC (permalink / raw) To: linux-kernel, linux-arm-kernel Cc: robh, satyakim, f.fainelli, vincent.guittot, sudeep.holla, broonie, cristian.marussi, james.quinlan, Jonathan.Cameron, souvik.chakravarty, etienne.carriere, lukasz.luba Add SCMI Voltage Domain device name to the core list of supported protocol devices. Signed-off-by: Cristian Marussi <cristian.marussi@arm.com> --- drivers/firmware/arm_scmi/driver.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/firmware/arm_scmi/driver.c b/drivers/firmware/arm_scmi/driver.c index ada35e63feae..5392e1fc6b4e 100644 --- a/drivers/firmware/arm_scmi/driver.c +++ b/drivers/firmware/arm_scmi/driver.c @@ -743,6 +743,7 @@ static struct scmi_prot_devnames devnames[] = { { SCMI_PROTOCOL_CLOCK, { "clocks" },}, { SCMI_PROTOCOL_SENSOR, { "hwmon" },}, { SCMI_PROTOCOL_RESET, { "reset" },}, + { SCMI_PROTOCOL_VOLTAGE, { "regulator" },}, }; static inline void -- 2.17.1 _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel ^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 3/4] regulator: add SCMI driver 2020-10-15 15:39 [PATCH v2 0/4] Add support for SCMIv3.0 Voltage Domain Protocol and SCMI-Regulator Cristian Marussi 2020-10-15 15:39 ` [PATCH v2 1/4] firmware: arm_scmi: Add Voltage Domain Support Cristian Marussi 2020-10-15 15:39 ` [PATCH v2 2/4] firmware: arm_scmi: add SCMI Voltage Domain devname Cristian Marussi @ 2020-10-15 15:40 ` Cristian Marussi 2020-10-16 14:09 ` Etienne Carriere 2020-10-15 15:40 ` [PATCH v2 4/4] dt-bindings: arm: add support for SCMI Regulators Cristian Marussi 3 siblings, 1 reply; 9+ messages in thread From: Cristian Marussi @ 2020-10-15 15:40 UTC (permalink / raw) To: linux-kernel, linux-arm-kernel Cc: robh, satyakim, f.fainelli, vincent.guittot, sudeep.holla, broonie, cristian.marussi, james.quinlan, Jonathan.Cameron, souvik.chakravarty, etienne.carriere, lukasz.luba Add a simple regulator based on SCMI Voltage Domain Protocol. Signed-off-by: Cristian Marussi <cristian.marussi@arm.com> ---- v1 --> v2 - removed duplicate regulator naming - removed redundant .get/set_voltage ops: only _sel variants implemented - removed condexpr on fail path to increase readability v0 --> v1 - fixed init_data constraint parsing - fixes for v5.8 (linear_range.h) - fixed commit message content and subject line format - factored out SCMI core specific changes to distinct patch - reworked Kconfig and Makefile to keep proper alphabetic order - fixed SPDX comment style - removed unneeded inline functions - reworked conditionals for legibility - fixed some return paths to properly report SCMI original errors codes - added some more descriptive error messages when fw returns invalid ranges - removed unneeded explicit devm_regulator_unregister from .remove() --- drivers/regulator/Kconfig | 9 + drivers/regulator/Makefile | 1 + drivers/regulator/scmi-regulator.c | 453 +++++++++++++++++++++++++++++ 3 files changed, 463 insertions(+) create mode 100644 drivers/regulator/scmi-regulator.c diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig index de17ef7e18f0..6d3a10cb9833 100644 --- a/drivers/regulator/Kconfig +++ b/drivers/regulator/Kconfig @@ -155,6 +155,15 @@ config REGULATOR_ARIZONA_MICSUPP and Wolfson Microelectronic Arizona codecs devices. +config REGULATOR_ARM_SCMI + tristate "SCMI based regulator driver" + depends on ARM_SCMI_PROTOCOL && OF + 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. + config REGULATOR_AS3711 tristate "AS3711 PMIC" depends on MFD_AS3711 diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile index d8d3ecf526a8..0532a7393d5d 100644 --- a/drivers/regulator/Makefile +++ b/drivers/regulator/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_REGULATOR_AD5398) += ad5398.o obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o obj-$(CONFIG_REGULATOR_ARIZONA_LDO1) += arizona-ldo1.o obj-$(CONFIG_REGULATOR_ARIZONA_MICSUPP) += arizona-micsupp.o +obj-$(CONFIG_REGULATOR_ARM_SCMI) += scmi-regulator.o obj-$(CONFIG_REGULATOR_AS3711) += as3711-regulator.o obj-$(CONFIG_REGULATOR_AS3722) += as3722-regulator.o obj-$(CONFIG_REGULATOR_AXP20X) += axp20x-regulator.o diff --git a/drivers/regulator/scmi-regulator.c b/drivers/regulator/scmi-regulator.c new file mode 100644 index 000000000000..e4e7d0345723 --- /dev/null +++ b/drivers/regulator/scmi-regulator.c @@ -0,0 +1,453 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// System Control and Management Interface (SCMI) based regulator driver +// +// Copyright (C) 2020 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 <linux/linear_range.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/regulator/of_regulator.h> +#include <linux/scmi_protocol.h> +#include <linux/slab.h> +#include <linux/types.h> + +struct scmi_regulator { + u32 id; + struct scmi_device *sdev; + struct regulator_dev *rdev; + struct device_node *of_node; + struct regulator_desc desc; + struct regulator_config conf; +}; + +struct scmi_regulator_info { + int num_doms; + struct scmi_regulator **sregv; +}; + +static int scmi_reg_enable(struct regulator_dev *rdev) +{ + struct scmi_regulator *sreg = rdev_get_drvdata(rdev); + const struct scmi_handle *handle = sreg->sdev->handle; + + return handle->voltage_ops->config_set(handle, sreg->id, + SCMI_VOLTAGE_ARCH_STATE_ON); +} + +static int scmi_reg_disable(struct regulator_dev *rdev) +{ + struct scmi_regulator *sreg = rdev_get_drvdata(rdev); + const struct scmi_handle *handle = sreg->sdev->handle; + + return handle->voltage_ops->config_set(handle, 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 = rdev_get_drvdata(rdev); + const struct scmi_handle *handle = sreg->sdev->handle; + + ret = handle->voltage_ops->config_get(handle, sreg->id, + &config); + if (ret) { + dev_err(&sreg->sdev->dev, + "Error %d reading regulator %s status.\n", + ret, sreg->desc.name); + return 0; + } + + 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 = rdev_get_drvdata(rdev); + const struct scmi_handle *handle = sreg->sdev->handle; + + ret = handle->voltage_ops->level_get(handle, sreg->id, &volt_uV); + if (ret) + return ret; + + return sreg->desc.ops->map_voltage(rdev, volt_uV, volt_uV); +} + +static int scmi_reg_set_voltage_sel(struct regulator_dev *rdev, + unsigned int selector) +{ + int ret; + s32 volt_uV; + struct scmi_regulator *sreg = rdev_get_drvdata(rdev); + const struct scmi_handle *handle = sreg->sdev->handle; + + volt_uV = sreg->desc.ops->list_voltage(rdev, selector); + if (volt_uV <= 0) + return -EINVAL; + + ret = handle->voltage_ops->level_set(handle, sreg->id, 0x0, volt_uV); + if (ret) + return ret; + + return ret; +} + +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_range_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_range, + .map_voltage = regulator_map_voltage_linear_range, +}; + +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) +{ + /* + * Note that SCMI voltage domains describable by linear ranges + * (segments) {low, high, step} are guaranteed to come in triplets by + * the SCMI Voltage Domain protocol support itself. + */ + if (vinfo->num_levels == 3) { + s32 delta_uV; + + 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 */ + sreg->desc.fixed_uV = + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; + sreg->desc.n_voltages = 1; + sreg->desc.ops = &scmi_reg_fixed_ops; + } else { + /* One simple linear mapping. */ + sreg->desc.min_uV = + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; + sreg->desc.uV_step = + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_STEP]; + sreg->desc.linear_min_sel = 0; + sreg->desc.n_voltages = delta_uV / sreg->desc.uV_step; + sreg->desc.ops = &scmi_reg_linear_ops; + } + } else { + /* Multiple linear mappings. */ + int i, num_ranges, last_max = -1; + struct linear_range *lr; + + num_ranges = vinfo->num_levels / 3; + lr = devm_kcalloc(&sreg->sdev->dev, num_ranges, + sizeof(*lr), GFP_KERNEL); + if (!lr) + return -ENOMEM; + + sreg->desc.n_linear_ranges = num_ranges; + sreg->desc.linear_ranges = lr; + for (i = 0; num_ranges; num_ranges--, i += 3, lr++) { + s32 delta_uV; + + lr->min = + vinfo->levels_uv[i + SCMI_VOLTAGE_SEGMENT_LOW]; + lr->step = + vinfo->levels_uv[i + SCMI_VOLTAGE_SEGMENT_STEP]; + delta_uV = + vinfo->levels_uv[i + SCMI_VOLTAGE_SEGMENT_HIGH] - + lr->min; + if (delta_uV <= 0 || !(delta_uV / lr->step)) { + 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; + } + lr->max_sel = delta_uV / lr->step - 1; + lr->min_sel = last_max + 1; + last_max = lr->max_sel; + } + sreg->desc.n_voltages = last_max + 1; + sreg->desc.ops = &scmi_reg_range_ops; + } + + return 0; +} + +static int +scmi_config_discrete_regulator_mappings(struct scmi_regulator *sreg, + const struct scmi_voltage_info *vinfo) +{ + /* Discrete non linear levels are mapped to volt_table */ + sreg->desc.n_voltages = vinfo->num_levels; + if (sreg->desc.n_voltages > 1) { + sreg->desc.volt_table = (const unsigned int *)vinfo->levels_uv; + sreg->desc.ops = &scmi_reg_discrete_ops; + } else { + sreg->desc.fixed_uV = vinfo->levels_uv[0]; + sreg->desc.ops = &scmi_reg_fixed_ops; + } + + return 0; +} + +static int scmi_regulator_common_init(struct scmi_regulator *sreg) +{ + int ret; + const struct scmi_handle *handle = sreg->sdev->handle; + struct device *dev = &sreg->sdev->dev; + const struct scmi_voltage_info *vinfo; + + vinfo = handle->voltage_ops->info_get(handle, sreg->id); + if (!vinfo) + return -ENODEV; + + if (!vinfo->num_levels) + return -EINVAL; + + /* + * 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->of_node->full_name); + return -EOPNOTSUPP; + } + + sreg->desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s", vinfo->name); + if (!sreg->desc.name) + return -ENOMEM; + + sreg->desc.id = sreg->id; + sreg->desc.type = REGULATOR_VOLTAGE; + sreg->desc.owner = THIS_MODULE; + sreg->desc.of_match = sreg->of_node->name; + sreg->desc.regulators_node = "regulators"; + if (vinfo->segmented) + ret = scmi_config_linear_regulator_mappings(sreg, vinfo); + else + ret = scmi_config_discrete_regulator_mappings(sreg, vinfo); + if (ret) + return ret; + + sreg->conf.dev = dev; + sreg->conf.driver_data = sreg; + + return 0; +} + +static int process_scmi_regulator_of_node(struct scmi_device *sdev, + 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] = devm_kzalloc(&sdev->dev, + sizeof(struct scmi_regulator), + GFP_KERNEL); + if (!rinfo->sregv[dom]) + return -ENOMEM; + + rinfo->sregv[dom]->id = dom; + rinfo->sregv[dom]->sdev = sdev; + /* get hold of good nodes */ + of_node_get(np); + rinfo->sregv[dom]->of_node = np; + dev_info(&sdev->dev, + "Found valid SCMI Regulator -- OF node [%d] -> %s\n", + dom, np->full_name); + + return ret; +} + +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; + + if (!handle || !handle->voltage_ops) + return -ENODEV; + + num_doms = handle->voltage_ops->num_domains_get(handle); + 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 = devm_kzalloc(&sdev->dev, sizeof(*rinfo), GFP_KERNEL); + if (!rinfo) + return -ENOMEM; + + /* Allocate pointers' array for all possible domains */ + rinfo->sregv = devm_kcalloc(&sdev->dev, num_doms, + sizeof(rinfo->sregv), 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->of_node, "regulators"); + for_each_child_of_node(np, child) { + ret = process_scmi_regulator_of_node(sdev, 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 + */ + for (d = 0; d < num_doms; d++) { + struct scmi_regulator *sreg = rinfo->sregv[d]; + + if (!sreg) + continue; + ret = scmi_regulator_common_init(sreg); + if (ret) + continue; + sreg->rdev = devm_regulator_register(&sdev->dev, &sreg->desc, + &sreg->conf); + if (IS_ERR(sreg->rdev)) { + sreg->rdev = NULL; + continue; + } + + dev_info(&sdev->dev, + "Regulator %s registered for domain [%d](%s)\n", + sreg->desc.name, sreg->id, sreg->desc.name); + } + + dev_set_drvdata(&sdev->dev, rinfo); + + return 0; +} + +static void scmi_regulator_remove(struct scmi_device *sdev) +{ + int d; + struct scmi_regulator_info *rinfo; + + rinfo = dev_get_drvdata(&sdev->dev); + if (!rinfo) + return; + + for (d = 0; d < rinfo->num_doms; d++) { + if (!rinfo->sregv[d]) + continue; + of_node_put(rinfo->sregv[d]->of_node); + } +} + +static const struct scmi_device_id scmi_regulator_id_table[] = { + { SCMI_PROTOCOL_VOLTAGE, "regulator" }, + { }, +}; +MODULE_DEVICE_TABLE(scmi, scmi_regulator_id_table); + +static struct scmi_driver scmi_drv = { + .name = "scmi-regulator", + .probe = scmi_regulator_probe, + .remove = scmi_regulator_remove, + .id_table = scmi_regulator_id_table, +}; + +module_scmi_driver(scmi_drv); + +MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>"); +MODULE_DESCRIPTION("ARM SCMI regulator driver"); +MODULE_LICENSE("GPL v2"); -- 2.17.1 _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2 3/4] regulator: add SCMI driver 2020-10-15 15:40 ` [PATCH v2 3/4] regulator: add SCMI driver Cristian Marussi @ 2020-10-16 14:09 ` Etienne Carriere 2020-10-16 16:55 ` Cristian Marussi 0 siblings, 1 reply; 9+ messages in thread From: Etienne Carriere @ 2020-10-16 14:09 UTC (permalink / raw) To: Cristian Marussi Cc: Rob Herring, satyakim, f.fainelli, Vincent Guittot, Sudeep Holla, linux-kernel, broonie, moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE, Jim Quinlan, Jonathan.Cameron, Souvik Chakravarty, lukasz.luba Hi Cristian, Some minor comments... Etienne On Thu, 15 Oct 2020 at 17:40, Cristian Marussi <cristian.marussi@arm.com> wrote: > > Add a simple regulator based on SCMI Voltage Domain Protocol. > > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com> > ---- > v1 --> v2 > - removed duplicate regulator naming > - removed redundant .get/set_voltage ops: only _sel variants implemented > - removed condexpr on fail path to increase readability > > v0 --> v1 > - fixed init_data constraint parsing > - fixes for v5.8 (linear_range.h) > - fixed commit message content and subject line format > - factored out SCMI core specific changes to distinct patch > - reworked Kconfig and Makefile to keep proper alphabetic order > - fixed SPDX comment style > - removed unneeded inline functions > - reworked conditionals for legibility > - fixed some return paths to properly report SCMI original errors codes > - added some more descriptive error messages when fw returns invalid ranges > - removed unneeded explicit devm_regulator_unregister from .remove() > --- > drivers/regulator/Kconfig | 9 + > drivers/regulator/Makefile | 1 + > drivers/regulator/scmi-regulator.c | 453 +++++++++++++++++++++++++++++ > 3 files changed, 463 insertions(+) > create mode 100644 drivers/regulator/scmi-regulator.c > > diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig > index de17ef7e18f0..6d3a10cb9833 100644 > --- a/drivers/regulator/Kconfig > +++ b/drivers/regulator/Kconfig > @@ -155,6 +155,15 @@ config REGULATOR_ARIZONA_MICSUPP > and Wolfson Microelectronic Arizona codecs > devices. > > +config REGULATOR_ARM_SCMI > + tristate "SCMI based regulator driver" > + depends on ARM_SCMI_PROTOCOL && OF > + 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. > + > config REGULATOR_AS3711 > tristate "AS3711 PMIC" > depends on MFD_AS3711 > diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile > index d8d3ecf526a8..0532a7393d5d 100644 > --- a/drivers/regulator/Makefile > +++ b/drivers/regulator/Makefile > @@ -24,6 +24,7 @@ obj-$(CONFIG_REGULATOR_AD5398) += ad5398.o > obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o > obj-$(CONFIG_REGULATOR_ARIZONA_LDO1) += arizona-ldo1.o > obj-$(CONFIG_REGULATOR_ARIZONA_MICSUPP) += arizona-micsupp.o > +obj-$(CONFIG_REGULATOR_ARM_SCMI) += scmi-regulator.o > obj-$(CONFIG_REGULATOR_AS3711) += as3711-regulator.o > obj-$(CONFIG_REGULATOR_AS3722) += as3722-regulator.o > obj-$(CONFIG_REGULATOR_AXP20X) += axp20x-regulator.o > diff --git a/drivers/regulator/scmi-regulator.c b/drivers/regulator/scmi-regulator.c > new file mode 100644 > index 000000000000..e4e7d0345723 > --- /dev/null > +++ b/drivers/regulator/scmi-regulator.c > @@ -0,0 +1,453 @@ > +// SPDX-License-Identifier: GPL-2.0 > +// > +// System Control and Management Interface (SCMI) based regulator driver > +// > +// Copyright (C) 2020 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 <linux/linear_range.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/regulator/driver.h> > +#include <linux/regulator/machine.h> > +#include <linux/regulator/of_regulator.h> > +#include <linux/scmi_protocol.h> > +#include <linux/slab.h> > +#include <linux/types.h> > + > +struct scmi_regulator { > + u32 id; > + struct scmi_device *sdev; > + struct regulator_dev *rdev; > + struct device_node *of_node; > + struct regulator_desc desc; > + struct regulator_config conf; > +}; > + > +struct scmi_regulator_info { > + int num_doms; > + struct scmi_regulator **sregv; > +}; > + > +static int scmi_reg_enable(struct regulator_dev *rdev) > +{ > + struct scmi_regulator *sreg = rdev_get_drvdata(rdev); > + const struct scmi_handle *handle = sreg->sdev->handle; > + > + return handle->voltage_ops->config_set(handle, sreg->id, > + SCMI_VOLTAGE_ARCH_STATE_ON); > +} > + > +static int scmi_reg_disable(struct regulator_dev *rdev) > +{ > + struct scmi_regulator *sreg = rdev_get_drvdata(rdev); > + const struct scmi_handle *handle = sreg->sdev->handle; > + > + return handle->voltage_ops->config_set(handle, 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 = rdev_get_drvdata(rdev); > + const struct scmi_handle *handle = sreg->sdev->handle; > + > + ret = handle->voltage_ops->config_get(handle, sreg->id, > + &config); > + if (ret) { > + dev_err(&sreg->sdev->dev, > + "Error %d reading regulator %s status.\n", > + ret, sreg->desc.name); > + return 0; > + } > + > + 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 = rdev_get_drvdata(rdev); > + const struct scmi_handle *handle = sreg->sdev->handle; > + > + ret = handle->voltage_ops->level_get(handle, sreg->id, &volt_uV); > + if (ret) > + return ret; > + > + return sreg->desc.ops->map_voltage(rdev, volt_uV, volt_uV); > +} > + > +static int scmi_reg_set_voltage_sel(struct regulator_dev *rdev, > + unsigned int selector) > +{ > + int ret; > + s32 volt_uV; > + struct scmi_regulator *sreg = rdev_get_drvdata(rdev); > + const struct scmi_handle *handle = sreg->sdev->handle; > + > + volt_uV = sreg->desc.ops->list_voltage(rdev, selector); > + if (volt_uV <= 0) > + return -EINVAL; > + > + ret = handle->voltage_ops->level_set(handle, sreg->id, 0x0, volt_uV); > + if (ret) > + return ret; > + > + return ret; return handle->voltage_ops->level_set(handle, 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_range_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_range, > + .map_voltage = regulator_map_voltage_linear_range, > +}; > + > +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) > +{ > + /* > + * Note that SCMI voltage domains describable by linear ranges > + * (segments) {low, high, step} are guaranteed to come in triplets by > + * the SCMI Voltage Domain protocol support itself. > + */ > + if (vinfo->num_levels == 3) { > + s32 delta_uV; > + > + 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 */ > + sreg->desc.fixed_uV = > + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; > + sreg->desc.n_voltages = 1; > + sreg->desc.ops = &scmi_reg_fixed_ops; > + } else { > + /* One simple linear mapping. */ > + sreg->desc.min_uV = > + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; > + sreg->desc.uV_step = > + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_STEP]; > + sreg->desc.linear_min_sel = 0; > + sreg->desc.n_voltages = delta_uV / sreg->desc.uV_step; > + sreg->desc.ops = &scmi_reg_linear_ops; > + } > + } else { > + /* Multiple linear mappings. */ I don't see this multi-linear mapping in the v3.0 beta of the spec. Is it something planned? > + int i, num_ranges, last_max = -1; > + struct linear_range *lr; > + > + num_ranges = vinfo->num_levels / 3; > + lr = devm_kcalloc(&sreg->sdev->dev, num_ranges, > + sizeof(*lr), GFP_KERNEL); > + if (!lr) > + return -ENOMEM; > + > + sreg->desc.n_linear_ranges = num_ranges; > + sreg->desc.linear_ranges = lr; > + for (i = 0; num_ranges; num_ranges--, i += 3, lr++) { > + s32 delta_uV; > + > + lr->min = > + vinfo->levels_uv[i + SCMI_VOLTAGE_SEGMENT_LOW]; > + lr->step = > + vinfo->levels_uv[i + SCMI_VOLTAGE_SEGMENT_STEP]; > + delta_uV = > + vinfo->levels_uv[i + SCMI_VOLTAGE_SEGMENT_HIGH] - > + lr->min; > + if (delta_uV <= 0 || !(delta_uV / lr->step)) { > + 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; > + } > + lr->max_sel = delta_uV / lr->step - 1; > + lr->min_sel = last_max + 1; > + last_max = lr->max_sel; > + } > + sreg->desc.n_voltages = last_max + 1; > + sreg->desc.ops = &scmi_reg_range_ops; > + } > + > + return 0; > +} > + > +static int > +scmi_config_discrete_regulator_mappings(struct scmi_regulator *sreg, > + const struct scmi_voltage_info *vinfo) > +{ > + /* Discrete non linear levels are mapped to volt_table */ > + sreg->desc.n_voltages = vinfo->num_levels; > + if (sreg->desc.n_voltages > 1) { > + sreg->desc.volt_table = (const unsigned int *)vinfo->levels_uv; > + sreg->desc.ops = &scmi_reg_discrete_ops; > + } else { > + sreg->desc.fixed_uV = vinfo->levels_uv[0]; > + sreg->desc.ops = &scmi_reg_fixed_ops; > + } > + > + return 0; > +} > + > +static int scmi_regulator_common_init(struct scmi_regulator *sreg) > +{ > + int ret; > + const struct scmi_handle *handle = sreg->sdev->handle; > + struct device *dev = &sreg->sdev->dev; > + const struct scmi_voltage_info *vinfo; > + > + vinfo = handle->voltage_ops->info_get(handle, sreg->id); > + if (!vinfo) > + return -ENODEV; > + > + if (!vinfo->num_levels) > + return -EINVAL; > + > + /* > + * 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->of_node->full_name); > + return -EOPNOTSUPP; > + } > + > + sreg->desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s", vinfo->name); > + if (!sreg->desc.name) > + return -ENOMEM; > + > + sreg->desc.id = sreg->id; > + sreg->desc.type = REGULATOR_VOLTAGE; > + sreg->desc.owner = THIS_MODULE; > + sreg->desc.of_match = sreg->of_node->name; > + sreg->desc.regulators_node = "regulators"; > + if (vinfo->segmented) > + ret = scmi_config_linear_regulator_mappings(sreg, vinfo); > + else > + ret = scmi_config_discrete_regulator_mappings(sreg, vinfo); > + if (ret) > + return ret; > + > + sreg->conf.dev = dev; > + sreg->conf.driver_data = sreg; > + > + return 0; > +} > + > +static int process_scmi_regulator_of_node(struct scmi_device *sdev, > + 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] = devm_kzalloc(&sdev->dev, > + sizeof(struct scmi_regulator), > + GFP_KERNEL); > + if (!rinfo->sregv[dom]) > + return -ENOMEM; > + > + rinfo->sregv[dom]->id = dom; > + rinfo->sregv[dom]->sdev = sdev; > + /* get hold of good nodes */ > + of_node_get(np); > + rinfo->sregv[dom]->of_node = np; > + dev_info(&sdev->dev, > + "Found valid SCMI Regulator -- OF node [%d] -> %s\n", > + dom, np->full_name); > + > + return ret; suggest return 0; here. > +} > + > +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; > + > + if (!handle || !handle->voltage_ops) > + return -ENODEV; > + > + num_doms = handle->voltage_ops->num_domains_get(handle); > + 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 = devm_kzalloc(&sdev->dev, sizeof(*rinfo), GFP_KERNEL); > + if (!rinfo) > + return -ENOMEM; > + > + /* Allocate pointers' array for all possible domains */ > + rinfo->sregv = devm_kcalloc(&sdev->dev, num_doms, > + sizeof(rinfo->sregv), 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->of_node, "regulators"); > + for_each_child_of_node(np, child) { > + ret = process_scmi_regulator_of_node(sdev, 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 > + */ > + for (d = 0; d < num_doms; d++) { > + struct scmi_regulator *sreg = rinfo->sregv[d]; > + > + if (!sreg) > + continue; > + ret = scmi_regulator_common_init(sreg); > + if (ret) > + continue; > + sreg->rdev = devm_regulator_register(&sdev->dev, &sreg->desc, > + &sreg->conf); > + if (IS_ERR(sreg->rdev)) { > + sreg->rdev = NULL; > + continue; > + } > + > + dev_info(&sdev->dev, > + "Regulator %s registered for domain [%d](%s)\n", > + sreg->desc.name, sreg->id, sreg->desc.name); Maybe not display sreg->desc.name twice. > + } > + > + dev_set_drvdata(&sdev->dev, rinfo); > + > + return 0; > +} > + > +static void scmi_regulator_remove(struct scmi_device *sdev) > +{ > + int d; > + struct scmi_regulator_info *rinfo; > + > + rinfo = dev_get_drvdata(&sdev->dev); > + if (!rinfo) > + return; > + > + for (d = 0; d < rinfo->num_doms; d++) { > + if (!rinfo->sregv[d]) > + continue; > + of_node_put(rinfo->sregv[d]->of_node); > + } > +} > + > +static const struct scmi_device_id scmi_regulator_id_table[] = { > + { SCMI_PROTOCOL_VOLTAGE, "regulator" }, > + { }, > +}; > +MODULE_DEVICE_TABLE(scmi, scmi_regulator_id_table); > + > +static struct scmi_driver scmi_drv = { > + .name = "scmi-regulator", > + .probe = scmi_regulator_probe, > + .remove = scmi_regulator_remove, > + .id_table = scmi_regulator_id_table, > +}; > + > +module_scmi_driver(scmi_drv); > + > +MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>"); > +MODULE_DESCRIPTION("ARM SCMI regulator driver"); > +MODULE_LICENSE("GPL v2"); > -- > 2.17.1 > _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 3/4] regulator: add SCMI driver 2020-10-16 14:09 ` Etienne Carriere @ 2020-10-16 16:55 ` Cristian Marussi 0 siblings, 0 replies; 9+ messages in thread From: Cristian Marussi @ 2020-10-16 16:55 UTC (permalink / raw) To: Etienne Carriere Cc: Rob Herring, satyakim, f.fainelli, Vincent Guittot, Sudeep Holla, linux-kernel, broonie, moderated list:ARM/FREESCALE IMX / MXC ARM ARCHITECTURE, Jim Quinlan, Jonathan.Cameron, Souvik Chakravarty, lukasz.luba On Fri, Oct 16, 2020 at 04:09:42PM +0200, Etienne Carriere wrote: > Hi Cristian, > > Some minor comments... > > Etienne > > > On Thu, 15 Oct 2020 at 17:40, Cristian Marussi <cristian.marussi@arm.com> wrote: > > > > Add a simple regulator based on SCMI Voltage Domain Protocol. > > > > Signed-off-by: Cristian Marussi <cristian.marussi@arm.com> > > ---- > > v1 --> v2 > > - removed duplicate regulator naming > > - removed redundant .get/set_voltage ops: only _sel variants implemented > > - removed condexpr on fail path to increase readability > > > > v0 --> v1 > > - fixed init_data constraint parsing > > - fixes for v5.8 (linear_range.h) > > - fixed commit message content and subject line format > > - factored out SCMI core specific changes to distinct patch > > - reworked Kconfig and Makefile to keep proper alphabetic order > > - fixed SPDX comment style > > - removed unneeded inline functions > > - reworked conditionals for legibility > > - fixed some return paths to properly report SCMI original errors codes > > - added some more descriptive error messages when fw returns invalid ranges > > - removed unneeded explicit devm_regulator_unregister from .remove() > > --- > > drivers/regulator/Kconfig | 9 + > > drivers/regulator/Makefile | 1 + > > drivers/regulator/scmi-regulator.c | 453 +++++++++++++++++++++++++++++ > > 3 files changed, 463 insertions(+) > > create mode 100644 drivers/regulator/scmi-regulator.c > > > > diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig > > index de17ef7e18f0..6d3a10cb9833 100644 > > --- a/drivers/regulator/Kconfig > > +++ b/drivers/regulator/Kconfig > > @@ -155,6 +155,15 @@ config REGULATOR_ARIZONA_MICSUPP > > and Wolfson Microelectronic Arizona codecs > > devices. > > > > +config REGULATOR_ARM_SCMI > > + tristate "SCMI based regulator driver" > > + depends on ARM_SCMI_PROTOCOL && OF > > + 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. > > + > > config REGULATOR_AS3711 > > tristate "AS3711 PMIC" > > depends on MFD_AS3711 > > diff --git a/drivers/regulator/Makefile b/drivers/regulator/Makefile > > index d8d3ecf526a8..0532a7393d5d 100644 > > --- a/drivers/regulator/Makefile > > +++ b/drivers/regulator/Makefile > > @@ -24,6 +24,7 @@ obj-$(CONFIG_REGULATOR_AD5398) += ad5398.o > > obj-$(CONFIG_REGULATOR_ANATOP) += anatop-regulator.o > > obj-$(CONFIG_REGULATOR_ARIZONA_LDO1) += arizona-ldo1.o > > obj-$(CONFIG_REGULATOR_ARIZONA_MICSUPP) += arizona-micsupp.o > > +obj-$(CONFIG_REGULATOR_ARM_SCMI) += scmi-regulator.o > > obj-$(CONFIG_REGULATOR_AS3711) += as3711-regulator.o > > obj-$(CONFIG_REGULATOR_AS3722) += as3722-regulator.o > > obj-$(CONFIG_REGULATOR_AXP20X) += axp20x-regulator.o > > diff --git a/drivers/regulator/scmi-regulator.c b/drivers/regulator/scmi-regulator.c > > new file mode 100644 > > index 000000000000..e4e7d0345723 > > --- /dev/null > > +++ b/drivers/regulator/scmi-regulator.c > > @@ -0,0 +1,453 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +// > > +// System Control and Management Interface (SCMI) based regulator driver > > +// > > +// Copyright (C) 2020 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 <linux/linear_range.h> > > +#include <linux/module.h> > > +#include <linux/of.h> > > +#include <linux/regulator/driver.h> > > +#include <linux/regulator/machine.h> > > +#include <linux/regulator/of_regulator.h> > > +#include <linux/scmi_protocol.h> > > +#include <linux/slab.h> > > +#include <linux/types.h> > > + > > +struct scmi_regulator { > > + u32 id; > > + struct scmi_device *sdev; > > + struct regulator_dev *rdev; > > + struct device_node *of_node; > > + struct regulator_desc desc; > > + struct regulator_config conf; > > +}; > > + > > +struct scmi_regulator_info { > > + int num_doms; > > + struct scmi_regulator **sregv; > > +}; > > + > > +static int scmi_reg_enable(struct regulator_dev *rdev) > > +{ > > + struct scmi_regulator *sreg = rdev_get_drvdata(rdev); > > + const struct scmi_handle *handle = sreg->sdev->handle; > > + > > + return handle->voltage_ops->config_set(handle, sreg->id, > > + SCMI_VOLTAGE_ARCH_STATE_ON); > > +} > > + > > +static int scmi_reg_disable(struct regulator_dev *rdev) > > +{ > > + struct scmi_regulator *sreg = rdev_get_drvdata(rdev); > > + const struct scmi_handle *handle = sreg->sdev->handle; > > + > > + return handle->voltage_ops->config_set(handle, 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 = rdev_get_drvdata(rdev); > > + const struct scmi_handle *handle = sreg->sdev->handle; > > + > > + ret = handle->voltage_ops->config_get(handle, sreg->id, > > + &config); > > + if (ret) { > > + dev_err(&sreg->sdev->dev, > > + "Error %d reading regulator %s status.\n", > > + ret, sreg->desc.name); > > + return 0; > > + } > > + > > + 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 = rdev_get_drvdata(rdev); > > + const struct scmi_handle *handle = sreg->sdev->handle; > > + > > + ret = handle->voltage_ops->level_get(handle, sreg->id, &volt_uV); > > + if (ret) > > + return ret; > > + > > + return sreg->desc.ops->map_voltage(rdev, volt_uV, volt_uV); > > +} > > + > > +static int scmi_reg_set_voltage_sel(struct regulator_dev *rdev, > > + unsigned int selector) > > +{ > > + int ret; > > + s32 volt_uV; > > + struct scmi_regulator *sreg = rdev_get_drvdata(rdev); > > + const struct scmi_handle *handle = sreg->sdev->handle; > > + > > + volt_uV = sreg->desc.ops->list_voltage(rdev, selector); > > + if (volt_uV <= 0) > > + return -EINVAL; > > + > > + ret = handle->voltage_ops->level_set(handle, sreg->id, 0x0, volt_uV); > > + if (ret) > > + return ret; > > + > > + return ret; > > return handle->voltage_ops->level_set(handle, 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_range_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_range, > > + .map_voltage = regulator_map_voltage_linear_range, > > +}; > > + > > +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) > > +{ > > + /* > > + * Note that SCMI voltage domains describable by linear ranges > > + * (segments) {low, high, step} are guaranteed to come in triplets by > > + * the SCMI Voltage Domain protocol support itself. > > + */ > > + if (vinfo->num_levels == 3) { > > + s32 delta_uV; > > + > > + 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 */ > > + sreg->desc.fixed_uV = > > + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; > > + sreg->desc.n_voltages = 1; > > + sreg->desc.ops = &scmi_reg_fixed_ops; > > + } else { > > + /* One simple linear mapping. */ > > + sreg->desc.min_uV = > > + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_LOW]; > > + sreg->desc.uV_step = > > + vinfo->levels_uv[SCMI_VOLTAGE_SEGMENT_STEP]; > > + sreg->desc.linear_min_sel = 0; > > + sreg->desc.n_voltages = delta_uV / sreg->desc.uV_step; > > + sreg->desc.ops = &scmi_reg_linear_ops; > > + } > > + } else { > > + /* Multiple linear mappings. */ > > I don't see this multi-linear mapping in the v3.0 beta of the spec. > Is it something planned? > No, it was indeed something included in previous spec iterations (multiple triplets) but now removed, as said in previous patch, I forgot to remove this redundant support. I'll do in V3. > > + int i, num_ranges, last_max = -1; > > + struct linear_range *lr; > > + > > + num_ranges = vinfo->num_levels / 3; > > + lr = devm_kcalloc(&sreg->sdev->dev, num_ranges, > > + sizeof(*lr), GFP_KERNEL); > > + if (!lr) > > + return -ENOMEM; > > + > > + sreg->desc.n_linear_ranges = num_ranges; > > + sreg->desc.linear_ranges = lr; > > + for (i = 0; num_ranges; num_ranges--, i += 3, lr++) { > > + s32 delta_uV; > > + > > + lr->min = > > + vinfo->levels_uv[i + SCMI_VOLTAGE_SEGMENT_LOW]; > > + lr->step = > > + vinfo->levels_uv[i + SCMI_VOLTAGE_SEGMENT_STEP]; > > + delta_uV = > > + vinfo->levels_uv[i + SCMI_VOLTAGE_SEGMENT_HIGH] - > > + lr->min; > > + if (delta_uV <= 0 || !(delta_uV / lr->step)) { > > + 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; > > + } > > + lr->max_sel = delta_uV / lr->step - 1; > > + lr->min_sel = last_max + 1; > > + last_max = lr->max_sel; > > + } > > + sreg->desc.n_voltages = last_max + 1; > > + sreg->desc.ops = &scmi_reg_range_ops; > > + } > > + > > + return 0; > > +} > > + > > +static int > > +scmi_config_discrete_regulator_mappings(struct scmi_regulator *sreg, > > + const struct scmi_voltage_info *vinfo) > > +{ > > + /* Discrete non linear levels are mapped to volt_table */ > > + sreg->desc.n_voltages = vinfo->num_levels; > > + if (sreg->desc.n_voltages > 1) { > > + sreg->desc.volt_table = (const unsigned int *)vinfo->levels_uv; > > + sreg->desc.ops = &scmi_reg_discrete_ops; > > + } else { > > + sreg->desc.fixed_uV = vinfo->levels_uv[0]; > > + sreg->desc.ops = &scmi_reg_fixed_ops; > > + } > > + > > + return 0; > > +} > > + > > +static int scmi_regulator_common_init(struct scmi_regulator *sreg) > > +{ > > + int ret; > > + const struct scmi_handle *handle = sreg->sdev->handle; > > + struct device *dev = &sreg->sdev->dev; > > + const struct scmi_voltage_info *vinfo; > > + > > + vinfo = handle->voltage_ops->info_get(handle, sreg->id); > > + if (!vinfo) > > + return -ENODEV; > > + > > + if (!vinfo->num_levels) > > + return -EINVAL; > > + > > + /* > > + * 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->of_node->full_name); > > + return -EOPNOTSUPP; > > + } > > + > > + sreg->desc.name = devm_kasprintf(dev, GFP_KERNEL, "%s", vinfo->name); > > + if (!sreg->desc.name) > > + return -ENOMEM; > > + > > + sreg->desc.id = sreg->id; > > + sreg->desc.type = REGULATOR_VOLTAGE; > > + sreg->desc.owner = THIS_MODULE; > > + sreg->desc.of_match = sreg->of_node->name; > > + sreg->desc.regulators_node = "regulators"; > > + if (vinfo->segmented) > > + ret = scmi_config_linear_regulator_mappings(sreg, vinfo); > > + else > > + ret = scmi_config_discrete_regulator_mappings(sreg, vinfo); > > + if (ret) > > + return ret; > > + > > + sreg->conf.dev = dev; > > + sreg->conf.driver_data = sreg; > > + > > + return 0; > > +} > > + > > +static int process_scmi_regulator_of_node(struct scmi_device *sdev, > > + 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] = devm_kzalloc(&sdev->dev, > > + sizeof(struct scmi_regulator), > > + GFP_KERNEL); > > + if (!rinfo->sregv[dom]) > > + return -ENOMEM; > > + > > + rinfo->sregv[dom]->id = dom; > > + rinfo->sregv[dom]->sdev = sdev; > > + /* get hold of good nodes */ > > + of_node_get(np); > > + rinfo->sregv[dom]->of_node = np; > > + dev_info(&sdev->dev, > > + "Found valid SCMI Regulator -- OF node [%d] -> %s\n", > > + dom, np->full_name); > > + > > + return ret; > > suggest return 0; here. Ok. > > > +} > > + > > +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; > > + > > + if (!handle || !handle->voltage_ops) > > + return -ENODEV; > > + > > + num_doms = handle->voltage_ops->num_domains_get(handle); > > + 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 = devm_kzalloc(&sdev->dev, sizeof(*rinfo), GFP_KERNEL); > > + if (!rinfo) > > + return -ENOMEM; > > + > > + /* Allocate pointers' array for all possible domains */ > > + rinfo->sregv = devm_kcalloc(&sdev->dev, num_doms, > > + sizeof(rinfo->sregv), 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->of_node, "regulators"); > > + for_each_child_of_node(np, child) { > > + ret = process_scmi_regulator_of_node(sdev, 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 > > + */ > > + for (d = 0; d < num_doms; d++) { > > + struct scmi_regulator *sreg = rinfo->sregv[d]; > > + > > + if (!sreg) > > + continue; > > + ret = scmi_regulator_common_init(sreg); > > + if (ret) > > + continue; > > + sreg->rdev = devm_regulator_register(&sdev->dev, &sreg->desc, > > + &sreg->conf); > > + if (IS_ERR(sreg->rdev)) { > > + sreg->rdev = NULL; > > + continue; > > + } > > + > > + dev_info(&sdev->dev, > > + "Regulator %s registered for domain [%d](%s)\n", > > + sreg->desc.name, sreg->id, sreg->desc.name); > > Maybe not display sreg->desc.name twice. > Yes does not make any sense indeed now that I have only one name o_O > > + } > > + > > + dev_set_drvdata(&sdev->dev, rinfo); > > + > > + return 0; > > +} > > + > > +static void scmi_regulator_remove(struct scmi_device *sdev) > > +{ > > + int d; > > + struct scmi_regulator_info *rinfo; > > + > > + rinfo = dev_get_drvdata(&sdev->dev); > > + if (!rinfo) > > + return; > > + > > + for (d = 0; d < rinfo->num_doms; d++) { > > + if (!rinfo->sregv[d]) > > + continue; > > + of_node_put(rinfo->sregv[d]->of_node); > > + } > > +} > > + > > +static const struct scmi_device_id scmi_regulator_id_table[] = { > > + { SCMI_PROTOCOL_VOLTAGE, "regulator" }, > > + { }, > > +}; > > +MODULE_DEVICE_TABLE(scmi, scmi_regulator_id_table); > > + > > +static struct scmi_driver scmi_drv = { > > + .name = "scmi-regulator", > > + .probe = scmi_regulator_probe, > > + .remove = scmi_regulator_remove, > > + .id_table = scmi_regulator_id_table, > > +}; > > + > > +module_scmi_driver(scmi_drv); > > + > > +MODULE_AUTHOR("Cristian Marussi <cristian.marussi@arm.com>"); > > +MODULE_DESCRIPTION("ARM SCMI regulator driver"); > > +MODULE_LICENSE("GPL v2"); > > -- > > 2.17.1 > > Thanks Cristian _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 4/4] dt-bindings: arm: add support for SCMI Regulators 2020-10-15 15:39 [PATCH v2 0/4] Add support for SCMIv3.0 Voltage Domain Protocol and SCMI-Regulator Cristian Marussi ` (2 preceding siblings ...) 2020-10-15 15:40 ` [PATCH v2 3/4] regulator: add SCMI driver Cristian Marussi @ 2020-10-15 15:40 ` Cristian Marussi 3 siblings, 0 replies; 9+ messages in thread From: Cristian Marussi @ 2020-10-15 15:40 UTC (permalink / raw) To: linux-kernel, linux-arm-kernel Cc: robh, satyakim, f.fainelli, vincent.guittot, sudeep.holla, broonie, cristian.marussi, james.quinlan, Jonathan.Cameron, souvik.chakravarty, etienne.carriere, lukasz.luba Add devicetree bindings to support regulators based on SCMI Voltage Domain Protocol. Signed-off-by: Cristian Marussi <cristian.marussi@arm.com> --- v1 --> v2 - removed any reference to negative voltages --- .../devicetree/bindings/arm/arm,scmi.txt | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Documentation/devicetree/bindings/arm/arm,scmi.txt b/Documentation/devicetree/bindings/arm/arm,scmi.txt index 55deb68230eb..6d3d0bdab322 100644 --- a/Documentation/devicetree/bindings/arm/arm,scmi.txt +++ b/Documentation/devicetree/bindings/arm/arm,scmi.txt @@ -62,6 +62,28 @@ Required properties: - #power-domain-cells : Should be 1. Contains the device or the power domain ID value used by SCMI commands. +Regulator bindings for the SCMI Regulator based on SCMI Message Protocol +------------------------------------------------------------ + +An SCMI Regulator is permanently bound to a well defined SCMI Voltage Domain, +and should be always positioned as a root regulator. +It does not support any current operation. + +This binding uses the common regulator binding[6]. + +SCMI Regulators are grouped under a 'regulators' node which in turn is a child +of the SCMI Voltage protocol node inside the desired SCMI instance node. + +Required properties: + - reg : shall identify an existent SCMI Voltage Domain. + +Optional properties: + - all of the other standard regulator bindings as in [6]: note that, since + the SCMI Protocol itself aims in fact to hide away many of the operational + capabilities usually exposed by the properties of a standard regulator, + most of the usual regulator bindings could have just no effect in the + context of this SCMI regulator. + Sensor bindings for the sensors based on SCMI Message Protocol -------------------------------------------------------------- SCMI provides an API to access the various sensors on the SoC. @@ -105,6 +127,7 @@ Required sub-node properties: [3] Documentation/devicetree/bindings/thermal/thermal*.yaml [4] Documentation/devicetree/bindings/sram/sram.yaml [5] Documentation/devicetree/bindings/reset/reset.txt +[6] Documentation/devicetree/bindings/regulator/regulator.yaml Example: @@ -169,6 +192,25 @@ firmware { reg = <0x16>; #reset-cells = <1>; }; + + scmi_voltage: protocol@17 { + reg = <0x17>; + + regulators { + regulator_cpu: regulator_scmi_cpu@0 { + reg = <0x0>; + regulator-max-microvolt = <3300000>; + }; + + regulator_gpu: regulator_scmi_gpu@9 { + reg = <0x9>; + regulator-min-microvolt = <500000>; + regulator-max-microvolt = <4200000>; + }; + + ... + }; + }; }; }; -- 2.17.1 _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel ^ permalink raw reply related [flat|nested] 9+ messages in thread
end of thread, other threads:[~2020-10-16 16:56 UTC | newest] Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2020-10-15 15:39 [PATCH v2 0/4] Add support for SCMIv3.0 Voltage Domain Protocol and SCMI-Regulator Cristian Marussi 2020-10-15 15:39 ` [PATCH v2 1/4] firmware: arm_scmi: Add Voltage Domain Support Cristian Marussi 2020-10-16 13:36 ` Etienne Carriere 2020-10-16 16:51 ` Cristian Marussi 2020-10-15 15:39 ` [PATCH v2 2/4] firmware: arm_scmi: add SCMI Voltage Domain devname Cristian Marussi 2020-10-15 15:40 ` [PATCH v2 3/4] regulator: add SCMI driver Cristian Marussi 2020-10-16 14:09 ` Etienne Carriere 2020-10-16 16:55 ` Cristian Marussi 2020-10-15 15:40 ` [PATCH v2 4/4] dt-bindings: arm: add support for SCMI Regulators Cristian Marussi
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).