diff --git a/cpu/sam0_common/Kconfig b/cpu/sam0_common/Kconfig index aadbae9d8af3..ff64fc119374 100644 --- a/cpu/sam0_common/Kconfig +++ b/cpu/sam0_common/Kconfig @@ -16,6 +16,10 @@ config CPU_COMMON_SAM0 select HAS_PERIPH_GPIO select HAS_PERIPH_GPIO_IRQ select HAS_PERIPH_GPIO_LL + select HAS_PERIPH_GPIO_LL_IRQ + select HAS_PERIPH_GPIO_LL_IRQ_LEVEL_TRIGGERED_HIGH + select HAS_PERIPH_GPIO_LL_IRQ_LEVEL_TRIGGERED_LOW + select HAS_PERIPH_GPIO_LL_IRQ_UNMASK select HAS_PERIPH_I2C_RECONFIGURE select HAS_PERIPH_RTT_SET_COUNTER select HAS_PERIPH_RTT_OVERFLOW diff --git a/cpu/sam0_common/Makefile.features b/cpu/sam0_common/Makefile.features index 505b4561ee0e..43d620c6f924 100644 --- a/cpu/sam0_common/Makefile.features +++ b/cpu/sam0_common/Makefile.features @@ -14,6 +14,10 @@ FEATURES_PROVIDED += periph_flashpage_pagewise FEATURES_PROVIDED += periph_flashpage_rwee FEATURES_PROVIDED += periph_gpio periph_gpio_irq FEATURES_PROVIDED += periph_gpio_ll +FEATURES_PROVIDED += periph_gpio_ll_irq +FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_high +FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_low +FEATURES_PROVIDED += periph_gpio_ll_irq_unmask FEATURES_PROVIDED += periph_i2c_reconfigure FEATURES_PROVIDED += periph_rtt_overflow FEATURES_PROVIDED += periph_rtt_set_counter @@ -36,6 +40,9 @@ FEATURES_PROVIDED += periph_wdt periph_wdt_cb periph_wdt_warning_period FEATURES_CONFLICT += periph_rtc:periph_rtt FEATURES_CONFLICT_MSG += "The RTC and RTT map to the same hardware peripheral." +FEATURES_CONFLICT += periph_gpio_irq:periph_gpio_ll_irq +FEATURES_CONFLICT_MSG += "GPIO IRQs can only be managed with one API." + include $(RIOTCPU)/cortexm_common/Makefile.features # Add sam0 configurations after including cortexm_common so sam0 takes precendence diff --git a/cpu/sam0_common/include/gpio_ll_arch.h b/cpu/sam0_common/include/gpio_ll_arch.h index 9d99c96deaa5..8f72b1997cd2 100644 --- a/cpu/sam0_common/include/gpio_ll_arch.h +++ b/cpu/sam0_common/include/gpio_ll_arch.h @@ -1,6 +1,7 @@ /* * Copyright (C) 2016 Freie Universität Berlin * 2017 OTA keys S.A. + * 2023 Otto-von-Guericke-Universität Magdeburg * * 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 @@ -15,11 +16,7 @@ * @file * @brief CPU specific part of the Peripheral GPIO Low-Level API * - * @author Troels Hoffmeyer - * @author Thomas Eichinger - * @author Kaspar Schleiser - * @author Hauke Petersen - * @author Juergen Fitschen + * @author Marian Buschsieweke */ #ifndef GPIO_LL_ARCH_H diff --git a/cpu/sam0_common/include/periph_cpu_common.h b/cpu/sam0_common/include/periph_cpu_common.h index 0b2627d33c4c..f8f63e01cc84 100644 --- a/cpu/sam0_common/include/periph_cpu_common.h +++ b/cpu/sam0_common/include/periph_cpu_common.h @@ -255,6 +255,7 @@ typedef enum { GPIO_MUX_L = 0xb, /**< select peripheral function L */ GPIO_MUX_M = 0xc, /**< select peripheral function M */ GPIO_MUX_N = 0xd, /**< select peripheral function N */ + GPIO_MUX_DISABLED = 0xff, /**< Disable */ } gpio_mux_t; #endif diff --git a/cpu/sam0_common/periph/gpio_ll.c b/cpu/sam0_common/periph/gpio_ll.c index 41a202520fce..8c399950dd1e 100644 --- a/cpu/sam0_common/periph/gpio_ll.c +++ b/cpu/sam0_common/periph/gpio_ll.c @@ -53,6 +53,29 @@ static inline void print_str(const char *str) } #endif +void gpio_ll_mux(gpio_port_t port, uint8_t pin, gpio_mux_t mux) +{ + assume(pin < 32); + assume(gpio_port_unpack_addr(port) == NULL); + PortGroup *iobus = (PortGroup *)port; + PortGroup *apb = sam0_gpio_iobus2ap(iobus); + + unsigned irq_state = irq_disable(); + if (mux == GPIO_MUX_DISABLED) { + apb->PINCFG[pin].bit.PMUXEN = 0; + } + else { + unsigned pmux_reg = pin >> 1; + unsigned pmux_pos = (pin & 0x01) << 2; + apb->PINCFG[pin].bit.PMUXEN = 1; + unsigned pmux = apb->PMUX[pmux_reg].reg; + pmux &= ~(PORT_PMUX_PMUXE_Msk << pmux_pos); + pmux |= (unsigned)mux << pmux_pos; + apb->PMUX[pmux_reg].reg = pmux; + } + irq_restore(irq_state); +} + int gpio_ll_init(gpio_port_t port, uint8_t pin, gpio_conf_t conf) { assume(pin < 32); diff --git a/cpu/sam0_common/periph/gpio_ll_irq.c b/cpu/sam0_common/periph/gpio_ll_irq.c new file mode 100644 index 000000000000..49c35bf88d72 --- /dev/null +++ b/cpu/sam0_common/periph/gpio_ll_irq.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2015 HAW Hamburg + * 2016 INRIA + * 2023 Gerson Fernando Budke + * 2023 Hugues Larrive + * 2023 Otto-von-Guericke-Universität Magdeburg + * + * 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_sam0_common + * @ingroup drivers_periph_gpio_ll_irq + * @{ + * + * @file + * @brief IRQ implementation of the GPIO Low-Level API for SAM0 + * + * @author Marian Buschsieweke + * + * @} + */ + +#include + +#include "bitarithm.h" +#include "cpu.h" +#include "irq.h" +#include "periph/gpio_ll_irq.h" +#include "periph_conf.h" +#include "periph_cpu.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/** + * @brief Number of external interrupt lines + */ +#ifdef CPU_COMMON_SAML1X +#define IRQS_NUMOF (8U) +#else +#define IRQS_NUMOF (16U) +#endif + +/** + * @brief The GCLK used for clocking EXTI + */ +#ifndef CONFIG_SAM0_GCLK_GPIO +#define CONFIG_SAM0_GCLK_GPIO (SAM0_GCLK_MAIN) +#endif + +/* Consistify naming */ +#ifndef EIC_SEC +#define EIC_SEC EIC +#endif + +struct isr_ctx { + gpio_ll_cb_t cb; + void *arg; +}; + +static struct isr_ctx isr_ctx[IRQS_NUMOF]; + +extern void gpio_ll_mux(gpio_port_t port, uint8_t pin, gpio_mux_t mux); + +static int get_exti_num(unsigned port_num, uint8_t pin) +{ + if (port_num >= ARRAY_SIZE(exti_config)) { + return -1; + } + return exti_config[port_num][pin]; +} + +static IRQn_Type exti2irqn(unsigned exti_num) +{ + (void)exti_num; + assume(exti_num < IRQS_NUMOF); +#if defined(CPU_COMMON_SAMD5X) + return EIC_0_IRQn + exti_num; +#elif defined(CPU_COMMON_SAML1X) + if (exti_num <= 3) { + return EIC_0_IRQn + exti_num; + } + return EIC_OTHER_IRQn; +#else + return EIC_IRQn; +#endif +} + +static void enable_trigger(unsigned exti_num, gpio_irq_trig_t trig) +{ + unsigned config_reg = exti_num >> 3; + unsigned config_pos = (exti_num & 0x7) << 2; + + /* configure trigger with IRQs disabled */ + unsigned irq_state = irq_disable(); + uint32_t conf = EIC_SEC->CONFIG[config_reg].reg; + conf &= ~(EIC_CONFIG_SENSE0_Msk << config_pos); + conf |= ((uint32_t)trig) << config_pos; + EIC_SEC->CONFIG[config_reg].reg = conf; + irq_restore(irq_state); + + NVIC_EnableIRQ(exti2irqn(exti_num)); +} + +static void disable_trigger(unsigned exti_num) +{ + unsigned config_reg = exti_num >> 3; + unsigned config_pos = (exti_num & 0x7) << 2; + + /* configure trigger with IRQs disabled */ + unsigned irq_state = irq_disable(); + uint32_t conf = EIC_SEC->CONFIG[config_reg].reg; + conf &= ~(EIC_CONFIG_SENSE0_Msk << config_pos); + EIC_SEC->CONFIG[config_reg].reg = conf; + irq_restore(irq_state); +} + +static void eic_sync(void) +{ +#ifdef EIC_STATUS_SYNCBUSY + while (EIC_SEC->STATUS.bit.SYNCBUSY) { } +#endif +#ifdef EIC_SYNCBUSY_ENABLE + while (EIC_SEC->SYNCBUSY.bit.ENABLE) { } +#endif +} + +static void eic_enable_clock(void) +{ + /* Enable EIC clock */ +#ifdef PM_APBAMASK_EIC + PM->APBAMASK.reg |= PM_APBAMASK_EIC; + GCLK->CLKCTRL.reg = EIC_GCLK_ID + | GCLK_CLKCTRL_CLKEN + | GCLK_CLKCTRL_GEN(CONFIG_SAM0_GCLK_GPIO); + while (GCLK->STATUS.bit.SYNCBUSY) {} +#endif +#ifdef MCLK_APBAMASK_EIC + MCLK->APBAMASK.reg |= MCLK_APBAMASK_EIC; + GCLK->PCHCTRL[EIC_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN(CONFIG_SAM0_GCLK_GPIO); + /* disable the EIC module*/ + EIC_SEC->CTRLA.reg = 0; + eic_sync(); +#endif +} + +static void eic_enable(void) +{ +#ifdef EIC_CTRL_ENABLE + EIC_SEC->CTRL.reg = EIC_CTRL_ENABLE; +#endif +#ifdef EIC_CTRLA_ENABLE + EIC_SEC->CTRLA.reg = EIC_CTRLA_ENABLE; +#endif +} + +void gpio_ll_irq_mask(gpio_port_t port, uint8_t pin) +{ + unsigned port_num = GPIO_PORT_NUM(port); + int exti_num = get_exti_num(port_num, pin); + assume((unsigned)exti_num < IRQS_NUMOF); + + EIC_SEC->INTENCLR.reg = 1U << exti_num; +} + +void gpio_ll_irq_unmask(gpio_port_t port, uint8_t pin) +{ + unsigned port_num = GPIO_PORT_NUM(port); + int exti_num = get_exti_num(port_num, pin); + assume((unsigned)exti_num < IRQS_NUMOF); + + EIC_SEC->INTENSET.reg = 1U << exti_num; +} + +void gpio_ll_irq_unmask_and_clear(gpio_port_t port, uint8_t pin) +{ + unsigned port_num = GPIO_PORT_NUM(port); + int exti_num = get_exti_num(port_num, pin); + assume(exti_num >= 0); + + uint32_t mask = 1U << exti_num; + EIC_SEC->INTFLAG.reg = mask; + EIC_SEC->INTENSET.reg = mask; +} + +int gpio_ll_irq(gpio_port_t port, uint8_t pin, gpio_irq_trig_t trig, + gpio_ll_cb_t cb, void *arg) +{ + unsigned port_num = GPIO_PORT_NUM(port); + int exti_num = get_exti_num(port_num, pin); + + assume(cb); + + if (exti_num < 0) { + return -ENOTSUP; + } + + if (isr_ctx[exti_num].cb) { + DEBUG("[gpio_ll_irq] IRQ already configured for EXTI %d (P%c%u)\n", + exti_num, 'A' + (char)port_num, (unsigned)pin); + } + + isr_ctx[exti_num].cb = cb; + isr_ctx[exti_num].arg = arg; + + gpio_ll_mux(port, pin, GPIO_MUX_A); + + eic_enable_clock(); + + enable_trigger(exti_num, trig); + + /* clear any spurious IRQ */ + EIC_SEC->INTFLAG.reg = 1U << exti_num; + + /* enable IRQ */ + EIC_SEC->INTENSET.reg = 1U << exti_num; + +#ifdef EIC_WAKEUP_WAKEUPEN0 + unsigned irq_state = irq_disable(); + EIC_SEC->WAKEUP.reg |= 1U << exti_num; + irq_restore(irq_state); +#endif + + eic_enable(); + + eic_sync(); + + return 0; +} + +void gpio_ll_irq_off(gpio_port_t port, uint8_t pin) +{ + unsigned port_num = GPIO_PORT_NUM(port); + int exti_num = get_exti_num(port_num, pin); + + assume((unsigned)exti_num < IRQS_NUMOF); + + /* First, disable IRQs */ + EIC_SEC->INTENCLR.reg = 1U << exti_num; + + gpio_ll_mux(port, pin, GPIO_MUX_DISABLED); + + /* Disabling the trigger may conserve power */ + disable_trigger(exti_num); + +#ifdef EIC_WAKEUP_WAKEUPEN0 + unsigned irq_state = irq_disable(); + EIC_SEC->WAKEUP.reg &= ~(1U << exti_num); + irq_restore(irq_state); +#endif + + /* Finally, clear the callback */ + isr_ctx[exti_num].cb = NULL; +} + +MAYBE_UNUSED +static void isr_eic_unknown_num(void) +{ + /* read & clear interrupt flags */ + uint32_t state = EIC_SEC->INTFLAG.reg & EIC_SEC->INTENSET.reg; + state &= EIC_INTFLAG_EXTINT_Msk; + EIC_SEC->INTFLAG.reg = state; + + /* execute interrupt callbacks */ + uint8_t num = 0; + while (state) { + state = bitarithm_test_and_clear(state, &num); + isr_ctx[num].cb(isr_ctx[num].arg); + } + + cortexm_isr_end(); +} + +MAYBE_UNUSED +static void isr_eic_known_num(unsigned num) +{ + EIC_SEC->INTFLAG.reg = 1U << num; + isr_ctx[num].cb(isr_ctx[num].arg); + cortexm_isr_end(); +} + +#if !defined(CPU_COMMON_SAML1X) && !defined(CPU_COMMON_SAMD5X) +void isr_eic(void) { isr_eic_unknown_num(); } +#endif + +#if defined(CPU_COMMON_SAML1X) +void isr_eic_other(void) { isr_eic_unknown_num(); } +#endif + +#if defined(CPU_COMMON_SAML1X) || defined(CPU_COMMON_SAMD5X) +void isr_eic0(void) { isr_eic_known_num(0); } +void isr_eic1(void) { isr_eic_known_num(1); } +void isr_eic2(void) { isr_eic_known_num(2); } +void isr_eic3(void) { isr_eic_known_num(3); } +#endif +#if defined(CPU_COMMON_SAMD5X) +void isr_eic4(void) { isr_eic_known_num(4); } +void isr_eic5(void) { isr_eic_known_num(5); } +void isr_eic6(void) { isr_eic_known_num(6); } +void isr_eic7(void) { isr_eic_known_num(7); } +void isr_eic8(void) { isr_eic_known_num(8); } +void isr_eic9(void) { isr_eic_known_num(9); } +void isr_eic10(void) { isr_eic_known_num(10); } +void isr_eic11(void) { isr_eic_known_num(11); } +void isr_eic12(void) { isr_eic_known_num(12); } +void isr_eic13(void) { isr_eic_known_num(13); } +void isr_eic14(void) { isr_eic_known_num(14); } +void isr_eic15(void) { isr_eic_known_num(15); } +#endif