diff --git a/boards/atxmega-a1u-xpro/include/periph_conf.h b/boards/atxmega-a1u-xpro/include/periph_conf.h index 5f045567995c..e52ded321fa1 100644 --- a/boards/atxmega-a1u-xpro/include/periph_conf.h +++ b/boards/atxmega-a1u-xpro/include/periph_conf.h @@ -36,6 +36,7 @@ extern "C" { static const timer_conf_t timer_config[] = { { .dev = (void *)&TCC1, + .pwr = PWR_RED_REG(PWR_PORT_C, PR_TC1_bm), .type = TC_TYPE_1, .int_lvl = { CPU_INT_LVL_LOW, CPU_INT_LVL_OFF, @@ -44,6 +45,7 @@ static const timer_conf_t timer_config[] = { }, { .dev = (void *)&TCC0, + .pwr = PWR_RED_REG(PWR_PORT_C, PR_TC0_bm), .type = TC_TYPE_0, .int_lvl = { CPU_INT_LVL_LOW, CPU_INT_LVL_LOW, @@ -69,6 +71,7 @@ static const timer_conf_t timer_config[] = { static const uart_conf_t uart_config[] = { { /* CDC-ACM */ .dev = &USARTE0, + .pwr = PWR_RED_REG(PWR_PORT_E, PR_USART0_bm), .rx_pin = GPIO_PIN(PORT_E, 2), .tx_pin = GPIO_PIN(PORT_E, 3), #ifdef MODULE_PERIPH_UART_HW_FC diff --git a/cpu/atxmega/atxmega_cpu.c b/cpu/atxmega/atxmega_cpu.c index e144ef2af19e..c6eba0c71d71 100644 --- a/cpu/atxmega/atxmega_cpu.c +++ b/cpu/atxmega/atxmega_cpu.c @@ -22,6 +22,7 @@ #include "cpu.h" #include "cpu_clock.h" +#include "cpu_pm.h" #include "panic.h" #define ENABLE_DEBUG 0 @@ -63,18 +64,7 @@ void avr8_reset_cause(void) void __attribute__((weak)) avr8_clk_init(void) { - volatile uint8_t *reg = (uint8_t *)&PR.PRGEN; - uint8_t i; - - /* Turn off all peripheral clocks that can be turned off. */ - for (i = 0; i <= 7; i++) { - reg[i] = 0xff; - } - - /* Turn on all peripheral clocks that can be turned on. */ - for (i = 0; i <= 7; i++) { - reg[i] = 0x00; - } + pm_periph_power_off(); /* XMEGA A3U [DATASHEET] p.23 After reset, the device starts up running * from the 2MHz internal oscillator. The other clock sources, DFLLs diff --git a/cpu/atxmega/include/cpu_pm.h b/cpu/atxmega/include/cpu_pm.h new file mode 100644 index 000000000000..f89cdd49eada --- /dev/null +++ b/cpu/atxmega/include/cpu_pm.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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 cpu_atxmega + * @{ + * + * @file + * @brief Power Management and Power Reduction API + * + * This help to save power disabling all non used peripherals. It can help to + * save power when in active or sleep modes. For any other low power modes + * xmega will freeze all peripherals clock. + * + * @author Gerson Fernando Budke + */ + +#include "periph_cpu.h" + +#ifndef CPU_PM_H +#define CPU_PM_H + +#ifdef __cplusplus +extern "C" { +#endif + +void pm_periph_enable(pwr_reduction_t pwr); +void pm_periph_disable(pwr_reduction_t pwr); +void pm_periph_power_off(void); + +#ifdef __cplusplus +} +#endif + +#endif /* CPU_PM_H */ +/** @} */ diff --git a/cpu/atxmega/include/periph_cpu.h b/cpu/atxmega/include/periph_cpu.h index 2597917d1887..f41ad112c280 100644 --- a/cpu/atxmega/include/periph_cpu.h +++ b/cpu/atxmega/include/periph_cpu.h @@ -68,6 +68,29 @@ enum { PORT_MAX, }; +/** + * @brief Power Reduction Peripheral Mask + */ +typedef uint16_t pwr_reduction_t; + +/** + * @brief Define a CPU specific Power Reduction index macro + */ +#define PWR_RED_REG(reg, dev) ((reg << 8) | dev) + +/** + * @brief Define a CPU specific Power Reduction index macro + */ +enum { + PWR_GENERAL_POWER, + PWR_PORT_A, + PWR_PORT_B, + PWR_PORT_C, + PWR_PORT_D, + PWR_PORT_E, + PWR_PORT_F, +}; + /** * @name Power management configuration * @{ @@ -190,6 +213,7 @@ typedef enum { */ typedef struct { USART_t *dev; /**< pointer to the used UART device */ + pwr_reduction_t pwr; /**< Power Management */ gpio_t rx_pin; /**< pin used for RX */ gpio_t tx_pin; /**< pin used for TX */ #ifdef MODULE_PERIPH_UART_HW_FC @@ -235,6 +259,7 @@ typedef enum { */ typedef struct { TC0_t *dev; /**< Pointer to the used as Timer device */ + pwr_reduction_t pwr; /**< Power Management */ timer_type_t type; /**< Timer Type */ cpu_int_lvl_t int_lvl[TIMER_CH_MAX_NUMOF]; /**< Interrupt channels level */ } timer_conf_t; diff --git a/cpu/atxmega/periph/pm.c b/cpu/atxmega/periph/pm.c index 08875134e4bc..2b2d75a4b891 100644 --- a/cpu/atxmega/periph/pm.c +++ b/cpu/atxmega/periph/pm.c @@ -23,10 +23,41 @@ #include "periph_conf.h" #include "periph/pm.h" +#include "cpu_pm.h" #define ENABLE_DEBUG 0 #include "debug.h" +#define PWR_REG_BASE ((uint16_t)&PR) +#define PWR_REG_OFFSET (0x01) + +/** + * @brief Extract the device id of the given power reduction mask + */ +static inline uint8_t _device_mask(pwr_reduction_t pwr) +{ + return (pwr & 0xff); +} + +/** + * @brief Extract the register id of the given power reduction mask + */ +static inline uint8_t _register_id(pwr_reduction_t pwr) +{ + return (pwr >> 8) & 0xff; +} + +/** + * @brief Generate the register index of the given power reduction mask + */ +static inline uint8_t *_register_addr(pwr_reduction_t pwr) +{ + uint8_t id = _register_id(pwr); + uint16_t addr = PWR_REG_BASE + (id * PWR_REG_OFFSET); + + return (uint8_t *)addr; +} + void pm_reboot(void) { DEBUG("Reboot Software Reset\n" ); @@ -78,3 +109,30 @@ void pm_set(unsigned mode) sleep_disable(); irq_restore(irq_state); } + +void pm_periph_enable(pwr_reduction_t pwr) +{ + uint8_t mask = _device_mask(pwr); + uint8_t *reg = _register_addr(pwr); + + *reg &= ~mask; +} + +void pm_periph_disable(pwr_reduction_t pwr) +{ + uint8_t mask = _device_mask(pwr); + uint8_t *reg = _register_addr(pwr); + + *reg |= mask; +} + +void pm_periph_power_off(void) +{ + uint8_t *reg = _register_addr(PWR_GENERAL_POWER); + uint8_t i; + + /* Freeze all peripheral clocks */ + for (i = 0; i <= 7; i++) { + reg[i] = 0xff; + } +} diff --git a/cpu/atxmega/periph/timer.c b/cpu/atxmega/periph/timer.c index f49fbfd510da..66768c81d16d 100644 --- a/cpu/atxmega/periph/timer.c +++ b/cpu/atxmega/periph/timer.c @@ -24,6 +24,7 @@ #include #include "cpu.h" +#include "cpu_pm.h" #include "thread.h" #include "periph/timer.h" @@ -112,6 +113,8 @@ int timer_init(tim_t tim, unsigned long freq, timer_cb_t cb, void *arg) return -1; } + pm_periph_enable(timer_config[tim].pwr); + dev = timer_config[tim].dev; /* stop and reset timer */ @@ -301,6 +304,7 @@ void timer_stop(tim_t tim) DEBUG("timer_stop\n"); timer_config[tim].dev->CTRLA = 0; timer_config[tim].dev->CTRLFSET = TC_CMD_RESTART_gc; + pm_periph_disable(timer_config[tim].pwr); } void timer_start(tim_t tim) @@ -310,6 +314,7 @@ void timer_start(tim_t tim) } DEBUG("timer_start\n"); + pm_periph_enable(timer_config[tim].pwr); timer_config[tim].dev->CTRLA = ctx[tim].prescaler; } diff --git a/cpu/atxmega/periph/uart.c b/cpu/atxmega/periph/uart.c index aeaebcda3b8e..449940893186 100644 --- a/cpu/atxmega/periph/uart.c +++ b/cpu/atxmega/periph/uart.c @@ -29,6 +29,7 @@ #include "board.h" #include "cpu.h" +#include "cpu_pm.h" #include "sched.h" #include "thread.h" #include "periph/uart.h" @@ -257,6 +258,8 @@ int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg) isr_ctx[uart].rx_cb = rx_cb; isr_ctx[uart].arg = arg; + pm_periph_enable(uart_config[uart].pwr); + /* disable and reset UART */ dev(uart)->CTRLA = 0; dev(uart)->CTRLB = 0; @@ -315,14 +318,12 @@ void uart_write(uart_t uart, const uint8_t *data, size_t len) void uart_poweron(uart_t uart) { - (void)uart; - /* not implemented (yet) */ + pm_periph_enable(uart_config[uart].pwr); } void uart_poweroff(uart_t uart) { - (void)uart; - /* not implemented (yet) */ + pm_periph_disable(uart_config[uart].pwr); } static inline void _rx_isr_handler(int num)