1. CONFIG配置總開關
CONFIG_SYS_POWER_MANAGEMENT=y
CONFIG_DEVICE_POWER_MANAGEMENT=y
CONFIG_DEVICE_IDLE_PM=y
2. 設備註冊API接口
設備
/**
* @brief Device PM info
*
* @param dev pointer to device structure
* @param lock lock to synchronize the get/put operations
* @param enable device pm enable flag
* @param usage device usage count
* @param fsm_state device idle internal power state
* @param event event object to listen to the sync request events
* @param signal signal to notify the Async API callers
*/
struct device_pm {
struct device *dev;
struct k_sem lock;
bool enable;
atomic_t usage;
atomic_t fsm_state;
struct k_work work;
struct k_poll_event event;
struct k_poll_signal signal;
};
/**
* @brief Static device information (In ROM) Per driver instance
*
* @param name name of the device
* @param init init function for the driver
* @param config_info address of driver instance config information
*/
struct device_config {
const char *name;
int (*init)(struct device *device);
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
int (*device_pm_control)(struct device *device, u32_t command,
void *context, device_pm_cb cb, void *arg);
struct device_pm *pm;
#endif
const void *config_info;
};
/**
* @brief Runtime device structure (In memory) Per driver instance
* @param device_config Build time config information
* @param driver_api pointer to structure containing the API functions for
* the device type. This pointer is filled in by the driver at init time.
* @param driver_data driver instance data. For driver use only
*/
struct device {
struct device_config *config;
const void *driver_api;
void *driver_data;
#if defined(__x86_64) && __SIZEOF_POINTER__ == 4
/* The x32 ABI hits an edge case. This is a 12 byte struct,
* but the x86_64 linker will pack them only in units of 8
* bytes, leading to alignment problems when iterating over
* the link-time array.
*/
void *padding;
#endif
};
API 接口定義
#define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn, \
data, cfg_info, level, prio, api) \
static struct device_pm _CONCAT(__pm_, dev_name) __used \
= { \
.usage = ATOMIC_INIT(0), \
.lock = _K_SEM_INITIALIZER( \
_CONCAT(__pm_, dev_name).lock, 1, 1), \
.signal = K_POLL_SIGNAL_INITIALIZER( \
_CONCAT(__pm_, dev_name).signal), \
.event = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL, \
K_POLL_MODE_NOTIFY_ONLY, \
&_CONCAT(__pm_, dev_name).signal), \
}; \
static struct device_config _CONCAT(__config_, dev_name) __used \
__attribute__((__section__(".devconfig.init"))) = { \
.name = drv_name, .init = (init_fn), \
.device_pm_control = (pm_control_fn), \
.pm = &_CONCAT(__pm_, dev_name), \
.config_info = (cfg_info) \
}; \
static struct device _CONCAT(__device_, dev_name) __used \
__attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \
.config = &_CONCAT(__config_, dev_name), \
.driver_api = api, \
.driver_data = data, \
}
2.2 驅動實現
#ifdef CONFIG_I2C_0
static struct i2c_qmsi_driver_data driver_data_0;
static const struct i2c_qmsi_config_info config_info_0 = {
.instance = QM_I2C_0,
.bitrate = DT_I2C_0_BITRATE,
.clock_gate = CLK_PERIPH_I2C_M0_REGISTER | CLK_PERIPH_CLK,
};
DEVICE_DEFINE(i2c_0, CONFIG_I2C_0_NAME, &i2c_qmsi_init,
i2c_device_ctrl, &driver_data_0, &config_info_0, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, NULL);
#endif /* CONFIG_I2C_0 */
//i2c_device_ctrl Power control
2.2 調用suspend/resume接口
/*
* Implements the driver control management functionality
* the *context may include IN data or/and OUT data
*/
static int i2c_device_ctrl(struct device *dev, u32_t ctrl_command,
void *context, device_pm_cb cb, void *arg)
{
int ret = 0;
if (ctrl_command == DEVICE_PM_SET_POWER_STATE) {
if (*((u32_t *)context) == DEVICE_PM_SUSPEND_STATE) {
ret = i2c_suspend_device(dev);
} else if (*((u32_t *)context) == DEVICE_PM_ACTIVE_STATE) {
ret = i2c_resume_device_from_suspend(dev);
}
} else if (ctrl_command == DEVICE_PM_GET_POWER_STATE) {
*((u32_t *)context) = i2c_qmsi_get_power_state(dev);
}
if (cb) {
cb(dev, ret, context, arg);
}
return ret;
}
具體實現內容 i2c_suspend_device 和 i2c_resume_device_from_suspend
static int i2c_suspend_device(struct device *dev)
{
if (device_busy_check(dev)) {
return -EBUSY;
}
struct i2c_qmsi_driver_data *drv_data = GET_DRIVER_DATA(dev);
qm_i2c_save_context(GET_CONTROLLER_INSTANCE(dev), &drv_data->i2c_ctx);
i2c_qmsi_set_power_state(dev, DEVICE_PM_SUSPEND_STATE);
return 0;
}
static int i2c_resume_device_from_suspend(struct device *dev)
{
struct i2c_qmsi_driver_data *drv_data = GET_DRIVER_DATA(dev);
qm_i2c_restore_context(GET_CONTROLLER_INSTANCE(dev),
&drv_data->i2c_ctx);
i2c_qmsi_set_power_state(dev, DEVICE_PM_ACTIVE_STATE);
return 0;
}
3. kernel 內核部門
3.1 ./subsys/power/power.c
3.2 ./kernel/device.c
./kernel/device.c:22:#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
./kernel/device.c:111:#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
./kernel/device.c:154:#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
./kernel/device.c:164:#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
3.3 ./include/linker/linker-defs.h
/*
* Space for storing per device busy bitmap. Since we do not know beforehand
* the number of devices, we go through the below mechanism to allocate the
* required space.
*/
#ifdef CONFIG_DEVICE_POWER_MANAGEMENT
#define DEVICE_COUNT \
((__device_init_end - __device_init_start) / _DEVICE_STRUCT_SIZEOF)
#define DEV_BUSY_SZ (((DEVICE_COUNT + 31) / 32) * 4)
#define DEVICE_BUSY_BITFIELD() \
FILL(0x00) ; \
__device_busy_start = .; \
. = . + DEV_BUSY_SZ; \
__device_busy_end = .;
#else
#define DEVICE_BUSY_BITFIELD()
#endif
3.4 \subsys\power\device.c
void sys_pm_resume_devices(void)//喚醒
{
int i;
for (i = 0; i < device_count; i++) {
if (!device_retval[i]) {
int idx = device_ordered_list[i];
device_set_power_state(&pm_device_list[idx],
DEVICE_PM_ACTIVE_STATE, NULL, NULL);
}
}
}
//\subsys\power\power.c
enum power_states _sys_suspend(s32_t ticks)
{
bool deep_sleep;
pm_state = (forced_pm_state == SYS_POWER_STATE_AUTO) ?
sys_pm_policy_next_state(ticks) : forced_pm_state;
if (pm_state == SYS_POWER_STATE_ACTIVE) {
LOG_DBG("No PM operations done.");
return pm_state;
}
deep_sleep = IS_ENABLED(CONFIG_SYS_POWER_DEEP_SLEEP_STATES) ?
sys_pm_is_deep_sleep_state(pm_state) : 0;
post_ops_done = 0;
sys_pm_notify_power_state_entry(pm_state);
if (deep_sleep) {
#if CONFIG_DEVICE_POWER_MANAGEMENT
/* Suspend peripherals. */
if (sys_pm_suspend_devices()) {
LOG_ERR("System level device suspend failed!");
sys_pm_notify_power_state_exit(pm_state);
pm_state = SYS_POWER_STATE_ACTIVE;
return pm_state;
}
#endif
/*
* Disable idle exit notification as it is not needed
* in deep sleep mode.
*/
_sys_pm_idle_exit_notification_disable();
}
/* Enter power state */
sys_pm_debug_start_timer();
sys_set_power_state(pm_state);
sys_pm_debug_stop_timer();
#if CONFIG_DEVICE_POWER_MANAGEMENT
if (deep_sleep) {
/* Turn on peripherals and restore device states as necessary */
sys_pm_resume_devices();
}
#endif
sys_pm_log_debug_info(pm_state);
if (!post_ops_done) {
post_ops_done = 1;
sys_pm_notify_power_state_exit(pm_state);
_sys_pm_power_state_exit_post_ops(pm_state);
}
return pm_state;
}
E:\work\code\zephyr_20190406\kernel\idle.c
static void sys_power_save_idle(void)
{
s32_t ticks = z_get_next_timeout_expiry();
set_kernel_idle_time_in_ticks(ticks);
#if (defined(CONFIG_SYS_POWER_SLEEP_STATES) || \
defined(CONFIG_SYS_POWER_DEEP_SLEEP_STATES))
sys_pm_idle_exit_notify = 1U;
/*
* Call the suspend hook function of the soc interface to allow
* entry into a low power state. The function returns
* SYS_POWER_STATE_ACTIVE if low power state was not entered, in which
* case, kernel does normal idle processing.
*
* This function is entered with interrupts disabled. If a low power
* state was entered, then the hook function should enable inerrupts
* before exiting. This is because the kernel does not do its own idle
* processing in those cases i.e. skips k_cpu_idle(). The kernel's
* idle processing re-enables interrupts which is essential for
* the kernel's scheduling logic.
*/
if (_sys_suspend(ticks) == SYS_POWER_STATE_ACTIVE) {
sys_pm_idle_exit_notify = 0U;
k_cpu_idle();
}
#else
k_cpu_idle();
#endif
}
#endif
void idle(void *unused1, void *unused2, void *unused3)
{
ARG_UNUSED(unused1);
ARG_UNUSED(unused2);
ARG_UNUSED(unused3);
#ifdef CONFIG_BOOT_TIME_MEASUREMENT
/* record timestamp when idling begins */
extern u64_t __idle_time_stamp;
__idle_time_stamp = (u64_t)k_cycle_get_32();
#endif
#ifdef CONFIG_SMP
/* Simplified idle for SMP CPUs pending driver support. The
* busy waiting is needed to prevent lock contention. Long
* term we need to wake up idle CPUs with an IPI.
*/
while (true) {
k_busy_wait(100);
k_yield();
}
#else
for (;;) {
(void)irq_lock();
sys_power_save_idle();
IDLE_YIELD_IF_COOP();
}
#endif
}
3.5 ./subsys/power/power.c:
3.6 suspends
./arch/arm/core/irq_manage.c:166: z_sys_power_save_idle_exit(idle_val);
./arch/arm/core/isr_wrapper.S:60: * interrupted. In each case, z_sys_power_save_idle_exit is called with
./arch/arm/core/isr_wrapper.S:76: bl z_sys_power_save_idle_exit
./arch/arm/core/isr_wrapper.S:84: blne z_sys_power_save_idle_exit
// \kernel\idle.c
void z_sys_power_save_idle_exit(s32_t ticks)
{
#if defined(CONFIG_SYS_POWER_SLEEP_STATES)
/* Some CPU low power states require notification at the ISR
* to allow any operations that needs to be done before kernel
* switches task or processes nested interrupts. This can be
* disabled by calling _sys_pm_idle_exit_notification_disable().
* Alternatively it can be simply ignored if not required.
*/
if (sys_pm_idle_exit_notify) {
_sys_resume();
}
#endif
z_clock_idle_exit();
}
3.8 CONFIG_SYS_POWER_MANAGEMENT
./arch/arm/core/cpu_idle.S:24:#ifdef CONFIG_SYS_POWER_MANAGEMENT
./arch/arm/core/cpu_idle.S:58:#ifdef CONFIG_SYS_POWER_MANAGEMENT
./arch/arm/core/cpu_idle.S:97:#endif /* CONFIG_SYS_POWER_MANAGEMENT */
./arch/arm/core/irq_manage.c:144:#ifdef CONFIG_SYS_POWER_MANAGEMENT
./kernel/idle.c:22:#ifdef CONFIG_SYS_POWER_MANAGEMENT
./kernel/idle.c:41:#endif /* CONFIG_SYS_POWER_MANAGEMENT */
./kernel/idle.c:57:#ifdef CONFIG_SYS_POWER_MANAGEMENT
/soc/arm/nordic_nrf/nrf51/CMakeLists.txt:5:zephyr_sources_ifdef(CONFIG_SYS_POWER_MANAGEMENT
./soc/arm/nordic_nrf/nrf52/CMakeLists.txt:5:zephyr_sources_ifdef(CONFIG_SYS_POWER_MANAGEMENT
3.9 subsys\power\device_pm.c
$ ls -l ./subsys/power/
total 34
-rw-r--r-- 1 xiao 197121 307 4月 6 11:11 CMakeLists.txt
-rw-r--r-- 1 xiao 197121 3186 4月 6 11:11 device.c
-rw-r--r-- 1 xiao 197121 4411 4月 6 11:11 device_pm.c
-rw-r--r-- 1 xiao 197121 883 4月 6 11:11 Kconfig
-rw-r--r-- 1 xiao 197121 1239 4月 6 11:11 pm_ctrl.c
drwxr-xr-x 1 xiao 197121 0 4月 6 11:11 policy/
-rw-r--r-- 1 xiao 197121 4390 4月 6 11:11 power.c
-rw-r--r-- 1 xiao 197121 608 4月 6 11:11 reboot.c
./doc/reference/power_management/index.rst:512::code:`CONFIG_DEVICE_IDLE_PM`
./include/device.h:498:#ifdef CONFIG_DEVICE_IDLE_PM
./samples/subsys/power/device_pm/prj.conf:3:CONFIG_DEVICE_IDLE_PM=y
./subsys/power/CMakeLists.txt:4:zephyr_sources_ifdef(CONFIG_DEVICE_IDLE_PM device_pm.c)
void device_pm_enable(struct device *dev)
{
k_sem_take(&dev->config->pm->lock, K_FOREVER);
dev->config->pm->enable = true;
/* During the driver init, device can set the
* PM state accordingly. For later cases we need
* to check the usage and set the device PM state.
*/
if (!dev->config->pm->dev) {
dev->config->pm->dev = dev;
atomic_set(&dev->config->pm->fsm_state,
DEVICE_PM_FSM_STATE_SUSPENDED);
k_work_init(&dev->config->pm->work, pm_work_handler);
} else {
k_work_submit(&dev->config->pm->work);
}
k_sem_give(&dev->config->pm->lock);
}
void device_pm_disable(struct device *dev)
{
k_sem_take(&dev->config->pm->lock, K_FOREVER);
dev->config->pm->enable = false;
/* Bring up the device before disabling the Idle PM */
k_work_submit(&dev->config->pm->work);
k_sem_give(&dev->config->pm->lock);
}