diff --git a/cpu/atmega1281/include/periph_cpu.h b/cpu/atmega1281/include/periph_cpu.h index 52ff42c91b..cec8221dbe 100644 --- a/cpu/atmega1281/include/periph_cpu.h +++ b/cpu/atmega1281/include/periph_cpu.h @@ -56,6 +56,27 @@ enum { GPIO_PIN(PORT_E, 6), \ GPIO_PIN(PORT_E, 7) } +/** + * @brief Get the interrupt vector number of the given GPIO pin + */ +static inline uint8_t atmega_pin2exti(uint8_t port_num, uint8_t pin_num) +{ + (void)port_num; + return pin_num; +} + +/** + * @brief Check if the given pin can be used as external interrupt + */ +static inline bool atmega_has_pin_exti(uint8_t port_num, uint8_t pin_num) +{ + if (pin_num < 4) { + return port_num == PORT_D; + } + + return port_num == PORT_E; +} + /** * @name Defines for the I2C interface * @{ diff --git a/cpu/atmega1284p/include/periph_cpu.h b/cpu/atmega1284p/include/periph_cpu.h index 53590eadb0..b2f292a624 100644 --- a/cpu/atmega1284p/include/periph_cpu.h +++ b/cpu/atmega1284p/include/periph_cpu.h @@ -53,6 +53,33 @@ enum { GPIO_PIN(PORT_D, 3), \ GPIO_PIN(PORT_B, 2) } +/** + * @brief Get the interrupt vector number of the given GPIO pin + */ +static inline uint8_t atmega_pin2exti(uint8_t port_num, uint8_t pin_num) +{ + if (port_num == PORT_B) { + return 2; + } + + return pin_num - 2; +} + +/** + * @brief Check if the given pin can be used as external interrupt + */ +static inline bool atmega_has_pin_exti(uint8_t port_num, uint8_t pin_num) +{ + switch (port_num) { + default: + return false; + case PORT_D: + return ((pin_num == 2) || (pin_num == 3)); + case PORT_B: + return pin_num == 2; + } +} + /** * @name Defines for the I2C interface * @{ diff --git a/cpu/atmega128rfa1/include/periph_cpu.h b/cpu/atmega128rfa1/include/periph_cpu.h index eeb80e4c7c..349b04c071 100644 --- a/cpu/atmega128rfa1/include/periph_cpu.h +++ b/cpu/atmega128rfa1/include/periph_cpu.h @@ -54,6 +54,27 @@ enum { GPIO_PIN(PORT_E, 6), \ GPIO_PIN(PORT_E, 7) } +/** + * @brief Get the interrupt vector number of the given GPIO pin + */ +static inline uint8_t atmega_pin2exti(uint8_t port_num, uint8_t pin_num) +{ + (void)port_num; + return pin_num; +} + +/** + * @brief Check if the given pin can be used as external interrupt + */ +static inline bool atmega_has_pin_exti(uint8_t port_num, uint8_t pin_num) +{ + if (pin_num < 4) { + return port_num == PORT_D; + } + + return port_num == PORT_E; +} + /** * @name Defines for the I2C interface * @{ diff --git a/cpu/atmega2560/include/periph_cpu.h b/cpu/atmega2560/include/periph_cpu.h index 00cd85dc21..7a4d546a52 100644 --- a/cpu/atmega2560/include/periph_cpu.h +++ b/cpu/atmega2560/include/periph_cpu.h @@ -58,6 +58,27 @@ enum { GPIO_PIN(PORT_E, 6), \ GPIO_PIN(PORT_E, 7) } +/** + * @brief Get the interrupt vector number of the given GPIO pin + */ +static inline uint8_t atmega_pin2exti(uint8_t port_num, uint8_t pin_num) +{ + (void)port_num; + return pin_num; +} + +/** + * @brief Check if the given pin can be used as external interrupt + */ +static inline bool atmega_has_pin_exti(uint8_t port_num, uint8_t pin_num) +{ + if (pin_num < 4) { + return port_num == PORT_D; + } + + return port_num == PORT_E; +} + /** * @name Defines for the I2C interface * @{ diff --git a/cpu/atmega256rfr2/include/periph_cpu.h b/cpu/atmega256rfr2/include/periph_cpu.h index d2ffd79cd2..496ffe901b 100644 --- a/cpu/atmega256rfr2/include/periph_cpu.h +++ b/cpu/atmega256rfr2/include/periph_cpu.h @@ -54,6 +54,27 @@ enum { GPIO_PIN(PORT_E, 6), \ GPIO_PIN(PORT_E, 7) } +/** + * @brief Get the interrupt vector number of the given GPIO pin + */ +static inline uint8_t atmega_pin2exti(uint8_t port_num, uint8_t pin_num) +{ + (void)port_num; + return pin_num; +} + +/** + * @brief Check if the given pin can be used as external interrupt + */ +static inline bool atmega_has_pin_exti(uint8_t port_num, uint8_t pin_num) +{ + if (pin_num < 4) { + return port_num == PORT_D; + } + + return port_num == PORT_E; +} + /** * @name Defines for the I2C interface * @{ diff --git a/cpu/atmega328p/include/periph_cpu.h b/cpu/atmega328p/include/periph_cpu.h index 9b8a7c9d5f..b9c2423d0c 100644 --- a/cpu/atmega328p/include/periph_cpu.h +++ b/cpu/atmega328p/include/periph_cpu.h @@ -49,6 +49,27 @@ enum { #define CPU_ATMEGA_EXT_INTS { GPIO_PIN(PORT_D, 2), \ GPIO_PIN(PORT_D, 3) } +/** + * @brief Get the interrupt vector number of the given GPIO pin + */ +static inline uint8_t atmega_pin2exti(uint8_t port_num, uint8_t pin_num) +{ + (void)port_num; + return pin_num - 2; +} + +/** + * @brief Check if the given pin can be used as external interrupt + */ +static inline bool atmega_has_pin_exti(uint8_t port_num, uint8_t pin_num) +{ + if (port_num == PORT_D) { + return ((pin_num == 2) || (pin_num == 3)); + } + + return false; +} + /** * @name Defines for the I2C interface * @{ diff --git a/cpu/atmega32u4/include/periph_cpu.h b/cpu/atmega32u4/include/periph_cpu.h index 6a6bf86b94..8429e00e2c 100644 --- a/cpu/atmega32u4/include/periph_cpu.h +++ b/cpu/atmega32u4/include/periph_cpu.h @@ -47,6 +47,23 @@ enum { GPIO_PIN(PORT_D, 2), \ GPIO_PIN(PORT_D, 3) } +/** + * @brief Get the interrupt vector number of the given GPIO pin + */ +static inline uint8_t atmega_pin2exti(uint8_t port_num, uint8_t pin_num) +{ + (void)port_num; + return pin_num; +} + +/** + * @brief Check if the given pin can be used as external interrupt + */ +static inline bool atmega_has_pin_exti(uint8_t port_num, uint8_t pin_num) +{ + return ((pin_num < 4) && (port_num == PORT_D)); +} + /** * @name Defines for the I2C interface * @{ diff --git a/cpu/atmega_common/Kconfig b/cpu/atmega_common/Kconfig index 8986f03893..7936291144 100644 --- a/cpu/atmega_common/Kconfig +++ b/cpu/atmega_common/Kconfig @@ -19,6 +19,10 @@ config CPU_COMMON_ATMEGA select HAS_PERIPH_EEPROM 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_LOW + select HAS_PERIPH_GPIO_LL_IRQ_UNMASK select HAS_PERIPH_PM select HAS_PERIPH_RTC_MS select HAS_PERIPH_RTT_SET_COUNTER diff --git a/cpu/atmega_common/Makefile.features b/cpu/atmega_common/Makefile.features index 1987a53914..53078f915f 100644 --- a/cpu/atmega_common/Makefile.features +++ b/cpu/atmega_common/Makefile.features @@ -9,6 +9,10 @@ FEATURES_PROVIDED += dbgpin FEATURES_PROVIDED += periph_cpuid FEATURES_PROVIDED += periph_eeprom 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_low +FEATURES_PROVIDED += periph_gpio_ll_irq_unmask FEATURES_PROVIDED += periph_pm FEATURES_PROVIDED += periph_rtc_ms FEATURES_PROVIDED += periph_rtt_set_counter diff --git a/cpu/atmega_common/include/atmega_gpio.h b/cpu/atmega_common/include/atmega_gpio.h index f4befd7719..c57e4a4735 100644 --- a/cpu/atmega_common/include/atmega_gpio.h +++ b/cpu/atmega_common/include/atmega_gpio.h @@ -38,11 +38,6 @@ extern "C" { #endif -#define ATMEGA_GPIO_BASE_PORT_A (0x20) -#define ATMEGA_GPIO_OFFSET_PORT_H (0xCB) -#define ATMEGA_GPIO_OFFSET_PIN_PORT (0x02) -#define ATMEGA_GPIO_OFFSET_PIN_PIN (0x03) - /** * @brief Extract the pin number of the given pin */ @@ -64,18 +59,7 @@ static inline uint8_t atmega_port_num(gpio_t pin) */ static inline uint16_t atmega_port_addr(gpio_t pin) { - uint8_t port_num = atmega_port_num(pin); - uint16_t port_addr = port_num * ATMEGA_GPIO_OFFSET_PIN_PIN; - - port_addr += ATMEGA_GPIO_BASE_PORT_A; - port_addr += ATMEGA_GPIO_OFFSET_PIN_PORT; - -#if defined (PORTG) - if (port_num > PORT_G) { - port_addr += ATMEGA_GPIO_OFFSET_PORT_H; - } -#endif - return port_addr; + return (uintptr_t)(&atmega_gpio_port(pin)->port); } /** diff --git a/cpu/atmega_common/include/gpio_ll_arch.h b/cpu/atmega_common/include/gpio_ll_arch.h new file mode 100644 index 0000000000..89f0121b68 --- /dev/null +++ b/cpu/atmega_common/include/gpio_ll_arch.h @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2015 Freie Universität Berlin + * + * 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_atmega_common + * @ingroup drivers_periph_gpio_ll + * @{ + * + * @file + * @brief CPU specific part of the Peripheral GPIO Low-Level API + * + * @note This GPIO driver implementation supports only one pin to be + * defined as external interrupt. + * + * @author Hauke Petersen + */ + +#ifndef GPIO_LL_ARCH_H +#define GPIO_LL_ARCH_H + +#include + +#include "cpu.h" +#include "irq.h" +#include "kernel_defines.h" +#include "periph_cpu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef DOXYGEN /* hide implementation specific details from Doxygen */ + +/* AVR generally requires two CPU cycles for loads from and stores to memory. + * However, there are special single cycle instructions, which however require + * the target address to be given as an 5 bit immediate. So only a 64 byte sized + * part of the data address space can be accessed this way, which is called + * the I/O memory (as (almost) only memory mapped I/O is mapped there). + * + * GPIO ports up to G are part of the I/O mapped area, but starting from port H + * the GPIO registers are only accessible via regular load and store operations + * and mapped slightly different in the data address space. For some reason, + * there is a gap between the GPIO memory regions for port A to G and H and + * above that results in special handling for GPIO ports H and above. + * + * Note that this implementation always uses the data address way to access GPIO + * registers and never the single cycle instructions. However, GCC converts the + * instructions into semantically equivalent single CPU cycle instructions + * whenever the target address is known at compile time (so can be expressed as + * immediate) and also mapped into the I/O address space. We rely on this + * optimization to claim single cycle GPIO accesses for GPIO ports below H, + * whenever the port number is known at compile time. + */ + +#ifdef PORTH +#define GPIO_PORT(num) \ + ((num >= PORT_H) ? \ + (ATMEGA_GPIO_BASE_H + ((num) - PORT_H) * ATMEGA_GPIO_SIZE) : \ + (ATMEGA_GPIO_BASE_A + (num) * ATMEGA_GPIO_SIZE)) +#define GPIO_PORT_NUM(port) \ + (((port) >= ATMEGA_GPIO_BASE_H) ? \ + (((port) - ATMEGA_GPIO_BASE_H) / ATMEGA_GPIO_SIZE + PORT_H) : \ + (((port) - ATMEGA_GPIO_BASE_A) / ATMEGA_GPIO_SIZE)) +#else +#define GPIO_PORT(num) (ATMEGA_GPIO_BASE_A + (num) * ATMEGA_GPIO_SIZE) +#define GPIO_PORT_NUM(port) (((port) - ATMEGA_GPIO_BASE_A) / ATMEGA_GPIO_SIZE) +#endif + +static inline uword_t gpio_ll_read(gpio_port_t port) +{ + atmega_gpio_port_t *p = (void *)port; + return p->pin; +} + +static inline uword_t gpio_ll_read_output(gpio_port_t port) +{ + atmega_gpio_port_t *p = (void *)port; + return p->port; +} + +/** + * @brief Check if a set/clear operation with the on the given port with the + * given mask can be done in a single-cycle bit-set / bit-clear + * instruction. + * + * @param port GPIO port to set/clear bits on + * @param mask Bitmask specifying the bits to set/clear + * + * The compiler can optimize a clear/set operation if all of the following is + * true: + * + * 1. The GPIO port number is known at compile time, so that it can be used as + * immediate + * 2. The GPIO port is alphabetically smaller than H, otherwise its registers + * are not within the I/O space (and cannot fit the 5 bit immediate field) + * 3. The bitmask is known at compile time + * 4. Exactly one bit is set in the bitmask + */ +static inline bool _can_bitwise_access(gpio_port_t port, uword_t mask) +{ + if (IS_CT_CONSTANT(port) +#ifdef ATMEGA_GPIO_BASE_H + && IS_CT_CONSTANT(port < ATMEGA_GPIO_BASE_H) + && (port < ATMEGA_GPIO_BASE_H) +#endif + && IS_CT_CONSTANT(mask) + && IS_CT_CONSTANT(__builtin_popcount(mask) == 1)) { + return __builtin_popcount(mask) == 1; + } + + return 0; +} + +static inline void gpio_ll_set(gpio_port_t port, uword_t mask) +{ + atmega_gpio_port_t *p = (void *)port; + if (_can_bitwise_access(port, mask)) { + p->port |= mask; + } + else { + unsigned state = irq_disable(); + p->port |= mask; + irq_restore(state); + } +} + +static inline void gpio_ll_clear(gpio_port_t port, uword_t mask) +{ + atmega_gpio_port_t *p = (void *)port; + if (_can_bitwise_access(port, mask)) { + p->port &= ~mask; + } + else { + unsigned state = irq_disable(); + p->port &= ~mask; + irq_restore(state); + } +} + +static inline void gpio_ll_toggle(gpio_port_t port, uword_t mask) +{ + atmega_gpio_port_t *p = (void *)port; + /* this is equivalent to `p->port ^= mask`, but faster and inherently + * atomically */ + p->pin = mask; +} + +static inline void gpio_ll_write(gpio_port_t port, uword_t value) +{ + atmega_gpio_port_t *p = (void *)port; + p->port = value; +} + +static inline gpio_port_t gpio_get_port(gpio_t pin) +{ + return GPIO_PORT(pin >> 4); +} + +static inline uint8_t gpio_get_pin_num(gpio_t pin) +{ + return pin & 0x0f; +} + +static inline uword_t gpio_ll_prepare_write_all_outputs(gpio_port_t port, + uword_t value) +{ + atmega_gpio_port_t *p = (void *)port; + uword_t result = (gpio_ll_read_output(port) & (~p->ddr)) | value; + return result; +} + +static inline uword_t gpio_ll_prepare_write(gpio_port_t port, uword_t mask, + uword_t value) +{ + atmega_gpio_port_t *p = (void *)port; + uword_t result = gpio_ll_read_output(port); + result &= (~p->ddr) | (~mask); + result |= value; + return result; +} + +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) +{ + switch (num) { + default: + return false; +#ifdef DDRA + case 0: +#endif +#ifdef DDRB + case 1: +#endif +#ifdef DDRC + case 2: +#endif +#ifdef DDRD + case 3: +#endif +#ifdef DDRE + case 4: +#endif +#ifdef DDRF + case 5: +#endif +#ifdef DDRG + case 6: +#endif +#ifdef DDRH + case 7: +#endif +#ifdef DDRI + case 8: +#endif +#ifdef DDRJ + case 9: +#endif +#ifdef DDRK + case 10: +#endif +#ifdef DDRL + case 11: +#endif +#ifdef DDRM + case 12: +#endif +#ifdef DDRN + case 13: +#endif +#ifdef DDRO + case 14: +#endif +#ifdef DDRP + case 15: +#endif +#ifdef DDRQ + case 16: +#endif +#ifdef DDRR + case 17: +#endif +#ifdef DDRS + case 18: +#endif +#ifdef DDRT + case 19: +#endif +#ifdef DDRU + case 20: +#endif +#ifdef DDRV + case 21: +#endif +#ifdef DDRW + case 22: +#endif +#ifdef DDRX + case 23: +#endif +#ifdef DDRY + case 24: +#endif +#ifdef DDRZ + case 25: +#endif + return true; + } +} + +#endif /* DOXYGEN */ +#ifdef __cplusplus +} +#endif + +#endif /* GPIO_LL_ARCH_H */ +/** @} */ diff --git a/cpu/atmega_common/include/periph_cpu_common.h b/cpu/atmega_common/include/periph_cpu_common.h index 4f0beb5ab4..5d2b04f240 100644 --- a/cpu/atmega_common/include/periph_cpu_common.h +++ b/cpu/atmega_common/include/periph_cpu_common.h @@ -56,6 +56,101 @@ typedef uint8_t gpio_t; */ #define GPIO_PIN(x, y) ((x << 4) | y) +/** + * @brief Base of the GPIO registers as memory address + * + * Must be identical to the address of `PINA` provided by avr/io.h + */ +#define ATMEGA_GPIO_BASE_A (0x20) +/** + * @brief Base of the GPIO port G register as memory address + * + * Must be identical to the address of `PING` provided by avr/io.h + */ +#define ATMEGA_GPIO_BASE_G (ATMEGA_GPIO_BASE_A + ATMEGA_GPIO_SIZE * ('G' - 'A')) +/** + * @brief Base of the GPIO registers of the second memory region (port >= H) + * + * Must be identical to the address of `PINH` provided by avr/io.h + */ +#define ATMEGA_GPIO_BASE_H (0x100) +/** + * @brief sizeof(atmega_gpio_port_t), but preprocessor friendly + */ +#define ATMEGA_GPIO_SIZE (0x03) + +#if defined(DOXYGEN) +/** + * @brief Number of external interrupt vectors + */ +#define GPIO_EXT_INT_NUMOF +#elif defined(INT7_vect) +#define GPIO_EXT_INT_NUMOF (8U) +#elif defined(INT6_vect) +#define GPIO_EXT_INT_NUMOF (7U) +#elif defined(INT5_vect) +#define GPIO_EXT_INT_NUMOF (6U) +#elif defined(INT4_vect) +#define GPIO_EXT_INT_NUMOF (5U) +#elif defined(INT3_vect) +#define GPIO_EXT_INT_NUMOF (4U) +#elif defined(INT2_vect) +#define GPIO_EXT_INT_NUMOF (3U) +#else +#define GPIO_EXT_INT_NUMOF (2U) +#endif + +/** + * @brief Structure describing the memory layout of the registers of a + * GPIO port on ATmega MCUs. + */ +typedef struct { + /** + * @brief Toggle bits in the port register + * @note The bits in the port register will be also toggled for inputs. + * This can be both a footgun as well as an efficient way to toggle + * the pull up resistor on inputs + * + * Referred to as "Input Pins Address" in the datasheet. + */ + volatile uint8_t pin; + /** + * @brief Configure pins as output (1) or input (0) using the Data + * Direction Register + */ + volatile uint8_t ddr; + /** + * @brief Read/write the state of GPIO pins using the Port Data Register + * + * @note When in input mode (see @ref atmega_gpio_port_t::ddr) writing a + * 1 will enable the pull up resistor, writing a 0 will put the + * pin in floating mode. + */ + volatile uint8_t port; +} atmega_gpio_port_t; + +/** + * @brief Get the GPIO PORT registers of the given GPIO PORT + * @param[in] port_num Number of the port to get the registers of + * @return Pointer to the registers controlling the given GPIO PORT + */ +static inline atmega_gpio_port_t *atmega_gpio_port(uint8_t port_num) +{ + static const uintptr_t base_addr = (uintptr_t)ATMEGA_GPIO_BASE_A; + uintptr_t res = base_addr + port_num * sizeof(atmega_gpio_port_t); + /* GPIO ports up to (including) G are mapped in the I/O address space, + * port H and higher (if present) are mapped in a different contiguous + * region afterwards (e.g. 0x100 for ATmega2560). */ +#ifdef PORTH + if (port_num > 'G'-'A') { + static const uintptr_t offset = ATMEGA_GPIO_BASE_H - ATMEGA_GPIO_BASE_G; + res += offset; + } +#endif + + return (atmega_gpio_port_t *)res; +} + #ifndef DOXYGEN /** * @brief Override the GPIO flanks @@ -76,6 +171,53 @@ typedef enum { /** @} */ #endif /* ndef DOXYGEN */ +#ifndef DOXYGEN /* BEGIN: GPIO LL overwrites */ +#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; + +#define HAVE_GPIO_IRQ_TRIG_T +typedef enum { + GPIO_TRIGGER_LEVEL_LOW = 0x00, + GPIO_TRIGGER_EDGE_BOTH = 0x01, + GPIO_TRIGGER_EDGE_FALLING = 0x02, + GPIO_TRIGGER_EDGE_RISING = 0x03, + GPIO_TRIGGER_LEVEL_HIGH = 0xff, /**< not supported */ +} gpio_irq_trig_t; + +#define HAVE_GPIO_PULL_T +typedef enum { + GPIO_FLOATING = 0, + GPIO_PULL_UP = 1, + GPIO_PULL_DOWN = 0xfe, /*< not supported */ + GPIO_PULL_KEEP = 0xff, /*< not supported */ +} gpio_pull_t; + +#define HAVE_GPIO_LL_PREPARE_WRITE_ALL_PINS +#define HAVE_GPIO_LL_PREPARE_WRITE + +#endif /* END: GPIO LL overwrites */ + /** * @brief Use some common SPI functions * @{ diff --git a/cpu/atmega_common/periph/gpio.c b/cpu/atmega_common/periph/gpio.c index 7a415962bf..2cca6e464f 100644 --- a/cpu/atmega_common/periph/gpio.c +++ b/cpu/atmega_common/periph/gpio.c @@ -41,26 +41,6 @@ #include "debug.h" #ifdef MODULE_PERIPH_GPIO_IRQ -/* - * @brief Define GPIO interruptions for an specific atmega CPU, by default - * 2 (for small atmega CPUs) - */ - -#if defined(INT7_vect) -#define GPIO_EXT_INT_NUMOF (8U) -#elif defined(INT6_vect) -#define GPIO_EXT_INT_NUMOF (7U) -#elif defined(INT5_vect) -#define GPIO_EXT_INT_NUMOF (6U) -#elif defined(INT4_vect) -#define GPIO_EXT_INT_NUMOF (5U) -#elif defined(INT3_vect) -#define GPIO_EXT_INT_NUMOF (4U) -#elif defined(INT2_vect) -#define GPIO_EXT_INT_NUMOF (3U) -#else -#define GPIO_EXT_INT_NUMOF (2U) -#endif static gpio_isr_ctx_t config[GPIO_EXT_INT_NUMOF]; diff --git a/cpu/atmega_common/periph/gpio_ll.c b/cpu/atmega_common/periph/gpio_ll.c new file mode 100644 index 0000000000..405a3589a6 --- /dev/null +++ b/cpu/atmega_common/periph/gpio_ll.c @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2015 HAW Hamburg + * 2016 INRIA + * 2021 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_atmega_common + * @ingroup drivers_periph_gpio_ll + * @{ + * + * @file + * @brief Peripheral GPIO Low-Level API implementation for the ATmega GPIO peripheral + * + * @author René Herthel + * @author Francisco Acosta + * @author Laurent Navet + * @author Robert Hartung + * @author Torben Petersen + * @author Marian Buschsieweke + * + * @} + */ + +#include +#include + +#include "cpu.h" +#include "irq.h" +#include "periph/gpio_ll.h" + +static void _set_dir(gpio_port_t port, uint8_t pin, bool output) +{ + atmega_gpio_port_t *p = (void *)port; + if (output) { + p->ddr |= 1U << pin; + } + else { + p-> ddr &= ~(1U << pin); + } +} + +static bool _is_output(gpio_port_t port, uint8_t pin) +{ + atmega_gpio_port_t *p = (void *)port; + return (p->ddr >> pin) & 1U; +} + +static void _set_pull_config(gpio_port_t port, uint8_t pin, gpio_pull_t pull) +{ + atmega_gpio_port_t *p = (void *)port; + p->port |= pull << pin; +} + +int gpio_ll_init(gpio_port_t port, uint8_t pin, const gpio_conf_t *conf) +{ + if ((conf->pull > GPIO_PULL_UP) + || (conf->state == GPIO_OUTPUT_OPEN_DRAIN) + || (conf->state == GPIO_OUTPUT_OPEN_SOURCE)) { + return -ENOTSUP; + } + + unsigned state = irq_disable(); + if (conf->initial_value) { + gpio_ll_set(port, 1UL << pin); + } + else { + gpio_ll_clear(port, 1UL << pin); + } + _set_dir(port, pin, conf->state == GPIO_OUTPUT_PUSH_PULL); + if (conf->state == GPIO_INPUT) { + _set_pull_config(port, pin, conf->pull); + } + irq_restore(state); + + return 0; +} + +void gpio_ll_query_conf(gpio_conf_t *dest, gpio_port_t port, uint8_t pin) +{ + assert(dest); + memset(dest, 0, sizeof(*dest)); + /* E.g. the schematics in figure 14-5 in the ATmega328P datasheet shows that + * a Schmitt Trigger is always connected before the digital input signal. + * Let's assume this is also true for all other ATmegas */ + dest->schmitt_trigger = true; + if (_is_output(port, pin)) { + dest->state = GPIO_OUTPUT_PUSH_PULL; + dest->initial_value = (gpio_ll_read_output(port) >> pin) & 1U; + } + else { + dest->state = GPIO_INPUT; + dest->pull = (gpio_ll_read_output(port) >> pin) & 1U; + dest->initial_value = (gpio_ll_read(port) >> pin) & 1U; + } +} diff --git a/cpu/atmega_common/periph/gpio_ll_irq.c b/cpu/atmega_common/periph/gpio_ll_irq.c new file mode 100644 index 0000000000..50a460eeb0 --- /dev/null +++ b/cpu/atmega_common/periph/gpio_ll_irq.c @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2015 HAW Hamburg + * 2016 INRIA + * 2022 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_atmega_common + * @ingroup drivers_periph_gpio_ll_irq + * @{ + * + * @file + * @brief IRQ implementation of the GPIO Low-Level API for ATmega + * + * @author René Herthel + * @author Francisco Acosta + * @author Laurent Navet + * @author Robert Hartung + * @author Torben Petersen + * @author Marian Buschsieweke + * + * @} + */ + +#include +#include + +#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" + +struct isr_ctx { + gpio_ll_cb_t cb; + void *arg; +}; + +static struct isr_ctx isr_ctx[GPIO_EXT_INT_NUMOF]; + +static void clear_pending_irqs(uint8_t exti) +{ + EIFR |= 1 << exti; +} + +void gpio_ll_irq_mask(gpio_port_t port, uint8_t pin) +{ + uint8_t exti = atmega_pin2exti(GPIO_PORT_NUM(port), pin); + EIMSK &= ~(1 << exti); +} + +void gpio_ll_irq_unmask(gpio_port_t port, uint8_t pin) +{ + uint8_t exti = atmega_pin2exti(GPIO_PORT_NUM(port), pin); + EIMSK |= 1 << exti; +} + +void gpio_ll_irq_unmask_and_clear(gpio_port_t port, uint8_t pin) +{ + uint8_t exti = atmega_pin2exti(GPIO_PORT_NUM(port), pin); + clear_pending_irqs(exti); + EIMSK |= 1 << exti; +} + +static void set_trigger(uint8_t exti, gpio_irq_trig_t trig) +{ + exti <<= 1; + volatile uint8_t *eicr = &EICRA; + +#ifdef EICRB + if (exti >= 8) { + eicr = & EICRB; + exti -= 8; + } +#endif + + /* being a bit more verbose here to avoid two read-modify-write cycles, + * as the compiler won't optimize access to volatile memory */ + uint8_t tmp = *eicr; + tmp &= ~(0x3 << exti); + tmp |= trig << exti; + *eicr = tmp; +} + +int gpio_ll_irq(gpio_port_t port, uint8_t pin, gpio_irq_trig_t trig, + gpio_ll_cb_t cb, void *arg) +{ + int port_num = GPIO_PORT_NUM(port); + assert((trig != GPIO_TRIGGER_LEVEL_HIGH) && cb); + if (!atmega_has_pin_exti(port_num, pin)) { + return -ENOTSUP; + } + + uint8_t exti = atmega_pin2exti(port_num, pin); + unsigned irq_state = irq_disable(); + + /* set callback */ + isr_ctx[exti].cb = cb; + isr_ctx[exti].arg = arg; + + /* setup IRQ */ + set_trigger(exti, trig); + clear_pending_irqs(exti); + EIMSK |= 1 << exti; + + irq_restore(irq_state); + + return 0; +} + +static void isr_exti(uint8_t exti) +{ + avr8_enter_isr(); + isr_ctx[exti].cb(isr_ctx[exti].arg); + avr8_exit_isr(); +} + +#ifdef INT0_vect +ISR(INT0_vect, ISR_BLOCK) +{ + isr_exti(0); +} +#endif + +#ifdef INT1_vect +ISR(INT1_vect, ISR_BLOCK) +{ + isr_exti(1); +} +#endif + +#ifdef INT2_vect +ISR(INT2_vect, ISR_BLOCK) +{ + isr_exti(2); +} +#endif + +#ifdef INT3_vect +ISR(INT3_vect, ISR_BLOCK) +{ + isr_exti(3); +} +#endif + +#ifdef INT4_vect +ISR(INT4_vect, ISR_BLOCK) +{ + isr_exti(4); +} +#endif + +#ifdef INT5_vect +ISR(INT5_vect, ISR_BLOCK) +{ + isr_exti(5); +} +#endif + +#ifdef INT6_vect +ISR(INT6_vect, ISR_BLOCK) +{ + isr_exti(6); +} +#endif + +#ifdef INT7_vect +ISR(INT7_vect, ISR_BLOCK) +{ + isr_exti(7); +} +#endif