Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions drivers/gpio/gpio_stellaris.c
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,34 @@ static int gpio_stellaris_configure(const struct device *dev,
return 0;
}

#ifdef CONFIG_GPIO_GET_CONFIG
static int gpio_stellaris_get_config(const struct device *dev,
gpio_pin_t pin,
gpio_flags_t *out_flags)
{
const struct gpio_stellaris_config *cfg = dev->config;
uint32_t base = cfg->base;
gpio_flags_t flags = 0;
mm_reg_t mask_addr;

if (sys_test_bit(GPIO_REG_ADDR(base, GPIO_DEN_OFFSET), pin) == 0) {
flags = GPIO_DISCONNECTED;
} else if (sys_test_bit(GPIO_REG_ADDR(base, GPIO_DIR_OFFSET), pin)) {
mask_addr = GPIO_RW_MASK_ADDR(base, GPIO_DATA_OFFSET, BIT(pin));

if (sys_test_bit(mask_addr, pin)) {
flags |= GPIO_OUTPUT_HIGH;
} else {
flags |= GPIO_OUTPUT_LOW;
}
} else {
flags = GPIO_INPUT;
}
*out_flags = flags;
return 0;
}
#endif

static int gpio_stellaris_port_get_raw(const struct device *dev,
uint32_t *value)
{
Expand Down Expand Up @@ -221,6 +249,9 @@ static int gpio_stellaris_manage_callback(const struct device *dev,

static const struct gpio_driver_api gpio_stellaris_driver_api = {
.pin_configure = gpio_stellaris_configure,
#ifdef CONFIG_GPIO_GET_CONFIG
.pin_get_config = gpio_stellaris_get_config,
#endif
.port_get_raw = gpio_stellaris_port_get_raw,
.port_set_masked_raw = gpio_stellaris_port_set_masked_raw,
.port_set_bits_raw = gpio_stellaris_port_set_bits_raw,
Expand Down
25 changes: 13 additions & 12 deletions drivers/power_domain/power_domain_gpio.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ struct pd_visitor_context {
enum pm_device_action action;
};

#ifdef CONFIG_PM_DEVICE_POWER_DOMAIN

static int pd_on_domain_visitor(const struct device *dev, void *context)
{
struct pd_visitor_context *visitor_context = context;
Expand All @@ -43,12 +45,16 @@ static int pd_on_domain_visitor(const struct device *dev, void *context)
return 0;
}

#endif

static int pd_gpio_pm_action(const struct device *dev,
enum pm_device_action action)
{
#ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
struct pd_visitor_context context = {.domain = dev};
#endif
const struct pd_gpio_config *cfg = dev->config;
struct pd_gpio_data *data = dev->data;
struct pd_visitor_context context = {.domain = dev};
int64_t next_boot_ticks;
int rc = 0;

Expand All @@ -67,14 +73,18 @@ static int pd_gpio_pm_action(const struct device *dev,
LOG_INF("%s is now ON", dev->name);
/* Wait for domain to come up */
k_sleep(K_USEC(cfg->startup_delay_us));
#ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
/* Notify devices on the domain they are now powered */
context.action = PM_DEVICE_ACTION_TURN_ON;
(void)device_supported_foreach(dev, pd_on_domain_visitor, &context);
#endif
break;
case PM_DEVICE_ACTION_SUSPEND:
#ifdef CONFIG_PM_DEVICE_POWER_DOMAIN
/* Notify devices on the domain that power is going down */
context.action = PM_DEVICE_ACTION_TURN_OFF;
(void)device_supported_foreach(dev, pd_on_domain_visitor, &context);
#endif
/* Switch power off */
gpio_pin_set_dt(&cfg->enable, 0);
LOG_INF("%s is now OFF", dev->name);
Expand Down Expand Up @@ -103,7 +113,6 @@ static int pd_gpio_init(const struct device *dev)
{
const struct pd_gpio_config *cfg = dev->config;
struct pd_gpio_data *data = dev->data;
int rc;

if (!device_is_ready(cfg->enable.port)) {
LOG_ERR("GPIO port %s is not ready", cfg->enable.port->name);
Expand All @@ -112,16 +121,8 @@ static int pd_gpio_init(const struct device *dev)
/* We can't know how long the domain has been off for before boot */
data->next_boot = K_TIMEOUT_ABS_US(cfg->off_on_delay_us);

if (pm_device_on_power_domain(dev)) {
/* Device is unpowered */
pm_device_init_off(dev);
rc = gpio_pin_configure_dt(&cfg->enable, GPIO_DISCONNECTED);
} else {
pm_device_init_suspended(dev);
rc = gpio_pin_configure_dt(&cfg->enable, GPIO_OUTPUT_INACTIVE);
}

return rc;
/* Boot according to state */
return pm_device_driver_init(dev, pd_gpio_pm_action);
}

#define POWER_DOMAIN_DEVICE(id) \
Expand Down
29 changes: 29 additions & 0 deletions include/zephyr/pm/device.h
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,22 @@ int pm_device_power_domain_remove(const struct device *dev,
* @retval false If device is not currently powered
*/
bool pm_device_is_powered(const struct device *dev);

/**
* @brief Setup a device driver into the lowest valid power mode
*
* This helper function is intended to be called at the end of a driver
* init function to automatically setup the device into the lowest power
* mode. It assumes that the device has been configured as if it is in
* @ref PM_DEVICE_STATE_OFF.
*
* @param dev Device instance.
* @param action_cb Device PM control callback function.
* @retval 0 On success.
* @retval -errno Error code from @a action_cb on failure.
*/
int pm_device_driver_init(const struct device *dev, pm_device_action_cb_t action_cb);

#else
static inline int pm_device_state_get(const struct device *dev,
enum pm_device_state *state)
Expand Down Expand Up @@ -667,6 +683,19 @@ static inline bool pm_device_is_powered(const struct device *dev)
ARG_UNUSED(dev);
return true;
}

static inline int pm_device_driver_init(const struct device *dev, pm_device_action_cb_t action_cb)
{
int rc;

/* When power management is not enabled, all drivers should initialise to active state */
rc = action_cb(dev, PM_DEVICE_ACTION_TURN_ON);
if (rc == 0) {
rc = action_cb(dev, PM_DEVICE_ACTION_RESUME);
}
return rc;
}

#endif /* CONFIG_PM_DEVICE */

/** @} */
Expand Down
38 changes: 38 additions & 0 deletions subsys/pm/device.c
Original file line number Diff line number Diff line change
Expand Up @@ -385,3 +385,41 @@ bool pm_device_is_powered(const struct device *dev)
return true;
#endif
}

int pm_device_driver_init(const struct device *dev,
pm_device_action_cb_t action_cb)
{
struct pm_device *pm = dev->pm;
int rc = 0;

/* Work only needs to be performed if the device is powered */
if (pm_device_is_powered(dev)) {
/* Run power-up logic */
rc = action_cb(dev, PM_DEVICE_ACTION_TURN_ON);
if (rc != 0) {
return rc;
}
/* If device has no PM structure */
if (pm == NULL) {
/* Device should always be active */
return action_cb(dev, PM_DEVICE_ACTION_RESUME);
}
/* If device will have PM device runtime enabled */
if (IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME) &&
atomic_test_bit(&pm->flags, PM_DEVICE_FLAG_RUNTIME_AUTO)) {
/* Init into suspend mode.
* This saves a SUSPENDED->ACTIVE->SUSPENDED cycle.
*/
pm_device_init_suspended(dev);
}
/* No PM enabled on the device by default */
else {
/* Startup into active mode */
return action_cb(dev, PM_DEVICE_ACTION_RESUME);
}
} else {
/* Start in off mode */
pm_device_init_off(dev);
}
return rc;
}
8 changes: 8 additions & 0 deletions tests/subsys/pm/device_driver_init/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) 2023, CSIRO.
# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(device_driver_init)

target_sources(app PRIVATE src/main.c)
44 changes: 44 additions & 0 deletions tests/subsys/pm/device_driver_init/app.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/ {
test_reg: test_reg {
compatible = "power-domain-gpio";
enable-gpios = <&gpio0 0 0>;
};

test_reg_chained: test_reg_chained {
compatible = "power-domain-gpio";
enable-gpios = <&gpio0 1 0>;
power-domain = <&test_reg>;
};

test_reg_chained_auto: test_reg_chained_auto {
compatible = "power-domain-gpio";
enable-gpios = <&gpio0 2 0>;
power-domain = <&test_reg>;
zephyr,pm-device-runtime-auto;
};

test_reg_auto: test_reg_auto {
compatible = "power-domain-gpio";
enable-gpios = <&gpio0 3 0>;
zephyr,pm-device-runtime-auto;
};

test_reg_auto_chained: test_reg_auto_chained {
compatible = "power-domain-gpio";
enable-gpios = <&gpio0 4 0>;
power-domain = <&test_reg_auto>;
};

test_reg_auto_chained_auto: test_reg_auto_chained_auto {
compatible = "power-domain-gpio";
enable-gpios = <&gpio0 5 0>;
power-domain = <&test_reg_auto>;
zephyr,pm-device-runtime-auto;
};

test_reg_disabled: test_reg_disabled {
compatible = "power-domain-gpio";
enable-gpios = <&gpio0 6 0>;
status = "disabled";
};
};
9 changes: 9 additions & 0 deletions tests/subsys/pm/device_driver_init/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2023, Commonwealth Scientific and Industrial Research
# Organisation (CSIRO) ABN 41 687 119 230.
CONFIG_ZTEST=y
CONFIG_MP_MAX_NUM_CPUS=1

CONFIG_GPIO=y
CONFIG_GPIO_GET_CONFIG=y
CONFIG_POWER_DOMAIN=y
CONFIG_ZTEST_NEW_API=y
66 changes: 66 additions & 0 deletions tests/subsys/pm/device_driver_init/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2023, Commonwealth Scientific and Industrial Research
* Organisation (CSIRO) ABN 41 687 119 230.
*
* SPDX-License-Identifier: Apache-2.0
*
* State checking in this test is done via the GPIO state instead of
* the PM API as this test runs without the PM api enabled.
*/

#include <zephyr/ztest.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/pm/device.h>
#include <zephyr/pm/device_runtime.h>

#define POWER_GPIO_CONFIG_IS(node_id, config)\
{\
const struct gpio_dt_spec gpio = GPIO_DT_SPEC_GET(node_id, enable_gpios);\
gpio_flags_t gpio_config; \
int rc = gpio_pin_get_config_dt(&gpio, &gpio_config); \
zassert_equal(rc, 0, "GPIO config retrieval failed"); \
zassert_equal(gpio_config, config, "Unexpected config");\
}

#define DEVICE_STATE_IS(node_id, value) \
rc = pm_device_state_get(DEVICE_DT_GET(node_id), &state); \
zassert_equal(rc, 0, "Device state retrieval failed"); \
zassert_equal(state, value, "Unexpected device state");

ZTEST(device_driver_init, test_demo)
{
#if IS_ENABLED(CONFIG_PM_DEVICE_RUNTIME)
enum pm_device_state state;
int rc;

/* No device runtime PM, starts on */
DEVICE_STATE_IS(DT_NODELABEL(test_reg), PM_DEVICE_STATE_ACTIVE);
DEVICE_STATE_IS(DT_NODELABEL(test_reg_chained), PM_DEVICE_STATE_ACTIVE);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg), GPIO_OUTPUT_HIGH);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_chained), GPIO_OUTPUT_HIGH);

/* Device powered, zephyr,pm-device-runtime-auto, starts suspended */
DEVICE_STATE_IS(DT_NODELABEL(test_reg_chained_auto), PM_DEVICE_STATE_SUSPENDED);
DEVICE_STATE_IS(DT_NODELABEL(test_reg_auto), PM_DEVICE_STATE_SUSPENDED);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_chained_auto), GPIO_OUTPUT_LOW);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_auto), GPIO_OUTPUT_LOW);
/* Device not powered, starts off */
DEVICE_STATE_IS(DT_NODELABEL(test_reg_auto_chained), PM_DEVICE_STATE_OFF);
DEVICE_STATE_IS(DT_NODELABEL(test_reg_auto_chained_auto), PM_DEVICE_STATE_OFF);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_auto_chained), GPIO_DISCONNECTED);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_auto_chained_auto), GPIO_DISCONNECTED);
#else
/* Every regulator should be in "active" mode automatically.
* State checking via GPIO as PM API is disabled.
*/
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg), GPIO_OUTPUT_HIGH);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_chained), GPIO_OUTPUT_HIGH);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_chained_auto), GPIO_OUTPUT_HIGH);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_auto), GPIO_OUTPUT_HIGH);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_auto_chained), GPIO_OUTPUT_HIGH);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_auto_chained_auto), GPIO_OUTPUT_HIGH);
POWER_GPIO_CONFIG_IS(DT_NODELABEL(test_reg_disabled), GPIO_DISCONNECTED);
#endif
}

ZTEST_SUITE(device_driver_init, NULL, NULL, NULL, NULL, NULL);
17 changes: 17 additions & 0 deletions tests/subsys/pm/device_driver_init/testcase.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
tests:
pm.device_driver_init:
tags: pm
platform_allow: qemu_cortex_m3
pm.device_driver_init.pm:
tags: pm
platform_allow: qemu_cortex_m3
extra_configs:
- CONFIG_PM_DEVICE=y
- CONFIG_PM_DEVICE_POWER_DOMAIN=y
pm.device_driver_init.pm_device_runtime:
tags: pm
platform_allow: qemu_cortex_m3
extra_configs:
- CONFIG_PM_DEVICE=y
- CONFIG_PM_DEVICE_POWER_DOMAIN=y
- CONFIG_PM_DEVICE_RUNTIME=y
12 changes: 6 additions & 6 deletions tests/subsys/pm/device_power_domains/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ ZTEST(device_power_domain, test_demo)
/* Initial power state */
zassert_true(pm_device_is_powered(reg_0), "");
zassert_true(pm_device_is_powered(reg_1), "");
zassert_false(pm_device_is_powered(reg_chained), "");
zassert_false(pm_device_is_powered(dev), "");
zassert_true(pm_device_is_powered(reg_chained), "");
zassert_true(pm_device_is_powered(dev), "");

TC_PRINT("Enabling runtime power management on regulators\n");

pm_device_runtime_enable(reg_0);
pm_device_runtime_enable(reg_1);
pm_device_runtime_enable(reg_chained);
pm_device_runtime_enable(dev);
pm_device_runtime_enable(reg_chained);
pm_device_runtime_enable(reg_1);
pm_device_runtime_enable(reg_0);

/* State shouldn't have changed */
/* Power domains should now be suspended */
zassert_true(pm_device_is_powered(reg_0), "");
zassert_true(pm_device_is_powered(reg_1), "");
zassert_false(pm_device_is_powered(reg_chained), "");
Expand Down