diff --git a/CODEOWNERS b/CODEOWNERS index 76b912a7c97..11e46c7f9f6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -233,6 +233,7 @@ /drivers/pwm/*npcx* @MulinChao /drivers/pwm/*sam0* @nzmichaelh /drivers/pwm/*stm32* @gmarull +/drivers/regulator/ @pabigot /drivers/sensor/ @MaureenHelm /drivers/sensor/ams_iAQcore/ @alexanderwachter /drivers/sensor/ens210/ @alexanderwachter diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index db17b7db98a..07cd26955b7 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -39,6 +39,7 @@ add_subdirectory_ifdef(CONFIG_VIDEO video) add_subdirectory_ifdef(CONFIG_EEPROM eeprom) add_subdirectory_ifdef(CONFIG_LORA lora) add_subdirectory_ifdef(CONFIG_PECI peci) +add_subdirectory_ifdef(CONFIG_REGULATOR regulator) add_subdirectory_ifdef(CONFIG_FLASH_HAS_DRIVER_ENABLED flash) add_subdirectory_ifdef(CONFIG_SERIAL_HAS_DRIVER serial) diff --git a/drivers/Kconfig b/drivers/Kconfig index eafe17c5ca7..3491113a1ba 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -99,4 +99,6 @@ source "drivers/eeprom/Kconfig" source "drivers/peci/Kconfig" +source "drivers/regulator/Kconfig" + endmenu diff --git a/drivers/regulator/CMakeLists.txt b/drivers/regulator/CMakeLists.txt new file mode 100644 index 00000000000..e89256d815b --- /dev/null +++ b/drivers/regulator/CMakeLists.txt @@ -0,0 +1,6 @@ +# Copyright 2020 Peter Bigot Consulting, LLC +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_REGULATOR_FIXED regulator_fixed.c) diff --git a/drivers/regulator/Kconfig b/drivers/regulator/Kconfig new file mode 100644 index 00000000000..8c879d4c4b8 --- /dev/null +++ b/drivers/regulator/Kconfig @@ -0,0 +1,18 @@ +# Copyright 2020 Peter Bigot Consulting, LLC +# SPDX-License-Identifier: Apache-2.0 + +menuconfig REGULATOR + bool "Regulator drivers" + default $(dt_compat_enabled,$(DT_COMPAT_REGULATOR_FIXED)) + help + Include drivers for current/voltage regulators in system config + +if REGULATOR + +module = REGULATOR +module-str = regulator +source "subsys/logging/Kconfig.template.log_config" + +source "drivers/regulator/Kconfig.fixed" + +endif # REGULATOR diff --git a/drivers/regulator/Kconfig.fixed b/drivers/regulator/Kconfig.fixed new file mode 100644 index 00000000000..2dcfbaab384 --- /dev/null +++ b/drivers/regulator/Kconfig.fixed @@ -0,0 +1,19 @@ +# Copyright 2020 Peter Bigot Consulting, LLC +# SPDX-License-Identifier: Apache-2.0 + +menuconfig REGULATOR_FIXED + bool "GPIO-controlled regulators" + default $(dt_compat_enabled,$(DT_COMPAT_REGULATOR_FIXED)) + depends on GPIO + help + Enable the driver for GPIO-controlled regulators + +if REGULATOR_FIXED + +config REGULATOR_FIXED_INIT_PRIORITY + int "Init priority" + default 75 + help + Device driver initialization priority + +endif # REGULATOR_FIXED diff --git a/drivers/regulator/regulator_fixed.c b/drivers/regulator/regulator_fixed.c new file mode 100644 index 00000000000..89e091715fc --- /dev/null +++ b/drivers/regulator/regulator_fixed.c @@ -0,0 +1,392 @@ +/* + * Copyright 2019-2020 Peter Bigot Consulting, LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT regulator_fixed + +#include +#include +#include + +#include +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_delayed_work delayed_work; +#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_delayed_work_submit(&data->delayed_work, + 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_delayed_work *delayed_work + = CONTAINER_OF(work, struct k_delayed_work, work); + struct driver_data_onoff *data + = CONTAINER_OF(delayed_work, struct driver_data_onoff, + delayed_work); + 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_submit(&data->delayed_work.work); + 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_submit(&data->delayed_work.work); + 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 = common_init(dev, &data->gpio); + + data->dev = dev; + onoff_manager_init(&data->mgr, &transitions); +#ifdef CONFIG_MULTITHREADING + k_delayed_work_init(&data->delayed_work, onoff_worker); +#endif /* CONFIG_MULTITHREADING */ + + 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_AND_API_INIT(regulator_##id, DT_INST_LABEL(id), \ + REG_INIT(id), \ + ®ulator_##id##_data, ®ulator_##id##_cfg, \ + POST_KERNEL, CONFIG_REGULATOR_FIXED_INIT_PRIORITY, \ + ®_API(id)); + +DT_INST_FOREACH_STATUS_OKAY(REGULATOR_DEVICE) diff --git a/dts/bindings/regulator/regulator-fixed.yaml b/dts/bindings/regulator/regulator-fixed.yaml new file mode 100644 index 00000000000..73ceab820d0 --- /dev/null +++ b/dts/bindings/regulator/regulator-fixed.yaml @@ -0,0 +1,50 @@ +# Copyright 2019-2020 Peter Bigot Consulting, LLC +# SPDX-License-Identifier: Apache-2.0 + +description: GPIO-controlled regulators + +include: regulator.yaml + +# NOTE: The driver supports "regulator-fixed-sync" as a specializing +# variant when it's known that the GPIO state change operations are +# isr-ok and not sleep, and both startup and off-on delays are zero. +# This bypasses the asynchronous onoff manager which optimizes both time +# and memory use. +# +# To enable this use both: +# compatible = "regulator-fixed-sync", "regulator-fixed"; +# + +compatible: "regulator-fixed" + +properties: + label: + required: true + + regulator-name: + type: string + required: true + description: Descriptive name for regulator outputs. + + enable-gpios: + type: phandle-array + required: true + description: | + GPIO to use to enable/disable the regulator. + + Unlike the gpio property in the Linux bindings this array must + provide the GPIO polarity and open-drain status in the phandle + selector. The Linux enable-active-high and gpio-open-drain + properties are not valid for Zephyr devicetree files. + + startup-delay-us: + type: int + required: false + default: 0 + description: Startup time, in microseconds + + off-on-delay-us: + type: int + required: false + default: 0 + description: Off delay time, in microseconds