diff --git a/core/drivers/regulator/regulator.c b/core/drivers/regulator/regulator.c new file mode 100644 index 00000000000..50b3b5f9dc0 --- /dev/null +++ b/core/drivers/regulator/regulator.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) 2023, STMicroelectronics + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static SLIST_HEAD(, regulator) regulator_device_list = + SLIST_HEAD_INITIALIZER(regulator); + +static void lock_regulator(struct regulator *regulator) +{ + /* + * Regulator operation may occur at runtime and during specific + * system power transition: power off, PM suspend and resume. + * These operate upon fastcall entries, under PSCI services + * execution, where non-secure world is not operational. In these + * cases we cannot take a mutex and will expect the mutex is + * unlocked. + */ + if (thread_get_id_may_fail() == THREAD_ID_INVALID) { + assert(!regulator->lock.state); + return; + } + + mutex_lock(®ulator->lock); +} + +static void unlock_regulator(struct regulator *regulator) +{ + if (thread_get_id_may_fail() == THREAD_ID_INVALID) { + /* Path for PM sequences when with local Monitor */ + return; + } + + mutex_unlock(®ulator->lock); +} + +static TEE_Result set_state(struct regulator *regulator, bool on_not_off) +{ + if (!regulator->ops->set_state) + return TEE_SUCCESS; + + return regulator->ops->set_state(regulator, on_not_off); +} + +static TEE_Result regulator_refcnt_enable(struct regulator *regulator) +{ + TEE_Result res = TEE_ERROR_GENERIC; + + FMSG("%s", regulator_name(regulator)); + + if (regulator->supply) { + res = regulator_enable(regulator->supply); + if (res) + return res; + } + + lock_regulator(regulator); + + if (!regulator->refcount) { + res = set_state(regulator, true); + if (res) { + EMSG("regul %s set state failed with %#"PRIx32, + regulator_name(regulator), res); + + unlock_regulator(regulator); + + if (regulator->supply && + regulator_disable(regulator->supply)) + panic(); + + return res; + } + } + + regulator->refcount++; + if (!regulator->refcount) + panic(); + + FMSG("%s refcount: %u", regulator_name(regulator), regulator->refcount); + + unlock_regulator(regulator); + + return TEE_SUCCESS; +} + +TEE_Result regulator_enable(struct regulator *regulator) +{ + assert(regulator); + FMSG("%s", regulator_name(regulator)); + + if (regulator_is_always_on(regulator)) + return TEE_SUCCESS; + + return regulator_refcnt_enable(regulator); +} + +static TEE_Result regulator_refcnt_disable(struct regulator *regulator) +{ + FMSG("%s", regulator_name(regulator)); + + lock_regulator(regulator); + + if (regulator->refcount == 1) { + TEE_Result res = set_state(regulator, false); + + if (res) { + EMSG("regul %s set state failed with %#"PRIx32, + regulator_name(regulator), res); + unlock_regulator(regulator); + return res; + } + } + + if (!regulator->refcount) { + EMSG("Unbalanced %s", regulator_name(regulator)); + panic(); + } + + regulator->refcount--; + + FMSG("%s refcount: %u", regulator_name(regulator), regulator->refcount); + + unlock_regulator(regulator); + + if (regulator->supply && regulator_disable(regulator->supply)) { + /* We can't leave this unbalanced */ + EMSG("Can't disable %s", regulator_name(regulator->supply)); + panic(); + } + + return TEE_SUCCESS; +} + +TEE_Result regulator_disable(struct regulator *regulator) +{ + assert(regulator); + FMSG("%s", regulator_name(regulator)); + + if (regulator_is_always_on(regulator)) + return TEE_SUCCESS; + + return regulator_refcnt_disable(regulator); +} + +bool regulator_is_enabled(struct regulator *regulator) +{ + TEE_Result res = TEE_SUCCESS; + bool enabled = false; + + if (!regulator->ops->get_state) + return true; + + lock_regulator(regulator); + res = regulator->ops->get_state(regulator, &enabled); + unlock_regulator(regulator); + + if (res) + EMSG("regul %s get state failed with %#"PRIx32, + regulator_name(regulator), res); + + return !res && enabled; +} + +TEE_Result regulator_set_voltage(struct regulator *regulator, int level_uv) +{ + TEE_Result res = TEE_ERROR_GENERIC; + + assert(regulator); + FMSG("%s %duV", regulator_name(regulator), level_uv); + + if (level_uv < regulator->min_uv || level_uv > regulator->max_uv) + return TEE_ERROR_BAD_PARAMETERS; + + if (level_uv == regulator->cur_uv) + return TEE_SUCCESS; + + if (!regulator->ops->set_voltage) + return TEE_ERROR_NOT_SUPPORTED; + + lock_regulator(regulator); + res = regulator->ops->set_voltage(regulator, level_uv); + unlock_regulator(regulator); + + if (res) { + EMSG("regul %s set volt failed with %#"PRIx32, + regulator_name(regulator), res); + return res; + } + + regulator->cur_uv = level_uv; + + return TEE_SUCCESS; +} + +TEE_Result regulator_register(struct regulator *regulator) +{ + TEE_Result res = TEE_SUCCESS; + int min_uv = 0; + int max_uv = 0; + int uv = 0; + + if (!regulator || !regulator->ops) + return TEE_ERROR_BAD_PARAMETERS; + + regulator_get_range(regulator, &min_uv, &max_uv); + if (min_uv > max_uv) + return TEE_ERROR_BAD_PARAMETERS; + + /* Sanitize regulator effective level */ + if (regulator->ops->get_voltage) { + res = regulator->ops->get_voltage(regulator, &uv); + if (res) + return res; + } else { + uv = min_uv; + } + regulator->cur_uv = uv; + + if (uv < min_uv || uv > max_uv) { + res = regulator_set_voltage(regulator, min_uv); + if (res) + return res; + } + + SLIST_INSERT_HEAD(®ulator_device_list, regulator, link); + + return TEE_SUCCESS; +} + +/* + * Log regulators state + */ +void regulator_print_state(const char *message __maybe_unused) +{ + struct regulator *regulator = NULL; + + DMSG("Regulator state: %s", message); + DMSG("name use\ten\tuV\tmin\tmax\tsupply"); + + SLIST_FOREACH(regulator, ®ulator_device_list, link) + DMSG("%8s %u\t%d\t%d\t%d\t%d\t%#x\t%s\n", + regulator->name, regulator->refcount, + regulator_is_enabled(regulator), + regulator_get_voltage(regulator), + regulator->min_uv, regulator->max_uv, + regulator->supply ? regulator_name(regulator->supply) : + ""); +} + +/* + * Clean-up regulators that are not used. + */ +static TEE_Result regulator_core_cleanup(void) +{ + struct regulator *regulator = NULL; + + SLIST_FOREACH(regulator, ®ulator_device_list, link) { + if (!regulator->refcount) { + DMSG("disable %s", regulator_name(regulator)); + lock_regulator(regulator); + set_state(regulator, false /* disable */); + unlock_regulator(regulator); + } + } + + regulator_print_state(__func__); + + return TEE_SUCCESS; +} + +release_init_resource(regulator_core_cleanup); diff --git a/core/drivers/regulator/sub.mk b/core/drivers/regulator/sub.mk new file mode 100644 index 00000000000..51c106f19d4 --- /dev/null +++ b/core/drivers/regulator/sub.mk @@ -0,0 +1 @@ +srcs-y += regulator.c diff --git a/core/drivers/sub.mk b/core/drivers/sub.mk index 046492eade5..9e7625ae50d 100644 --- a/core/drivers/sub.mk +++ b/core/drivers/sub.mk @@ -83,6 +83,7 @@ subdirs-$(CFG_DRIVERS_CLK) += clk subdirs-$(CFG_DRIVERS_GPIO) += gpio subdirs-$(CFG_DRIVERS_I2C) += i2c subdirs-$(CFG_DRIVERS_PINCTRL) += pinctrl +subdirs-$(CFG_DRIVERS_REGULATOR) += regulator subdirs-$(CFG_DRIVERS_RSTCTRL) += rstctrl subdirs-$(CFG_SCMI_MSG_DRIVERS) += scmi-msg subdirs-y += imx diff --git a/core/include/drivers/regulator.h b/core/include/drivers/regulator.h new file mode 100644 index 00000000000..ed1570d4d6b --- /dev/null +++ b/core/include/drivers/regulator.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2023, STMicroelectronics + */ +#ifndef DRIVERS_REGULATOR_H +#define DRIVERS_REGULATOR_H + +#include +#include +#include +#include +#include +#include +#include + +struct regulator_ops; + +/* + * struct regulator - A regulator instance + * @ops: Operation handlers for the regulator + * @supply: Regulator supply reference or NULL if none + * @priv: Regulator driver private data + * @name: Regulator string name for debug purpose or NULL + * @min_uv: Min possible voltage level in microvolt (uV) + * @max_uv: Max possible voltage level in microvolt (uV) + * @cur_uv: Current voltage level in microvolt (uV) + * @refcount: Regulator enable request reference counter + * @lock: Mutex for concurrent access protection + * @link: Link in initialized regulator list + */ +struct regulator { + /* Fields initialized by caller of regulator_register() */ + const struct regulator_ops *ops; + struct regulator *supply; + void *priv; + char *name; + int min_uv; + int max_uv; + /* Fields internal to regulator framework */ + int cur_uv; + unsigned int refcount; + struct mutex lock; /* Concurrent access protection */ + SLIST_ENTRY(regulator) link; +}; + +/* + * struct regulator_ops - Regulator operation handlers + * + * @set_state: Enable or disable a regulator + * @get_state: Get regulator effective state + * @set_voltage: Set voltage level in microvolt (uV) + * @get_voltage: Get current voltage in microvolt (uV) + */ +struct regulator_ops { + TEE_Result (*set_state)(struct regulator *r, bool enabled); + TEE_Result (*get_state)(struct regulator *r, bool *enabled); + TEE_Result (*set_voltage)(struct regulator *r, int level_uv); + TEE_Result (*get_voltage)(struct regulator *r, int *level_uv); +}; + +#ifdef CFG_DRIVERS_REGULATOR +/* + * regulator_enable() - Enable regulator + * @regulator: Regulator reference + */ +TEE_Result regulator_enable(struct regulator *regulator); + +/* + * regulator_disable() - Disable regulator + * @regulator: Regulator reference + */ +TEE_Result regulator_disable(struct regulator *regulator); + +/* + * regulator_is_enabled() - Return whether or not regulator is currently enabled + * despite its refcount value. + * @regulator: Regulator reference + */ +bool regulator_is_enabled(struct regulator *regulator); + +/* + * regulator_set_voltage() - Set regulator to target level in microvolt + * @regulator: Regulator reference + * @level_uv: Level in microvolt + */ +TEE_Result regulator_set_voltage(struct regulator *regulator, int level_uv); + +/* + * regulator_register() - Register and initialize a regulator + * @regulator: Regulator reference + */ +TEE_Result regulator_register(struct regulator *regulator); + +/* Print registered regulators and their state to the output console */ +void regulator_print_state(const char *message); +#else +static inline TEE_Result regulator_enable(struct regulator *regulator __unused) +{ + return TEE_ERROR_NOT_SUPPORTED; +} + +static inline TEE_Result regulator_disable(struct regulator *regulator __unused) +{ + return TEE_ERROR_NOT_SUPPORTED; +} + +static inline bool regulator_is_enabled(struct regulator *regulator __unused) +{ + return false; +} + +static inline TEE_Result regulator_set_voltage(struct regulator *regul __unused, + int level_mv __unused) +{ + return TEE_ERROR_NOT_SUPPORTED; +} + +static inline TEE_Result regulator_init(struct regulator *regulator __unused) +{ + return TEE_ERROR_NOT_SUPPORTED; +} + +static inline void regulator_print_state(const char *message __unused) +{ +} +#endif /*CFG_DRIVERS_REGULATOR*/ + +/* + * regulator_name() - Return regulator name or NULL + * @regulator: Regulator reference + */ +static inline const char *regulator_name(struct regulator *regulator) +{ + return regulator->name; +} + +/* + * regulator_set_min_voltage() - Set regulator to its min level + * @regulator: Regulator reference + */ +static inline TEE_Result regulator_set_min_voltage(struct regulator *regulator) +{ + return regulator_set_voltage(regulator, regulator->min_uv); +} + +/* + * regulator_get_voltage() - Get regulator current level in microvolt + * @regulator: Regulator reference + */ +static inline int regulator_get_voltage(struct regulator *regulator) +{ + return regulator->cur_uv; +} + +/* + * regulator_get_range() - Get regulator min and/or max support levels + * @regulator: Regulator reference + * @min_mv: Output reference to min level in microvolt (uV) or NULL + * @max_mv: Output reference to max level in microvolt (uV) or NULL + */ +static inline void regulator_get_range(struct regulator *regulator, int *min_uv, + int *max_uv) +{ + assert(regulator); + if (min_uv) + *min_uv = regulator->min_uv; + if (max_uv) + *max_uv = regulator->max_uv; +} +#endif /* DRIVERS_REGULATOR_H */ diff --git a/mk/config.mk b/mk/config.mk index 693b1c7b132..5f915812726 100644 --- a/mk/config.mk +++ b/mk/config.mk @@ -883,6 +883,10 @@ CFG_DRIVERS_I2C ?= n # on device-tree. CFG_DRIVERS_PINCTRL ?= n +# When enabled, CFG_DRIVERS_REGULATOR embeds a voltage regulator framework in +# OP-TEE core to provide drivers a common regulator interface. +CFG_DRIVERS_REGULATOR ?= n + # The purpose of this flag is to show a print when booting up the device that # indicates whether the board runs a standard developer configuration or not. # A developer configuration doesn't necessarily has to be secure. The intention