drivers: regulator: add GPIO-controlled regulator driver
This provides structure for the regulator device hierarchy and a driver for GPIO-controlled regulators along with its binding. Signed-off-by: Peter A. Bigot <pab@pabigot.com>
This commit is contained in:
parent
b7639551ee
commit
f951566e56
8 changed files with 489 additions and 0 deletions
6
drivers/regulator/CMakeLists.txt
Normal file
6
drivers/regulator/CMakeLists.txt
Normal file
|
@ -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)
|
18
drivers/regulator/Kconfig
Normal file
18
drivers/regulator/Kconfig
Normal file
|
@ -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
|
19
drivers/regulator/Kconfig.fixed
Normal file
19
drivers/regulator/Kconfig.fixed
Normal file
|
@ -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
|
392
drivers/regulator/regulator_fixed.c
Normal file
392
drivers/regulator/regulator_fixed.c
Normal file
|
@ -0,0 +1,392 @@
|
|||
/*
|
||||
* 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_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)
|
Loading…
Add table
Add a link
Reference in a new issue