/* * Copyright (C) 2014 Freie UniversitÀt Berlin * Copyright (C) 2014 PHYTEC Messtechnik GmbH * Copyright (C) 2014 Eistec AB * * 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_kinetis_common * @ingroup drivers_periph_gpio * * @{ * * @file * @brief Low-level GPIO driver implementation * * @author Hauke Petersen * @author Johann Fischer * @author Jonas Remmert * @author Joakim NohlgÄrd * * @} */ #include #include #include "cpu.h" #include "bit.h" #include "periph/gpio.h" /** * @brief Get the OCR reg value from the gpio_mode_t value */ #define MODE_PCR_MASK (PORT_PCR_ODE_MASK | PORT_PCR_PE_MASK | PORT_PCR_PS_MASK) /** * @brief This bit in the mode is set to 1 for output configuration */ #define MODE_OUT (0x80) /** * @brief Shifting a gpio_t value by this number of bit we can extract the * port number from the GPIO base address */ #define GPIO_SHIFT (6) /** * @brief Mask used to extract the PORT base address from the gpio_t value */ #define PORT_ADDR_MASK (0x00007000) /** * @brief Mask used to extract the GPIO base address from the gpio_t value */ #define GPIO_ADDR_MASK (0x000001c0) /** * @brief Cleaned up PORT base address */ #define PORT_ADDR_BASE (PORTA_BASE & ~(PORT_ADDR_MASK)) /** * @brief Cleaned up GPIO base address */ #define GPIO_ADDR_BASE (GPIOA_BASE & ~(GPIO_ADDR_MASK)) /** * @brief Kinetis CPUs have 32 pins per port */ #define PINS_PER_PORT (32) /** * @brief Calculate the needed memory (in byte) needed to save 4 bits per MCU * pin */ #define ISR_MAP_SIZE (GPIO_PORTS_NUMOF * PINS_PER_PORT * 4 / 8) /** * @brief Define the number of simultaneously configurable interrupt channels * * We have configured 4-bits per pin, so we can go up to 16 simultaneous active * extern interrupt sources. */ #define CTX_NUMOF (8U) /** * @brief Interrupt context data */ typedef struct { gpio_cb_t cb; void *arg; uint32_t state; } isr_ctx_t; /** * @brief Allocation of memory for each independent interrupt slot * * We trust the start-up code here to initialize all bytes of this array to * zero. */ static isr_ctx_t isr_ctx[CTX_NUMOF]; /** * @brief Allocation of 4 bit per pin to map a pin to an interrupt context */ static uint32_t isr_map[ISR_MAP_SIZE]; static inline PORT_Type *port(gpio_t pin) { return (PORT_Type *)(PORT_ADDR_BASE | (pin & PORT_ADDR_MASK)); } static inline GPIO_Type *gpio(gpio_t pin) { return (GPIO_Type *)(GPIO_ADDR_BASE | (pin & GPIO_ADDR_MASK)); } static inline int port_num(gpio_t pin) { return (int)((pin >> GPIO_SHIFT) & 0x7); } static inline int pin_num(gpio_t pin) { return (int)(pin & 0x3f); } static inline void clk_en(gpio_t pin) { bit_set32(&SIM->SCGC5, SIM_SCGC5_PORTA_SHIFT + port_num(pin)); } /** * @brief Get context for a specific pin */ static inline int get_ctx(int port, int pin) { return (isr_map[(port * 4) + (pin >> 3)] >> ((pin & 0x7) * 4)) & 0xf; } /** * @brief Find a free spot in the array containing the interrupt contexts */ static int get_free_ctx(void) { for (unsigned int i = 0; i < CTX_NUMOF; i++) { if (isr_ctx[i].cb == NULL) { return i; } } return -1; } /** * @brief Write an entry to the context map array */ static void write_map(int port, int pin, int ctx) { isr_map[(port * 4) + (pin >> 3)] &= ~(0xf << ((pin & 0x7) * 4)); isr_map[(port * 4) + (pin >> 3)] |= (ctx << ((pin & 0x7) * 4)); } /** * @brief Clear the context for the given pin */ static void ctx_clear(int port, int pin) { int ctx = get_ctx(port, pin); write_map(port, pin, ctx); } int gpio_init(gpio_t pin, gpio_mode_t mode) { /* set pin to analog mode while configuring it */ gpio_init_port(pin, GPIO_AF_ANALOG); /* set pin direction */ if (mode & MODE_OUT) { gpio(pin)->PDDR |= (1 << pin_num(pin)); } else { gpio(pin)->PDDR &= ~(1 << pin_num(pin)); } /* enable GPIO function */ port(pin)->PCR[pin_num(pin)] = (GPIO_AF_GPIO | (mode & MODE_PCR_MASK)); return 0; } int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, gpio_cb_t cb, void *arg) { if (gpio_init(pin, mode) < 0) { return -1; } /* try go grab a free spot in the context array */ int ctx_num = get_free_ctx(); if (ctx_num < 0) { return -1; } /* save interrupt context */ isr_ctx[ctx_num].cb = cb; isr_ctx[ctx_num].arg = arg; isr_ctx[ctx_num].state = flank; write_map(port_num(pin), pin_num(pin), ctx_num); /* clear interrupt flags */ port(pin)->ISFR &= ~(1 << pin_num(pin)); /* enable global port interrupts in the NVIC */ NVIC_EnableIRQ(PORTA_IRQn + port_num(pin)); /* finally, enable the interrupt for the select pin */ port(pin)->PCR[pin_num(pin)] |= flank; return 0; } void gpio_init_port(gpio_t pin, uint32_t pcr) { /* enable PORT clock in case it was not active before */ clk_en(pin); /* if the given interrupt was previously configured as interrupt source, we * need to free its interrupt context. We to this only after we * re-configured the pin in case an event is happening just in between... */ uint32_t isr_state = port(pin)->PCR[pin_num(pin)]; /* set new PCR value */ port(pin)->PCR[pin_num(pin)] = pcr; /* and clear the interrupt context if needed */ if (isr_state & PORT_PCR_IRQC_MASK) { ctx_clear(port_num(pin), pin_num(pin)); } } void gpio_irq_enable(gpio_t pin) { int ctx = get_ctx(port_num(pin), pin_num(pin)); port(pin)->PCR[pin_num(pin)] |= isr_ctx[ctx].state; } void gpio_irq_disable(gpio_t pin) { int ctx = get_ctx(port_num(pin), pin_num(pin)); isr_ctx[ctx].state = port(pin)->PCR[pin_num(pin)] & PORT_PCR_IRQC_MASK; port(pin)->PCR[pin_num(pin)] &= ~(PORT_PCR_IRQC_MASK); } int gpio_read(gpio_t pin) { if (gpio(pin)->PDDR & (1 << pin_num(pin))) { return (gpio(pin)->PDOR & (1 << pin_num(pin))) ? 1 : 0; } else { return (gpio(pin)->PDIR & (1 << pin_num(pin))) ? 1 : 0; } } void gpio_set(gpio_t pin) { gpio(pin)->PSOR = (1 << pin_num(pin)); } void gpio_clear(gpio_t pin) { gpio(pin)->PCOR = (1 << pin_num(pin)); } void gpio_toggle(gpio_t pin) { gpio(pin)->PTOR = (1 << pin_num(pin)); } void gpio_write(gpio_t pin, int value) { if (value) { gpio(pin)->PSOR = (1 << pin_num(pin)); } else { gpio(pin)->PCOR = (1 << pin_num(pin)); } } static inline void irq_handler(PORT_Type *port, int port_num) { /* take interrupt flags only from pins which interrupt is enabled */ uint32_t status = port->ISFR; for (int i = 0; i < 32; i++) { if ((status & (1 << i)) && (port->PCR[i] & PORT_PCR_IRQC_MASK)) { port->ISFR = (1 << i); int ctx = get_ctx(port_num, i); isr_ctx[ctx].cb(isr_ctx[ctx].arg); } } cortexm_isr_end(); } #ifdef PORTA_BASE void isr_porta(void) { irq_handler(PORTA, 0); } #endif /* PORTA_BASE */ #ifdef PORTB_BASE void isr_portb(void) { irq_handler(PORTB, 1); } #endif /* ISR_PORT_B */ #ifdef PORTC_BASE void isr_portc(void) { irq_handler(PORTC, 2); } #endif /* ISR_PORT_C */ #ifdef PORTD_BASE void isr_portd(void) { irq_handler(PORTD, 3); } #endif /* ISR_PORT_D */ #ifdef PORTE_BASE void isr_porte(void) { irq_handler(PORTE, 4); } #endif /* ISR_PORT_E */ #ifdef PORTF_BASE void isr_portf(void) { irq_handler(PORTF, 5); } #endif /* ISR_PORT_F */ #ifdef PORTG_BASE void isr_portg(void) { irq_handler(PORTG, 6); } #endif /* ISR_PORT_G */