From 92f6fc688822d472513a053e119fd9ef48e810c3 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Wed, 11 Jan 2017 21:48:16 +0100 Subject: [PATCH] drivers: add layered power modes module --- sys/include/pm_layered.h | 75 +++++++++++++++++++++++++++++++ sys/pm_layered/Makefile | 1 + sys/pm_layered/pm.c | 95 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 sys/include/pm_layered.h create mode 100644 sys/pm_layered/Makefile create mode 100644 sys/pm_layered/pm.c diff --git a/sys/include/pm_layered.h b/sys/include/pm_layered.h new file mode 100644 index 0000000000..85db1ea020 --- /dev/null +++ b/sys/include/pm_layered.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_periph_pm + * @{ + * + * This module provides a base infrastructure that MCU's may use to implement + * periph/pm. + * + * This simple power management interface is based on the following assumptions: + * + * - CPUs define up to 4 power modes (from zero, the lowest power mode, to + * PM_NUM_MODES-1, the highest) + * - there is an implicit extra idle mode (which has the number PM_NUM_MODES) + * - individual power modes can be blocked/unblocked, e.g., by peripherals + * - if a mode is blocked, so are implicitly all lower modes + * - the idle thread automatically selects and sets the lowest unblocked mode + * + * In order to use this module, you'll need to implement pm_set(). + * + * @file + * @brief Layered low power mode infrastructure + * + * @author Kaspar Schleiser + */ + +#ifndef PM_LAYERED_H_ +#define PM_LAYERED_H_ + +#include "assert.h" +#include "periph/pm.h" +#include "periph_cpu.h" + +#ifdef __cplusplus + extern "C" { +#endif + +/** + * @brief Block a power mode + * + * @param[in] mode power mode to block + */ +void pm_block(unsigned mode); + +/** + * @brief Unblock a power mode + * + * @param[in] mode power mode to unblock + */ +void pm_unblock(unsigned mode); + +/** + * @brief Switches the MCU to a new power mode + * + * This function will be called by @ref pm_set_lowest() after determining the + * lowest non-blocked mode. + * + * It needs to be implemented for each MCU using this module. + * + * @param[in] mode Target power mode + */ +void pm_set(unsigned mode); + +#ifdef __cplusplus +} +#endif + +#endif /* __PM_LAYERED_H_ */ +/** @} */ diff --git a/sys/pm_layered/Makefile b/sys/pm_layered/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/pm_layered/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/pm_layered/pm.c b/sys/pm_layered/pm.c new file mode 100644 index 0000000000..c187e5a4b1 --- /dev/null +++ b/sys/pm_layered/pm.c @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup drivers_periph_pm + * @{ + * + * @file + * @brief Platform-independent power management code + * + * @author Kaspar Schleiser + * + * @} + */ + +#include "irq.h" +#include "periph/pm.h" +#include "pm_layered.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#ifndef PM_NUM_MODES +#error PM_NUM_MODES must be defined in periph_cpu.h! +#endif + +#ifndef PM_BLOCKER_INITIAL +#define PM_BLOCKER_INITIAL { .val_u32=0x01010101 } +#endif + +/** + * @brief Power Management mode typedef + */ +typedef union { + uint32_t val_u32; + uint8_t val_u8[PM_NUM_MODES]; +} pm_blocker_t; + +/** + * @brief Global variable for keeping track of blocked modes + */ +volatile pm_blocker_t pm_blocker = PM_BLOCKER_INITIAL; + +void pm_set_lowest(void) +{ + pm_blocker_t blocker = (pm_blocker_t) pm_blocker; + unsigned mode = PM_NUM_MODES; + while (mode) { + if (blocker.val_u8[mode-1]) { + break; + } + mode--; + } + + /* set lowest mode if blocker is still the same */ + unsigned state = irq_disable(); + if (blocker.val_u32 == pm_blocker.val_u32) { + DEBUG("pm: setting mode %u\n", mode); + pm_set(mode); + } + else { + DEBUG("pm: mode block changed\n"); + } + irq_restore(state); +} + +void pm_block(unsigned mode) +{ + assert(pm_blocker.val_u8[mode] != 255); + + unsigned state = irq_disable(); + pm_blocker.val_u8[mode]++; + irq_restore(state); +} + +void pm_unblock(unsigned mode) +{ + assert(pm_blocker.val_u8[mode] > 0); + + unsigned state = irq_disable(); + pm_blocker.val_u8[mode]--; + irq_restore(state); +} + +void __attribute__((weak)) pm_off(void) +{ + pm_blocker.val_u32 = 0; + pm_set_lowest(); + while(1); +}