|  | /* | 
|  | * Copyright 2019-2020 Peter Bigot Consulting, LLC | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #define DT_DRV_COMPAT regulator_fixed | 
|  |  | 
|  | #include <kernel.h> | 
|  | #include <drivers/regulator.h> | 
|  | #include <drivers/gpio.h> | 
|  |  | 
|  | #include <logging/log.h> | 
|  | LOG_MODULE_REGISTER(regulator_fixed, CONFIG_REGULATOR_LOG_LEVEL); | 
|  |  | 
|  | #define OPTION_ALWAYS_ON_POS 0 | 
|  | #define OPTION_ALWAYS_ON BIT(OPTION_ALWAYS_ON_POS) | 
|  | #define OPTION_BOOT_ON_POS 1 | 
|  | #define OPTION_BOOT_ON BIT(OPTION_BOOT_ON_POS) | 
|  |  | 
|  | struct driver_config { | 
|  | const char *regulator_name; | 
|  | const char *gpio_name; | 
|  | uint32_t startup_delay_us; | 
|  | uint32_t off_on_delay_us; | 
|  | gpio_pin_t gpio_pin; | 
|  | gpio_dt_flags_t gpio_flags; | 
|  | uint8_t options; | 
|  | }; | 
|  |  | 
|  | enum work_task { | 
|  | WORK_TASK_UNDEFINED, | 
|  | WORK_TASK_ENABLE, | 
|  | WORK_TASK_DISABLE, | 
|  | WORK_TASK_DELAY, | 
|  | }; | 
|  |  | 
|  | struct driver_data_onoff { | 
|  | const struct device *gpio; | 
|  | const struct device *dev; | 
|  | struct onoff_manager mgr; | 
|  | #ifdef CONFIG_MULTITHREADING | 
|  | struct k_work_delayable dwork; | 
|  | #endif /* CONFIG_MULTITHREADING */ | 
|  | onoff_notify_fn notify; | 
|  | enum work_task task; | 
|  | }; | 
|  |  | 
|  | /* Common initialization of GPIO device and pin state. | 
|  | * | 
|  | * @param dev the regulator device, whether sync or onoff | 
|  | * | 
|  | * @param gpiop where to store the GPIO device pointer | 
|  | * | 
|  | * @return negative on error, otherwise zero. | 
|  | */ | 
|  | static int common_init(const struct device *dev, | 
|  | const struct device **gpiop) | 
|  | { | 
|  | const struct driver_config *cfg = dev->config; | 
|  | const struct device *gpio = device_get_binding(cfg->gpio_name); | 
|  |  | 
|  | if (gpio == NULL) { | 
|  | LOG_ERR("no GPIO device: %s", cfg->gpio_name); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | *gpiop = gpio; | 
|  |  | 
|  | gpio_flags_t flags = cfg->gpio_flags; | 
|  | bool on = cfg->options & (OPTION_ALWAYS_ON | OPTION_BOOT_ON); | 
|  | uint32_t delay_us = 0; | 
|  |  | 
|  | if (on) { | 
|  | flags |= GPIO_OUTPUT_ACTIVE; | 
|  | delay_us = cfg->startup_delay_us; | 
|  | } else { | 
|  | flags |= GPIO_OUTPUT_INACTIVE; | 
|  | } | 
|  |  | 
|  | int rc = gpio_pin_configure(gpio, cfg->gpio_pin, flags); | 
|  |  | 
|  | if ((rc == 0) && (delay_us > 0)) { | 
|  | /* Turned on and we have to wait until the on | 
|  | * completes.  Since this is in the driver init we | 
|  | * can't sleep. | 
|  | */ | 
|  | k_busy_wait(delay_us); | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | static void finalize_transition(struct driver_data_onoff *data, | 
|  | onoff_notify_fn notify, | 
|  | uint32_t delay_us, | 
|  | int rc) | 
|  | { | 
|  | const struct driver_config *cfg = data->dev->config; | 
|  |  | 
|  | LOG_DBG("%s: finalize %d delay %u us", cfg->regulator_name, rc, delay_us); | 
|  |  | 
|  | /* If there's no error and we have to delay, do so. */ | 
|  | if ((rc >= 0) && (delay_us > 0)) { | 
|  | /* If the delay is less than a tick or we're not | 
|  | * sleep-capable we have to busy-wait. | 
|  | */ | 
|  | if ((k_us_to_ticks_floor32(delay_us) == 0) | 
|  | || k_is_pre_kernel() | 
|  | || !IS_ENABLED(CONFIG_MULTITHREADING)) { | 
|  | k_busy_wait(delay_us); | 
|  | #ifdef CONFIG_MULTITHREADING | 
|  | } else { | 
|  | /* Otherwise sleep in the work queue. */ | 
|  | __ASSERT_NO_MSG(data->task == WORK_TASK_UNDEFINED); | 
|  | data->task = WORK_TASK_DELAY; | 
|  | data->notify = notify; | 
|  | rc = k_work_schedule(&data->dwork, K_USEC(delay_us)); | 
|  | if (rc >= 0) { | 
|  | return; | 
|  | } | 
|  | #endif /* CONFIG_MULTITHREADING */ | 
|  | } | 
|  | } | 
|  |  | 
|  | notify(&data->mgr, rc); | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_MULTITHREADING | 
|  | /* The worker is used for several things: | 
|  | * | 
|  | * * If a transition occurred in a context where the GPIO state could | 
|  | *   not be changed that's done here. | 
|  | * * If a start or stop transition requires a delay that exceeds one | 
|  | *   tick the notification after the delay is performed here. | 
|  | */ | 
|  | static void onoff_worker(struct k_work *work) | 
|  | { | 
|  | struct k_work_delayable *dwork | 
|  | = k_work_delayable_from_work(work); | 
|  | struct driver_data_onoff *data | 
|  | = CONTAINER_OF(dwork, struct driver_data_onoff, | 
|  | dwork); | 
|  | onoff_notify_fn notify = data->notify; | 
|  | const struct driver_config *cfg = data->dev->config; | 
|  | uint32_t delay_us = 0; | 
|  | int rc = 0; | 
|  |  | 
|  | if (data->task == WORK_TASK_ENABLE) { | 
|  | rc = gpio_pin_set(data->gpio, cfg->gpio_pin, true); | 
|  | LOG_DBG("%s: work enable: %d", cfg->regulator_name, rc); | 
|  | delay_us = cfg->startup_delay_us; | 
|  | } else if (data->task == WORK_TASK_DISABLE) { | 
|  | rc = gpio_pin_set(data->gpio, cfg->gpio_pin, false); | 
|  | LOG_DBG("%s: work disable: %d", cfg->regulator_name, rc); | 
|  | delay_us = cfg->off_on_delay_us; | 
|  | } else if (data->task == WORK_TASK_DELAY) { | 
|  | LOG_DBG("%s: work delay complete", cfg->regulator_name); | 
|  | } | 
|  |  | 
|  | data->notify = NULL; | 
|  | data->task = WORK_TASK_UNDEFINED; | 
|  | finalize_transition(data, notify, delay_us, rc); | 
|  | } | 
|  | #endif /* CONFIG_MULTITHREADING */ | 
|  |  | 
|  | static void start(struct onoff_manager *mgr, | 
|  | onoff_notify_fn notify) | 
|  | { | 
|  | struct driver_data_onoff *data = | 
|  | CONTAINER_OF(mgr, struct driver_data_onoff, mgr); | 
|  | const struct driver_config *cfg = data->dev->config; | 
|  | uint32_t delay_us = cfg->startup_delay_us; | 
|  | int rc = 0; | 
|  |  | 
|  | LOG_DBG("%s: start", cfg->regulator_name); | 
|  |  | 
|  | if ((cfg->options & OPTION_ALWAYS_ON) != 0) { | 
|  | delay_us = 0; | 
|  | goto finalize; | 
|  | } | 
|  |  | 
|  | rc = gpio_pin_set(data->gpio, cfg->gpio_pin, true); | 
|  |  | 
|  | #ifdef CONFIG_MULTITHREADING | 
|  | if (rc == -EWOULDBLOCK) { | 
|  | /* Perform the enable and finalization in a work item. | 
|  | */ | 
|  | LOG_DBG("%s: start deferred", cfg->regulator_name); | 
|  | __ASSERT_NO_MSG(data->task == WORK_TASK_UNDEFINED); | 
|  | data->task = WORK_TASK_ENABLE; | 
|  | data->notify = notify; | 
|  | k_work_schedule(&data->dwork, K_NO_WAIT); | 
|  | return; | 
|  | } | 
|  | #endif /* CONFIG_MULTITHREADING */ | 
|  |  | 
|  | finalize: | 
|  | finalize_transition(data, notify, delay_us, rc); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | static void stop(struct onoff_manager *mgr, | 
|  | onoff_notify_fn notify) | 
|  | { | 
|  | struct driver_data_onoff *data = | 
|  | CONTAINER_OF(mgr, struct driver_data_onoff, mgr); | 
|  | const struct driver_config *cfg = data->dev->config; | 
|  | uint32_t delay_us = cfg->off_on_delay_us; | 
|  | int rc = 0; | 
|  |  | 
|  | LOG_DBG("%s: stop", cfg->regulator_name); | 
|  |  | 
|  | if ((cfg->options & OPTION_ALWAYS_ON) != 0) { | 
|  | delay_us = 0; | 
|  | goto finalize; | 
|  | } | 
|  |  | 
|  | rc = gpio_pin_set(data->gpio, cfg->gpio_pin, false); | 
|  |  | 
|  | #ifdef CONFIG_MULTITHREADING | 
|  | if (rc == -EWOULDBLOCK) { | 
|  | /* Perform the disable and finalization in a work | 
|  | * item. | 
|  | */ | 
|  | LOG_DBG("%s: stop deferred", cfg->regulator_name); | 
|  | __ASSERT_NO_MSG(data->task == WORK_TASK_UNDEFINED); | 
|  | data->task = WORK_TASK_DISABLE; | 
|  | data->notify = notify; | 
|  | k_work_schedule(&data->dwork, K_NO_WAIT); | 
|  | return; | 
|  | } | 
|  | #endif /* CONFIG_MULTITHREADING */ | 
|  |  | 
|  | finalize: | 
|  | finalize_transition(data, notify, delay_us, rc); | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | static int enable_onoff(const struct device *dev, struct onoff_client *cli) | 
|  | { | 
|  | struct driver_data_onoff *data = dev->data; | 
|  |  | 
|  | return onoff_request(&data->mgr, cli); | 
|  | } | 
|  |  | 
|  | static int disable_onoff(const struct device *dev) | 
|  | { | 
|  | struct driver_data_onoff *data = dev->data; | 
|  |  | 
|  | return onoff_release(&data->mgr); | 
|  | } | 
|  |  | 
|  | static const struct onoff_transitions transitions = | 
|  | ONOFF_TRANSITIONS_INITIALIZER(start, stop, NULL); | 
|  |  | 
|  | static const struct regulator_driver_api api_onoff = { | 
|  | .enable = enable_onoff, | 
|  | .disable = disable_onoff, | 
|  | }; | 
|  |  | 
|  | static int regulator_fixed_init_onoff(const struct device *dev) | 
|  | { | 
|  | struct driver_data_onoff *data = dev->data; | 
|  | int rc; | 
|  |  | 
|  | data->dev = dev; | 
|  | rc = onoff_manager_init(&data->mgr, &transitions); | 
|  | __ASSERT_NO_MSG(rc == 0); | 
|  |  | 
|  | #ifdef CONFIG_MULTITHREADING | 
|  | k_work_init_delayable(&data->dwork, onoff_worker); | 
|  | #endif /* CONFIG_MULTITHREADING */ | 
|  |  | 
|  | rc = common_init(dev, &data->gpio); | 
|  | if (rc >= 0) { | 
|  | rc = 0; | 
|  | } | 
|  |  | 
|  | LOG_INF("%s onoff: %d", dev->name, rc); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | struct driver_data_sync { | 
|  | const struct device *gpio; | 
|  | struct onoff_sync_service srv; | 
|  | }; | 
|  |  | 
|  | #if DT_HAS_COMPAT_STATUS_OKAY(regulator_fixed_sync) - 0 | 
|  |  | 
|  | static int enable_sync(const struct device *dev, struct onoff_client *cli) | 
|  | { | 
|  | struct driver_data_sync *data = dev->data; | 
|  | const struct driver_config *cfg = dev->config; | 
|  | k_spinlock_key_t key; | 
|  | int rc = onoff_sync_lock(&data->srv, &key); | 
|  |  | 
|  | if ((rc == 0) | 
|  | && ((cfg->options & OPTION_ALWAYS_ON) == 0)) { | 
|  | rc = gpio_pin_set(data->gpio, cfg->gpio_pin, true); | 
|  | } | 
|  |  | 
|  | return onoff_sync_finalize(&data->srv, key, cli, rc, true); | 
|  | } | 
|  |  | 
|  | static int disable_sync(const struct device *dev) | 
|  | { | 
|  | struct driver_data_sync *data = dev->data; | 
|  | const struct driver_config *cfg = dev->config; | 
|  | k_spinlock_key_t key; | 
|  | int rc = onoff_sync_lock(&data->srv, &key); | 
|  |  | 
|  | if  ((cfg->options & OPTION_ALWAYS_ON) != 0) { | 
|  | rc = 0; | 
|  | } else if (rc == 1) { | 
|  | rc = gpio_pin_set(data->gpio, cfg->gpio_pin, false); | 
|  | } else if (rc == 0) { | 
|  | rc = -EINVAL; | 
|  | } /* else rc > 0, leave it on */ | 
|  |  | 
|  | return onoff_sync_finalize(&data->srv, key, NULL, rc, false); | 
|  | } | 
|  |  | 
|  | static const struct regulator_driver_api api_sync = { | 
|  | .enable = enable_sync, | 
|  | .disable = disable_sync, | 
|  | }; | 
|  |  | 
|  | static int regulator_fixed_init_sync(const struct device *dev) | 
|  | { | 
|  | struct driver_data_sync *data = dev->data; | 
|  | const struct driver_config *cfg = dev->config; | 
|  | int rc = common_init(dev, &data->gpio); | 
|  |  | 
|  | (void)regulator_fixed_init_onoff; | 
|  | (void)api_onoff; | 
|  | (void)cfg; | 
|  |  | 
|  | __ASSERT(cfg->startup_delay_us == 0, | 
|  | "sync not valid with startup delay"); | 
|  | __ASSERT(cfg->off_on_delay_us == 0, | 
|  | "sync not valid with shutdown delay"); | 
|  |  | 
|  | LOG_INF("%s sync: %d", dev->name, rc); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | #endif /* DT_HAS_COMPAT_STATUS_OK(regulator_fixed_sync) */ | 
|  |  | 
|  | /* This should also check: | 
|  | *  && DT_INST_PROP(id, startup_delay_us) == 0 | 
|  | *  && DT_INST_PROP(id, off_on_delay_us) == 0 | 
|  | * but the preprocessor magic doesn't seem able to do that so we'll assert | 
|  | * in init instead. | 
|  | */ | 
|  | #define REG_IS_SYNC(id)						\ | 
|  | DT_NODE_HAS_COMPAT(DT_DRV_INST(id), regulator_fixed_sync) | 
|  |  | 
|  | #define REG_DATA_TAG(id) COND_CODE_1(REG_IS_SYNC(id),	\ | 
|  | (driver_data_sync),			\ | 
|  | (driver_data_onoff)) | 
|  | #define REG_API(id) COND_CODE_1(REG_IS_SYNC(id),	\ | 
|  | (api_sync),				\ | 
|  | (api_onoff)) | 
|  | #define REG_INIT(id) COND_CODE_1(REG_IS_SYNC(id),	\ | 
|  | (regulator_fixed_init_sync),		\ | 
|  | (regulator_fixed_init_onoff)) | 
|  |  | 
|  | #define REGULATOR_DEVICE(id) \ | 
|  | static const struct driver_config regulator_##id##_cfg = { \ | 
|  | .regulator_name = DT_INST_PROP(id, regulator_name), \ | 
|  | .gpio_name = DT_INST_GPIO_LABEL(id, enable_gpios), \ | 
|  | .startup_delay_us = DT_INST_PROP(id, startup_delay_us), \ | 
|  | .off_on_delay_us = DT_INST_PROP(id, off_on_delay_us), \ | 
|  | .gpio_pin = DT_INST_GPIO_PIN(id, enable_gpios),	\ | 
|  | .gpio_flags = DT_INST_GPIO_FLAGS(id, enable_gpios), \ | 
|  | .options = (DT_INST_PROP(id, regulator_boot_on)	\ | 
|  | << OPTION_BOOT_ON_POS) \ | 
|  | | (DT_INST_PROP(id, regulator_always_on) \ | 
|  | << OPTION_ALWAYS_ON_POS), \ | 
|  | }; \ | 
|  | \ | 
|  | static struct REG_DATA_TAG(id) regulator_##id##_data; \ | 
|  | \ | 
|  | DEVICE_DT_INST_DEFINE(id, REG_INIT(id), NULL,			       \ | 
|  | ®ulator_##id##_data, ®ulator_##id##_cfg,	       \ | 
|  | POST_KERNEL, CONFIG_REGULATOR_FIXED_INIT_PRIORITY,    \ | 
|  | ®_API(id)); | 
|  |  | 
|  | DT_INST_FOREACH_STATUS_OKAY(REGULATOR_DEVICE) |