【zephyr】PM 電源管理

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);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章