| /* |
| * Copyright (c) 2021-2022, STMicroelectronics - All Rights Reserved |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <limits.h> |
| #include <stdint.h> |
| #include <string.h> |
| |
| #include <common/debug.h> |
| #include <drivers/delay_timer.h> |
| #include <drivers/st/regulator.h> |
| #include <libfdt.h> |
| |
| #define MAX_PROPERTY_LEN 64 |
| |
| CASSERT(PLAT_NB_RDEVS >= 1U, plat_nb_rdevs_must_be_higher); |
| |
| static struct rdev rdev_array[PLAT_NB_RDEVS]; |
| |
| #define for_each_rdev(rdev) \ |
| for ((rdev) = rdev_array; (rdev) <= &rdev_array[PLAT_NB_RDEVS - 1U]; (rdev)++) |
| |
| #define for_each_registered_rdev(rdev) \ |
| for ((rdev) = rdev_array; \ |
| ((rdev) <= &rdev_array[PLAT_NB_RDEVS - 1U]) && ((rdev)->desc != NULL); (rdev)++) |
| |
| static void lock_driver(const struct rdev *rdev) |
| { |
| if (rdev->desc->ops->lock != NULL) { |
| rdev->desc->ops->lock(rdev->desc); |
| } |
| } |
| |
| static void unlock_driver(const struct rdev *rdev) |
| { |
| if (rdev->desc->ops->unlock != NULL) { |
| rdev->desc->ops->unlock(rdev->desc); |
| } |
| } |
| |
| static struct rdev *regulator_get_by_phandle(int32_t phandle) |
| { |
| struct rdev *rdev; |
| |
| for_each_registered_rdev(rdev) { |
| if (rdev->phandle == phandle) { |
| return rdev; |
| } |
| } |
| |
| WARN("%s: phandle %d not found\n", __func__, phandle); |
| return NULL; |
| } |
| |
| /* |
| * Get a regulator from its node name |
| * |
| * @fdt - pointer to device tree memory |
| * @node_name - name of the node "ldo1" |
| * Return pointer to rdev if succeed, NULL else. |
| */ |
| struct rdev *regulator_get_by_name(const char *node_name) |
| { |
| struct rdev *rdev; |
| |
| assert(node_name != NULL); |
| VERBOSE("get %s\n", node_name); |
| |
| for_each_registered_rdev(rdev) { |
| if (strcmp(rdev->desc->node_name, node_name) == 0) { |
| return rdev; |
| } |
| } |
| |
| WARN("%s: %s not found\n", __func__, node_name); |
| return NULL; |
| } |
| |
| static int32_t get_supply_phandle(const void *fdt, int node, const char *name) |
| { |
| const fdt32_t *cuint; |
| int len __unused; |
| int supply_phandle = -FDT_ERR_NOTFOUND; |
| char prop_name[MAX_PROPERTY_LEN]; |
| |
| len = snprintf(prop_name, MAX_PROPERTY_LEN - 1, "%s-supply", name); |
| assert((len >= 0) && (len < (MAX_PROPERTY_LEN - 1))); |
| |
| cuint = fdt_getprop(fdt, node, prop_name, NULL); |
| if (cuint != NULL) { |
| supply_phandle = fdt32_to_cpu(*cuint); |
| VERBOSE("%s: supplied by %d\n", name, supply_phandle); |
| } |
| |
| return supply_phandle; |
| } |
| |
| /* |
| * Get a regulator from a supply name |
| * |
| * @fdt - pointer to device tree memory |
| * @node - offset of the node that contains the supply description |
| * @name - name of the supply "vdd" for "vdd-supply' |
| * Return pointer to rdev if succeed, NULL else. |
| */ |
| struct rdev *regulator_get_by_supply_name(const void *fdt, int node, const char *name) |
| { |
| const int p = get_supply_phandle(fdt, node, name); |
| |
| if (p < 0) { |
| return NULL; |
| } |
| |
| return regulator_get_by_phandle(p); |
| } |
| |
| static int __regulator_set_state(struct rdev *rdev, bool state) |
| { |
| if (rdev->desc->ops->set_state == NULL) { |
| return -ENODEV; |
| } |
| |
| return rdev->desc->ops->set_state(rdev->desc, state); |
| } |
| |
| /* |
| * Enable regulator |
| * |
| * @rdev - pointer to rdev struct |
| * Return 0 if succeed, non 0 else. |
| */ |
| int regulator_enable(struct rdev *rdev) |
| { |
| int ret; |
| |
| assert(rdev != NULL); |
| |
| ret = __regulator_set_state(rdev, STATE_ENABLE); |
| |
| udelay(rdev->enable_ramp_delay); |
| |
| return ret; |
| } |
| |
| /* |
| * Disable regulator |
| * |
| * @rdev - pointer to rdev struct |
| * Return 0 if succeed, non 0 else. |
| */ |
| int regulator_disable(struct rdev *rdev) |
| { |
| int ret; |
| |
| assert(rdev != NULL); |
| |
| if ((rdev->flags & REGUL_ALWAYS_ON) != 0U) { |
| return 0; |
| } |
| |
| ret = __regulator_set_state(rdev, STATE_DISABLE); |
| |
| udelay(rdev->enable_ramp_delay); |
| |
| return ret; |
| } |
| |
| /* |
| * Regulator enabled query |
| * |
| * @rdev - pointer to rdev struct |
| * Return 0 if disabled, 1 if enabled, <0 else. |
| */ |
| int regulator_is_enabled(const struct rdev *rdev) |
| { |
| int ret; |
| |
| assert(rdev != NULL); |
| |
| VERBOSE("%s: is en\n", rdev->desc->node_name); |
| |
| if (rdev->desc->ops->get_state == NULL) { |
| return -ENODEV; |
| } |
| |
| lock_driver(rdev); |
| |
| ret = rdev->desc->ops->get_state(rdev->desc); |
| if (ret < 0) { |
| ERROR("regul %s get state failed: err:%d\n", |
| rdev->desc->node_name, ret); |
| } |
| |
| unlock_driver(rdev); |
| |
| return ret; |
| } |
| |
| /* |
| * Set regulator voltage |
| * |
| * @rdev - pointer to rdev struct |
| * @mvolt - Target voltage level in millivolt |
| * Return 0 if succeed, non 0 else. |
| */ |
| int regulator_set_voltage(struct rdev *rdev, uint16_t mvolt) |
| { |
| int ret; |
| |
| assert(rdev != NULL); |
| |
| VERBOSE("%s: set mvolt\n", rdev->desc->node_name); |
| |
| if (rdev->desc->ops->set_voltage == NULL) { |
| return -ENODEV; |
| } |
| |
| if ((mvolt < rdev->min_mv) || (mvolt > rdev->max_mv)) { |
| return -EPERM; |
| } |
| |
| lock_driver(rdev); |
| |
| ret = rdev->desc->ops->set_voltage(rdev->desc, mvolt); |
| if (ret < 0) { |
| ERROR("regul %s set volt failed: err:%d\n", |
| rdev->desc->node_name, ret); |
| } |
| |
| unlock_driver(rdev); |
| |
| return ret; |
| } |
| |
| /* |
| * Set regulator min voltage |
| * |
| * @rdev - pointer to rdev struct |
| * Return 0 if succeed, non 0 else. |
| */ |
| int regulator_set_min_voltage(struct rdev *rdev) |
| { |
| return regulator_set_voltage(rdev, rdev->min_mv); |
| } |
| |
| /* |
| * Get regulator voltage |
| * |
| * @rdev - pointer to rdev struct |
| * Return milli volts if succeed, <0 else. |
| */ |
| int regulator_get_voltage(const struct rdev *rdev) |
| { |
| int ret; |
| |
| assert(rdev != NULL); |
| |
| VERBOSE("%s: get volt\n", rdev->desc->node_name); |
| |
| if (rdev->desc->ops->get_voltage == NULL) { |
| return rdev->min_mv; |
| } |
| |
| lock_driver(rdev); |
| |
| ret = rdev->desc->ops->get_voltage(rdev->desc); |
| if (ret < 0) { |
| ERROR("regul %s get voltage failed: err:%d\n", |
| rdev->desc->node_name, ret); |
| } |
| |
| unlock_driver(rdev); |
| |
| return ret; |
| } |
| |
| /* |
| * List regulator voltages |
| * |
| * @rdev - pointer to rdev struct |
| * @levels - out: array of supported millitvolt levels from min to max value |
| * @count - out: number of possible millivolt values |
| * Return 0 if succeed, non 0 else. |
| */ |
| int regulator_list_voltages(const struct rdev *rdev, const uint16_t **levels, size_t *count) |
| { |
| int ret; |
| size_t n; |
| |
| assert(rdev != NULL); |
| assert(levels != NULL); |
| assert(count != NULL); |
| |
| VERBOSE("%s: list volt\n", rdev->desc->node_name); |
| |
| if (rdev->desc->ops->list_voltages == NULL) { |
| return -ENODEV; |
| } |
| |
| lock_driver(rdev); |
| |
| ret = rdev->desc->ops->list_voltages(rdev->desc, levels, count); |
| |
| unlock_driver(rdev); |
| |
| if (ret < 0) { |
| ERROR("regul %s list_voltages failed: err: %d\n", |
| rdev->desc->node_name, ret); |
| return ret; |
| } |
| |
| /* |
| * Reduce the possible values depending on min and max from device-tree |
| */ |
| n = *count; |
| while ((n > 1U) && ((*levels)[n - 1U] > rdev->max_mv)) { |
| n--; |
| } |
| |
| /* Verify that max val is a valid value */ |
| if (rdev->max_mv != (*levels)[n - 1]) { |
| ERROR("regul %s: max value %u is invalid\n", |
| rdev->desc->node_name, rdev->max_mv); |
| return -EINVAL; |
| } |
| |
| while ((n > 1U) && ((*levels[0U]) < rdev->min_mv)) { |
| (*levels)++; |
| n--; |
| } |
| |
| /* Verify that min is not too high */ |
| if (n == 0U) { |
| ERROR("regul %s set min voltage is too high\n", |
| rdev->desc->node_name); |
| return -EINVAL; |
| } |
| |
| /* Verify that min val is a valid vlue */ |
| if (rdev->min_mv != (*levels)[0U]) { |
| ERROR("regul %s: min value %u is invalid\n", |
| rdev->desc->node_name, rdev->min_mv); |
| return -EINVAL; |
| } |
| |
| *count = n; |
| |
| VERBOSE("rdev->min_mv=%u rdev->max_mv=%u\n", rdev->min_mv, rdev->max_mv); |
| |
| return 0; |
| } |
| |
| /* |
| * Get regulator voltages range |
| * |
| * @rdev - pointer to rdev struct |
| * @min_mv - out: min possible millivolt value |
| * @max_mv - out: max possible millivolt value |
| * Return 0 if succeed, non 0 else. |
| */ |
| void regulator_get_range(const struct rdev *rdev, uint16_t *min_mv, uint16_t *max_mv) |
| { |
| assert(rdev != NULL); |
| |
| if (min_mv != NULL) { |
| *min_mv = rdev->min_mv; |
| } |
| if (max_mv != NULL) { |
| *max_mv = rdev->max_mv; |
| } |
| } |
| |
| /* |
| * Set regulator flag |
| * |
| * @rdev - pointer to rdev struct |
| * @flag - flag value to set (eg: REGUL_OCP) |
| * Return 0 if succeed, non 0 else. |
| */ |
| int regulator_set_flag(struct rdev *rdev, uint16_t flag) |
| { |
| int ret; |
| |
| /* check that only one bit is set on flag */ |
| if (__builtin_popcount(flag) != 1) { |
| return -EINVAL; |
| } |
| |
| /* REGUL_ALWAYS_ON and REGUL_BOOT_ON are internal properties of the core */ |
| if ((flag == REGUL_ALWAYS_ON) || (flag == REGUL_BOOT_ON)) { |
| rdev->flags |= flag; |
| return 0; |
| } |
| |
| if (rdev->desc->ops->set_flag == NULL) { |
| ERROR("%s can not set any flag\n", rdev->desc->node_name); |
| return -ENODEV; |
| } |
| |
| lock_driver(rdev); |
| |
| ret = rdev->desc->ops->set_flag(rdev->desc, flag); |
| |
| unlock_driver(rdev); |
| |
| if (ret != 0) { |
| ERROR("%s: could not set flag %d ret=%d\n", |
| rdev->desc->node_name, flag, ret); |
| return ret; |
| } |
| |
| rdev->flags |= flag; |
| |
| return 0; |
| } |
| |
| static int parse_properties(const void *fdt, struct rdev *rdev, int node) |
| { |
| int ret; |
| |
| if (fdt_getprop(fdt, node, "regulator-always-on", NULL) != NULL) { |
| VERBOSE("%s: set regulator-always-on\n", rdev->desc->node_name); |
| ret = regulator_set_flag(rdev, REGUL_ALWAYS_ON); |
| if (ret != 0) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Parse the device-tree for a regulator |
| * |
| * Read min/max voltage from dt and check its validity |
| * Read the properties, and call the driver to set flags |
| * Read power supply phandle |
| * Read and store low power mode states |
| * |
| * @rdev - pointer to rdev struct |
| * @node - device-tree node offset of the regulator |
| * Return 0 if disabled, 1 if enabled, <0 else. |
| */ |
| static int parse_dt(struct rdev *rdev, int node) |
| { |
| void *fdt; |
| const fdt32_t *cuint; |
| const uint16_t *levels; |
| size_t size; |
| int ret; |
| |
| VERBOSE("%s: parse dt\n", rdev->desc->node_name); |
| |
| if (fdt_get_address(&fdt) == 0) { |
| return -ENOENT; |
| } |
| |
| rdev->phandle = fdt_get_phandle(fdt, node); |
| |
| cuint = fdt_getprop(fdt, node, "regulator-min-microvolt", NULL); |
| if (cuint != NULL) { |
| uint16_t min_mv; |
| |
| min_mv = (uint16_t)(fdt32_to_cpu(*cuint) / 1000U); |
| VERBOSE("%s: min_mv=%d\n", rdev->desc->node_name, (int)min_mv); |
| if (min_mv <= rdev->max_mv) { |
| rdev->min_mv = min_mv; |
| } else { |
| ERROR("%s: min_mv=%d is too high\n", |
| rdev->desc->node_name, (int)min_mv); |
| return -EINVAL; |
| } |
| } |
| |
| cuint = fdt_getprop(fdt, node, "regulator-max-microvolt", NULL); |
| if (cuint != NULL) { |
| uint16_t max_mv; |
| |
| max_mv = (uint16_t)(fdt32_to_cpu(*cuint) / 1000U); |
| VERBOSE("%s: max_mv=%d\n", rdev->desc->node_name, (int)max_mv); |
| if (max_mv >= rdev->min_mv) { |
| rdev->max_mv = max_mv; |
| } else { |
| ERROR("%s: max_mv=%d is too low\n", |
| rdev->desc->node_name, (int)max_mv); |
| return -EINVAL; |
| } |
| } |
| |
| /* validate that min and max values can be used */ |
| ret = regulator_list_voltages(rdev, &levels, &size); |
| if ((ret != 0) && (ret != -ENODEV)) { |
| return ret; |
| } |
| |
| ret = parse_properties(fdt, rdev, node); |
| if (ret != 0) { |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * Register a regulator driver in regulator framework. |
| * Initialize voltage range from driver description |
| * |
| * @desc - pointer to the regulator description |
| * @node - device-tree node offset of the regulator |
| * Return 0 if succeed, non 0 else. |
| */ |
| int regulator_register(const struct regul_description *desc, int node) |
| { |
| struct rdev *rdev; |
| |
| assert(desc != NULL); |
| |
| VERBOSE("register %s\n", desc->node_name); |
| |
| for_each_rdev(rdev) { |
| if (rdev->desc == NULL) { |
| break; |
| } |
| } |
| |
| if (rdev > &rdev_array[PLAT_NB_RDEVS - 1U]) { |
| WARN("Not enough place for regulators, PLAT_NB_RDEVS should be increased.\n"); |
| return -ENOMEM; |
| } |
| |
| rdev->desc = desc; |
| rdev->enable_ramp_delay = rdev->desc->enable_ramp_delay; |
| |
| if (rdev->desc->ops->list_voltages != NULL) { |
| int ret; |
| const uint16_t *levels; |
| size_t count; |
| |
| lock_driver(rdev); |
| |
| ret = rdev->desc->ops->list_voltages(rdev->desc, &levels, &count); |
| |
| unlock_driver(rdev); |
| |
| if (ret < 0) { |
| ERROR("regul %s set state failed: err:%d\n", |
| rdev->desc->node_name, ret); |
| return ret; |
| } |
| |
| rdev->min_mv = levels[0]; |
| rdev->max_mv = levels[count - 1U]; |
| } else { |
| rdev->max_mv = UINT16_MAX; |
| } |
| |
| return parse_dt(rdev, node); |
| } |