| /* | 
 |  * Copyright 2022 Nordic Semiconductor ASA | 
 |  * Copyright 2023 Meta Platforms | 
 |  * SPDX-License-Identifier: Apache-2.0 | 
 |  */ | 
 |  | 
 | #include <zephyr/kernel.h> | 
 | #include <zephyr/drivers/regulator.h> | 
 |  | 
 | static void regulator_delay(uint32_t delay_us) | 
 | { | 
 | 	if (delay_us > 0U) { | 
 | 		k_sleep(K_USEC(delay_us)); | 
 | 	} | 
 | } | 
 |  | 
 | void regulator_common_data_init(const struct device *dev) | 
 | { | 
 | 	struct regulator_common_data *data = dev->data; | 
 |  | 
 | #ifdef CONFIG_REGULATOR_THREAD_SAFE_REFCNT | 
 | 	(void)k_mutex_init(&data->lock); | 
 | #endif | 
 | 	data->refcnt = 0; | 
 | } | 
 |  | 
 | int regulator_common_init(const struct device *dev, bool is_enabled) | 
 | { | 
 | 	const struct regulator_driver_api *api = dev->api; | 
 | 	const struct regulator_common_config *config = dev->config; | 
 | 	struct regulator_common_data *data = dev->data; | 
 | 	int32_t current_uv; | 
 | 	int ret; | 
 |  | 
 | 	if (config->initial_mode != REGULATOR_INITIAL_MODE_UNKNOWN) { | 
 | 		ret = regulator_set_mode(dev, config->initial_mode); | 
 | 		if (ret < 0) { | 
 | 			return ret; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (REGULATOR_ACTIVE_DISCHARGE_GET_BITS(config->flags) != | 
 | 	    REGULATOR_ACTIVE_DISCHARGE_DEFAULT) { | 
 | 		ret = regulator_set_active_discharge(dev, | 
 | 		    (bool)REGULATOR_ACTIVE_DISCHARGE_GET_BITS(config->flags)); | 
 | 		if (ret < 0) { | 
 | 			return ret; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (config->init_uv > INT32_MIN) { | 
 | 		ret = regulator_set_voltage(dev, config->init_uv, config->init_uv); | 
 | 		if (ret < 0) { | 
 | 			return ret; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (config->init_ua > INT32_MIN) { | 
 | 		ret = regulator_set_current_limit(dev, config->init_ua, config->init_ua); | 
 | 		if (ret < 0) { | 
 | 			return ret; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/* If we have valid range values, we try to match them before enabling */ | 
 | 	if ((config->min_uv > INT32_MIN) || (config->max_uv < INT32_MAX)) { | 
 |  | 
 | 		ret = regulator_get_voltage(dev, ¤t_uv); | 
 | 		if (ret < 0) { | 
 | 			return ret; | 
 | 		} | 
 |  | 
 | 		/* Snap to closest interval value if out of range */ | 
 | 		if (current_uv < config->min_uv) { | 
 | 			ret = regulator_set_voltage(dev, config->min_uv, config->min_uv); | 
 | 			if (ret < 0) { | 
 | 				return ret; | 
 | 			} | 
 | 		} else if (current_uv > config->max_uv) { | 
 | 			ret = regulator_set_voltage(dev, config->max_uv, config->max_uv); | 
 | 			if (ret < 0) { | 
 | 				return ret; | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (is_enabled) { | 
 | 		data->refcnt++; | 
 | 		if ((config->flags & REGULATOR_BOOT_OFF) != 0U) { | 
 | 			return regulator_disable(dev); | 
 | 		} | 
 | 	} else if ((config->flags & REGULATOR_INIT_ENABLED) != 0U) { | 
 | 		ret = api->enable(dev); | 
 | 		if (ret < 0) { | 
 | 			return ret; | 
 | 		} | 
 |  | 
 | 		regulator_delay(config->startup_delay_us); | 
 | 		data->refcnt++; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | int regulator_enable(const struct device *dev) | 
 | { | 
 | 	const struct regulator_driver_api *api = dev->api; | 
 | 	const struct regulator_common_config *config = dev->config; | 
 | 	struct regulator_common_data *data = dev->data; | 
 | 	int ret = 0; | 
 |  | 
 | 	/* enable not supported (always on) */ | 
 | 	if (api->enable == NULL) { | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	/* regulator must stay always on */ | 
 | 	if  ((config->flags & REGULATOR_ALWAYS_ON) != 0U) { | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | #ifdef CONFIG_REGULATOR_THREAD_SAFE_REFCNT | 
 | 	(void)k_mutex_lock(&data->lock, K_FOREVER); | 
 | #endif | 
 |  | 
 | 	data->refcnt++; | 
 |  | 
 | 	if (data->refcnt == 1) { | 
 | 		ret = api->enable(dev); | 
 | 		if (ret < 0) { | 
 | 			data->refcnt--; | 
 | 		} else { | 
 | 			regulator_delay(config->off_on_delay_us); | 
 | 		} | 
 | 	} | 
 |  | 
 | #ifdef CONFIG_REGULATOR_THREAD_SAFE_REFCNT | 
 | 	k_mutex_unlock(&data->lock); | 
 | #endif | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | bool regulator_is_enabled(const struct device *dev) | 
 | { | 
 | 	const struct regulator_common_config *config = dev->config; | 
 | 	struct regulator_common_data *data = dev->data; | 
 | 	bool enabled; | 
 |  | 
 | 	if ((config->flags & REGULATOR_ALWAYS_ON) != 0U) { | 
 | 		enabled = true; | 
 | 	} else { | 
 | #ifdef CONFIG_REGULATOR_THREAD_SAFE_REFCNT | 
 | 		(void)k_mutex_lock(&data->lock, K_FOREVER); | 
 | #endif | 
 | 		enabled = data->refcnt != 0; | 
 | #ifdef CONFIG_REGULATOR_THREAD_SAFE_REFCNT | 
 | 		k_mutex_unlock(&data->lock); | 
 | #endif | 
 | 	} | 
 |  | 
 | 	return enabled; | 
 | } | 
 |  | 
 | int regulator_disable(const struct device *dev) | 
 | { | 
 | 	const struct regulator_driver_api *api = dev->api; | 
 | 	const struct regulator_common_config *config = dev->config; | 
 | 	struct regulator_common_data *data = dev->data; | 
 | 	int ret = 0; | 
 |  | 
 | 	/* disable not supported (always on) */ | 
 | 	if (api->disable == NULL) { | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	/* regulator must stay always on */ | 
 | 	if  ((config->flags & REGULATOR_ALWAYS_ON) != 0U) { | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | #ifdef CONFIG_REGULATOR_THREAD_SAFE_REFCNT | 
 | 	(void)k_mutex_lock(&data->lock, K_FOREVER); | 
 | #endif | 
 |  | 
 | 	if (data->refcnt > 0) { | 
 | 		data->refcnt--; | 
 |  | 
 | 		if (data->refcnt == 0) { | 
 | 			ret = api->disable(dev); | 
 | 			if (ret < 0) { | 
 | 				data->refcnt++; | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | #ifdef CONFIG_REGULATOR_THREAD_SAFE_REFCNT | 
 | 	k_mutex_unlock(&data->lock); | 
 | #endif | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | bool regulator_is_supported_voltage(const struct device *dev, int32_t min_uv, | 
 | 				    int32_t max_uv) | 
 | { | 
 | 	const struct regulator_common_config *config = dev->config; | 
 | 	unsigned int volt_cnt; | 
 |  | 
 | 	/* voltage may not be allowed, even if supported */ | 
 | 	if ((min_uv > config->max_uv) || (max_uv < config->min_uv)) { | 
 | 		return false; | 
 | 	} | 
 |  | 
 | 	volt_cnt = regulator_count_voltages(dev); | 
 |  | 
 | 	for (unsigned int idx = 0U; idx < volt_cnt; idx++) { | 
 | 		int32_t volt_uv; | 
 |  | 
 | 		(void)regulator_list_voltage(dev, idx, &volt_uv); | 
 |  | 
 | 		if ((volt_uv >= min_uv) && (volt_uv <= max_uv)) { | 
 | 			return true; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return false; | 
 | } | 
 |  | 
 | int regulator_set_voltage(const struct device *dev, int32_t min_uv, | 
 | 			  int32_t max_uv) | 
 | { | 
 | 	const struct regulator_common_config *config = dev->config; | 
 | 	const struct regulator_driver_api *api = dev->api; | 
 |  | 
 | 	if (api->set_voltage == NULL) { | 
 | 		return -ENOSYS; | 
 | 	} | 
 |  | 
 | 	/* voltage may not be allowed, even if supported */ | 
 | 	if ((min_uv > config->max_uv) || (max_uv < config->min_uv)) { | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	return api->set_voltage(dev, min_uv, max_uv); | 
 | } | 
 |  | 
 | int regulator_set_current_limit(const struct device *dev, int32_t min_ua, | 
 | 				int32_t max_ua) | 
 | { | 
 | 	const struct regulator_common_config *config = dev->config; | 
 | 	const struct regulator_driver_api *api = dev->api; | 
 |  | 
 | 	if (api->set_current_limit == NULL) { | 
 | 		return -ENOSYS; | 
 | 	} | 
 |  | 
 | 	/* current limit may not be allowed, even if supported */ | 
 | 	if ((min_ua > config->max_ua) || (max_ua < config->min_ua)) { | 
 | 		return -EINVAL; | 
 | 	} | 
 |  | 
 | 	return api->set_current_limit(dev, min_ua, max_ua); | 
 | } | 
 |  | 
 | int regulator_set_mode(const struct device *dev, regulator_mode_t mode) | 
 | { | 
 | 	const struct regulator_common_config *config = dev->config; | 
 | 	const struct regulator_driver_api *api = dev->api; | 
 |  | 
 | 	if (api->set_mode == NULL) { | 
 | 		return -ENOSYS; | 
 | 	} | 
 |  | 
 | 	/* no mode restrictions */ | 
 | 	if (config->allowed_modes_cnt == 0U) { | 
 | 		return api->set_mode(dev, mode); | 
 | 	} | 
 |  | 
 | 	/* check if mode is allowed, apply if it is */ | 
 | 	for (uint8_t i = 0U; i < config->allowed_modes_cnt; i++) { | 
 | 		if (mode == config->allowed_modes[i]) { | 
 | 			return api->set_mode(dev, mode); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	return -ENOTSUP; | 
 | } |