diff --git a/cpu/atmega_common/Makefile.features b/cpu/atmega_common/Makefile.features index 952f58126ec1..5520c13a2b41 100644 --- a/cpu/atmega_common/Makefile.features +++ b/cpu/atmega_common/Makefile.features @@ -10,6 +10,7 @@ FEATURES_PROVIDED += periph_gpio periph_gpio_irq FEATURES_PROVIDED += periph_gpio_ll FEATURES_PROVIDED += periph_gpio_ll_input_pull_up FEATURES_PROVIDED += periph_gpio_ll_irq +FEATURES_PROVIDED += periph_gpio_ll_irq_edge_triggered_both FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_low FEATURES_PROVIDED += periph_gpio_ll_irq_unmask FEATURES_PROVIDED += periph_gpio_ll_switch_dir diff --git a/cpu/esp32/Makefile.features b/cpu/esp32/Makefile.features index 68ee7f15d038..95f518ffba53 100644 --- a/cpu/esp32/Makefile.features +++ b/cpu/esp32/Makefile.features @@ -26,6 +26,7 @@ FEATURES_PROVIDED += periph_gpio_ll_disconnect FEATURES_PROVIDED += periph_gpio_ll_input_pull_down FEATURES_PROVIDED += periph_gpio_ll_input_pull_up FEATURES_PROVIDED += periph_gpio_ll_irq +FEATURES_PROVIDED += periph_gpio_ll_irq_edge_triggered_both FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_high FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_low FEATURES_PROVIDED += periph_gpio_ll_open_drain diff --git a/cpu/gd32v/Makefile.features b/cpu/gd32v/Makefile.features index cf67a7b3b833..7436a8954c9a 100644 --- a/cpu/gd32v/Makefile.features +++ b/cpu/gd32v/Makefile.features @@ -9,6 +9,7 @@ FEATURES_PROVIDED += periph_gpio_ll_disconnect FEATURES_PROVIDED += periph_gpio_ll_input_pull_down FEATURES_PROVIDED += periph_gpio_ll_input_pull_up FEATURES_PROVIDED += periph_gpio_ll_irq +FEATURES_PROVIDED += periph_gpio_ll_irq_edge_triggered_both FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_high FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_low FEATURES_PROVIDED += periph_gpio_ll_open_drain diff --git a/cpu/msp430/Makefile.dep b/cpu/msp430/Makefile.dep index fd5f4e6f34c1..217815dc1f5a 100644 --- a/cpu/msp430/Makefile.dep +++ b/cpu/msp430/Makefile.dep @@ -5,5 +5,10 @@ ifneq (,$(filter newlib,$(USEMODULE))) DEFAULT_MODULE += newlib_nano endif +ifneq (,$(filter periph_gpio,$(USEMODULE))) + # the legacy periph_gpio driver uses gpio_port from periph_gpio_ll + FEATURES_REQUIRED += periph_gpio_ll +endif + # Make calls to malloc and friends thread-safe USEMODULE += malloc_thread_safe diff --git a/cpu/msp430/Makefile.features b/cpu/msp430/Makefile.features index 5ff6154290f1..7107cccde90f 100644 --- a/cpu/msp430/Makefile.features +++ b/cpu/msp430/Makefile.features @@ -3,6 +3,8 @@ CPU_CORE = msp430 ifneq (,$(filter msp430f2% msp430g2%,$(CPU_MODEL))) CPU_FAM := msp430_f2xx_g2xx + FEATURES_PROVIDED += periph_gpio_ll_input_pull_down + FEATURES_PROVIDED += periph_gpio_ll_input_pull_up FEATURES_PROVIDED += periph_spi_reconfigure endif @@ -20,3 +22,7 @@ FEATURES_PROVIDED += periph_flashpage_in_address_space FEATURES_PROVIDED += periph_flashpage_pagewise FEATURES_PROVIDED += periph_pm FEATURES_PROVIDED += periph_timer_query_freqs + +FEATURES_PROVIDED += periph_gpio_ll +FEATURES_PROVIDED += periph_gpio_ll_irq +FEATURES_PROVIDED += periph_gpio_ll_switch_dir diff --git a/cpu/msp430/clock.c b/cpu/msp430/clock.c index 6a76bedee907..4c31999d0b3c 100644 --- a/cpu/msp430/clock.c +++ b/cpu/msp430/clock.c @@ -410,9 +410,13 @@ void pm_set_lowest(void) state |= OSCOFF; } - /* write new state */ + /* Write new state. This should not need NOPs before and after, but the + * assembler warning about possibly disabled IRQs cannot be disabled, so + * let's waste two instructions for less noise. */ __asm__ volatile( + "nop" "\n\t" "mov.w %[state], SR" "\n\t" + "nop" "\n\t" : /* no outputs */ : [state] "r"(state) : "memory" diff --git a/cpu/msp430/cpu.c b/cpu/msp430/cpu.c index cdd4acbb43bd..fe9dfbd83a2f 100644 --- a/cpu/msp430/cpu.c +++ b/cpu/msp430/cpu.c @@ -9,7 +9,6 @@ */ #include "cpu.h" -#include "kernel_init.h" #include "irq.h" #include "sched.h" #include "thread.h" diff --git a/cpu/msp430/include/f2xx_g2xx/msp430_regs.h b/cpu/msp430/include/f2xx_g2xx/msp430_regs.h index f51e368acd5e..9b4608553c93 100644 --- a/cpu/msp430/include/f2xx_g2xx/msp430_regs.h +++ b/cpu/msp430/include/f2xx_g2xx/msp430_regs.h @@ -57,6 +57,31 @@ extern "C" { #define MSP430_USCI_B_FROM_USCI_A(usci_a) \ ((msp430_usci_b_t *)((uintptr_t)(usci_a) + MSP430_USCI_A_B_OFFSET)) +/** + * @brief GPIO Port 1/2 (with interrupt functionality) + */ +typedef struct { + msp430_port_t base; /**< common GPIO port registers */ + REG8 IFG; /**< interrupt flag */ + REG8 IES; /**< interrupt edge select */ + REG8 IE; /**< interrupt enable */ + REG8 SEL; /**< alternative function select */ + REG8 REN; /**< pull resistor enable */ +} msp430_port_p1_p2_t; + +/** + * @brief GPIO Port 7/8 (different register layout than Ports 1-6) + */ +typedef struct { + REG8 IN; /**< input data */ + uint8_t _padding1; /**< unrelated I/O */ + REG8 OD; /**< output data */ + uint8_t _padding2; /**< unrelated I/O */ + REG8 DIR; /**< pin direction */ + uint8_t _padding3; /**< unrelated I/O */ + REG8 SEL; /**< alternative function select */ +} msp430_port_p7_p8_t; + /** * @brief Universal Serial Control Interface Type A (USCI_A) Registers */ diff --git a/cpu/msp430/include/f2xx_g2xx/periph_cpu.h b/cpu/msp430/include/f2xx_g2xx/periph_cpu.h index 9f88879a93e7..65f39ed8a1a7 100644 --- a/cpu/msp430/include/f2xx_g2xx/periph_cpu.h +++ b/cpu/msp430/include/f2xx_g2xx/periph_cpu.h @@ -183,6 +183,16 @@ typedef struct { const msp430_usci_spi_params_t *spi; /**< The SPI configuration to use */ } spi_conf_t; +/** + * @brief Register map of GPIO PORT 7 + */ +extern msp430_port_p7_p8_t PORT_7; + +/** + * @brief Register map of GPIO PORT 8 + */ +extern msp430_port_p7_p8_t PORT_8; + /** * @brief Acquire and initialize USCI for use a SPI/UART peripheral * diff --git a/cpu/msp430/include/gpio_ll_arch.h b/cpu/msp430/include/gpio_ll_arch.h new file mode 100644 index 000000000000..4fb1838d7a04 --- /dev/null +++ b/cpu/msp430/include/gpio_ll_arch.h @@ -0,0 +1,208 @@ +/* + * Copyright (C) 2024 Marian Buschsieweke + * + * 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_msp430 + * @ingroup drivers_periph_gpio_ll + * @{ + * + * @file + * @brief CPU specific part of the Peripheral GPIO Low-Level API + * + * @author Marian Buschsieweke + */ + +#ifndef GPIO_LL_ARCH_H +#define GPIO_LL_ARCH_H + +#include "cpu.h" +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef DOXYGEN /* hide implementation specific details from Doxygen */ + +/* the memory layout of all GPIO peripherals is compatible, but the location + * in the address space is pretty much random */ + +#define GPIO_PORT_1 ((gpio_port_t)&PORT_1.base) +#define GPIO_PORT_2 ((gpio_port_t)&PORT_2.base) +#define GPIO_PORT_3 ((gpio_port_t)&PORT_3.base) +#define GPIO_PORT_4 ((gpio_port_t)&PORT_4.base) +#define GPIO_PORT_5 ((gpio_port_t)&PORT_5.base) +#define GPIO_PORT_6 ((gpio_port_t)&PORT_6.base) +/* Port 7 and 8 have different memory layout and are only available on F2xx/G2xx + * MCUs */ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) +# define GPIO_PORT_7 ((gpio_port_t)&PORT_7) +# define GPIO_PORT_8 ((gpio_port_t)&PORT_8) +#endif + +/* IMPORTANT IMPLEMENTATION INFO + * ============================= + * + * - MSP430 F2xx/G2xx do have PORT 7 and PORT 8, but those have an incompatible + * memory layout compared to the other ports. Hence, they need extra handling. + * However, constant folding should get ride of the branch and overhead if the + * GPIO port is a compile time constant + * - MSP430 has bit manipulation instructions that work on memory. E.g. + * `BIC.B %[mask], @%[ptr]` will implement `*ptr &= ~(mask)` in a single + * instruction. Same for setting or XORing bits. Hence, the code below + * may often look like it is missing `irq_disable()` ... `irq_restore()`, but + * in fact will be atomic due to the MSP430 instruction set. + */ + +gpio_port_t gpio_port(uword_t num); + +static inline uword_t gpio_ll_read(gpio_port_t port) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + const msp430_port_p7_p8_t *p = (void *)port; + return p->IN; + } +#endif + const msp430_port_t *p = (void *)port; + return p->IN; +} + +static inline uword_t gpio_ll_read_output(gpio_port_t port) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + const msp430_port_p7_p8_t *p = (void *)port; + return p->OD; + } +#endif + const msp430_port_t *p = (void *)port; + return p->OD; +} + +static inline void gpio_ll_set(gpio_port_t port, uword_t mask) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->OD |= mask; + return; + } +#endif + msp430_port_t *p = (void *)port; + p->OD |= mask; +} + +static inline void gpio_ll_clear(gpio_port_t port, uword_t mask) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->OD &= ~(mask); + return; + } +#endif + msp430_port_t *p = (void *)port; + p->OD &= ~(mask); +} + +static inline void gpio_ll_toggle(gpio_port_t port, uword_t mask) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->OD ^= mask; + return; + } +#endif + msp430_port_t *p = (void *)port; + p->OD ^= mask; +} + +static inline void gpio_ll_write(gpio_port_t port, uword_t value) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->OD = value; + return; + } +#endif + msp430_port_t *p = (void *)port; + p->OD = value; +} + +static inline gpio_port_t gpio_get_port(gpio_t pin) +{ + return gpio_port(gpio_get_pin_num(pin)); +} + +static inline uint8_t gpio_get_pin_num(gpio_t pin) +{ + return pin >> 8; +} + +static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t outputs) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->DIR |= outputs; + return; + } +#endif + msp430_port_t *p = (void *)port; + p->DIR |= outputs; +} + +static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t inputs) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + if (port >= (uintptr_t)(&PORT_7)) { + msp430_port_p7_p8_t *p = (void *)port; + p->DIR &= ~(inputs); + return; + } +#endif + msp430_port_t *p = (void *)port; + p->DIR &= ~(inputs); +} + +static inline gpio_port_t gpio_port_pack_addr(void *addr) +{ + return (gpio_port_t)addr; +} + +static inline void * gpio_port_unpack_addr(gpio_port_t port) +{ + if (port < RAMSTART) { + return NULL; + } + + return (void *)port; +} + +static inline bool is_gpio_port_num_valid(uint_fast8_t num) +{ +#if defined(CPU_FAM_MSP430_F2XX_G2XX) + return (num > 0) && (num <= 8); +#else + return (num > 0) && (num <= 6); +#endif +} + +uword_t gpio_port_num(gpio_port_t port); + +#endif /* DOXYGEN */ + +#ifdef __cplusplus +} +#endif + +#endif /* GPIO_LL_ARCH_H */ +/** @} */ diff --git a/cpu/msp430/include/msp430_regs_common.h b/cpu/msp430/include/msp430_regs_common.h index 9d3abd9e594d..1b57f4fddc18 100644 --- a/cpu/msp430/include/msp430_regs_common.h +++ b/cpu/msp430/include/msp430_regs_common.h @@ -92,17 +92,6 @@ typedef struct { REG8 DIR; /**< pin direction */ } msp430_port_t; -/** - * @brief GPIO Port 1/2 (with interrupt functionality) - */ -typedef struct { - msp430_port_t base; /**< common GPIO port registers */ - REG8 IFG; /**< interrupt flag */ - REG8 IES; /**< interrupt edge select */ - REG8 IE; /**< interrupt enable */ - REG8 SEL; /**< alternative function select */ -} msp430_port_p1_p2_t; - /** * @brief GPIO Port 3..6 (without interrupt functionality) */ @@ -124,67 +113,6 @@ typedef struct { REG16 CCR[7]; /**< capture compare channel values */ } msp430_timer_t; -/** - * @name MSP430 Common Peripheral Register Maps - * - * @details The addresses will be provided by the linker script using the - * vendor files. - * @{ - */ -/** - * @brief Register map of GPIO PORT 1 - */ -extern msp430_port_p1_p2_t PORT_1; -/** - * @brief Register map of GPIO PORT 2 - */ -extern msp430_port_p1_p2_t PORT_2; -/** - * @brief Register map of GPIO PORT 3 - */ -extern msp430_port_p3_p6_t PORT_3; -/** - * @brief Register map of GPIO PORT 4 - */ -extern msp430_port_p3_p6_t PORT_4; -/** - * @brief Register map of GPIO PORT 5 - */ -extern msp430_port_p3_p6_t PORT_5; -/** - * @brief Register map of GPIO PORT 6 - */ -extern msp430_port_p3_p6_t PORT_6; - -/** - * @brief Register map of the timer A control registers - */ -extern msp430_timer_t TIMER_A; - -/** - * @brief IRQ flags for TIMER_A - * - * Called TAIV in the data sheet / vendor files. This shallow alias - * makes the name more readable and does impedance matching for the type - * (`volatile uint16_t` vs `volatile short`). - */ -extern REG16 TIMER_A_IRQFLAGS; - -/** - * @brief IRQ flags for TIMER_B - * - * Called TBIV in the data sheet / vendor files. This shallow alias - * makes the name more readable and does impedance matching for the type - * (`volatile uint16_t` vs `volatile short`). - */ -extern REG16 TIMER_B_IRQFLAGS; - -/** - * @brief Register map of the timer B control registers - */ -extern msp430_timer_t TIMER_B; -/** @} */ - #ifdef __cplusplus } #endif diff --git a/cpu/msp430/include/periph_cpu_common.h b/cpu/msp430/include/periph_cpu_common.h index f23099cfd055..a3f5010d0169 100644 --- a/cpu/msp430/include/periph_cpu_common.h +++ b/cpu/msp430/include/periph_cpu_common.h @@ -64,6 +64,11 @@ typedef uint16_t gpio_t; */ #define TIMER_CHANNEL_NUMOF 7 +/** + * @brief Lowest address of the RAM, peripherals are below + */ +#define RAMSTART 0x200 + /** * @name Override flank selection values * @{ @@ -91,6 +96,42 @@ enum { P6 = 6, /**< PORT 6 */ }; +#ifndef DOXYGEN +#define HAVE_GPIO_STATE_T +typedef enum { + GPIO_INPUT, + GPIO_OUTPUT_PUSH_PULL, + GPIO_OUTPUT_OPEN_DRAIN, /**< not supported */ + GPIO_OUTPUT_OPEN_SOURCE, /**< not supported */ + GPIO_USED_BY_PERIPHERAL, /**< not supported */ + GPIO_DISCONNECT = GPIO_INPUT, +} gpio_state_t; + +#define HAVE_GPIO_SLEW_T +typedef enum { + GPIO_SLEW_SLOWEST = 0, + GPIO_SLEW_SLOW = 0, + GPIO_SLEW_FAST = 0, + GPIO_SLEW_FASTEST = 0, +} gpio_slew_t; + +#define HAVE_GPIO_PULL_STRENGTH_T +typedef enum { + GPIO_PULL_WEAKEST = 0, + GPIO_PULL_WEAK = 0, + GPIO_PULL_STRONG = 0, + GPIO_PULL_STRONGEST = 0 +} gpio_pull_strength_t; + +#define HAVE_GPIO_DRIVE_STRENGTH_T +typedef enum { + GPIO_DRIVE_WEAKEST = 0, + GPIO_DRIVE_WEAK = 0, + GPIO_DRIVE_STRONG = 0, + GPIO_DRIVE_STRONGEST = 0 +} gpio_drive_strength_t; +#endif /* !DOXYGEN */ + /** * @brief Enable or disable a pin to be used by peripheral modules * @@ -337,6 +378,68 @@ typedef struct { msp430_timer_clock_source_t clock_source; /**< Clock source to use */ } timer_conf_t; +/** + * @name MSP430 Common Peripheral Register Maps + * + * @details The addresses will be provided by the linker script using the + * vendor files. + * @{ + */ +/** + * @brief Register map of GPIO PORT 1 + */ +extern msp430_port_p1_p2_t PORT_1; +/** + * @brief Register map of GPIO PORT 2 + */ +extern msp430_port_p1_p2_t PORT_2; +/** + * @brief Register map of GPIO PORT 3 + */ +extern msp430_port_p3_p6_t PORT_3; +/** + * @brief Register map of GPIO PORT 4 + */ +extern msp430_port_p3_p6_t PORT_4; +/** + * @brief Register map of GPIO PORT 5 + */ +extern msp430_port_p3_p6_t PORT_5; +/** + * @brief Register map of GPIO PORT 6 + */ +extern msp430_port_p3_p6_t PORT_6; + +/** + * @brief Register map of the timer A control registers + */ +extern msp430_timer_t TIMER_A; + +/** + * @brief IRQ flags for TIMER_A + * + * Called TAIV in the data sheet / vendor files. This shallow alias + * makes the name more readable and does impedance matching for the type + * (`volatile uint16_t` vs `volatile short`). + */ +extern REG16 TIMER_A_IRQFLAGS; + +/** + * @brief IRQ flags for TIMER_B + * + * Called TBIV in the data sheet / vendor files. This shallow alias + * makes the name more readable and does impedance matching for the type + * (`volatile uint16_t` vs `volatile short`). + */ +extern REG16 TIMER_B_IRQFLAGS; + +/** + * @brief Register map of the timer B control registers + */ +extern msp430_timer_t TIMER_B; +/** @} */ + + /** * @brief Initialize the basic clock system to provide the main clock, * the subsystem clock, and the auxiliary clock. diff --git a/cpu/msp430/include/x1xx/msp430_regs.h b/cpu/msp430/include/x1xx/msp430_regs.h index 02b3c5053017..461a586a0e95 100644 --- a/cpu/msp430/include/x1xx/msp430_regs.h +++ b/cpu/msp430/include/x1xx/msp430_regs.h @@ -31,6 +31,17 @@ extern "C" { #endif +/** + * @brief GPIO Port 1/2 (with interrupt functionality) + */ +typedef struct { + msp430_port_t base; /**< common GPIO port registers */ + REG8 IFG; /**< interrupt flag */ + REG8 IES; /**< interrupt edge select */ + REG8 IE; /**< interrupt enable */ + REG8 SEL; /**< alternative function select */ +} msp430_port_p1_p2_t; + /** * @brief USART (UART, SPI and I2C) Registers */ diff --git a/cpu/msp430/ldscripts/msp430_f2xx_g2xx.ld b/cpu/msp430/ldscripts/msp430_f2xx_g2xx.ld index cb90d22587f2..e09994388d45 100644 --- a/cpu/msp430/ldscripts/msp430_f2xx_g2xx.ld +++ b/cpu/msp430/ldscripts/msp430_f2xx_g2xx.ld @@ -4,3 +4,5 @@ PROVIDE(USCI_A0 = UCA0ABCTL); PROVIDE(USCI_A1 = UCA1ABCTL); PROVIDE(USCI_B0 = UCB0CTL0); PROVIDE(USCI_B1 = UCB1CTL0); +PROVIDE(PORT_7 = P7IN); +PROVIDE(PORT_8 = P8IN); diff --git a/cpu/msp430/periph/gpio.c b/cpu/msp430/periph/gpio.c index d1800bb8bcfa..2629e906d518 100644 --- a/cpu/msp430/periph/gpio.c +++ b/cpu/msp430/periph/gpio.c @@ -20,9 +20,15 @@ */ #include "bitarithm.h" +#include "compiler_hints.h" #include "container.h" -#include "cpu.h" #include "periph/gpio.h" +#include "periph/gpio_ll.h" + +#ifdef MODULE_PERIPH_GPIO_IRQ +#include "modules.h" +#include "periph/gpio_ll_irq.h" +#endif /** * @brief Number of possible interrupt lines: 2 ports * 8 pins @@ -36,29 +42,20 @@ static msp430_port_t *_port(gpio_t pin) { - switch (pin >> 8) { - case 1: - return &PORT_1.base; - case 2: - return &PORT_2.base; - case 3: - return &PORT_3.base; - case 4: - return &PORT_4.base; - case 5: - return &PORT_5.base; - case 6: - return &PORT_6.base; - default: - return NULL; - } + return (msp430_port_t *)gpio_port(pin >> 8); } -static inline uint8_t _pin(gpio_t pin) +static inline uint8_t _pin_mask(gpio_t pin) { return (uint8_t)(pin & 0xff); } +MAYBE_UNUSED +static uint8_t _pin_num(gpio_t pin) +{ + return bitarithm_lsb(_pin_mask(pin)); +} + static inline msp430_port_p1_p2_t *_isr_port(gpio_t pin) { /* checking for (pin >> 8) <= 2 requires 6 byte of .text more than @@ -82,10 +79,10 @@ int gpio_init(gpio_t pin, gpio_mode_t mode) /* set pin direction */ if (mode == GPIO_OUT) { - port->DIR |= _pin(pin); + port->DIR |= _pin_mask(pin); } else { - port->DIR &= ~(_pin(pin)); + port->DIR &= ~(_pin_mask(pin)); } return 0; @@ -108,46 +105,46 @@ void gpio_periph_mode(gpio_t pin, bool enable) } } if (enable) { - *sel |= _pin(pin); + *sel |= _pin_mask(pin); } else { - *sel &= ~(_pin(pin)); + *sel &= ~(_pin_mask(pin)); } } int gpio_read(gpio_t pin) { msp430_port_t *port = _port(pin); - if (port->DIR & _pin(pin)) { - return (int)(port->OD & _pin(pin)); + if (port->DIR & _pin_mask(pin)) { + return (int)(port->OD & _pin_mask(pin)); } else { - return (int)(port->IN & _pin(pin)); + return (int)(port->IN & _pin_mask(pin)); } } void gpio_set(gpio_t pin) { - _port(pin)->OD |= _pin(pin); + _port(pin)->OD |= _pin_mask(pin); } void gpio_clear(gpio_t pin) { - _port(pin)->OD &= ~(_pin(pin)); + _port(pin)->OD &= ~(_pin_mask(pin)); } void gpio_toggle(gpio_t pin) { - _port(pin)->OD ^= _pin(pin); + _port(pin)->OD ^= _pin_mask(pin); } void gpio_write(gpio_t pin, int value) { if (value) { - _port(pin)->OD |= _pin(pin); + _port(pin)->OD |= _pin_mask(pin); } else { - _port(pin)->OD &= ~(_pin(pin)); + _port(pin)->OD &= ~(_pin_mask(pin)); } } @@ -159,7 +156,7 @@ static gpio_isr_ctx_t isr_ctx[ISR_NUMOF]; static int _ctx(gpio_t pin) { - int i = bitarithm_lsb(_pin(pin)); + int i = _pin_num(pin); return (_port(pin) == &PORT_1.base) ? i : (i + 8); } @@ -174,20 +171,28 @@ int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, } /* disable any activated interrupt */ - port->IE &= ~(_pin(pin)); + port->IE &= ~(_pin_mask(pin)); /* configure as input */ if (gpio_init(pin, mode) < 0) { return -1; } - /* save ISR context */ - isr_ctx[_ctx(pin)].cb = cb; - isr_ctx[_ctx(pin)].arg = arg; - /* configure flank */ - port->IES &= ~(_pin(pin)); - port->IES |= (flank & _pin(pin)); - /* clear pending interrupts and enable the IRQ */ - port->IFG &= ~(_pin(pin)); - gpio_irq_enable(pin); + + if (IS_USED(MODULE_GPIO_LL_IRQ)) { + gpio_irq_trig_t trig = (flank == GPIO_RISING) ? GPIO_TRIGGER_EDGE_RISING + : GPIO_TRIGGER_EDGE_FALLING; + return gpio_ll_irq((gpio_port_t)&port->base, _pin_num(pin), trig, cb, arg); + } + else { + /* save ISR context */ + isr_ctx[_ctx(pin)].cb = cb; + isr_ctx[_ctx(pin)].arg = arg; + /* configure flank */ + port->IES &= ~(_pin_mask(pin)); + port->IES |= (flank & _pin_mask(pin)); + /* clear pending interrupts and enable the IRQ */ + port->IFG &= ~(_pin_mask(pin)); + gpio_irq_enable(pin); + } return 0; } @@ -195,7 +200,7 @@ void gpio_irq_enable(gpio_t pin) { msp430_port_p1_p2_t *port = _isr_port(pin); if (port) { - port->IE |= _pin(pin); + port->IE |= _pin_mask(pin); } } @@ -203,10 +208,11 @@ void gpio_irq_disable(gpio_t pin) { msp430_port_p1_p2_t *port = _isr_port(pin); if (port) { - port->IE &= ~(_pin(pin)); + port->IE &= ~(_pin_mask(pin)); } } +# ifndef MODULE_GPIO_LL_IRQ static inline void isr_handler(msp430_port_p1_p2_t *port, int ctx) { for (unsigned i = 0; i < PINS_PER_PORT; i++) { @@ -230,4 +236,5 @@ ISR(PORT2_VECTOR, isr_port2) isr_handler(&PORT_2, 8); __exit_isr(); } +# endif #endif /* MODULE_PERIPH_GPIO_IRQ */ diff --git a/cpu/msp430/periph/gpio_ll.c b/cpu/msp430/periph/gpio_ll.c new file mode 100644 index 000000000000..772261fb3038 --- /dev/null +++ b/cpu/msp430/periph/gpio_ll.c @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2024 Marian Buschsieweke + * + * 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_msp430 + * @ingroup drivers_periph_gpio_ll + * @{ + * + * @file + * @brief Peripheral GPIO Low-Level API implementation for the MSP430 GPIO peripheral + * + * @author Marian Buschsieweke + * + * @} + */ + +#include +#include + +#include "periph/gpio_ll.h" + +gpio_port_t gpio_port(uword_t num) +{ + switch (num) { + case 1: + return (gpio_port_t)&PORT_1.base; + case 2: + return (gpio_port_t)&PORT_2.base; + case 3: + return (gpio_port_t)&PORT_3.base; + case 4: + return (gpio_port_t)&PORT_4.base; + case 5: + return (gpio_port_t)&PORT_5.base; + case 6: + return (gpio_port_t)&PORT_6.base; + default: + return 0; + } +} + +uword_t gpio_port_num(gpio_port_t port) +{ + /* we need to hard-code the values here to have constant initializers in + * the look-up table :-/ */ + static const uint8_t map[] = { + 0x20, /* == GPIO_PORT_1 */ + 0x28, /* == GPIO_PORT_2 */ + 0x10, /* == GPIO_PORT_3 */ + 0x1C, /* == GPIO_PORT_4 */ + 0x30, /* == GPIO_PORT_5 */ + 0x34, /* == GPIO_PORT_6 */ +#if CPU_FAM_MSP430_F2XX_G2XX + 0x38, /* == GPIO_PORT_7 */ + 0x39, /* == GPIO_PORT_8 */ +#endif + }; + + for (unsigned i = 0; i < ARRAY_SIZE(map); i++) { + if (port == (gpio_port_t)map[i]) { + return i + 1; + } + } + return 0; +} + +#if CPU_FAM_MSP430_F2XX_G2XX +static void _set_pull(unsigned port_num, uword_t mask, bool enable) +{ + volatile uint8_t *ren_reg; + switch (port_num) { + case 1: + ren_reg = &PORT_1.REN; + break; + case 2: + ren_reg = &PORT_2.REN; + break; + default: + ren_reg = &P3REN; + ren_reg += (port_num - 3); + } + + if (enable) { + *ren_reg |= mask; + } + else { + *ren_reg &= ~(mask); + } +} + +static bool _get_pull(unsigned port_num, uword_t mask) +{ + volatile uint8_t *ren_reg; + switch (port_num) { + case 1: + ren_reg = &PORT_1.REN; + break; + case 2: + ren_reg = &PORT_2.REN; + break; + default: + ren_reg = &P3REN; + ren_reg += (port_num - 3); + } + + return*ren_reg & mask; +} +#endif + +int gpio_ll_init(gpio_port_t port, uint8_t pin, gpio_conf_t conf) +{ + msp430_port_t *p = (void *)port; + + if (!IS_ACTIVE(CPU_FAM_MSP430_F2XX_G2XX) && (conf.pull != GPIO_FLOATING)) { + return -ENOTSUP; + } + + switch (conf.state) { + case GPIO_INPUT: + case GPIO_OUTPUT_PUSH_PULL: + break; + default: + return -ENOTSUP; + } + + uword_t mask = 1U << pin; + bool initial_value = conf.initial_value; + +#if CPU_FAM_MSP430_F2XX_G2XX + unsigned port_num = gpio_port_num(port); + switch (conf.pull) { + default: + return -ENOTSUP; + case GPIO_FLOATING: + _set_pull(port_num, mask, false); + break; + case GPIO_PULL_UP: + _set_pull(port_num, mask, true); + initial_value = true; + break; + case GPIO_PULL_DOWN: + _set_pull(port_num, mask, true); + initial_value = false; + break; + } +#endif + + if (initial_value) { + gpio_ll_set(port, mask); + } + else { + gpio_ll_clear(port, mask); + } + + if (conf.state == GPIO_OUTPUT_PUSH_PULL) { + /* No need to disable IRQs, the BIS.B instruction is atomically */ + p->DIR |= mask; + } + else { + /* No need to disable IRQs, the BIC.B instruction is atomically */ + p->DIR &= ~mask; + } + + return 0; +} + +gpio_conf_t gpio_ll_query_conf(gpio_port_t port, uint8_t pin) +{ + msp430_port_t *p = (void *)port; + uword_t mask = 1U << pin; + gpio_conf_t result = { 0 }; + if (p->DIR & mask) { + result.state = GPIO_OUTPUT_PUSH_PULL; + result.initial_value = gpio_ll_read_output(port) & mask; + } + else { + result.state = GPIO_INPUT; + result.initial_value = gpio_ll_read(port) & mask; +#if CPU_FAM_MSP430_F2XX_G2XX + uword_t port_num = gpio_port_num(port); + if (_get_pull(port_num, mask)) { + if (gpio_ll_read_output(port) & mask) { + result.pull = GPIO_PULL_UP; + } + else { + result.pull = GPIO_PULL_DOWN; + } + } +#endif + } + + return result; +} diff --git a/cpu/msp430/periph/gpio_ll_irq.c b/cpu/msp430/periph/gpio_ll_irq.c new file mode 100644 index 000000000000..5ebaa1f09da5 --- /dev/null +++ b/cpu/msp430/periph/gpio_ll_irq.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2024 Marian Buschsieweke + * + * 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_msp430 + * @ingroup drivers_periph_gpio_ll_irq + * @{ + * + * @file + * @brief IRQ implementation of the GPIO Low-Level API for MSP430 + * + * @author Marian Buschsieweke + * + * @} + */ + +#include +#include + +#include "compiler_hints.h" +#include "container.h" +#include "periph/gpio_ll.h" +#include "periph/gpio_ll_irq.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define ISR_PORTS_NUMOF 2U +#define PINS_PER_PORT 8U +#define ISR_NUMOF (ISR_PORTS_NUMOF * PINS_PER_PORT) + +struct isr_ctx { + gpio_ll_cb_t cb; + void *arg; +}; + +static struct isr_ctx _isr_ctx[ISR_NUMOF]; + +static unsigned _idx(uword_t port_num, uint8_t pin) +{ + if (port_num == 2) { + return pin + 8; + } + + return pin; +} + +int gpio_ll_irq(gpio_port_t _port, uint8_t pin, gpio_irq_trig_t trig, + gpio_ll_cb_t cb, void *arg) +{ + uword_t port_num = gpio_port_num(_port); + if (unlikely((port_num > 2) || (port_num == 0))) { + DEBUG_PUTS("[gpio_ll_irq] GPIO port without IRQ support used"); + return -ENOTSUP; + } + + switch (trig) { + case GPIO_TRIGGER_EDGE_RISING: + case GPIO_TRIGGER_EDGE_FALLING: + break; + default: + DEBUG_PUTS("[gpio_ll_irq] unsupported trigger used"); + return -ENOTSUP; + } + + msp430_port_p1_p2_t *port = container_of((msp430_port_t *)_port, msp430_port_p1_p2_t, base); + + unsigned idx = _idx(port_num, pin); + uword_t mask = 1U << pin; + + /* Disable IRQs to avoid race when updating CTX. Relying on compiler to + * use BIC.B instruction to implement this, which is inherently atomic. */ + port->IE &= ~mask; + _isr_ctx[idx].cb = cb; + _isr_ctx[idx].arg = arg; + + /* Configuring interrupt edge select. Relying on atomic BIS.B and BIC.B + * instructions used by the compiler */ + port->IES &= ~mask; + if (trig == GPIO_TRIGGER_EDGE_FALLING) { + port->IES |= mask; + } + + /* Clear pending IRQs, relying on atomic BIC.B instruction used by the + * compiler. */ + port->IFG &= ~mask; + + /* Finally, enabling the IRQ, relying on atomic BIS.B instruction used by + * the compiler */ + port->IE |= mask; + + return 0; +} + +/* It appears that disabling the interrupt in IE does not only mask IRQs, + * but disables the IRQ edge detector hardware completely. Hence, IRQs + * that came in while masked will not trigger after unmasking, as the + * API would expect. For this reason, we don't provide gpio_ll_irq_unmask() but + * only gpio_ll_irq_unmask_and_clear(). + */ +void gpio_ll_irq_mask(gpio_port_t _port, uint8_t pin) +{ + msp430_port_p1_p2_t *port = container_of((msp430_port_t *)_port, msp430_port_p1_p2_t, base); + port->IE &= ~(1U << pin); +} + +void gpio_ll_irq_unmask_and_clear(gpio_port_t _port, uint8_t pin) +{ + msp430_port_p1_p2_t *port = container_of((msp430_port_t *)_port, msp430_port_p1_p2_t, base); + uword_t mask = 1U << pin; + /* We clear IFG anyway despite bits in IFG not getting set without IE, a + * call may rely on this function clearing IRQs while already unmasked. */ + port->IFG &= ~mask; + port->IE |= mask; +} + +static void _isr_handler(msp430_port_p1_p2_t *port, int ctx) +{ + for (unsigned i = 0; i < PINS_PER_PORT; i++) { + unsigned mask = 1U << i; + if ((port->IE & mask) && (port->IFG & mask)) { + port->IFG &= ~mask; + _isr_ctx[i + ctx].cb(_isr_ctx[i + ctx].arg); + } + } +} + +ISR(PORT1_VECTOR, isr_port1) +{ + __enter_isr(); + _isr_handler(&PORT_1, 0); + __exit_isr(); +} + +ISR(PORT2_VECTOR, isr_port2) +{ + __enter_isr(); + _isr_handler(&PORT_2, 8); + __exit_isr(); +} diff --git a/cpu/nrf5x_common/Makefile.features b/cpu/nrf5x_common/Makefile.features index 806a925e81d6..99043d56c4af 100644 --- a/cpu/nrf5x_common/Makefile.features +++ b/cpu/nrf5x_common/Makefile.features @@ -16,6 +16,7 @@ ifeq (,$(filter nrf5340_app,$(CPU_MODEL))) FEATURES_PROVIDED += periph_gpio_ll_input_pull_down FEATURES_PROVIDED += periph_gpio_ll_input_pull_up FEATURES_PROVIDED += periph_gpio_ll_irq + FEATURES_PROVIDED += periph_gpio_ll_irq_edge_triggered_both FEATURES_PROVIDED += periph_gpio_ll_irq_unmask FEATURES_PROVIDED += periph_gpio_ll_open_drain FEATURES_PROVIDED += periph_gpio_ll_open_drain_pull_up diff --git a/cpu/sam0_common/Makefile.features b/cpu/sam0_common/Makefile.features index 04ce16facb53..17a745166a9b 100644 --- a/cpu/sam0_common/Makefile.features +++ b/cpu/sam0_common/Makefile.features @@ -18,6 +18,7 @@ FEATURES_PROVIDED += periph_gpio_ll_disconnect FEATURES_PROVIDED += periph_gpio_ll_input_pull_down FEATURES_PROVIDED += periph_gpio_ll_input_pull_up FEATURES_PROVIDED += periph_gpio_ll_irq +FEATURES_PROVIDED += periph_gpio_ll_irq_edge_triggered_both 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 diff --git a/cpu/stm32/Makefile.features b/cpu/stm32/Makefile.features index 8d9fb8d78078..ce010bf48125 100644 --- a/cpu/stm32/Makefile.features +++ b/cpu/stm32/Makefile.features @@ -12,6 +12,7 @@ FEATURES_PROVIDED += periph_gpio_ll_disconnect FEATURES_PROVIDED += periph_gpio_ll_input_pull_down FEATURES_PROVIDED += periph_gpio_ll_input_pull_up FEATURES_PROVIDED += periph_gpio_ll_irq +FEATURES_PROVIDED += periph_gpio_ll_irq_edge_triggered_both FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_high FEATURES_PROVIDED += periph_gpio_ll_irq_level_triggered_low FEATURES_PROVIDED += periph_gpio_ll_open_drain diff --git a/drivers/include/periph/gpio_ll_irq.h b/drivers/include/periph/gpio_ll_irq.h index 17f9b2804bd7..caecf4e99e58 100644 --- a/drivers/include/periph/gpio_ll_irq.h +++ b/drivers/include/periph/gpio_ll_irq.h @@ -58,6 +58,16 @@ extern "C" { #if !defined(HAVE_GPIO_IRQ_TRIG_T) || defined(DOXYGEN) /** * @brief Definition of possible IRQ triggers + * + * The following features indicate support: + * + * | Trigger | Feature provided if trigger is supported | + * |:------------------------------ |:----------------------------------------- | + * | `GPIO_TRIGGER_EDGE_FALLING` | Always available | + * | `GPIO_TRIGGER_EDGE_RISING` | Always available | + * | `GPIO_TRIGGER_EDGE_BOTH` | `periph_gpio_ll_irq_edge_triggered_both` | + * | `GPIO_TRIGGER_LEVEL_HIGH` | `periph_gpio_ll_irq_level_triggered_high` | + * | `GPIO_TRIGGER_LEVEL_LOW` | `periph_gpio_ll_irq_level_triggered_low` | */ typedef enum { GPIO_TRIGGER_EDGE_FALLING, /**< edge triggered IRQ on falling flanks only diff --git a/drivers/periph_common/Makefile.dep b/drivers/periph_common/Makefile.dep index 9d164a994485..285131db7d4a 100644 --- a/drivers/periph_common/Makefile.dep +++ b/drivers/periph_common/Makefile.dep @@ -1,15 +1,14 @@ # Always use hardware features, if available -ifneq (,$(filter periph_gpio_ll%,$(USEMODULE))) - FEATURES_OPTIONAL += periph_gpio_ll_disconnect - FEATURES_OPTIONAL += periph_gpio_ll_input_pull_down - FEATURES_OPTIONAL += periph_gpio_ll_input_pull_keep - FEATURES_OPTIONAL += periph_gpio_ll_input_pull_up - FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_high - FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_low - FEATURES_OPTIONAL += periph_gpio_ll_irq_unmask - FEATURES_OPTIONAL += periph_gpio_ll_open_drain - FEATURES_OPTIONAL += periph_gpio_ll_open_drain_pull_up - FEATURES_OPTIONAL += periph_gpio_ll_open_source - FEATURES_OPTIONAL += periph_gpio_ll_open_source_pull_down - FEATURES_OPTIONAL += periph_gpio_ll_switch_dir -endif +FEATURES_OPTIONAL += periph_gpio_ll_disconnect +FEATURES_OPTIONAL += periph_gpio_ll_input_pull_down +FEATURES_OPTIONAL += periph_gpio_ll_input_pull_keep +FEATURES_OPTIONAL += periph_gpio_ll_input_pull_up +FEATURES_OPTIONAL += periph_gpio_ll_irq_edge_triggered_both +FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_high +FEATURES_OPTIONAL += periph_gpio_ll_irq_level_triggered_low +FEATURES_OPTIONAL += periph_gpio_ll_irq_unmask +FEATURES_OPTIONAL += periph_gpio_ll_open_drain +FEATURES_OPTIONAL += periph_gpio_ll_open_drain_pull_up +FEATURES_OPTIONAL += periph_gpio_ll_open_source +FEATURES_OPTIONAL += periph_gpio_ll_open_source_pull_down +FEATURES_OPTIONAL += periph_gpio_ll_switch_dir diff --git a/features.yaml b/features.yaml index 4013f303d6d9..6821cad0a8db 100644 --- a/features.yaml +++ b/features.yaml @@ -534,6 +534,9 @@ groups: help: Level triggered IRQs are supported for level high. - name: periph_gpio_ll_irq_level_triggered_low help: Level triggered IRQs are supported for level low. + - name: periph_gpio_ll_irq_edge_triggered_both + help: Edge triggered IRQs are supported with both falling and rising + edges as trigger - name: periph_gpio_ll_irq_unmask help: The GPIO LL driver supports unmasking interrupts without clearing pending IRQs that came in while masked. diff --git a/makefiles/features_existing.inc.mk b/makefiles/features_existing.inc.mk index 79c055c6fc67..7ca64434f4dc 100644 --- a/makefiles/features_existing.inc.mk +++ b/makefiles/features_existing.inc.mk @@ -170,6 +170,7 @@ FEATURES_EXISTING := \ periph_gpio_ll_input_pull_keep \ periph_gpio_ll_input_pull_up \ periph_gpio_ll_irq \ + periph_gpio_ll_irq_edge_triggered_both \ periph_gpio_ll_irq_level_triggered_high \ periph_gpio_ll_irq_level_triggered_low \ periph_gpio_ll_irq_unmask \ diff --git a/tests/periph/gpio_ll/main.c b/tests/periph/gpio_ll/main.c index ed263f702f00..2c1011c78ebb 100644 --- a/tests/periph/gpio_ll/main.c +++ b/tests/periph/gpio_ll/main.c @@ -718,26 +718,31 @@ static void test_irq_edge(void) puts_optional("... OK"); puts_optional("Testing both edges on PIN_IN_0"); - expect(0 == gpio_ll_irq(port_in, PIN_IN_0, GPIO_TRIGGER_EDGE_BOTH, - irq_edge_cb, &irq_mut)); - /* test for spurious IRQ */ - expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut, - mutex_timeout)); - gpio_ll_set(port_out, 1UL << PIN_OUT_0); - /* test for IRQ on rising edge */ - expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut, - mutex_timeout)); - /* test for spurious IRQ */ - expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut, - mutex_timeout)); - gpio_ll_clear(port_out, 1UL << PIN_OUT_0); - /* test for IRQ on falling edge */ - expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut, - mutex_timeout)); - /* test for spurious IRQ */ - expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut, - mutex_timeout)); - puts_optional("... OK"); + if (IS_USED(MODULE_PERIPH_GPIO_LL_IRQ_EDGE_TRIGGERED_BOTH)) { + expect(0 == gpio_ll_irq(port_in, PIN_IN_0, GPIO_TRIGGER_EDGE_BOTH, + irq_edge_cb, &irq_mut)); + /* test for spurious IRQ */ + expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut, + mutex_timeout)); + gpio_ll_set(port_out, 1UL << PIN_OUT_0); + /* test for IRQ on rising edge */ + expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut, + mutex_timeout)); + /* test for spurious IRQ */ + expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut, + mutex_timeout)); + gpio_ll_clear(port_out, 1UL << PIN_OUT_0); + /* test for IRQ on falling edge */ + expect(0 == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut, + mutex_timeout)); + /* test for spurious IRQ */ + expect(-ECANCELED == ztimer_mutex_lock_timeout(ZTIMER_USEC, &irq_mut, + mutex_timeout)); + puts_optional("... OK"); + } + else { + puts_optional("... SKIPPED (not supported)"); + } puts_optional("Testing masking of IRQs (still both edges on PIN_IN_0)"); gpio_ll_irq_mask(port_in, PIN_IN_0);