diff --git a/CMakeLists.txt b/CMakeLists.txt index 2684d0e869122f..5af9a858850a8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -801,6 +801,12 @@ zephyr_get_include_directories_for_lang(C STRIP_PREFIX # Don't use a -I prefix ) +if(CONFIG_PM_DEVICE_POWER_DOMAIN_DYNAMIC) + set(number_of_dynamic_devices ${CONFIG_PM_DEVICE_POWER_DOMAIN_DYNAMIC_NUM}) +else() + set(number_of_dynamic_devices 0) +endif() + if(CONFIG_HAS_DTS) # dev_handles.c is generated from ${ZEPHYR_LINK_STAGE_EXECUTABLE} by # gen_handles.py @@ -810,6 +816,7 @@ if(CONFIG_HAS_DTS) ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/gen_handles.py --output-source dev_handles.c + --num-dynamic-devices ${number_of_dynamic_devices} --kernel $ --zephyr-base ${ZEPHYR_BASE} --start-symbol "$" diff --git a/drivers/display/ssd1306.c b/drivers/display/ssd1306.c index 9408ac4f95c1f6..bdfbb132c0a7bf 100644 --- a/drivers/display/ssd1306.c +++ b/drivers/display/ssd1306.c @@ -15,6 +15,7 @@ LOG_MODULE_REGISTER(ssd1306, CONFIG_DISPLAY_LOG_LEVEL); #include #include #include +#include #include "ssd1306_regs.h" #include @@ -425,6 +426,34 @@ static int ssd1306_init(const struct device *dev) return 0; } +#ifdef CONFIG_PM_DEVICE +static int ssd1306_pm_action(const struct device *dev, + enum pm_device_action action) +{ + int ret = 0; + + switch (action) { + case PM_DEVICE_ACTION_RESUME: + LOG_DBG("Initializing display..."); + ssd1306_init(dev); + + break; + + case PM_DEVICE_ACTION_TURN_OFF: + // We don't need to do anything when turning off, but we also + // don't want to return -ENOTSUP + // So we keep this empty on purpose. + break; + + default: + ret = -ENOTSUP; + } + + return ret; +} +PM_DEVICE_DT_INST_DEFINE(0, ssd1306_pm_action); +#endif /* CONFIG_PM_DEVICE */ + static const struct ssd1306_config ssd1306_config = { #if DT_INST_ON_BUS(0, i2c) .bus = I2C_DT_SPEC_INST_GET(0), @@ -451,7 +480,7 @@ static struct display_driver_api ssd1306_driver_api = { .set_orientation = ssd1306_set_orientation, }; -DEVICE_DT_INST_DEFINE(0, ssd1306_init, NULL, +DEVICE_DT_INST_DEFINE(0, ssd1306_init, PM_DEVICE_DT_INST_GET(0), &ssd1306_driver, &ssd1306_config, POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &ssd1306_driver_api); diff --git a/drivers/i2c/i2c_nrfx_twi.c b/drivers/i2c/i2c_nrfx_twi.c index 8dd760912af936..aeca8cb043ace4 100644 --- a/drivers/i2c/i2c_nrfx_twi.c +++ b/drivers/i2c/i2c_nrfx_twi.c @@ -228,10 +228,18 @@ static int twi_nrfx_pm_action(const struct device *dev, switch (action) { case PM_DEVICE_ACTION_RESUME: + LOG_DBG("Initializing i2c-twi..."); init_twi(dev); if (data->dev_config) { i2c_nrfx_twi_configure(dev, data->dev_config); } + + break; + + case PM_DEVICE_ACTION_TURN_OFF: + // We don't want to uninit as it causes issues on re-init + // But we also don't want to return -ENOTSUP + // So we keep this empty. break; case PM_DEVICE_ACTION_SUSPEND: diff --git a/drivers/power_domain/power_domain_gpio.c b/drivers/power_domain/power_domain_gpio.c index 6fbf012e7beabc..ff93928a586625 100644 --- a/drivers/power_domain/power_domain_gpio.c +++ b/drivers/power_domain/power_domain_gpio.c @@ -13,7 +13,7 @@ #include #include -LOG_MODULE_REGISTER(power_domain_gpio, LOG_LEVEL_INF); +LOG_MODULE_REGISTER(power_domain_gpio, CONFIG_PM_DEVICE_LOG_LEVEL); struct pd_gpio_config { struct gpio_dt_spec enable; @@ -36,6 +36,8 @@ const char *actions[] = { static int pd_gpio_pm_action(const struct device *dev, enum pm_device_action action) { + LOG_DBG("In pd_gpio_pm_action for power domain %s with action: %s", dev->name, pm_device_action_str(action)); + const struct pd_gpio_config *cfg = dev->config; int rc = 0; @@ -43,26 +45,55 @@ static int pd_gpio_pm_action(const struct device *dev, case PM_DEVICE_ACTION_RESUME: /* Switch power on */ gpio_pin_set_dt(&cfg->enable, 1); + + if(cfg->startup_delay_us > 0) { + k_sleep(K_USEC(cfg->startup_delay_us)); + } + LOG_DBG("%s is now ON", dev->name); + /* Notify supported devices they are now powered */ - pm_device_children_action_run(dev, PM_DEVICE_ACTION_TURN_ON, NULL); + pm_device_children_action_run(dev, action, NULL); break; case PM_DEVICE_ACTION_SUSPEND: /* Notify supported devices power is going down */ - pm_device_children_action_run(dev, PM_DEVICE_ACTION_TURN_OFF, NULL); + pm_device_children_action_run(dev, action, NULL); + + if(cfg->off_on_delay_us > 0) { + k_sleep(K_USEC(cfg->off_on_delay_us)); + } + /* Switch power off */ gpio_pin_set_dt(&cfg->enable, 0); LOG_DBG("%s is now OFF and powered", dev->name); break; case PM_DEVICE_ACTION_TURN_ON: /* Actively control the enable pin now that the device is powered */ - gpio_pin_configure_dt(&cfg->enable, GPIO_OUTPUT_INACTIVE); + gpio_pin_set_dt(&cfg->enable, 1); + + if(cfg->startup_delay_us > 0) { + k_sleep(K_USEC(cfg->startup_delay_us)); + } + LOG_DBG("%s is OFF and powered", dev->name); + + /* Notify supported devices they are now powered */ + pm_device_children_action_run(dev, action, NULL); + break; case PM_DEVICE_ACTION_TURN_OFF: + /* Notify supported devices power is going down */ + pm_device_children_action_run(dev, action, NULL); + + if(cfg->off_on_delay_us > 0) { + k_sleep(K_USEC(cfg->off_on_delay_us)); + } + /* Let the enable pin float while device is not powered */ - gpio_pin_configure_dt(&cfg->enable, GPIO_DISCONNECTED); + gpio_pin_set_dt(&cfg->enable, 0); + LOG_DBG("%s is OFF and not powered", dev->name); + break; default: rc = -ENOTSUP; @@ -73,6 +104,8 @@ static int pd_gpio_pm_action(const struct device *dev, static int pd_gpio_init(const struct device *dev) { + LOG_DBG("Initing power-domain-gpio: %s", dev->name); + const struct pd_gpio_config *cfg = dev->config; int rc; @@ -85,12 +118,18 @@ static int pd_gpio_init(const struct device *dev) /* Device is unpowered */ pm_device_runtime_init_off(dev); rc = gpio_pin_configure_dt(&cfg->enable, GPIO_DISCONNECTED); + if(rc != 0) { + LOG_WRN("Could not configure pin to GPIO_DISCONNECTED: %d", rc); + } } else { pm_device_runtime_init_suspended(dev); rc = gpio_pin_configure_dt(&cfg->enable, GPIO_OUTPUT_INACTIVE); + if(rc != 0) { + LOG_WRN("Could not configure pin to GPIO_OUTPUT_INACTIVE: %d", rc); + } } - return rc; + return 0; } #define POWER_DOMAIN_DEVICE(id) \ diff --git a/include/device.h b/include/device.h index f06cc06c78c3c0..02228a474cd32e 100644 --- a/include/device.h +++ b/include/device.h @@ -444,6 +444,12 @@ struct device_state { struct pm_device; +#ifdef CONFIG_HAS_DYNAMIC_DEVICE_HANDLES +#define Z_DEVICE_HANDLES_CONST +#else +#define Z_DEVICE_HANDLES_CONST const +#endif + /** * @brief Runtime device structure (in ROM) per driver instance */ @@ -465,7 +471,8 @@ struct device { * extracted with dedicated API, such as * device_required_handles_get(). */ - const device_handle_t *const handles; + Z_DEVICE_HANDLES_CONST device_handle_t * const handles; + #ifdef CONFIG_PM_DEVICE /** Reference to the device PM resources. */ struct pm_device * const pm; @@ -877,9 +884,9 @@ __deprecated static inline int device_usable_check(const struct device *dev) */ BUILD_ASSERT(sizeof(device_handle_t) == 2, "fix the linker scripts"); #define Z_DEVICE_DEFINE_HANDLES(node_id, dev_name, ...) \ - extern const device_handle_t \ + extern Z_DEVICE_HANDLES_CONST device_handle_t \ Z_DEVICE_HANDLE_NAME(node_id, dev_name)[]; \ - const device_handle_t \ + Z_DEVICE_HANDLES_CONST device_handle_t \ __aligned(sizeof(device_handle_t)) \ __attribute__((__weak__, \ __section__(".__device_handles_pass1"))) \ diff --git a/include/linker/common-ram.ld b/include/linker/common-ram.ld index 7df193531d4b7c..529734bd48abc7 100644 --- a/include/linker/common-ram.ld +++ b/include/linker/common-ram.ld @@ -45,6 +45,19 @@ } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) #endif +#if defined(CONFIG_HAS_DYNAMIC_DEVICE_HANDLES) + SECTION_DATA_PROLOGUE(device_handles,,) + { + __device_handles_start = .; +#ifdef LINKER_DEVICE_HANDLES_PASS1 + KEEP(*(SORT(.__device_handles_pass1*))); +#else + KEEP(*(SORT(.__device_handles_pass2*))); +#endif /* LINKER_DEVICE_HANDLES_PASS1 */ + __device_handles_end = .; + } GROUP_DATA_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) +#endif /* CONFIG_HAS_DYNAMIC_DEVICE_HANDLES */ + SECTION_DATA_PROLOGUE(initshell,,) { /* link in shell initialization objects for all modules that diff --git a/include/linker/common-rom.ld b/include/linker/common-rom.ld index afdc76434bbf39..d1e48172100507 100644 --- a/include/linker/common-rom.ld +++ b/include/linker/common-rom.ld @@ -216,6 +216,7 @@ KEEP(*(".dbg_thread_info")); } GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) +#if !defined(CONFIG_HAS_DYNAMIC_DEVICE_HANDLES) SECTION_DATA_PROLOGUE(device_handles,,) { __device_handles_start = .; @@ -226,3 +227,4 @@ #endif /* LINKER_DEVICE_HANDLES_PASS1 */ __device_handles_end = .; } GROUP_ROM_LINK_IN(RAMABLE_REGION, ROMABLE_REGION) +#endif /* !CONFIG_HAS_DYNAMIC_DEVICE_HANDLES */ diff --git a/include/pm/device.h b/include/pm/device.h index 028dbd06feddf3..57c5b8232f1174 100644 --- a/include/pm/device.h +++ b/include/pm/device.h @@ -314,6 +314,13 @@ struct pm_device { */ const char *pm_device_state_str(enum pm_device_state state); +/** + * @brief Get name of device PM action + * + * @param action Action id which name should be returned + */ +const char *pm_device_action_str(enum pm_device_action action); + /** * @brief Set the power state of a device. * @@ -507,6 +514,36 @@ bool pm_device_state_is_locked(const struct device *dev); */ bool pm_device_on_power_domain(const struct device *dev); +/** + * @brief Add a device to a power domain. + * + * This function adds a device to a given power domain. + * + * @param dev Device to be added to the power domain. + * @param domain Power domain. + * + * @retval 0 If successful. + * @retval -EALREADY If device is already part of the power domain. + * @retval -ENOSYS If the application was built without power domain support. + * @retval -ENOSPC If there is no space available in the power domain to add the device. + */ +int pm_device_power_domain_add(const struct device *dev, + const struct device *domain); + +/** + * @brief Remove a device from a power domain. + * + * This function removes a device from a given power domain. + * + * @param dev Device to be removed from the power domain. + * @param domain Power domain. + * + * @retval 0 If successful. + * @retval -ENOSYS If the application was built without power domain support. + * @retval -ENOENT If device is not in the given domain. + */ +int pm_device_power_domain_remove(const struct device *dev, + const struct device *domain); #else static inline void pm_device_busy_set(const struct device *dev) { @@ -556,6 +593,19 @@ static inline bool pm_device_on_power_domain(const struct device *dev) ARG_UNUSED(dev); return false; } + +static inline int pm_device_power_domain_add(const struct device *dev, + const struct device *domain) +{ + return -ENOSYS; +} + +static inline int pm_device_power_domain_remove(const struct device *dev, + const struct device *domain) +{ + return -ENOSYS; +} + #endif /* CONFIG_PM_DEVICE */ /** diff --git a/kernel/Kconfig b/kernel/Kconfig index 6e040d045c1dfb..1749c0b4694cd7 100644 --- a/kernel/Kconfig +++ b/kernel/Kconfig @@ -920,4 +920,14 @@ config THREAD_LOCAL_STORAGE endmenu +menu "Device Options" + +config HAS_DYNAMIC_DEVICE_HANDLES + bool + help + Hidden option that makes possible to manipulate device handles at + runtime. + +endmenu + rsource "Kconfig.vm" diff --git a/scripts/gen_handles.py b/scripts/gen_handles.py index aa046a13b4db7c..ba027e1ff16bac 100755 --- a/scripts/gen_handles.py +++ b/scripts/gen_handles.py @@ -62,6 +62,8 @@ def parse_args(): parser.add_argument("-k", "--kernel", required=True, help="Input zephyr ELF binary") + parser.add_argument("-d", "--num-dynamic-devices", required=False, default=0, + type=int, help="Input number of dynamic devices allowed") parser.add_argument("-o", "--output-source", required=True, help="Output source file") @@ -112,6 +114,7 @@ def symbol_handle_data(elf, sym): # These match the corresponding constants in DEVICE_HANDLE_SEP = -32768 DEVICE_HANDLE_ENDS = 32767 +DEVICE_HANDLE_NULL = 0 def handle_name(hdl): if hdl == DEVICE_HANDLE_SEP: return "DEVICE_HANDLE_SEP" @@ -336,6 +339,7 @@ def main(): else: sup_paths.append('(%s)' % dn.path) hdls.extend(dn.__device.dev_handle for dn in sn.__supports) + hdls.extend(DEVICE_HANDLE_NULL for dn in range(args.num_dynamic_devices)) # Terminate the array with the end symbol hdls.append(DEVICE_HANDLE_ENDS) diff --git a/subsys/pm/Kconfig b/subsys/pm/Kconfig index 0938d823c62458..7bd73170c71606 100644 --- a/subsys/pm/Kconfig +++ b/subsys/pm/Kconfig @@ -63,6 +63,20 @@ config PM_DEVICE_POWER_DOMAIN devices that depend on a domain will be notified when this domain is suspended or resumed. +config PM_DEVICE_POWER_DOMAIN_DYNAMIC + bool "Dynamically bind devices to a Power Pomain" + depends on PM_DEVICE_POWER_DOMAIN + select HAS_DYNAMIC_DEVICE_HANDLES + help + Enable support for dynamically bind devices to a Power Domain. + +config PM_DEVICE_POWER_DOMAIN_DYNAMIC_NUM + int "Number of devices that can dynamically be bind to a Power Domain" + depends on PM_DEVICE_POWER_DOMAIN_DYNAMIC + default 1 + help + The number of devices that can dynamically be bind to a Power Domain. + config PM_DEVICE_RUNTIME bool "Runtime Device Power Management" help diff --git a/subsys/pm/device.c b/subsys/pm/device.c index 92c2598c5056ae..cf06d520ac8479 100644 --- a/subsys/pm/device.c +++ b/subsys/pm/device.c @@ -25,6 +25,24 @@ const char *pm_device_state_str(enum pm_device_state state) } } +const char *pm_device_action_str(enum pm_device_action action) +{ + switch (action) { + case PM_DEVICE_ACTION_SUSPEND: + return "suspend"; + case PM_DEVICE_ACTION_RESUME: + return "resume"; + case PM_DEVICE_ACTION_TURN_OFF: + return "turn_off"; + case PM_DEVICE_ACTION_TURN_ON: + return "turn_on"; + case PM_DEVICE_ACTION_FORCE_SUSPEND: + return "force_suspend"; + default: + return "unknown_action"; + } +} + int pm_device_state_set(const struct device *dev, enum pm_device_state state) { @@ -154,6 +172,82 @@ int pm_device_action_run(const struct device *dev, return 0; } +static int power_domain_add_or_remove(const struct device *dev, + const struct device *domain, + bool add) +{ +#if defined(CONFIG_HAS_DYNAMIC_DEVICE_HANDLES) + device_handle_t *rv = domain->handles; + device_handle_t dev_handle = -1; + extern const struct device __device_start[]; + extern const struct device __device_end[]; + size_t i, region = 0; + size_t numdev = __device_end - __device_start; + + /* + * Supported devices are stored as device handle and not + * device pointers. So, it is necessary to find what is + * the handle associated to the given device. + */ + for (i = 0; i < numdev; i++) { + if (&__device_start[i] == dev) { + dev_handle = i + 1; + break; + } + } + + /* + * The last part is to find an available slot in the + * supported section of handles array and replace it + * with the device handle. + */ + while (region != 2) { + if (*rv == DEVICE_HANDLE_SEP) { + region++; + } + rv++; + } + + i = 0; + while (rv[i] != DEVICE_HANDLE_ENDS) { + if (add == false) { + if (rv[i] == dev_handle) { + dev->pm->domain = NULL; + rv[i] = DEVICE_HANDLE_NULL; + return 0; + } + } else { + if (rv[i] == DEVICE_HANDLE_NULL) { + dev->pm->domain = domain; + rv[i] = dev_handle; + return 0; + } + } + ++i; + } + + return add ? -ENOSPC : -ENOENT; +#else + ARG_UNUSED(dev); + ARG_UNUSED(domain); + ARG_UNUSED(add); + + return -ENOSYS; +#endif +} + +int pm_device_power_domain_remove(const struct device *dev, + const struct device *domain) +{ + return power_domain_add_or_remove(dev, domain, false); +} + +int pm_device_power_domain_add(const struct device *dev, + const struct device *domain) +{ + return power_domain_add_or_remove(dev, domain, true); +} + void pm_device_children_action_run(const struct device *dev, enum pm_device_action action, pm_device_action_failed_cb_t failure_cb) @@ -174,6 +268,10 @@ void pm_device_children_action_run(const struct device *dev, device_handle_t dh = handles[i]; const struct device *cdev = device_from_handle(dh); + if (cdev == NULL) { + continue; + } + rc = pm_device_action_run(cdev, action); if ((failure_cb != NULL) && (rc < 0)) { /* Stop the iteration if the callback requests it */