From 855756524ffd4a97ed0154fafc5f8ff5b9d0c8d2 Mon Sep 17 00:00:00 2001 From: Marian Buschsieweke Date: Sun, 10 Dec 2023 12:53:21 +0100 Subject: [PATCH] cpu/sam0_common: Implement periph_gpio_ll Co-authored-by: benpicco --- cpu/sam0_common/Kconfig | 1 + cpu/sam0_common/Makefile.features | 1 + cpu/sam0_common/include/gpio_ll_arch.h | 150 +++++++++++++++ cpu/sam0_common/include/periph_cpu_common.h | 97 ++++++++++ cpu/sam0_common/periph/gpio_ll.c | 199 ++++++++++++++++++++ 5 files changed, 448 insertions(+) create mode 100644 cpu/sam0_common/include/gpio_ll_arch.h create mode 100644 cpu/sam0_common/periph/gpio_ll.c diff --git a/cpu/sam0_common/Kconfig b/cpu/sam0_common/Kconfig index 3d45cbcedb..aadbae9d8a 100644 --- a/cpu/sam0_common/Kconfig +++ b/cpu/sam0_common/Kconfig @@ -15,6 +15,7 @@ config CPU_COMMON_SAM0 select HAS_PERIPH_FLASHPAGE_RWEE select HAS_PERIPH_GPIO select HAS_PERIPH_GPIO_IRQ + select HAS_PERIPH_GPIO_LL 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 bc8ae8f278..505b4561ee 100644 --- a/cpu/sam0_common/Makefile.features +++ b/cpu/sam0_common/Makefile.features @@ -13,6 +13,7 @@ FEATURES_PROVIDED += periph_flashpage_in_address_space 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_i2c_reconfigure FEATURES_PROVIDED += periph_rtt_overflow FEATURES_PROVIDED += periph_rtt_set_counter diff --git a/cpu/sam0_common/include/gpio_ll_arch.h b/cpu/sam0_common/include/gpio_ll_arch.h new file mode 100644 index 0000000000..9d99c96dea --- /dev/null +++ b/cpu/sam0_common/include/gpio_ll_arch.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2016 Freie Universität Berlin + * 2017 OTA keys S.A. + * + * 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 + * @{ + * + * @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 + */ + +#ifndef GPIO_LL_ARCH_H +#define GPIO_LL_ARCH_H + +#include "architecture.h" +#include "periph/gpio_ll.h" +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef DOXYGEN /* hide implementation specific details from Doxygen */ + +/* Provide base address of the GPIO peripheral via APB */ +#if defined(PORT_SEC) +# define GPIO_APB_BASE PORT_SEC +#else +# define GPIO_APB_BASE PORT +#endif + +/* Provide base address of the GPIO peripheral via IOBUS */ +#if defined(PORT_IOBUS_SEC) +# define GPIO_IOBUS_BASE PORT_IOBUS_SEC +#elif defined(PORT_IOBUS) +# define GPIO_IOBUS_BASE PORT_IOBUS +#else +# define GPIO_IOBUS_BASE GPIO_APB_BASE /* no IOBUS present, fall back to APB */ +#endif + +/** + * @brief Get a GPIO port by number + */ +#define GPIO_PORT(num) ((uintptr_t)&GPIO_IOBUS_BASE->Group[(num)]) + +/** + * @brief Get a GPIO port number by gpio_port_t value + */ +#define GPIO_PORT_NUM(port) \ + (((port) - (uintptr_t)&GPIO_IOBUS_BASE->Group[0]) / sizeof(GPIO_IOBUS_BASE->Group[0])) + +static inline PortGroup *sam0_gpio_iobus2ap(PortGroup *iobus) +{ + const uintptr_t iobus_base = (uintptr_t)GPIO_IOBUS_BASE; + const uintptr_t apb_base = (uintptr_t)GPIO_APB_BASE; + + return (PortGroup *)((uintptr_t)iobus - (iobus_base - apb_base)); +} + +static inline uword_t gpio_ll_read(gpio_port_t port) +{ + PortGroup *p = (PortGroup *)port; + if (!IS_USED(MODULE_PERIPH_GPIO_FAST_READ)) { + p = sam0_gpio_iobus2ap(p); + } + return p->IN.reg; +} + +static inline uword_t gpio_ll_read_output(gpio_port_t port) +{ + PortGroup *p = (PortGroup *)port; + return p->OUT.reg; +} + +static inline void gpio_ll_set(gpio_port_t port, uword_t mask) +{ + PortGroup *p = (PortGroup *)port; + p->OUTSET.reg = mask; +} + +static inline void gpio_ll_clear(gpio_port_t port, uword_t mask) +{ + PortGroup *p = (PortGroup *)port; + p->OUTCLR.reg = mask; +} + +static inline void gpio_ll_toggle(gpio_port_t port, uword_t mask) +{ + PortGroup *p = (PortGroup *)port; + p->OUTTGL.reg = mask; +} + +static inline void gpio_ll_write(gpio_port_t port, uword_t mask) +{ + PortGroup *p = (PortGroup *)port; + p->OUT.reg = mask; +} + +static inline gpio_port_t gpio_get_port(gpio_t pin) +{ + return (gpio_port_t)(pin & ~(0x1f)); +} + +static inline uint8_t gpio_get_pin_num(gpio_t pin) +{ + return pin & 0x1f; +} + +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 < GPIO_PORT(0)) { + return (void *)port; + } + if (port > GPIO_PORT(ARRAY_SIZE(GPIO_IOBUS_BASE->Group))) { + return (void *)port; + } + + return NULL; +} + +static inline bool is_gpio_port_num_valid(uint_fast8_t num) +{ + return (num < ARRAY_SIZE(GPIO_IOBUS_BASE->Group)); +} + +#endif /* DOXYGEN */ +#ifdef __cplusplus +} +#endif + +#endif /* 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 18af20c4c5..0b2627d33c 100644 --- a/cpu/sam0_common/include/periph_cpu_common.h +++ b/cpu/sam0_common/include/periph_cpu_common.h @@ -126,6 +126,60 @@ typedef enum { GPIO_OD_PU = 0xff /**< not supported by HW */ } gpio_mode_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 = 1, + GPIO_DRIVE_STRONGEST = 1 +} gpio_drive_strength_t; + +#define HAVE_GPIO_PULL_T +typedef enum { + GPIO_FLOATING, + GPIO_PULL_UP, + GPIO_PULL_DOWN, + GPIO_PULL_KEEP, +} gpio_pull_t; + +#define HAVE_GPIO_STATE_T +typedef enum { + GPIO_OUTPUT_PUSH_PULL, + GPIO_OUTPUT_OPEN_DRAIN, + GPIO_OUTPUT_OPEN_SOURCE, + GPIO_INPUT, + GPIO_USED_BY_PERIPHERAL, + GPIO_DISCONNECT, +} gpio_state_t; + +#define HAVE_GPIO_IRQ_TRIG_T +typedef enum { + GPIO_TRIGGER_EDGE_RISING = EIC_CONFIG_SENSE0_RISE_Val, + GPIO_TRIGGER_EDGE_FALLING = EIC_CONFIG_SENSE0_FALL_Val, + GPIO_TRIGGER_EDGE_BOTH = EIC_CONFIG_SENSE0_BOTH_Val, + GPIO_TRIGGER_LEVEL_HIGH = EIC_CONFIG_SENSE0_HIGH_Val, + GPIO_TRIGGER_LEVEL_LOW = EIC_CONFIG_SENSE0_LOW_Val, +} gpio_irq_trig_t; + +#define HAVE_GPIO_CONF_T +typedef union gpio_conf_sam0 gpio_conf_t; + /** * @brief Override active flank configuration values * @{ @@ -139,6 +193,49 @@ typedef enum { /** @} */ #endif /* ndef DOXYGEN */ +/** + * @brief GPIO pin configuration for SAM0 MCUs + * @ingroup drivers_periph_gpio_ll + */ +union gpio_conf_sam0 { + uint8_t bits; /**< the raw bits */ + struct { + /** + * @brief State of the pin + */ + gpio_state_t state : 3; + /** + * @brief Pull resistor configuration + */ + gpio_pull_t pull : 2; + /** + * @brief Drive strength of the GPIO + * + * @warning If the requested drive strength is not available, the + * closest fit supported will be configured instead. + * + * This value is ignored when @ref gpio_conf_nrf5x::state is configured + * to @ref GPIO_INPUT or @ref GPIO_DISCONNECT. + */ + gpio_drive_strength_t drive_strength : 1; + /** + * @brief Initial value of the output + * + * Ignored if @ref gpio_conf_nrf5x::state is set to @ref GPIO_INPUT or + * @ref GPIO_DISCONNECT. If the pin was previously in a high impedance + * state, it is guaranteed to directly transition to the given initial + * value. + * + * @ref gpio_ll_query_conf will write the current value of the specified + * pin here, which is read from the input register when the state is + * @ref GPIO_INPUT, otherwise the state from the output register is + * consulted. + */ + bool initial_value : 1; + uint8_t : 1; /*< padding */ + }; +}; + /** * @brief Available MUX values for configuring a pin's alternate function */ diff --git a/cpu/sam0_common/periph/gpio_ll.c b/cpu/sam0_common/periph/gpio_ll.c new file mode 100644 index 0000000000..41a202520f --- /dev/null +++ b/cpu/sam0_common/periph/gpio_ll.c @@ -0,0 +1,199 @@ +/* + * Copyright (C) 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_stm32 + * @ingroup drivers_periph_gpio + * @{ + * + * @file + * @brief GPIO Low-level API implementation for the SAM0 GPIO peripheral + * + * @author Marian Buschsieweke + * + * This implementation uses the IOBUS for single-cycle I/O for writes in any + * case. Reading via the IOBUS requires however for continuous sampling to + * be enabled, as reads on the IOBUS cannot stall the CPU to wait for the + * on-demand sampling result to be available. Therefore, reads are done by + * default via the slower APB bus. + * + * To also enable reading via the IOBUS, add the following snipped to your + * `Makefile`: + * + * ``` + * FEATURES_OPTIONAL += periph_gpio_fast_read + * ``` + * + * This enables continuous sampling on any pin configured as input, so that + * the IOBUS can safely be used for reads as well. Consequently, it will now + * consistently use the IOBUS for I/O. + * + * @} + */ + +#include +#include + +#include "compiler_hints.h" +#include "cpu.h" +#include "irq.h" +#include "periph/gpio_ll.h" + +#ifdef MODULE_FMT +#include "fmt.h" +#else +static inline void print_str(const char *str) +{ + fputs(str, stdout); +} +#endif + +int gpio_ll_init(gpio_port_t port, uint8_t pin, gpio_conf_t conf) +{ + assume(pin < 32); + assume(gpio_port_unpack_addr(port) == NULL); + PortGroup *iobus = (PortGroup *)port; + PortGroup *apb = sam0_gpio_iobus2ap(iobus); + uint32_t pin_mask = 1U << pin; + uint8_t pin_cfg = 0; + bool initial_value = false; + bool output_enable = false; + + initial_value = conf.initial_value; + + switch (conf.state) { + case GPIO_INPUT: + pin_cfg |= PORT_PINCFG_INEN; + break; + case GPIO_OUTPUT_PUSH_PULL: + output_enable = true; + break; + case GPIO_USED_BY_PERIPHERAL: + pin_cfg |= PORT_PINCFG_PMUXEN; + break; + case GPIO_DISCONNECT: + break; + case GPIO_OUTPUT_OPEN_DRAIN: + case GPIO_OUTPUT_OPEN_SOURCE: + default: + return -ENOTSUP; + } + + switch (conf.pull) { + case GPIO_PULL_UP: + pin_cfg |= PORT_PINCFG_PULLEN; + initial_value = true; + break; + case GPIO_PULL_DOWN: + pin_cfg |= PORT_PINCFG_PULLEN; + initial_value = false; + break; + case GPIO_FLOATING: + break; + default: + return -ENOTSUP; + } + + if (conf.drive_strength == GPIO_DRIVE_STRONG) { + pin_cfg |= PORT_PINCFG_DRVSTR; + } + + if (IS_USED(MODULE_PERIPH_GPIO_FAST_READ)) { + /* This read-modify-write needs to be made atomic to avoid + * corrupting the control register. */ + unsigned state = irq_disable(); + if (conf.state == GPIO_INPUT) { + apb->CTRL.reg |= pin_mask; + } + else { + apb->CTRL.reg &= ~pin_mask; + } + irq_restore(state); + } + + /* Writing the settings now in careful order. All accesses are done via + * the clear / set special registers that are naturally atomic, except + * for the PINCFG register. But that is not shared with other pins, so + * no need to sync that. (The API says concurrent configurations of the + * exact same GPIO pin are forbidden.) */ + if (initial_value) { + iobus->OUTSET.reg = pin_mask; + } + else { + iobus->OUTCLR.reg = pin_mask; + } + + apb->PINCFG[pin].reg = pin_cfg; + + if (output_enable) { + iobus->DIRSET.reg = pin_mask; + } + else { + iobus->DIRCLR.reg = pin_mask; + } + + return 0; +} + +gpio_conf_t gpio_ll_query_conf(gpio_port_t port, uint8_t pin) +{ + gpio_conf_t result = { 0 }; + assume(pin < 32); + assume(gpio_port_unpack_addr(port) == NULL); + PortGroup *iobus = (PortGroup *)port; + PortGroup *apb = sam0_gpio_iobus2ap(iobus); + + uint32_t pin_mask = 1U << pin; + uint8_t pin_cfg = apb->PINCFG[pin].reg; + + if (pin_cfg & PORT_PINCFG_DRVSTR) { + result.drive_strength = GPIO_DRIVE_STRONG; + } + + if (pin_cfg & PORT_PINCFG_PULLEN) { + if (iobus->OUT.reg & pin_mask) { + result.pull = GPIO_PULL_UP; + } + else { + result.pull = GPIO_PULL_DOWN; + } + } + + if (pin_cfg & PORT_PINCFG_PMUXEN) { + result.state = GPIO_USED_BY_PERIPHERAL; + } + else { + if (iobus->DIR.reg & pin_mask) { + result.state = GPIO_OUTPUT_PUSH_PULL; + } + else { + if (pin_cfg & PORT_PINCFG_INEN) { + result.state = GPIO_INPUT; + } + else { + result.state = GPIO_DISCONNECT; + } + } + } + + result.initial_value = iobus->OUT.reg & pin_mask; + + return result; +} + +void gpio_ll_print_conf(gpio_conf_t conf) +{ + static const char *drive_strs[] = { + [GPIO_DRIVE_WEAK] = "weak", + [GPIO_DRIVE_STRONG] = "strong", + }; + + gpio_ll_print_conf_common(conf); + print_str(", drive: "); + print_str(drive_strs[conf.drive_strength]); +}