/* * Copyright (C) 2015 HAW Hamburg * 2016 INRIA * * 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 * @{ * * @file * @brief Low-level GPIO driver implementation for ATmega family * * @author René Herthel * @author Francisco Acosta * @author Laurent Navet * @author Robert Hartung * @author Torben Petersen * @author Marian Buschsieweke * * @} */ #include #include #include "cpu.h" #include "board.h" #include "periph/gpio.h" #include "periph_conf.h" #include "periph_cpu.h" #include "atmega_gpio.h" #define ENABLE_DEBUG 0 #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]; /* Detects amount of possible PCINTs */ #if defined(MODULE_ATMEGA_PCINT0) || defined(MODULE_ATMEGA_PCINT1) || \ defined(MODULE_ATMEGA_PCINT2) || defined(MODULE_ATMEGA_PCINT3) #include "atmega_pcint.h" #define ENABLE_PCINT /* Check which pcints should be enabled */ #if defined(MODULE_ATMEGA_PCINT0) && !defined(ATMEGA_PCINT_MAP_PCINT0) #error \ Either mapping for pin change interrupt bank 0 is missing or not supported by the MCU #endif #if defined(MODULE_ATMEGA_PCINT1) && !defined(ATMEGA_PCINT_MAP_PCINT1) #error \ Either mapping for pin change interrupt bank 1 is missing or not supported by the MCU #endif #if defined(MODULE_ATMEGA_PCINT2) && !defined(ATMEGA_PCINT_MAP_PCINT2) #error \ Either mapping for pin change interrupt bank 2 is missing or not supported by the MCU #endif #if defined(MODULE_ATMEGA_PCINT3) && !defined(ATMEGA_PCINT_MAP_PCINT3) #error \ Either mapping for pin change interrupt bank 3 is missing or not supported by the MCU #endif /** * @brief Use anonymous enum as for addressing the @ref pcint_state */ enum { #ifdef MODULE_ATMEGA_PCINT0 PCINT0_IDX, /**< Index of PCINT0, if used */ #endif /* MODULE_ATMEGA_PCINT0 */ #ifdef MODULE_ATMEGA_PCINT1 PCINT1_IDX, /**< Index of PCINT1, if used */ #endif /* MODULE_ATMEGA_PCINT1 */ #ifdef MODULE_ATMEGA_PCINT2 PCINT2_IDX, /**< Index of PCINT2, if used */ #endif /* MODULE_ATMEGA_PCINT2 */ #ifdef MODULE_ATMEGA_PCINT3 PCINT3_IDX, /**< Index of PCINT3, if used */ #endif /* MODULE_ATMEGA_PCINT3 */ PCINT_NUM_BANKS /**< Number of PCINT banks used */ }; /** * @brief stores the last pcint state of each port */ static uint8_t pcint_state[PCINT_NUM_BANKS]; /** * @brief stores all cb and args for all defined pcint. */ typedef struct { gpio_flank_t flank; /**< type of interrupt the flank should be triggered on */ gpio_cb_t cb; /**< interrupt callback */ void *arg; /**< optional argument */ } gpio_isr_ctx_pcint_t; /** * @brief */ static const gpio_t pcint_mapping[] = { #ifdef MODULE_ATMEGA_PCINT0 ATMEGA_PCINT_MAP_PCINT0, #endif /* PCINT0_IDX */ #ifdef MODULE_ATMEGA_PCINT1 ATMEGA_PCINT_MAP_PCINT1, #endif /* PCINT1_IDX */ #ifdef MODULE_ATMEGA_PCINT2 ATMEGA_PCINT_MAP_PCINT2, #endif /* PCINT2_IDX */ #ifdef MODULE_ATMEGA_PCINT3 ATMEGA_PCINT_MAP_PCINT3, #endif /* PCINT3_IDX */ }; /** * @brief */ static gpio_isr_ctx_pcint_t pcint_config[8 * PCINT_NUM_BANKS]; #endif /* MODULE_ATMEGA_PCINTn */ #endif /* MODULE_PERIPH_GPIO_IRQ */ int gpio_init(gpio_t pin, gpio_mode_t mode) { uint8_t pin_mask = (1 << atmega_pin_num(pin)); switch (mode) { case GPIO_OUT: _SFR_MEM8(atmega_ddr_addr(pin)) |= pin_mask; break; case GPIO_IN: _SFR_MEM8(atmega_ddr_addr(pin)) &= ~pin_mask; _SFR_MEM8(atmega_port_addr(pin)) &= ~pin_mask; break; case GPIO_IN_PU: _SFR_MEM8(atmega_ddr_addr(pin)) &= ~pin_mask; _SFR_MEM8(atmega_port_addr(pin)) |= pin_mask; break; default: return -1; } return 0; } int gpio_read(gpio_t pin) { return (_SFR_MEM8(atmega_pin_addr(pin)) & (1 << atmega_pin_num(pin))); } void gpio_set(gpio_t pin) { _SFR_MEM8(atmega_port_addr(pin)) |= (1 << atmega_pin_num(pin)); } void gpio_clear(gpio_t pin) { _SFR_MEM8(atmega_port_addr(pin)) &= ~(1 << atmega_pin_num(pin)); } void gpio_toggle(gpio_t pin) { if (gpio_read(pin)) { gpio_clear(pin); } else { gpio_set(pin); } } void gpio_write(gpio_t pin, int value) { if (value) { gpio_set(pin); } else { gpio_clear(pin); } } #ifdef MODULE_PERIPH_GPIO_IRQ static inline int8_t _int_num(gpio_t pin) { uint8_t num; const gpio_t ext_ints[GPIO_EXT_INT_NUMOF] = CPU_ATMEGA_EXT_INTS; /* find pin in ext_ints array to get the interrupt number */ for (num = 0; num < GPIO_EXT_INT_NUMOF; num++) { if (pin == ext_ints[num]) { return num; } } return -1; } #ifdef ENABLE_PCINT static inline int pcint_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, gpio_cb_t cb, void *arg) { int8_t offset = -1; uint8_t pin_num = atmega_pin_num(pin); for (unsigned i = 0; i < ARRAY_SIZE(pcint_mapping); i++) { if (pin != GPIO_UNDEF && pin == pcint_mapping[i]) { offset = i; break; } } /* if pcint was not found: return -1 */ if (offset < 0) { return offset; } uint8_t bank = offset / 8; uint8_t bank_idx = offset % 8; DEBUG("PCINT enabled for bank %u offset %u\n", (unsigned)bank, (unsigned)offset); /* save configuration for pin change interrupt */ pcint_config[offset].flank = flank; pcint_config[offset].arg = arg; pcint_config[offset].cb = cb; /* init gpio */ gpio_init(pin, mode); /* configure pcint */ cli(); switch (bank) { #ifdef MODULE_ATMEGA_PCINT0 case PCINT0_IDX: PCMSK0 |= (1 << bank_idx); PCICR |= (1 << PCIE0); break; #endif /* MODULE_ATMEGA_PCINT0 */ #ifdef MODULE_ATMEGA_PCINT1 case PCINT1_IDX: PCMSK1 |= (1 << bank_idx); PCICR |= (1 << PCIE1); break; #endif /* MODULE_ATMEGA_PCINT1 */ #ifdef MODULE_ATMEGA_PCINT2 case PCINT2_IDX: PCMSK2 |= (1 << bank_idx); PCICR |= (1 << PCIE2); break; #endif /* MODULE_ATMEGA_PCINT2 */ #ifdef MODULE_ATMEGA_PCINT3 case PCINT3_IDX: PCMSK3 |= (1 << bank_idx); PCICR |= (1 << PCIE3); break; #endif /* MODULE_ATMEGA_PCINT3 */ default: return -1; break; } /* As ports are mixed in a bank (e.g. PCINT0), we can only save a single bit here! */ uint8_t port_value = (_SFR_MEM8(atmega_pin_addr( pin ))); uint8_t pin_mask = (1 << pin_num); uint8_t pin_value = ((port_value & pin_mask) != 0); if (pin_value) { pcint_state[bank] |= pin_mask; } else { pcint_state[bank] &= ~pin_mask; } sei(); return 0; } #endif /* ENABLE_PCINT */ int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, gpio_cb_t cb, void *arg) { int8_t int_num = _int_num(pin); /* mode not supported */ if ((mode != GPIO_IN) && (mode != GPIO_IN_PU)) { return -1; } /* not a valid interrupt pin. Set as pcint instead if pcints are enabled */ if (int_num < 0) { #ifdef ENABLE_PCINT /* If pin change interrupts are enabled, enable mask and interrupt */ return pcint_init_int(pin, mode, flank, cb, arg); #else return -1; #endif /* ENABLE_PCINT */ } /* flank not supported */ if (flank > GPIO_RISING) { return -1; } gpio_init(pin, mode); /* clear global interrupt flag */ cli(); /* enable interrupt number int_num */ EIFR |= (1 << int_num); EIMSK |= (1 << int_num); /* apply flank to interrupt number int_num */ if (int_num < 4) { EICRA &= ~(0x3 << (int_num * 2)); EICRA |= (flank << (int_num * 2)); } #if defined(EICRB) else { EICRB &= ~(0x3 << ((int_num % 4) * 2)); EICRB |= (flank << ((int_num % 4) * 2)); } #endif /* set callback */ config[int_num].cb = cb; config[int_num].arg = arg; /* set global interrupt flag */ sei(); return 0; } void gpio_irq_enable(gpio_t pin) { EIFR |= (1 << _int_num(pin)); EIMSK |= (1 << _int_num(pin)); } void gpio_irq_disable(gpio_t pin) { EIMSK &= ~(1 << _int_num(pin)); } static inline void irq_handler(uint8_t int_num) { avr8_enter_isr(); config[int_num].cb(config[int_num].arg); avr8_exit_isr(); } #ifdef ENABLE_PCINT /* inline function that is used by the PCINT ISR */ static inline void pcint_handler(uint8_t bank, uint8_t enabled_pcints) { avr8_enter_isr(); /* Find right item */ uint8_t idx = 0; while (enabled_pcints > 0) { /* check if this pin is enabled & has changed */ if (enabled_pcints & 0x1) { /* get pin from mapping (assumes 8 entries per bank!) */ gpio_t pin = pcint_mapping[bank * 8 + idx]; /* re-construct mask from pin */ uint8_t pin_mask = (1 << (atmega_pin_num(pin))); uint8_t idx_mask = (1 << idx); uint8_t port_value = (_SFR_MEM8(atmega_pin_addr( pin ))); uint8_t pin_value = ((port_value & pin_mask) != 0); uint8_t old_state = ((pcint_state[bank] & idx_mask) != 0); gpio_isr_ctx_pcint_t *conf = &pcint_config[bank * 8 + idx]; if (old_state != pin_value) { pcint_state[bank] ^= idx_mask; if ((conf->flank == GPIO_BOTH || (pin_value && conf->flank == GPIO_RISING) || (!pin_value && conf->flank == GPIO_FALLING))) { /* execute callback routine */ conf->cb(conf->arg); } } } enabled_pcints = enabled_pcints >> 1; idx++; } avr8_exit_isr(); } #ifdef MODULE_ATMEGA_PCINT0 ISR(PCINT0_vect, ISR_BLOCK) { pcint_handler(PCINT0_IDX, PCMSK0); } #endif /* MODULE_ATMEGA_PCINT0 */ #ifdef MODULE_ATMEGA_PCINT1 ISR(PCINT1_vect, ISR_BLOCK) { pcint_handler(PCINT1_IDX, PCMSK1); } #endif /* MODULE_ATMEGA_PCINT1 */ #ifdef MODULE_ATMEGA_PCINT2 ISR(PCINT2_vect, ISR_BLOCK) { pcint_handler(PCINT2_IDX, PCMSK2); } #endif /* MODULE_ATMEGA_PCINT2 */ #ifdef MODULE_ATMEGA_PCINT3 ISR(PCINT3_vect, ISR_BLOCK) { pcint_handler(PCINT3_IDX, PCMSK3); } #endif /* MODULE_ATMEGA_PCINT3 */ #endif /* ENABLE_PCINT */ ISR(INT0_vect, ISR_BLOCK) { irq_handler(0); /**< predefined interrupt pin */ } ISR(INT1_vect, ISR_BLOCK) { irq_handler(1); /**< predefined interrupt pin */ } #if defined(INT2_vect) ISR(INT2_vect, ISR_BLOCK) { irq_handler(2); /**< predefined interrupt pin */ } #endif #if defined(INT3_vect) ISR(INT3_vect, ISR_BLOCK) { irq_handler(3); /**< predefined interrupt pin */ } #endif #if defined(INT4_vect) ISR(INT4_vect, ISR_BLOCK) { irq_handler(4); /**< predefined interrupt pin */ } #endif #if defined(INT5_vect) ISR(INT5_vect, ISR_BLOCK) { irq_handler(5); /**< predefined interrupt pin */ } #endif #if defined(INT6_vect) ISR(INT6_vect, ISR_BLOCK) { irq_handler(6); /**< predefined interrupt pin */ } #endif #if defined(INT7_vect) ISR(INT7_vect, ISR_BLOCK) { irq_handler(7); /**< predefined interrupt pin */ } #endif #endif /* MODULE_PERIPH_GPIO_IRQ */