power_mgmt: Add device power management support Added device power management hook infrastructure. Added DEVICE_INIT_PM and SYS_INIT_PM macros that creates device structures with the supplied device_ops structure containing the hooks. Added example support in gpio_dw driver. Updated the sample app and tested using LPS and Device Suspend Only policies. Change-Id: I2fe347f8d8fd1041d8318e02738990deb8c5d68e Signed-off-by: Ramesh Thomas <ramesh.thomas@intel.com> Signed-off-by: Anas Nashif <anas.nashif@intel.com>
diff --git a/drivers/gpio/gpio_atmel_sam3.c b/drivers/gpio/gpio_atmel_sam3.c index 2ba3bfa..b93748c 100644 --- a/drivers/gpio/gpio_atmel_sam3.c +++ b/drivers/gpio/gpio_atmel_sam3.c
@@ -284,20 +284,6 @@ return 0; } -static int gpio_sam3_suspend_port(struct device *dev) -{ - ARG_UNUSED(dev); - - return -ENOTSUP; -} - -static int gpio_sam3_resume_port(struct device *dev) -{ - ARG_UNUSED(dev); - - return -ENOTSUP; -} - static struct gpio_driver_api gpio_sam3_drv_api_funcs = { .config = gpio_sam3_config, .write = gpio_sam3_write, @@ -305,8 +291,6 @@ .set_callback = gpio_sam3_set_callback, .enable_callback = gpio_sam3_enable_callback, .disable_callback = gpio_sam3_disable_callback, - .suspend = gpio_sam3_suspend_port, - .resume = gpio_sam3_resume_port, }; /**
diff --git a/drivers/gpio/gpio_dw.c b/drivers/gpio/gpio_dw.c index e7f9056..29c2974 100644 --- a/drivers/gpio/gpio_dw.c +++ b/drivers/gpio/gpio_dw.c
@@ -34,6 +34,10 @@ #include <drivers/ioapic.h> #endif +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +#include <power.h> +#endif + /* * ARC architecture configure IP through IO auxiliary registers. * Other architectures as ARM and x86 configure IP through MMIO registers @@ -286,19 +290,21 @@ return 0; } -static inline int gpio_dw_suspend_port(struct device *port) +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +static inline int gpio_dw_suspend_port(struct device *port, int pm_policy) { _gpio_dw_clock_off(port); return 0; } -static inline int gpio_dw_resume_port(struct device *port) +static inline int gpio_dw_resume_port(struct device *port, int pm_policy) { _gpio_dw_clock_on(port); return 0; } +#endif #ifdef CONFIG_SOC_QUARK_SE static inline void gpio_dw_unmask_int(uint32_t mask_addr) @@ -362,8 +368,6 @@ .set_callback = gpio_dw_set_callback, .enable_callback = gpio_dw_enable_callback, .disable_callback = gpio_dw_disable_callback, - .suspend = gpio_dw_suspend_port, - .resume = gpio_dw_resume_port }; #ifdef CONFIG_PCI @@ -456,9 +460,20 @@ struct gpio_dw_runtime gpio_0_runtime; +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +struct device_pm_ops gpio_dev_pm_ops = { + .suspend = gpio_dw_suspend_port, + .resume = gpio_dw_resume_port +}; + +DEVICE_INIT_PM(gpio_dw_0, CONFIG_GPIO_DW_0_NAME, gpio_dw_initialize, + &gpio_dev_pm_ops, &gpio_0_runtime, &gpio_config_0, + SECONDARY, CONFIG_GPIO_DW_INIT_PRIORITY); +#else DEVICE_INIT(gpio_dw_0, CONFIG_GPIO_DW_0_NAME, gpio_dw_initialize, &gpio_0_runtime, &gpio_config_0, SECONDARY, CONFIG_GPIO_DW_INIT_PRIORITY); +#endif #ifdef CONFIG_GPIO_DW_0_IRQ_DIRECT #ifdef CONFIG_IOAPIC @@ -531,9 +546,15 @@ struct gpio_dw_runtime gpio_1_runtime; +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +DEVICE_INIT_PM(gpio_dw_1, CONFIG_GPIO_DW_1_NAME, gpio_dw_initialize, + &gpio_dev_pm_ops, &gpio_1_runtime, &gpio_dw_config_1, + SECONDARY, CONFIG_GPIO_DW_INIT_PRIORITY); +#else DEVICE_INIT(gpio_dw_1, CONFIG_GPIO_DW_1_NAME, gpio_dw_initialize, &gpio_1_runtime, &gpio_dw_config_1, SECONDARY, CONFIG_GPIO_DW_INIT_PRIORITY); +#endif #ifdef CONFIG_GPIO_DW_1_IRQ_DIRECT #ifdef CONFIG_IOAPIC
diff --git a/drivers/gpio/gpio_k64.c b/drivers/gpio/gpio_k64.c index 7c05066..ba7bfcc 100644 --- a/drivers/gpio/gpio_k64.c +++ b/drivers/gpio/gpio_k64.c
@@ -221,22 +221,6 @@ } -static int gpio_k64_suspend_port(struct device *dev) -{ - ARG_UNUSED(dev); - - return -ENOTSUP; -} - - -static int gpio_k64_resume_port(struct device *dev) -{ - ARG_UNUSED(dev); - - return -ENOTSUP; -} - - /** * @brief Handler for port interrupts * @param dev Pointer to device structure for driver instance @@ -290,8 +274,6 @@ .set_callback = gpio_k64_set_callback, .enable_callback = gpio_k64_enable_callback, .disable_callback = gpio_k64_disable_callback, - .suspend = gpio_k64_suspend_port, - .resume = gpio_k64_resume_port, };
diff --git a/drivers/gpio/gpio_mmio.c b/drivers/gpio/gpio_mmio.c index 8824ff59..b7a6436 100644 --- a/drivers/gpio/gpio_mmio.c +++ b/drivers/gpio/gpio_mmio.c
@@ -277,20 +277,6 @@ return -ENOTSUP; } -static int gpio_mmio_suspend_port(struct device *dev) -{ - ARG_UNUSED(dev); - - return -ENOTSUP; -} - -static int gpio_mmio_resume_port(struct device *dev) -{ - ARG_UNUSED(dev); - - return -ENOTSUP; -} - static struct gpio_driver_api gpio_mmio_drv_api_funcs = { .config = gpio_mmio_config, .write = gpio_mmio_write, @@ -298,8 +284,6 @@ .set_callback = gpio_mmio_set_callback, .enable_callback = gpio_mmio_enable_callback, .disable_callback = gpio_mmio_disable_callback, - .suspend = gpio_mmio_suspend_port, - .resume = gpio_mmio_resume_port, }; /**
diff --git a/drivers/gpio/gpio_pcal9535a.c b/drivers/gpio/gpio_pcal9535a.c index 25bb5b6..8b97be6 100644 --- a/drivers/gpio/gpio_pcal9535a.c +++ b/drivers/gpio/gpio_pcal9535a.c
@@ -544,24 +544,6 @@ return -ENOTSUP; } -static int gpio_pcal9535a_suspend_port(struct device *dev) -{ - if (!_has_i2c_master(dev)) { - return -EINVAL; - } - - return -ENOTSUP; -} - -static int gpio_pcal9535a_resume_port(struct device *dev) -{ - if (!_has_i2c_master(dev)) { - return -EINVAL; - } - - return -ENOTSUP; -} - static struct gpio_driver_api gpio_pcal9535a_drv_api_funcs = { .config = gpio_pcal9535a_config, .write = gpio_pcal9535a_write, @@ -569,8 +551,6 @@ .set_callback = gpio_pcal9535a_set_callback, .enable_callback = gpio_pcal9535a_enable_callback, .disable_callback = gpio_pcal9535a_disable_callback, - .suspend = gpio_pcal9535a_suspend_port, - .resume = gpio_pcal9535a_resume_port, }; /**
diff --git a/drivers/gpio/gpio_qmsi.c b/drivers/gpio/gpio_qmsi.c index 8467b0b..7d98833 100644 --- a/drivers/gpio/gpio_qmsi.c +++ b/drivers/gpio/gpio_qmsi.c
@@ -271,16 +271,6 @@ return 0; } -static inline int gpio_qmsi_suspend_port(struct device *port) -{ - return -ENODEV; -} - -static inline int gpio_qmsi_resume_port(struct device *port) -{ - return -ENODEV; -} - static struct gpio_driver_api api_funcs = { .config = gpio_qmsi_config, .write = gpio_qmsi_write, @@ -288,8 +278,6 @@ .set_callback = gpio_qmsi_set_callback, .enable_callback = gpio_qmsi_enable_callback, .disable_callback = gpio_qmsi_disable_callback, - .suspend = gpio_qmsi_suspend_port, - .resume = gpio_qmsi_resume_port }; int gpio_qmsi_init(struct device *port)
diff --git a/drivers/gpio/gpio_sch.c b/drivers/gpio/gpio_sch.c index 2ca2998..ad1dc91 100644 --- a/drivers/gpio/gpio_sch.c +++ b/drivers/gpio/gpio_sch.c
@@ -316,16 +316,6 @@ return 0; } -static int gpio_sch_suspend(struct device *dev) -{ - return 0; -} - -static int gpio_sch_resume(struct device *dev) -{ - return 0; -} - static struct gpio_driver_api gpio_sch_api = { .config = gpio_sch_config, .write = gpio_sch_write, @@ -333,8 +323,6 @@ .set_callback = gpio_sch_set_callback, .enable_callback = gpio_sch_enable_callback, .disable_callback = gpio_sch_disable_callback, - .suspend = gpio_sch_suspend, - .resume = gpio_sch_resume }; int gpio_sch_init(struct device *dev)
diff --git a/drivers/gpio/gpio_stm32.c b/drivers/gpio/gpio_stm32.c index 6c1fb39..07b6a6e 100644 --- a/drivers/gpio/gpio_stm32.c +++ b/drivers/gpio/gpio_stm32.c
@@ -178,20 +178,6 @@ return 0; } -static int gpio_stm32_suspend_port(struct device *dev) -{ - ARG_UNUSED(dev); - - return -ENOTSUP; -} - -static int gpio_stm32_resume_port(struct device *dev) -{ - ARG_UNUSED(dev); - - return -ENOTSUP; -} - static struct gpio_driver_api gpio_stm32_driver = { .config = gpio_stm32_config, .write = gpio_stm32_write, @@ -199,8 +185,6 @@ .set_callback = gpio_stm32_set_callback, .enable_callback = gpio_stm32_enable_callback, .disable_callback = gpio_stm32_disable_callback, - .suspend = gpio_stm32_suspend_port, - .resume = gpio_stm32_resume_port, };
diff --git a/include/device.h b/include/device.h index f9b7cb2..a784c78 100644 --- a/include/device.h +++ b/include/device.h
@@ -87,6 +87,7 @@ * (e.g. CONFIG_KERNEL_INIT_PRIORITY_DEFAULT + 5). */ +#ifndef CONFIG_DEVICE_POWER_MANAGEMENT #define DEVICE_INIT(dev_name, drv_name, init_fn, data, cfg_info, level, prio) \ \ static struct device_config __config_##dev_name __used \ @@ -100,6 +101,88 @@ .config = &(__config_##dev_name), \ .driver_data = data \ } +#else +/** + * @def DEVICE_INIT_PM + * + * @brief create device object and set it up for boot time initialization + * + * @details This macro defines a device object that is automatically + * configured by the kernel during system initialization. + * + * @param dev_name Device name. + * + * @param drv_name The name this instance of the driver exposes to + * the system. + * + * @param init_fn Address to the init function of the driver. + * + * @param device_pm_ops Address to the device_pm_ops structure of the driver. + * + * @param data Pointer to the device's configuration data. + * + * @param cfg_info The address to the structure containing the + * configuration information for this instance of the driver. + * + * @param level The initialization level at which configuration occurs. + * Must be one of the following symbols, which are listed in the order + * they are performed by the kernel: + * + * PRIMARY: Used for devices that have no dependencies, such as those + * that rely solely on hardware present in the processor/SOC. These devices + * cannot use any kernel services during configuration, since they are not + * yet available. + * + * SECONDARY: Used for devices that rely on the initialization of devices + * initialized as part of the PRIMARY level. These devices cannot use any + * kernel services during configuration, since they are not yet available. + * + * NANOKERNEL: Used for devices that require nanokernel services during + * configuration. + * + * MICROKERNEL: Used for devices that require microkernel services during + * configuration. + * + * APPLICATION: Used for application components (i.e. non-kernel components) + * that need automatic configuration. These devices can use all services + * provided by the kernel during configuration. + * + * @param prio The initialization priority of the device, relative to + * other devices of the same initialization level. Specified as an integer + * value in the range 0 to 99; lower values indicate earlier initialization. + * Must be a decimal integer literal without leading zeroes or sign (e.g. 32), + * or an equivalent symbolic name (e.g. \#define MY_INIT_PRIO 32); symbolic + * expressions are *not* permitted + * (e.g. CONFIG_KERNEL_INIT_PRIORITY_DEFAULT + 5). + */ + +#define DEVICE_INIT_PM(dev_name, drv_name, init_fn, device_pm_ops, \ + data, cfg_info, level, prio) \ + \ + static struct device_config __config_##dev_name __used \ + __attribute__((__section__(".devconfig.init"))) = { \ + .name = drv_name, .init = (init_fn), \ + .dev_pm_ops = (device_pm_ops), \ + .config_info = (cfg_info) \ + }; \ + \ + static struct device (__device_##dev_name) __used \ + __attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \ + .config = &(__config_##dev_name), \ + .driver_data = data \ + } + + /* + * Create a default device_pm_ops for devices that do not call the + * DEVICE_INIT_PM macro so that caller of hook functions + * need not check dev_pm_ops != NULL. + */ +extern struct device_pm_ops device_pm_ops_nop; +#define DEVICE_INIT(dev_name, drv_name, init_fn, data, cfg_info, level, prio) \ + DEVICE_INIT_PM(dev_name, drv_name, init_fn, \ + &device_pm_ops_nop, data, cfg_info, \ + level, prio) +#endif /** * @def DEVICE_NAME_GET @@ -165,6 +248,13 @@ struct device; +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +struct device_pm_ops { + int (*suspend)(struct device *device, int pm_policy); + int (*resume)(struct device *device, int pm_policy); +}; +#endif + /** * @brief Static device information (In ROM) Per driver instance * @param name name of the device @@ -174,6 +264,9 @@ struct device_config { char *name; int (*init)(struct device *device); +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT + struct device_pm_ops *dev_pm_ops; +#endif void *config_info; }; @@ -193,6 +286,74 @@ void _sys_device_do_config_level(int level); struct device* device_get_binding(char *name); +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +/** + * Device PM functions + */ + +/** + * @brief No-op function to initialize unimplemented pm hooks + * + * This function should be used to initialize device pm hooks + * for which a device has no operation. + * + * @param unused_device + * @param unused_policy + * + * @retval Always returns 0 + */ +int device_pm_nop(struct device *unused_device, int unused_policy); + +/** + * @brief Call the suspend function of a device + * + * Called by the Power Manager application to let the device do + * any policy based PM suspend operations. + * + * @param device Pointer to device structure of the driver instance. + * @param pm_policy PM policy for which this call is made. + * + * @retval 0 If successful. + * @retval -EBUSY If device is busy + * @retval Other negative errno code if failure. + */ +static inline int device_suspend(struct device *device, int pm_policy) +{ + return device->config->dev_pm_ops->suspend(device, pm_policy); +} + +/** + * @brief Call the resume function of a device + * + * Called by the Power Manager application to let the device do + * any policy based PM resume operations. + * + * @param device Pointer to device structure of the driver instance. + * @param pm_policy PM policy for which this call is made. + * + * @retval 0 If successful. + * @retval Negative errno code if failure. + */ +static inline int device_resume(struct device *device, int pm_policy) +{ + return device->config->dev_pm_ops->resume(device, pm_policy); +} + +/** + * @brief Gets the device structure list array and device count + * + * Called by the Power Manager application to get the list of + * device structures associated with the devices in the system. + * The PM app would use this list to create its own sorted list + * based on the order it wishes to suspend or resume the devices. + * + * @param device_list Pointer to receive the device list array + * @param device_count Pointer to receive the device count + */ +void device_list_get(struct device **device_list, int *device_count); + +#endif + /** * Synchronous calls API */
diff --git a/include/gpio.h b/include/gpio.h index b79c393..b4407fb 100644 --- a/include/gpio.h +++ b/include/gpio.h
@@ -150,8 +150,6 @@ typedef int (*gpio_disable_callback_t)(struct device *port, int access_op, uint32_t pin); -typedef int (*gpio_suspend_port_t)(struct device *port); -typedef int (*gpio_resume_port_t)(struct device *port); struct gpio_driver_api { gpio_config_t config; @@ -160,8 +158,6 @@ gpio_set_callback_t set_callback; gpio_enable_callback_t enable_callback; gpio_disable_callback_t disable_callback; - gpio_suspend_port_t suspend; - gpio_resume_port_t resume; }; /** * @endcond @@ -325,31 +321,6 @@ return api->disable_callback(port, GPIO_ACCESS_BY_PORT, 0); } -/** - * @brief Save the state of the device and make it go to the - * low power state. - * @param port Pointer to the device structure for the driver instance. - */ -static inline int gpio_suspend(struct device *port) -{ - struct gpio_driver_api *api; - - api = (struct gpio_driver_api *) port->driver_api; - return api->suspend(port); -} - -/** - * @brief Restore the state stored during suspend and resume operation. - * @param port Pointer to the device structure for the driver instance. - */ -static inline int gpio_resume(struct device *port) -{ - struct gpio_driver_api *api; - - api = (struct gpio_driver_api *) port->driver_api; - return api->resume(port); -} - #ifdef __cplusplus } #endif
diff --git a/include/init.h b/include/init.h index 7adec2f..56d99cb 100644 --- a/include/init.h +++ b/include/init.h
@@ -41,6 +41,12 @@ #define SYS_INIT(init_fn, level, prio) \ DEVICE_INIT(sys_init_##init_fn, "", init_fn, NULL, NULL, level, prio) +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +#define SYS_INIT_PM(drv_name, init_fn, device_pm_ops, level, prio) \ + DEVICE_INIT_PM(sys_init_##init_fn, drv_name, init_fn, device_pm_ops, \ + NULL, NULL, level, prio) +#endif + #ifdef __cplusplus } #endif
diff --git a/kernel/nanokernel/device.c b/kernel/nanokernel/device.c index 0d6856f..e2a597e 100644 --- a/kernel/nanokernel/device.c +++ b/kernel/nanokernel/device.c
@@ -35,6 +35,10 @@ __device_init_end, }; +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +struct device_pm_ops device_pm_ops_nop = {device_pm_nop, device_pm_nop}; +#endif + /** * @brief Execute all the device initialization functions at a given level * @@ -81,3 +85,17 @@ return NULL; } + +#ifdef CONFIG_DEVICE_POWER_MANAGEMENT +int device_pm_nop(struct device *unused_device, int unused_policy) +{ + return 0; +} + +void device_list_get(struct device **device_list, int *device_count) +{ + + *device_list = __device_init_start; + *device_count = __device_init_end - __device_init_start; +} +#endif
diff --git a/samples/power/power_hooks/prj.conf b/samples/power/power_hooks/prj.conf index 129bd7b..d220f4e 100644 --- a/samples/power/power_hooks/prj.conf +++ b/samples/power/power_hooks/prj.conf
@@ -1,4 +1,4 @@ CONFIG_STDOUT_CONSOLE=y CONFIG_SYS_POWER_MANAGEMENT=y -CONFIG_SYS_POWER_LOW_POWER_STATE=y +CONFIG_DEVICE_POWER_MANAGEMENT=y CONFIG_TICKLESS_IDLE=y
diff --git a/samples/power/power_mgr/prj.conf b/samples/power/power_mgr/prj.conf index 89ab8d4..1bdf850 100644 --- a/samples/power/power_mgr/prj.conf +++ b/samples/power/power_mgr/prj.conf
@@ -1,5 +1,7 @@ CONFIG_SYS_POWER_MANAGEMENT=y CONFIG_SYS_POWER_LOW_POWER_STATE=y +CONFIG_DEVICE_POWER_MANAGEMENT=y CONFIG_TICKLESS_IDLE=y CONFIG_RTC=y +CONFIG_GPIO=y CONFIG_STDOUT_CONSOLE=y
diff --git a/samples/power/power_mgr/src/main.c b/samples/power/power_mgr/src/main.c index 0a8b9fd..f2b2e6c 100644 --- a/samples/power/power_mgr/src/main.c +++ b/samples/power/power_mgr/src/main.c
@@ -24,6 +24,7 @@ #include <misc/printk.h> #define PRINT printk #endif +#include <string.h> #include <rtc.h> #define SLEEPTICKS SECONDS(5) @@ -33,6 +34,15 @@ static void quark_low_power(void); static struct device *rtc_dev; static uint32_t start_time, end_time; +static void create_device_list(void); +static void suspend_devices(int pm_policy); +static void resume_devices(int pm_policy); + +#define DEVICE_POLICY_MAX 10 +static struct device *device_list; +static int device_count; +static char device_policy_list[DEVICE_POLICY_MAX]; +static char device_retval[DEVICE_POLICY_MAX]; void main(void) { @@ -49,6 +59,8 @@ rtc_enable(rtc_dev); rtc_set_config(rtc_dev, &config); + create_device_list(); + while (1) { task_sleep(SLEEPTICKS); } @@ -79,6 +91,7 @@ PRINT("\n\nLow power state policy entry!\n"); /* Turn off peripherals/clocks here */ + suspend_devices(SYS_PM_LOW_POWER_STATE); quark_low_power(); @@ -90,6 +103,7 @@ PRINT("Device suspend only policy entry!\n"); /* Turn off peripherals/clocks here */ + suspend_devices(SYS_PM_DEVICE_SUSPEND_ONLY); return SYS_PM_DEVICE_SUSPEND_ONLY; } @@ -120,6 +134,8 @@ static void low_power_state_exit(void) { + resume_devices(SYS_PM_LOW_POWER_STATE); + end_time = rtc_read(rtc_dev); PRINT("\nLow power state policy exit!\n"); PRINT("Total Elapsed From Suspend To Resume = %d RTC Cycles\n", @@ -128,6 +144,8 @@ static void device_suspend_only_exit(void) { + resume_devices(SYS_PM_DEVICE_SUSPEND_ONLY); + end_time = rtc_read(rtc_dev); PRINT("\nDevice suspend only policy exit!\n"); PRINT("Total Elapsed From Suspend To Resume = %d RTC Cycles\n", @@ -164,3 +182,48 @@ ::"a"(P_LVL2)); } + +static void suspend_devices(int pm_policy) +{ + int i; + + for (i = 0; i < device_count; i++) { + int idx = device_policy_list[i]; + + device_retval[i] = device_suspend(&device_list[idx], pm_policy); + } +} + +static void resume_devices(int pm_policy) +{ + int i; + + for (i = device_count - 1; i >= 0; i--) { + if (!device_retval[i]) { + int idx = device_policy_list[i]; + + device_resume(&device_list[idx], pm_policy); + } + } +} + +static void create_device_list(void) +{ + int count; + int i; + + /* + * Create a list of devices that will be suspended. + * For the demo we will create a simple list containing + * gpio devices. + */ + device_list_get(&device_list, &count); + + for (i = 0; (i < count) && (device_count < DEVICE_POLICY_MAX); i++) { + if (!strcmp(device_list[i].config->name, CONFIG_GPIO_DW_0_NAME) || + !strcmp(device_list[i].config->name, + CONFIG_GPIO_DW_1_NAME)) { + device_policy_list[device_count++] = i; + } + } +}