diff --git a/boards/common/frdm/dist/openocd-klx.cfg b/boards/common/frdm/dist/openocd-klx.cfg index 47a447b7b8d5..5d7f29cb13ec 100644 --- a/boards/common/frdm/dist/openocd-klx.cfg +++ b/boards/common/frdm/dist/openocd-klx.cfg @@ -16,7 +16,10 @@ transport select swd # Kinetis L series CPUs source [find target/klx.cfg] -reset_config srst_only +# The debug module is stopped in low leakage modes, we use connect_assert_srst +# to hold the device in reset while connecting with OpenOCD to make sure that we +# always get a connection even when the application uses low power modes. +reset_config srst_only connect_assert_srst $_TARGETNAME configure -event gdb-attach { halt diff --git a/boards/common/frdm/dist/openocd-kx.cfg b/boards/common/frdm/dist/openocd-kx.cfg index 021f22074e26..2e128dcb908a 100644 --- a/boards/common/frdm/dist/openocd-kx.cfg +++ b/boards/common/frdm/dist/openocd-kx.cfg @@ -13,7 +13,10 @@ # Kinetis K series CPUs source [find target/kx.cfg] -reset_config srst_only +# The debug module is stopped in low leakage modes, we use connect_assert_srst +# to hold the device in reset while connecting with OpenOCD to make sure that we +# always get a connection even when the application uses low power modes. +reset_config srst_only connect_assert_srst $_TARGETNAME configure -event gdb-attach { halt diff --git a/boards/frdm-k22f/include/periph_conf.h b/boards/frdm-k22f/include/periph_conf.h index 3d769c85f929..336bc21e5a81 100644 --- a/boards/frdm-k22f/include/periph_conf.h +++ b/boards/frdm-k22f/include/periph_conf.h @@ -80,14 +80,15 @@ static const clock_config_t clock_config = { }, \ } #define LPTMR_NUMOF (1U) -#define LPTMR_CONFIG { \ - { \ - .dev = LPTMR0, \ - .irqn = LPTMR0_IRQn, \ - .src = 2, \ - .base_freq = 32768u, \ - }, \ -} +#define LPTMR_CONFIG { \ + { \ + .dev = LPTMR0, \ + .base_freq = 32768u, \ + .llwu = LLWU_WAKEUP_MODULE_LPTMR0, \ + .src = 2, \ + .irqn = LPTMR0_IRQn, \ + }, \ + } #define TIMER_NUMOF ((PIT_NUMOF) + (LPTMR_NUMOF)) #define PIT_BASECLOCK (CLOCK_BUSCLOCK) @@ -113,6 +114,7 @@ static const uart_conf_t uart_config[] = { .scgc_bit = SIM_SCGC4_UART1_SHIFT, .mode = UART_MODE_8N1, .type = KINETIS_UART, + .llwu_rx = LLWU_WAKEUP_PIN_PTE1, }, }; diff --git a/boards/frdm-k64f/include/periph_conf.h b/boards/frdm-k64f/include/periph_conf.h index f27cd18198b6..1540676f15d7 100644 --- a/boards/frdm-k64f/include/periph_conf.h +++ b/boards/frdm-k64f/include/periph_conf.h @@ -70,18 +70,26 @@ static const clock_config_t clock_config = { * @{ */ #define PIT_NUMOF (2U) -#define PIT_CONFIG { \ - { \ - .prescaler_ch = 0, \ - .count_ch = 1, \ - }, \ - { \ - .prescaler_ch = 2, \ - .count_ch = 3, \ - }, \ +#define PIT_CONFIG { \ + { \ + .prescaler_ch = 0, \ + .count_ch = 1, \ + }, \ + { \ + .prescaler_ch = 2, \ + .count_ch = 3, \ + }, \ + } +#define LPTMR_NUMOF (1U) +#define LPTMR_CONFIG { \ + { \ + .dev = LPTMR0, \ + .base_freq = 32768u, \ + .llwu = LLWU_WAKEUP_MODULE_LPTMR0, \ + .src = 2, \ + .irqn = LPTMR0_IRQn, \ + }, \ } -#define LPTMR_NUMOF (0U) -#define LPTMR_CONFIG {} #define TIMER_NUMOF ((PIT_NUMOF) + (LPTMR_NUMOF)) #define PIT_BASECLOCK (CLOCK_BUSCLOCK) @@ -107,6 +115,7 @@ static const uart_conf_t uart_config[] = { .scgc_bit = SIM_SCGC4_UART0_SHIFT, .mode = UART_MODE_8N1, .type = KINETIS_UART, + .llwu_rx = LLWU_WAKEUP_PIN_UNDEF, }, }; diff --git a/boards/frdm-kw41z/board.c b/boards/frdm-kw41z/board.c index 9251c626a17e..68a81a9bcb78 100644 --- a/boards/frdm-kw41z/board.c +++ b/boards/frdm-kw41z/board.c @@ -23,6 +23,42 @@ void board_init(void) { + bit_set32(&SIM->SCGC5, SIM_SCGC5_DCDC_SHIFT); + + /* We don't have a status bit for checking if the DCDC is in bypass mode + * so we check if the PSWITCH bit is set, assuming that the power switch + * has not been released since the DCDC was started. This is run early in + * the boot process so it should be fine. */ + if (DCDC->REG0 & DCDC_REG0_PSWITCH_STATUS_MASK) { + /* DCDC is on */ + /* Spin until DCDC output is stable */ + while (!(DCDC->REG0 & DCDC_REG0_DCDC_STS_DC_OK_MASK)) {} + /* Set up DCDC for pulsed mode in VLPS, LLS */ + DCDC->REG0 = (DCDC->REG0 & ~(DCDC_REG0_DCDC_VBAT_DIV_CTRL_MASK | DCDC_REG0_VLPS_CONFIG_DCDC_HP_MASK)) | + DCDC_REG0_DCDC_VBAT_DIV_CTRL(0b10) | /* provide VBAT/2 to ADC input to measure battery level */ + DCDC_REG0_DCDC_LP_DF_CMP_ENABLE_MASK; + DCDC->REG1 = DCDC->REG1 | + DCDC_REG1_DCDC_LOOPCTRL_EN_CM_HYST_MASK | + DCDC_REG1_DCDC_LOOPCTRL_EN_DF_HYST_MASK; + DCDC->REG2 = DCDC->REG2 | + DCDC_REG2_DCDC_LOOPCTRL_HYST_SIGN_MASK; + DCDC->REG3 = (DCDC->REG3 & + ~(DCDC_REG3_DCDC_MINPWR_HALF_FETS_MASK | + DCDC_REG3_DCDC_MINPWR_DOUBLE_FETS_MASK | + DCDC_REG3_DCDC_MINPWR_DOUBLE_FETS_PULSED_MASK)) | + DCDC_REG3_DCDC_MINPWR_HALF_FETS_PULSED_MASK; + + /* Spin until DCDC is stable */ + while (!(DCDC->REG0 & DCDC_REG0_DCDC_STS_DC_OK_MASK)) {} + + /* DCDC has stabilized, halt stepping + * This must be set before entering low power modes */ + DCDC->REG3 |= DCDC_REG3_DCDC_VDD1P5CTRL_DISABLE_STEP_MASK | + DCDC_REG3_DCDC_VDD1P8CTRL_DISABLE_STEP_MASK; + + /* Spin until DCDC is stable */ + while (!(DCDC->REG0 & DCDC_REG0_DCDC_STS_DC_OK_MASK)) {} + } /* initialize the CPU core */ cpu_init(); diff --git a/boards/frdm-kw41z/include/periph_conf.h b/boards/frdm-kw41z/include/periph_conf.h index 435fac503f05..1c6721a83e02 100644 --- a/boards/frdm-kw41z/include/periph_conf.h +++ b/boards/frdm-kw41z/include/periph_conf.h @@ -87,10 +87,11 @@ static const clock_config_t clock_config = { #define LPTMR_CONFIG { \ { \ .dev = LPTMR0, \ - .irqn = LPTMR0_IRQn, \ - .src = 2, \ .base_freq = 32768u, \ - } \ + .llwu = LLWU_WAKEUP_MODULE_LPTMR0, \ + .src = 2, \ + .irqn = LPTMR0_IRQn, \ + }, \ } #define TIMER_NUMOF ((PIT_NUMOF) + (LPTMR_NUMOF)) #define PIT_BASECLOCK (CLOCK_BUSCLOCK) @@ -114,6 +115,13 @@ static const uart_conf_t uart_config[] = { .scgc_bit = SIM_SCGC5_LPUART0_SHIFT, .mode = UART_MODE_8N1, .type = KINETIS_LPUART, + /* Undocumented behavior: LPUART fails to detect the START bit at wake up + * with LLWU sometimes. This seem to be related to using the builtin + * DCDC for powering the MCU. */ + /* LLWU_WAKEUP_PIN_PTC6 is the correct setting on this dev board if you + * want to try using LLS mode, or if it does not matter that the UART RX + * byte is sometimes is corrupt */ + .llwu_rx = LLWU_WAKEUP_PIN_UNDEF, }, }; #define UART_NUMOF (sizeof(uart_config) / sizeof(uart_config[0])) diff --git a/boards/mulle/include/periph_conf.h b/boards/mulle/include/periph_conf.h index 491cf2ca89db..de4048c1e8b0 100644 --- a/boards/mulle/include/periph_conf.h +++ b/boards/mulle/include/periph_conf.h @@ -106,10 +106,11 @@ static const clock_config_t clock_config = { #define LPTMR_CONFIG { \ { \ .dev = LPTMR0, \ - .irqn = LPTMR0_IRQn, \ - .src = 2, \ .base_freq = 32768u, \ - } \ + .llwu = LLWU_WAKEUP_MODULE_LPTMR0, \ + .src = 2, \ + .irqn = LPTMR0_IRQn, \ + }, \ } #define TIMER_NUMOF ((PIT_NUMOF) + (LPTMR_NUMOF)) @@ -137,6 +138,7 @@ static const uart_conf_t uart_config[] = { .scgc_bit = SIM_SCGC4_UART0_SHIFT, .mode = UART_MODE_8N1, .type = KINETIS_UART, + .llwu_rx = LLWU_WAKEUP_PIN_UNDEF, }, { .dev = UART1, @@ -150,6 +152,7 @@ static const uart_conf_t uart_config[] = { .scgc_bit = SIM_SCGC4_UART1_SHIFT, .mode = UART_MODE_8N1, .type = KINETIS_UART, + .llwu_rx = LLWU_WAKEUP_PIN_PTC3, }, }; diff --git a/boards/pba-d-01-kw2x/include/periph_conf.h b/boards/pba-d-01-kw2x/include/periph_conf.h index c8afe2c38c3e..5d467e46c323 100644 --- a/boards/pba-d-01-kw2x/include/periph_conf.h +++ b/boards/pba-d-01-kw2x/include/periph_conf.h @@ -108,6 +108,7 @@ static const uart_conf_t uart_config[] = { .scgc_bit = SIM_SCGC4_UART2_SHIFT, .mode = UART_MODE_8N1, .type = KINETIS_UART, + .llwu_rx = LLWU_WAKEUP_PIN_UNDEF, }, { .dev = UART0, @@ -121,6 +122,7 @@ static const uart_conf_t uart_config[] = { .scgc_bit = SIM_SCGC4_UART0_SHIFT, .mode = UART_MODE_8N1, .type = KINETIS_UART, + .llwu_rx = LLWU_WAKEUP_PIN_UNDEF, } }; diff --git a/boards/teensy31/include/periph_conf.h b/boards/teensy31/include/periph_conf.h index c9024e04dd80..c87900b6fb6f 100644 --- a/boards/teensy31/include/periph_conf.h +++ b/boards/teensy31/include/periph_conf.h @@ -113,6 +113,7 @@ static const uart_conf_t uart_config[] = { .scgc_bit = SIM_SCGC4_UART0_SHIFT, .mode = UART_MODE_8N1, .type = KINETIS_UART, + .llwu_rx = LLWU_WAKEUP_PIN_UNDEF, }, { .dev = UART1, @@ -126,6 +127,7 @@ static const uart_conf_t uart_config[] = { .scgc_bit = SIM_SCGC4_UART1_SHIFT, .mode = UART_MODE_8N1, .type = KINETIS_UART, + .llwu_rx = LLWU_WAKEUP_PIN_PTC3, }, }; diff --git a/boards/usb-kw41z/include/periph_conf.h b/boards/usb-kw41z/include/periph_conf.h index 570527b90737..2b0575fbbb2f 100644 --- a/boards/usb-kw41z/include/periph_conf.h +++ b/boards/usb-kw41z/include/periph_conf.h @@ -87,10 +87,11 @@ static const clock_config_t clock_config = { #define LPTMR_CONFIG { \ { \ .dev = LPTMR0, \ - .irqn = LPTMR0_IRQn, \ - .src = 2, \ .base_freq = 32768u, \ - } \ + .llwu = LLWU_WAKEUP_MODULE_LPTMR0, \ + .src = 2, \ + .irqn = LPTMR0_IRQn, \ + }, \ } #define TIMER_NUMOF ((PIT_NUMOF) + (LPTMR_NUMOF)) #define PIT_BASECLOCK (CLOCK_BUSCLOCK) @@ -114,6 +115,10 @@ static const uart_conf_t uart_config[] = { .scgc_bit = SIM_SCGC5_LPUART0_SHIFT, .mode = UART_MODE_8N1, .type = KINETIS_LPUART, + /* LLWU_WAKEUP_PIN_PTC6 is the correct setting on this dev board if you + * want to try using LLS mode. The current setting will block LLS mode + * if UART RX is used, which is true in most applications */ + .llwu_rx = LLWU_WAKEUP_PIN_UNDEF, }, }; #define UART_NUMOF (sizeof(uart_config) / sizeof(uart_config[0])) diff --git a/cpu/kinetis/Makefile.features b/cpu/kinetis/Makefile.features index 8ebcde8bed25..8b214c30578d 100644 --- a/cpu/kinetis/Makefile.features +++ b/cpu/kinetis/Makefile.features @@ -2,6 +2,7 @@ FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_hwrng FEATURES_PROVIDED += periph_gpio FEATURES_PROVIDED += periph_gpio_irq +FEATURES_PROVIDED += periph_llwu FEATURES_PROVIDED += periph_mcg include $(RIOTCPU)/cortexm_common/Makefile.features diff --git a/cpu/kinetis/Makefile.include b/cpu/kinetis/Makefile.include index 59a0b948fd61..37e11cc3bf8b 100644 --- a/cpu/kinetis/Makefile.include +++ b/cpu/kinetis/Makefile.include @@ -36,10 +36,14 @@ export UNDEF += $(BINDIR)/cpu/fcfield.o # include common periph drivers USEMODULE += periph_common -# select kinetis periph drivers +# select Kinetis periph drivers +USEMODULE += periph_llwu USEMODULE += periph_mcg USEMODULE += periph_wdog +# Enable power management by default +USEMODULE += pm_layered + # Define a recipe to build the watchdog disable binary, used when flashing $(RIOTCPU)/$(CPU)/dist/wdog-disable.bin: $(RIOTCPU)/$(CPU)/dist/wdog-disable.s $(Q)$(MAKE) -C $(RIOTCPU)/$(CPU)/dist/ $(notdir $@) diff --git a/cpu/kinetis/cpu.c b/cpu/kinetis/cpu.c index f5785dd8b3e8..3e8152d0f682 100644 --- a/cpu/kinetis/cpu.c +++ b/cpu/kinetis/cpu.c @@ -22,6 +22,11 @@ #ifdef MODULE_PERIPH_MCG #include "mcg.h" #endif +#if defined(MODULE_PERIPH_LLWU) +#include "llwu.h" +#elif defined(MODULE_PM_LAYERED) +#include "pm_layered.h" +#endif /** * @brief Initialize the CPU, set IRQ priorities @@ -39,6 +44,16 @@ void cpu_init(void) /* initialize the CPU clocking provided by the MCG module */ kinetis_mcg_init(); #endif + +#if defined(MODULE_PERIPH_LLWU) + /* initialize the LLWU module for sleep/wakeup management */ + llwu_init(); +#elif defined(MODULE_PM_LAYERED) + /* Block LLS mode since we are not using the LLWU module, which is required + * to be able to wake up from LLS */ + pm_block(KINETIS_PM_LLS); +#endif + /* trigger static peripheral initialization */ periph_init(); } diff --git a/cpu/kinetis/include/cpu_conf_kinetis.h b/cpu/kinetis/include/cpu_conf_kinetis.h index 6e5dc9cb6fb5..68a4d1ece48f 100644 --- a/cpu/kinetis/include/cpu_conf_kinetis.h +++ b/cpu/kinetis/include/cpu_conf_kinetis.h @@ -61,6 +61,145 @@ extern "C" #define PIN_INTERRUPT_EDGE 0b1011 /** @} */ +/** + * @brief Mapping internal module interrupts to LLWU wake up sources + * + * @note Modules not listed here CAN NOT be used to wake the CPU from LLS or + * VLLSx power modes. + * + * The numbers are hardware specific, but have the same values across all the + * supported Kinetis CPUs + */ +typedef enum llwu_wakeup_module { + LLWU_WAKEUP_MODULE_LPTMR0 = 0, + LLWU_WAKEUP_MODULE_CMP0 = 1, + LLWU_WAKEUP_MODULE_RADIO = 2, /* KWx1Z devices */ + LLWU_WAKEUP_MODULE_CMP1 = 2, /* others */ + LLWU_WAKEUP_MODULE_DCDC = 3, /* KWx1Z devices */ + LLWU_WAKEUP_MODULE_CMP2 = 3, /* others */ + LLWU_WAKEUP_MODULE_TSI = 4, + LLWU_WAKEUP_MODULE_RTC_ALARM = 5, + LLWU_WAKEUP_MODULE_RESERVED = 6, + LLWU_WAKEUP_MODULE_RTC_SECONDS = 7, + LLWU_WAKEUP_MODULE_NUMOF +} llwu_wakeup_module_t; + +/** + * @brief Mapping LLWU wakeup pin sources to PORT interrupt numbers + */ +typedef struct { + PORT_Type *port; /**< PORT register */ + uint32_t isfr_mask; /**< ISFR bitmask */ +} llwu_wakeup_pin_to_port_t; + +/** + * @brief Mapping physical pins to LLWU wakeup pin source numbers + * + * @note Pins not listed here CAN NOT be used to wake the CPU from LLS or + * VLLSx power modes. + * + * The numbers are hardware specific, but have the same values across all the + * supported Kinetis CPUs. + */ +#if defined(KINETIS_SERIES_W) && defined(KINETIS_CORE_Z) && (KINETIS_SUBFAMILY == 1) +/* KW41Z has different LLWU pins */ +typedef enum llwu_wakeup_pin { + LLWU_WAKEUP_PIN_PTC16 = 0, + LLWU_WAKEUP_PIN_PTC17 = 1, + LLWU_WAKEUP_PIN_PTC18 = 2, + LLWU_WAKEUP_PIN_PTC19 = 3, + LLWU_WAKEUP_PIN_PTA16 = 4, + LLWU_WAKEUP_PIN_PTA17 = 5, + LLWU_WAKEUP_PIN_PTA18 = 6, + LLWU_WAKEUP_PIN_PTA19 = 7, + LLWU_WAKEUP_PIN_PTB0 = 8, + LLWU_WAKEUP_PIN_PTC0 = 9, + LLWU_WAKEUP_PIN_PTC2 = 10, + LLWU_WAKEUP_PIN_PTC3 = 11, + LLWU_WAKEUP_PIN_PTC4 = 12, + LLWU_WAKEUP_PIN_PTC5 = 13, + LLWU_WAKEUP_PIN_PTC6 = 14, + LLWU_WAKEUP_PIN_PTC7 = 15, + LLWU_WAKEUP_PIN_NUMOF, + LLWU_WAKEUP_PIN_UNDEF +} llwu_wakeup_pin_t; + +/** + * @brief Mapping LLWU wakeup pin number to PORT module interrupt flags + */ +static const llwu_wakeup_pin_to_port_t llwu_wakeup_pin_to_port[LLWU_WAKEUP_PIN_NUMOF] = { + [LLWU_WAKEUP_PIN_PTC16] = { .port = PORTC, .isfr_mask = (1u << 16), }, + [LLWU_WAKEUP_PIN_PTC17] = { .port = PORTC, .isfr_mask = (1u << 17), }, + [LLWU_WAKEUP_PIN_PTC18] = { .port = PORTC, .isfr_mask = (1u << 18), }, + [LLWU_WAKEUP_PIN_PTC19] = { .port = PORTC, .isfr_mask = (1u << 19), }, + [LLWU_WAKEUP_PIN_PTA16] = { .port = PORTA, .isfr_mask = (1u << 16), }, + [LLWU_WAKEUP_PIN_PTA17] = { .port = PORTA, .isfr_mask = (1u << 17), }, + [LLWU_WAKEUP_PIN_PTA18] = { .port = PORTA, .isfr_mask = (1u << 18), }, + [LLWU_WAKEUP_PIN_PTA19] = { .port = PORTA, .isfr_mask = (1u << 19), }, + [LLWU_WAKEUP_PIN_PTB0 ] = { .port = PORTB, .isfr_mask = (1u << 0), }, + [LLWU_WAKEUP_PIN_PTC0 ] = { .port = PORTC, .isfr_mask = (1u << 0), }, + [LLWU_WAKEUP_PIN_PTC2 ] = { .port = PORTC, .isfr_mask = (1u << 2), }, + [LLWU_WAKEUP_PIN_PTC3 ] = { .port = PORTC, .isfr_mask = (1u << 3), }, + [LLWU_WAKEUP_PIN_PTC4 ] = { .port = PORTC, .isfr_mask = (1u << 4), }, + [LLWU_WAKEUP_PIN_PTC5 ] = { .port = PORTC, .isfr_mask = (1u << 5), }, + [LLWU_WAKEUP_PIN_PTC6 ] = { .port = PORTC, .isfr_mask = (1u << 6), }, + [LLWU_WAKEUP_PIN_PTC7 ] = { .port = PORTC, .isfr_mask = (1u << 7), }, +}; +#else +typedef enum llwu_wakeup_pin { + LLWU_WAKEUP_PIN_PTE1 = 0, + LLWU_WAKEUP_PIN_PTE2 = 1, + LLWU_WAKEUP_PIN_PTE4 = 2, + LLWU_WAKEUP_PIN_PTA4 = 3, + LLWU_WAKEUP_PIN_PTA13 = 4, + LLWU_WAKEUP_PIN_PTB0 = 5, + LLWU_WAKEUP_PIN_PTC1 = 6, + LLWU_WAKEUP_PIN_PTC3 = 7, + LLWU_WAKEUP_PIN_PTC4 = 8, + LLWU_WAKEUP_PIN_PTC5 = 9, + LLWU_WAKEUP_PIN_PTC6 = 10, + LLWU_WAKEUP_PIN_PTC11 = 11, + LLWU_WAKEUP_PIN_PTD0 = 12, + LLWU_WAKEUP_PIN_PTD2 = 13, + LLWU_WAKEUP_PIN_PTD4 = 14, + LLWU_WAKEUP_PIN_PTD6 = 15, + LLWU_WAKEUP_PIN_NUMOF, + LLWU_WAKEUP_PIN_UNDEF +} llwu_wakeup_pin_t; + +/** + * @brief Mapping LLWU wakeup pin number to PORT module interrupt flags + */ +static const llwu_wakeup_pin_to_port_t llwu_wakeup_pin_to_port[LLWU_WAKEUP_PIN_NUMOF] = { + [LLWU_WAKEUP_PIN_PTE1 ] = { .port = PORTE, .isfr_mask = (1u << 1), }, + [LLWU_WAKEUP_PIN_PTE2 ] = { .port = PORTE, .isfr_mask = (1u << 2), }, + [LLWU_WAKEUP_PIN_PTE4 ] = { .port = PORTE, .isfr_mask = (1u << 4), }, + [LLWU_WAKEUP_PIN_PTA4 ] = { .port = PORTA, .isfr_mask = (1u << 4), }, + [LLWU_WAKEUP_PIN_PTA13] = { .port = PORTA, .isfr_mask = (1u << 13), }, + [LLWU_WAKEUP_PIN_PTB0 ] = { .port = PORTB, .isfr_mask = (1u << 0), }, + [LLWU_WAKEUP_PIN_PTC1 ] = { .port = PORTC, .isfr_mask = (1u << 1), }, + [LLWU_WAKEUP_PIN_PTC3 ] = { .port = PORTC, .isfr_mask = (1u << 3), }, + [LLWU_WAKEUP_PIN_PTC4 ] = { .port = PORTC, .isfr_mask = (1u << 4), }, + [LLWU_WAKEUP_PIN_PTC5 ] = { .port = PORTC, .isfr_mask = (1u << 5), }, + [LLWU_WAKEUP_PIN_PTC6 ] = { .port = PORTC, .isfr_mask = (1u << 6), }, + [LLWU_WAKEUP_PIN_PTC11] = { .port = PORTC, .isfr_mask = (1u << 11), }, + [LLWU_WAKEUP_PIN_PTD0 ] = { .port = PORTD, .isfr_mask = (1u << 0), }, + [LLWU_WAKEUP_PIN_PTD2 ] = { .port = PORTD, .isfr_mask = (1u << 2), }, + [LLWU_WAKEUP_PIN_PTD4 ] = { .port = PORTD, .isfr_mask = (1u << 4), }, + [LLWU_WAKEUP_PIN_PTD6 ] = { .port = PORTD, .isfr_mask = (1u << 6), }, +}; +#endif + +/** + * @brief LLWU wakeup pin edge settings + */ +typedef enum llwu_wakeup_edge { + LLWU_WAKEUP_EDGE_NONE = 0, + LLWU_WAKEUP_EDGE_RISING = 1, + LLWU_WAKEUP_EDGE_FALLING = 2, + LLWU_WAKEUP_EDGE_BOTH = 3, +} llwu_wakeup_edge_t; + /** * @name Compatibility definitions between vendor headers * @{ diff --git a/cpu/kinetis/include/llwu.h b/cpu/kinetis/include/llwu.h new file mode 100644 index 000000000000..8ca36124935f --- /dev/null +++ b/cpu/kinetis/include/llwu.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2017 SKF AB + * + * 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. + */ + +/** + * @defgroup cpu_kinetis_llwu Kinetis LLWU + * @ingroup cpu_kinetis + * @brief Kinetis low leakage wakeup unit (LLWU) driver + + * @{ + * + * @file + * @brief Interface definition for the Kinetis LLWU driver. + * + * @author Joakim Nohlgård + */ + +#ifndef LLWU_H +#define LLWU_H + +#include "cpu.h" +#include "bit.h" +#include "periph_conf.h" +#include "periph/gpio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Initialize the Low-Leakage Wake Up (LLWU) hardware module + */ +void llwu_init(void); + +/** + * @brief Enable a wakeup module in the LLWU + */ +inline static void llwu_wakeup_module_enable(llwu_wakeup_module_t mod) +{ + assert(mod < LLWU_WAKEUP_MODULE_NUMOF); + bit_set8(&LLWU->ME, mod); +} + +/** + * @brief Disable a wakeup module in the LLWU + */ +inline static void llwu_wakeup_module_disable(llwu_wakeup_module_t mod) +{ + assert(mod < LLWU_WAKEUP_MODULE_NUMOF); + bit_clear8(&LLWU->ME, mod); +} + +/** + * @brief Set the mode for a wakeup pin in the LLWU + * + * If @p cb is NULL when the pin edge is detected, the CPU will wake up for a + * few cycles which can allow other hardware modules to detect other interrupts. + * This may be particularily useful when using the wakeup pin for communication + * functions such as UART RX etc. + * + * @param[in] pin The pin to modify + * @param[in] edge Edge detection setting (rising, falling, both, none) + * @param[in] cb Callback function to execute when woken with this pin + * @param[in] arg Argument that will be passed to the callback + */ +void llwu_wakeup_pin_set(llwu_wakeup_pin_t pin, llwu_wakeup_edge_t edge, gpio_cb_t cb, void *arg); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* LLWU_H */ diff --git a/cpu/kinetis/include/periph_cpu.h b/cpu/kinetis/include/periph_cpu.h index 8acb05a60bdd..614377b7253c 100644 --- a/cpu/kinetis/include/periph_cpu.h +++ b/cpu/kinetis/include/periph_cpu.h @@ -26,6 +26,22 @@ #include "cpu.h" +#if MODULE_PM_LAYERED +#include "pm_layered.h" +/** + * @brief pm_block iff pm_layered is used + */ +#define PM_BLOCK(x) pm_block(x) +/** + * @brief pm_unblock iff pm_layered is used + */ +#define PM_UNBLOCK(x) pm_unblock(x) +#else +/* ignore these calls when not using pm_layered */ +#define PM_BLOCK(x) +#define PM_UNBLOCK(x) +#endif + #ifdef __cplusplus extern "C" { #endif @@ -110,9 +126,18 @@ typedef uint16_t gpio_t; #define PERIPH_TIMER_PROVIDES_SET /** - * @brief number of usable power modes + * @name Kinetis power mode configuration + * @{ */ -#define PM_NUM_MODES (1U) +#define PM_NUM_MODES (4U) +enum { + KINETIS_PM_LLS = 0, + KINETIS_PM_VLPS = 1, + KINETIS_PM_STOP = 2, + KINETIS_PM_WAIT = 3, +}; +#define PM_BLOCKER_INITIAL { .val_u32 = 0 } +/** @} */ #ifdef RTC /* All Kinetis CPUs have exactly one RTC hardware module, except for the KL02 @@ -337,6 +362,8 @@ typedef struct { LPTMR_Type *dev; /** Input clock frequency */ uint32_t base_freq; + /** LLWU wakeup module number for this timer */ + llwu_wakeup_module_t llwu; /** Clock source setting */ uint8_t src; /** IRQn interrupt number */ @@ -447,9 +474,21 @@ typedef struct { volatile uint32_t *scgc_addr; /**< Clock enable register, in SIM module */ uint8_t scgc_bit; /**< Clock enable bit, within the register */ uart_mode_t mode; /**< UART mode: data bits, parity, stop bits */ - uart_type_t type; /**< Hardware module type (KINETIS_UART or KINETIS_LPUART)*/ + uart_type_t type; /**< Hardware module type (KINETIS_UART or KINETIS_LPUART) */ + /** + * @brief LLWU wakeup source RX pin to allow RX while in LLS mode + * + * Set to @c LLWU_WAKEUP_PIN_UNDEF if the chosen RX pin is not available to + * the LLWU. + */ + llwu_wakeup_pin_t llwu_rx; } uart_conf_t; +/** + * @brief Override the default uart_isr_ctx_t in uart.c + */ +#define HAVE_UART_ISR_CTX_T + #if !defined(KINETIS_HAVE_PLL) #if defined(MCG_C6_PLLS_MASK) || DOXYGEN /** diff --git a/cpu/kinetis/periph/i2c.c b/cpu/kinetis/periph/i2c.c index 2e5eeb0f7820..e910948cf6ed 100644 --- a/cpu/kinetis/periph/i2c.c +++ b/cpu/kinetis/periph/i2c.c @@ -382,9 +382,12 @@ int i2c_read_bytes(i2c_t dev, uint16_t addr, void *data, size_t len, uint8_t fla volatile uint8_t dummy; dummy = i2c->D; ++dummy; - /* Wait until the ISR signals back */ TRACE("i2c: read C1=%02x S=%02x\n", (unsigned)i2c->C1, (unsigned)i2c->S); + /* Keep module clock enabled by blocking low power modes */ + PM_BLOCK(KINETIS_PM_STOP); + /* Wait until the ISR signals back */ thread_flags_t tflg = thread_flags_wait_any(THREAD_FLAG_KINETIS_I2C | THREAD_FLAG_TIMEOUT); + PM_UNBLOCK(KINETIS_PM_STOP); TRACE("i2c: rx done, %u left, ret: %d\n", i2c_state[dev].rx.bytes_left, i2c_state[dev].retval); if (!(tflg & THREAD_FLAG_KINETIS_I2C)) { @@ -432,8 +435,11 @@ int i2c_write_bytes(i2c_t dev, uint16_t addr, const void *data, size_t len, uint /* Initiate transfer by writing the first byte, the remaining bytes will * be fed by the ISR */ i2c->D = *((const uint8_t *)data); + /* Keep module clock enabled by blocking low power modes */ + PM_BLOCK(KINETIS_PM_STOP); /* Wait until the ISR signals back */ thread_flags_t tflg = thread_flags_wait_any(THREAD_FLAG_KINETIS_I2C | THREAD_FLAG_TIMEOUT); + PM_UNBLOCK(KINETIS_PM_STOP); TRACE("i2c: rx done, %u left, ret: %d\n", i2c_state[dev].rx.bytes_left, i2c_state[dev].retval); if (!(tflg & THREAD_FLAG_KINETIS_I2C)) { diff --git a/cpu/kinetis/periph/llwu.c b/cpu/kinetis/periph/llwu.c new file mode 100644 index 000000000000..df94a829c3da --- /dev/null +++ b/cpu/kinetis/periph/llwu.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2017 SKF AB + * + * 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_kinetis + * @{ + * + * @file + * @brief Low-leakage wakeup unit (LLWU) driver + * + * @author Joakim Nohlgård + * + * @} + */ + +#include "cpu.h" +#include "bit.h" +#include "llwu.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +typedef struct { + gpio_cb_t cb; + void *arg; +} llwu_pin_config_t; + +static llwu_pin_config_t llwu_pin_config[LLWU_WAKEUP_PIN_NUMOF]; + +void llwu_init(void) +{ + /* Setup Low Leakage Wake-up Unit (LLWU) */ +#ifdef SIM_SCGC4_LLWU_SHIFT + /* Only the first generation Kinetis K CPUs have a clock gate for the LLWU, + * for all others the LLWU clock is always on */ + bit_set32(&SIM->SCGC4, SIM_SCGC4_LLWU_SHIFT); /* Enable LLWU clock gate */ +#endif + + /* LLWU needs to have a priority which is equal to or more urgent than the + * PORT module to avoid races between the LLWU pin detect interrupt and the + * PORT pin detect interrupt */ + NVIC_SetPriority(LLWU_IRQn, 0); +} + +void llwu_wakeup_pin_set(llwu_wakeup_pin_t pin, llwu_wakeup_edge_t edge, gpio_cb_t cb, void *arg) +{ + assert(pin < LLWU_WAKEUP_PIN_NUMOF); + llwu_pin_config[pin].cb = cb; + llwu_pin_config[pin].arg = arg; + + /* The fields are two bits per pin, and the setting registers are 8 bits + * wide, hence 4 pins per register. */ + unsigned field_offset = (pin & 0x03) << 1; + unsigned reg_offset = pin >> 2; + (&LLWU->PE1)[reg_offset] = ((&LLWU->PE1)[reg_offset] & + (~(0b11 << field_offset))) | + (edge << field_offset); +} + +void isr_llwu(void) +{ + DEBUG("LLWU IRQ\n"); + + for (unsigned reg = 0; reg < ((LLWU_WAKEUP_PIN_NUMOF + 7) / 8); ++reg) { + uint8_t status = *(&LLWU->F1 + reg); + /* Clear pin interrupt flags */ + *(&LLWU->F1 + reg) = status; + DEBUG("llwu: F%u = %02x\n", reg + 1, (unsigned) status); + + while (status) { + unsigned pin = bitarithm_lsb(status); + status &= ~(1 << pin); + pin += reg * 8; + DEBUG("llwu: wakeup pin %u\n", pin); + gpio_cb_t cb = llwu_pin_config[pin].cb; + if (cb) { + cb(llwu_pin_config[pin].arg); + } + /* Clear PORT interrupt flag to avoid spurious duplicates. */ + /* In essence, this behavior is similar to a software debounce. Even + * very quick contact bounces after the LLWU has begun to wake the + * CPU may cause the PORT module to pick up the same trigger event, + * which may lead to duplicate software events when using the same + * callback for gpio_init_int and llwu_wakeup_pin_set. The bounces + * would normally be ignored because of the processing delay in the + * interrupt handling before the interrupt flag is cleared, but + * since there are two interrupt flags, one in the LLWU module and + * one in the PORT module, we can get two events. */ + DEBUG("PORT ISFR: %08" PRIx32 "\n", llwu_wakeup_pin_to_port[pin].port->ISFR); + llwu_wakeup_pin_to_port[pin].port->ISFR = llwu_wakeup_pin_to_port[pin].isfr_mask; + } + } + /* Read only register F3, the flag will need to be cleared in the peripheral + * instead of writing a 1 to the MWUFx bit. */ + DEBUG("llwu: F3 = %02x\n", LLWU->F3); + /* Mask the LLWU IRQ until the module interrupt handlers have had a chance + * to run and clear the F3 flags */ + NVIC_DisableIRQ(LLWU_IRQn); + + cortexm_isr_end(); +} diff --git a/cpu/kinetis/periph/pm.c b/cpu/kinetis/periph/pm.c index 4a9961ba7ab0..726e13b88b13 100644 --- a/cpu/kinetis/periph/pm.c +++ b/cpu/kinetis/periph/pm.c @@ -26,17 +26,68 @@ #define ENABLE_DEBUG (0) #include "debug.h" -/** - * @note The current PM implementation is very much simplified down to only - * using the 'WAIT' mode. This implementation must be further expanded - * to make use of the available and more efficient (deep) sleep modes - * of the Kinetis CPUs. - */ +/* Set to 1 to use the LEDx macros to show which sleep mode is entered */ +#define ENABLE_LEDS (0) + +#if ENABLE_LEDS +#include "board.h" +#define PM_LED(x, state) LED ## x ## _ ## state +#else +#define PM_LED(x, state) +#endif + +/* SMC_PMCTRL_STOPM masks */ +enum { + SMC_PMCTRL_STOPM_STOP = 0, + /* 1 is reserved */ + SMC_PMCTRL_STOPM_VLPS = 2, + SMC_PMCTRL_STOPM_LLS = 3, + /* VLLS is not supported */ +}; + +/** Configure which stop mode will be entered when cortexm_sleep(1) is called */ +static inline void pm_stopm(uint8_t stopm) +{ + SMC->PMCTRL = (SMC->PMCTRL & ~(SMC_PMCTRL_STOPM_MASK)) | SMC_PMCTRL_STOPM(stopm); +} + void pm_set(unsigned mode) { + unsigned deep = 1; switch (mode) { - case 0: - cortexm_sleep(0); + case KINETIS_PM_WAIT: + /* WAIT */ + deep = 0; + PM_LED(0, ON); + break; + case KINETIS_PM_STOP: + /* STOP */ + pm_stopm(SMC_PMCTRL_STOPM_STOP); + PM_LED(1, ON); + break; + case KINETIS_PM_VLPS: + /* VLPS */ + pm_stopm(SMC_PMCTRL_STOPM_VLPS); + PM_LED(2, ON); + break; + case KINETIS_PM_LLS: + /* LLSx */ + pm_stopm(SMC_PMCTRL_STOPM_LLS); + PM_LED(0, ON); + PM_LED(2, ON); + /* Enable LLWU interrupt, or else we can never resume from LLS */ + /* Clear pending flag first, the LLWU has no purpose in RUN mode, so + * if the flag is set then it must be a remainder from handling the + * previous wakeup. */ + LLWU->F1 = 0xffu; + LLWU->F2 = 0xffu; + NVIC_ClearPendingIRQ(LLWU_IRQn); + NVIC_EnableIRQ(LLWU_IRQn); break; } + DEBUG("pm_set(%u)\n", mode); + cortexm_sleep(deep); + PM_LED(0, OFF); + PM_LED(1, OFF); + PM_LED(2, OFF); } diff --git a/cpu/kinetis/periph/rtt.c b/cpu/kinetis/periph/rtt.c index 4bbf3240d0c9..0a7d7a8c816a 100644 --- a/cpu/kinetis/periph/rtt.c +++ b/cpu/kinetis/periph/rtt.c @@ -31,6 +31,9 @@ #include "cpu.h" #include "bit.h" #include "periph/rtt.h" +#ifdef MODULE_PERIPH_LLWU +#include "llwu.h" +#endif #include "periph_conf.h" #define ENABLE_DEBUG (0) @@ -65,7 +68,7 @@ void rtt_init(void) /* Time Invalid Flag is set, clear TIF by writing TSR */ /* Stop counter to make TSR writable */ - bit_clear32(&RTC->SR, RTC_SR_TCE_SHIFT); + rtt_poweroff(); RTC->TSR = 0; } @@ -76,6 +79,10 @@ void rtt_init(void) /* Enable RTC interrupts */ NVIC_EnableIRQ(RTC_IRQn); +#ifdef MODULE_PERIPH_LLWU + /* Enable RTC wake from LLS */ + llwu_wakeup_module_enable(LLWU_WAKEUP_MODULE_RTC_ALARM); +#endif rtt_poweron(); } diff --git a/cpu/kinetis/periph/timer.c b/cpu/kinetis/periph/timer.c index 021c6bee3ea6..7d1de57e32e9 100644 --- a/cpu/kinetis/periph/timer.c +++ b/cpu/kinetis/periph/timer.c @@ -30,7 +30,20 @@ #include "bit.h" #include "board.h" #include "periph_conf.h" +#if MODULE_PERIPH_LLWU +#include "llwu.h" +#endif #include "periph/timer.h" +#if MODULE_PM_LAYERED +#include "pm_layered.h" +#define PM_BLOCK(x) pm_block(x) +#define PM_UNBLOCK(x) pm_unblock(x) +#else +#define PM_BLOCK(x) +#define PM_UNBLOCK(x) +#endif + + #ifdef PIT_LTMR64H_LTH_MASK /* The KW41Z PIT module provides only one IRQ for all PIT channels combined. */ @@ -186,6 +199,11 @@ static inline int pit_set(uint8_t dev, uint32_t timeout) const uint8_t ch = pit_config[dev].count_ch; /* Disable IRQs to minimize the number of lost ticks */ unsigned int mask = irq_disable(); + if (!(PIT->CHANNEL[ch].TCTRL & PIT_TCTRL_TIE_MASK)) { + /* The PIT is halted in low power modes, block them when a timer target + * has been set */ + PM_BLOCK(KINETIS_PM_STOP); + } /* Subtract if there was anything left on the counter */ pit[dev].count -= PIT->CHANNEL[ch].CVAL; /* Set new timeout */ @@ -207,6 +225,11 @@ static inline int pit_set_absolute(uint8_t dev, uint32_t target) uint8_t ch = pit_config[dev].count_ch; /* Disable IRQs to minimize the number of lost ticks */ unsigned int mask = irq_disable(); + if (!(PIT->CHANNEL[ch].TCTRL & PIT_TCTRL_TIE_MASK)) { + /* The PIT is halted in low power modes, block them when a timer target + * has been set */ + PM_BLOCK(KINETIS_PM_STOP); + } uint32_t now = pit[dev].count - PIT->CHANNEL[ch].CVAL; uint32_t offset = target - now; /* Set new timeout */ @@ -228,6 +251,10 @@ static inline int pit_clear(uint8_t dev) uint8_t ch = pit_config[dev].count_ch; /* Disable IRQs to minimize the number of lost ticks */ unsigned int mask = irq_disable(); + if (PIT->CHANNEL[ch].TCTRL & PIT_TCTRL_TIE_MASK) { + /* Allow low power modes again */ + PM_UNBLOCK(KINETIS_PM_STOP); + } /* Subtract if there was anything left on the counter */ pit[dev].count -= PIT->CHANNEL[ch].CVAL; /* No need to add PIT_MAX_VALUE + 1 to the counter because of modulo 2**32 */ @@ -253,12 +280,20 @@ static inline uint32_t pit_read(uint8_t dev) static inline void pit_start(uint8_t dev) { uint8_t ch = pit_config[dev].prescaler_ch; + if (PIT->CHANNEL[ch].TCTRL & PIT_TCTRL_TEN_MASK) { + /* Already running */ + return; + } PIT->CHANNEL[ch].TCTRL = PIT_TCTRL_TEN_MASK; } static inline void pit_stop(uint8_t dev) { uint8_t ch = pit_config[dev].prescaler_ch; + if (!(PIT->CHANNEL[ch].TCTRL & PIT_TCTRL_TEN_MASK)) { + /* Already stopped */ + return; + } PIT->CHANNEL[ch].TCTRL = 0; } @@ -278,6 +313,8 @@ static inline void pit_irq_handler(tim_t dev) PIT->CHANNEL[ch].LDVAL = PIT_MAX_VALUE; PIT->CHANNEL[ch].TFLG = PIT_TFLG_TIF_MASK; PIT->CHANNEL[ch].TCTRL = PIT_TCTRL_CHN_MASK | PIT_TCTRL_TEN_MASK; + /* Allow low power modes again */ + PM_UNBLOCK(KINETIS_PM_STOP); if (pit_ctx->isr_ctx.cb != NULL) { pit_ctx->isr_ctx.cb(pit_ctx->isr_ctx.arg, 0); @@ -355,6 +392,9 @@ static inline int lptmr_init(uint8_t dev, uint32_t freq, timer_cb_t cb, void *ar /* Enable IRQs on the counting channel */ NVIC_ClearPendingIRQ(lptmr_config[dev].irqn); NVIC_EnableIRQ(lptmr_config[dev].irqn); +#if MODULE_PERIPH_LLWU + llwu_wakeup_module_enable(lptmr_config[dev].llwu); +#endif _lptmr_set_cb_config(dev, cb, arg); diff --git a/cpu/kinetis/periph/uart.c b/cpu/kinetis/periph/uart.c index f1c5edf378cd..83dbc0a21334 100644 --- a/cpu/kinetis/periph/uart.c +++ b/cpu/kinetis/periph/uart.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Eistec AB + * Copyright (C) 2017-2018 Eistec AB * Copyright (C) 2014 PHYTEC Messtechnik GmbH * Copyright (C) 2014 Freie Universität Berlin * @@ -28,6 +28,9 @@ #include "bit.h" #include "periph_conf.h" #include "periph/uart.h" +#if MODULE_PERIPH_LLWU +#include "llwu.h" +#endif #define ENABLE_DEBUG (0) #include "debug.h" @@ -77,6 +80,20 @@ #define LPUART_1_SRC 0 #endif +#ifndef LPUART_IDLECFG +/* See IDLECFG in the reference manual. Longer idle configurations give more + * robust LPUART RX from low power modes, but will also keep the CPU awake for + * longer periods. */ +#define LPUART_IDLECFG (0b001) +#endif + +typedef struct { + uart_rx_cb_t rx_cb; /**< data received interrupt callback */ + void *arg; /**< argument to both callback routines */ + unsigned active; /**< set to 1 while the receiver is active, to avoid mismatched PM_BLOCK/PM_UNBLOCK */ + unsigned enabled; /**< set to 1 while the receiver is enabled, to avoid mismatched PM_BLOCK/PM_UNBLOCK */ +} uart_isr_ctx_t; + /** * @brief Runtime configuration space, holds pointers to callback functions for RX */ @@ -86,9 +103,13 @@ static inline void uart_init_pins(uart_t uart); #if KINETIS_HAVE_UART static inline void uart_init_uart(uart_t uart, uint32_t baudrate); +static inline void uart_poweron_uart(uart_t uart); +static inline void uart_poweroff_uart(uart_t uart); #endif #if KINETIS_HAVE_LPUART static inline void uart_init_lpuart(uart_t uart, uint32_t baudrate); +static inline void uart_poweron_lpuart(uart_t uart); +static inline void uart_poweroff_lpuart(uart_t uart); #endif /* Only use the dispatch function for uart_write if both UART and LPUART are @@ -106,19 +127,47 @@ KINETIS_UART_WRITE_INLINE void uart_write_lpuart(uart_t uart, const uint8_t *dat #endif #endif +#ifdef MODULE_PERIPH_LLWU +/** + * @brief LLWU callback for UART RX pin + * + * This function is called only when the CPU is in LLS mode and a falling edge + * occurs on the RX pin. + */ +static void uart_llwu_cb(void *arg) +{ + uart_t uart = (uart_t)arg; + if (!config[uart].active) { + config[uart].active = 1; + /* Keep CPU on until we are finished with RX */ + DEBUG("LLS UART\n"); + PM_BLOCK(KINETIS_PM_STOP); + } +} +#endif + int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg) { assert(uart < UART_NUMOF); + /* disable interrupts from UART module */ + NVIC_DisableIRQ(uart_config[uart].irqn); + + /* Turn on module clock gate */ + bit_set32(uart_config[uart].scgc_addr, uart_config[uart].scgc_bit); + + /* Power off before messing with settings, this will ensure a consistent + * state if we are re-initializing an already initialized module */ + uart_poweroff(uart); + /* remember callback addresses */ config[uart].rx_cb = rx_cb; config[uart].arg = arg; + config[uart].active = 0; + config[uart].enabled = 0; uart_init_pins(uart); - /* Turn on module clock gate */ - bit_set32(uart_config[uart].scgc_addr, uart_config[uart].scgc_bit); - switch (uart_config[uart].type) { #if KINETIS_HAVE_UART case KINETIS_UART: @@ -133,19 +182,97 @@ int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg) default: return UART_NODEV; } + + /* Turn on module */ + uart_poweron(uart); + + /* enable interrupts from UART module */ + NVIC_EnableIRQ(uart_config[uart].irqn); + return UART_OK; } void uart_poweron(uart_t uart) { - (void)uart; - /* not implemented (yet) */ + assert(uart < UART_NUMOF); + unsigned state = irq_disable(); + if (config[uart].rx_cb) { + if (!config[uart].enabled) { + config[uart].enabled = 1; +#ifdef MODULE_PERIPH_LLWU + if (uart_config[uart].llwu_rx != LLWU_WAKEUP_PIN_UNDEF) { + /* Configure the RX pin for LLWU wakeup to be able to use RX in LLS mode */ + llwu_wakeup_pin_set(uart_config[uart].llwu_rx, LLWU_WAKEUP_EDGE_FALLING, uart_llwu_cb, (void*)uart); + } + else +#endif + { + /* UART and LPUART receivers are stopped in LLS, prevent LLS when there + * is a configured RX callback and no LLWU wakeup pin configured */ + DEBUG("uart: Blocking LLS\n"); + PM_BLOCK(KINETIS_PM_LLS); + } + } + } + switch (uart_config[uart].type) { +#if KINETIS_HAVE_UART + case KINETIS_UART: + uart_poweron_uart(uart); + break; +#endif +#if KINETIS_HAVE_LPUART + case KINETIS_LPUART: + uart_poweron_lpuart(uart); + break; +#endif + default: + break; + } + irq_restore(state); } void uart_poweroff(uart_t uart) { - (void)uart; - /* not implemented (yet) */ + assert(uart < UART_NUMOF); + unsigned state = irq_disable(); + if (config[uart].rx_cb) { + if (config[uart].enabled) { + config[uart].enabled = 0; +#ifdef MODULE_PERIPH_LLWU + if (uart_config[uart].llwu_rx != LLWU_WAKEUP_PIN_UNDEF) { + /* Disable LLWU wakeup for the RX pin */ + llwu_wakeup_pin_set(uart_config[uart].llwu_rx, LLWU_WAKEUP_EDGE_NONE, NULL, NULL); + } + else +#endif + { + /* re-enable LLS since we are not listening anymore */ + DEBUG("uart: unblocking LLS\n"); + PM_UNBLOCK(KINETIS_PM_LLS); + } + if (config[uart].active) { + /* We were in the middle of a RX sequence, need to release that + * PM blocker as well */ + PM_UNBLOCK(KINETIS_PM_STOP); + config[uart].active = 0; + } + } + } + switch (uart_config[uart].type) { +#if KINETIS_HAVE_UART + case KINETIS_UART: + uart_poweroff_uart(uart); + break; +#endif +#if KINETIS_HAVE_LPUART + case KINETIS_LPUART: + uart_poweroff_lpuart(uart); + break; +#endif + default: + break; + } + irq_restore(state); } #if KINETIS_HAVE_UART && KINETIS_HAVE_LPUART @@ -189,7 +316,7 @@ static inline void uart_init_uart(uart_t uart, uint32_t baudrate) clk = uart_config[uart].freq; /* disable transmitter and receiver */ - dev->C2 &= ~(UART_C2_TE_MASK | UART_C2_RE_MASK); + dev->C2 = 0; /* Select mode */ dev->C1 = uart_config[uart].mode; @@ -213,8 +340,8 @@ static inline void uart_init_uart(uart_t uart, uint32_t baudrate) * TXFIFOSIZE == 0 means size = 1 (i.e. only one byte, no hardware FIFO) */ if ((dev->PFIFO & UART_PFIFO_TXFIFOSIZE_MASK) != 0) { uint8_t txfifo_size = - (2 << ((dev->PFIFO & UART_PFIFO_TXFIFOSIZE_MASK) >> - UART_PFIFO_TXFIFOSIZE_SHIFT)); + (2 << ((dev->PFIFO & UART_PFIFO_TXFIFOSIZE_MASK) >> + UART_PFIFO_TXFIFOSIZE_SHIFT)); dev->TWFIFO = UART_TWFIFO_TXWATER(txfifo_size - 1); } else { @@ -228,15 +355,19 @@ static inline void uart_init_uart(uart_t uart, uint32_t baudrate) dev->CFIFO = UART_CFIFO_RXFLUSH_MASK | UART_CFIFO_TXFLUSH_MASK; #endif /* KINETIS_UART_ADVANCED */ - /* enable transmitter and receiver + RX interrupt */ - dev->C2 |= UART_C2_TE_MASK | UART_C2_RE_MASK | UART_C2_RIE_MASK; - - /* enable receive interrupt */ - NVIC_EnableIRQ(uart_config[uart].irqn); + if (config[uart].rx_cb) { + /* enable RX active edge interrupt */ + bit_set8(&dev->BDH, UART_BDH_RXEDGIE_SHIFT); + /* enable receiver + RX interrupt + IDLE interrupt */ + dev->C2 = UART_C2_RIE_MASK | UART_C2_ILIE_MASK; + /* enable interrupts on failure flags */ + dev->C3 |= UART_C3_ORIE_MASK | UART_C3_PEIE_MASK | UART_C3_FEIE_MASK | UART_C3_NEIE_MASK; + } + /* clear interrupt flags */ + uint8_t s = dev->S2; + dev->S2 = s; } -#endif /* KINETIS_HAVE_UART */ -#if KINETIS_HAVE_UART KINETIS_UART_WRITE_INLINE void uart_write_uart(uart_t uart, const uint8_t *data, size_t len) { UART_Type *dev = uart_config[uart].dev; @@ -245,6 +376,30 @@ KINETIS_UART_WRITE_INLINE void uart_write_uart(uart_t uart, const uint8_t *data, while (!(dev->S1 & UART_S1_TDRE_MASK)) {} dev->D = data[i]; } + /* Wait for transmission complete */ + while ((dev->S1 & UART_S1_TC_MASK) == 0) {} +} + +static inline void uart_poweron_uart(uart_t uart) +{ + UART_Type *dev = uart_config[uart].dev; + + /* Enable transmitter */ + bit_set8(&dev->C2, UART_C2_TE_SHIFT); + if (config[uart].rx_cb) { + /* Enable receiver */ + bit_set8(&dev->C2, UART_C2_RE_SHIFT); + } +} + +static inline void uart_poweroff_uart(uart_t uart) +{ + UART_Type *dev = uart_config[uart].dev; + + /* Disable receiver */ + bit_clear8(&dev->C2, UART_C2_RE_SHIFT); + /* Disable transmitter */ + bit_clear8(&dev->C2, UART_C2_TE_SHIFT); } #if defined(UART_0_ISR) || defined(UART_1_ISR) || defined(UART_2_ISR) || \ @@ -253,31 +408,64 @@ static inline void irq_handler_uart(uart_t uart) { UART_Type *dev = uart_config[uart].dev; - /* - * On Cortex-M0, it happens that S1 is read with LDR - * instruction instead of LDRB. This will read the data register - * at the same time and arrived byte will be lost. Maybe it's a GCC bug. - * - * Observed with: arm-none-eabi-gcc (4.8.3-8+..) - * It does not happen with: arm-none-eabi-gcc (4.8.3-9+11) - */ - - if (dev->S1 & UART_S1_RDRF_MASK) { - /* RDRF flag will be cleared when dev-D was read */ - uint8_t data = dev->D; - - if (config[uart].rx_cb != NULL) { + uint8_t s1 = dev->S1; + uint8_t s2 = dev->S2; + /* Clear IRQ flags */ + dev->S2 = s2; + /* The IRQ flags in S1 are cleared by reading the D register */ + uint8_t data = dev->D; + (void) data; + if (dev->SFIFO & UART_SFIFO_RXUF_MASK) { + /* RX FIFO underrun occurred, flush the RX FIFO to get the internal + * pointer back in sync */ + dev->CFIFO |= UART_CFIFO_RXFLUSH_MASK; + /* Clear SFIFO flags */ + dev->SFIFO = dev->SFIFO; + DEBUG("UART RXUF\n"); + } + DEBUG("U: %c\n", data); + if (s2 & UART_S2_RXEDGIF_MASK) { + if (!config[uart].active) { + config[uart].active = 1; + /* Keep CPU on until we are finished with RX */ + DEBUG("UART ACTIVE\n"); + PM_BLOCK(KINETIS_PM_STOP); + } + } + if (s1 & UART_S1_OR_MASK) { + /* UART overrun, some data has been lost */ + DEBUG("UART OR\n"); + } + if (s1 & UART_S1_RDRF_MASK) { + if (s1 & (UART_S1_FE_MASK | UART_S1_PF_MASK | UART_S1_NF_MASK)) { + if (s1 & UART_S1_FE_MASK) { + DEBUG("UART framing error %02x\n", (unsigned) s1); + } + if (s1 & UART_S1_PF_MASK) { + DEBUG("UART parity error %02x\n", (unsigned) s1); + } + if (s1 & UART_S1_NF_MASK) { + DEBUG("UART noise %02x\n", (unsigned) s1); + } + /* FE is set when a logic 0 is accepted as the stop bit. */ + /* PF is set when PE is set, S2[LBKDE] is disabled, and the parity + * of the received data does not match its parity bit. */ + /* NF is set when the UART detects noise on the receiver input. */ + } + /* Only run callback if no error occurred */ + else if (config[uart].rx_cb != NULL) { config[uart].rx_cb(config[uart].arg, data); } } - -#if (KINETIS_UART_ADVANCED == 0) - /* clear overrun flag */ - if (dev->S1 & UART_S1_OR_MASK) { - dev->S1 = UART_S1_OR_MASK; + if (s1 & UART_S1_IDLE_MASK) { + if (config[uart].active) { + config[uart].active = 0; + /* Let the CPU sleep when idle */ + PM_UNBLOCK(KINETIS_PM_STOP); + DEBUG("UART IDLE\n"); + } } -#endif - + DEBUG("UART: s1 %x C1 %x C2 %x S1 %x S2 %x D %x SF %x\n", s1, dev->C1, dev->C2, dev->S1, dev->S2, data, dev->SFIFO); cortexm_isr_end(); } #endif @@ -358,14 +546,21 @@ static inline void uart_init_lpuart(uart_t uart, uint32_t baudrate) } uint32_t sbr = clk / (best_osr * baudrate); - /* set baud rate */ + /* set baud rate, enable RX active edge interrupt */ dev->BAUD = LPUART_BAUD_OSR(best_osr - 1) | LPUART_BAUD_SBR(sbr); - /* enable transmitter and receiver + RX interrupt */ - dev->CTRL |= LPUART_CTRL_TE_MASK | LPUART_CTRL_RE_MASK | LPUART_CTRL_RIE_MASK; - - /* enable receive interrupt */ - NVIC_EnableIRQ(uart_config[uart].irqn); + if (config[uart].rx_cb) { + /* enable RX active edge interrupt */ + bit_set32(&dev->BAUD, LPUART_BAUD_RXEDGIE_SHIFT); + /* enable RX interrupt + error interrupts */ + dev->CTRL |= LPUART_CTRL_RIE_MASK | + LPUART_CTRL_ILIE_MASK | LPUART_CTRL_IDLECFG(LPUART_IDLECFG) | + LPUART_CTRL_ORIE_MASK | LPUART_CTRL_PEIE_MASK | + LPUART_CTRL_FEIE_MASK | LPUART_CTRL_NEIE_MASK; + } + /* clear interrupt flags */ + uint32_t s = dev->STAT; + dev->STAT = s; } KINETIS_UART_WRITE_INLINE void uart_write_lpuart(uart_t uart, const uint8_t *data, size_t len) @@ -376,6 +571,30 @@ KINETIS_UART_WRITE_INLINE void uart_write_lpuart(uart_t uart, const uint8_t *dat while ((dev->STAT & LPUART_STAT_TDRE_MASK) == 0) {} dev->DATA = data[i]; } + /* Wait for transmission complete */ + while ((dev->STAT & LPUART_STAT_TC_MASK) == 0) {} +} + +static inline void uart_poweron_lpuart(uart_t uart) +{ + LPUART_Type *dev = uart_config[uart].dev; + + /* Enable transmitter */ + bit_set32(&dev->CTRL, LPUART_CTRL_TE_SHIFT); + if (config[uart].rx_cb) { + /* Enable receiver */ + bit_set32(&dev->CTRL, LPUART_CTRL_RE_SHIFT); + } +} + +static inline void uart_poweroff_lpuart(uart_t uart) +{ + LPUART_Type *dev = uart_config[uart].dev; + + /* Disable receiver */ + bit_clear32(&dev->CTRL, LPUART_CTRL_RE_SHIFT); + /* Disable transmitter */ + bit_clear32(&dev->CTRL, LPUART_CTRL_TE_SHIFT); } #if defined(LPUART_0_ISR) || defined(LPUART_1_ISR) || defined(LPUART_2_ISR) || \ @@ -387,6 +606,14 @@ static inline void irq_handler_lpuart(uart_t uart) /* Clear all IRQ flags */ dev->STAT = stat; + if (stat & LPUART_STAT_RXEDGIF_MASK) { + if (!config[uart].active) { + config[uart].active = 1; + /* Keep CPU on until we are finished with RX */ + DEBUG("LPUART ACTIVE\n"); + PM_BLOCK(KINETIS_PM_STOP); + } + } if (stat & LPUART_STAT_RDRF_MASK) { /* RDRF flag will be cleared when LPUART_DATA is read */ uint8_t data = dev->DATA; @@ -413,6 +640,14 @@ static inline void irq_handler_lpuart(uart_t uart) * receive the data */ DEBUG("LPUART overrun %08" PRIx32 "\n", stat); } + if (stat & LPUART_STAT_IDLE_MASK) { + if (config[uart].active) { + config[uart].active = 0; + /* Let the CPU sleep when idle */ + PM_UNBLOCK(KINETIS_PM_STOP); + DEBUG("LPUART IDLE\n"); + } + } cortexm_isr_end(); }