/* * Copyright (C) 2020 iosabi * * 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_qn908x * @ingroup drivers_periph_gpio * * @{ * * @file * @brief Low-level GPIO driver implementation * * @author iosabi * * @} */ #include #include #include "cpu.h" #include "bitarithm.h" #include "periph/gpio.h" #include "vectors_qn908x.h" #include "gpio_mux.h" #include "vendor/drivers/fsl_clock.h" #include "vendor/drivers/fsl_iocon.h" /* The pull-up / pull-down / high-z mode in the gpio_mode_t enum matches the * values in the IOCON_PinMuxSet() function. */ #if (GPIO_MODE(0, 0, 0) & 0x30) != IOCON_MODE_HIGHZ #error "GPIO_MODE(x, y, 0) must be High-Z mode" #endif #if (GPIO_MODE(0, 0, 1) & 0x30) != IOCON_MODE_PULLDOWN #error "GPIO_MODE(x, y, 0) must be pull-down mode" #endif #if (GPIO_MODE(0, 0, 2) & 0x30) != IOCON_MODE_PULLUP #error "GPIO_MODE(x, y, 0) must be pull-up mode" #endif /* Bit mask indicating if a GPIO is set to open_drain. */ static uint32_t gpio_open_drain[GPIO_PORTS_NUMOF] = {}; int gpio_init(gpio_t pin, gpio_mode_t mode) { GPIO_Type *const base = GPIO_T_ADDR(pin); const uint32_t mask = 1u << GPIO_T_PIN(pin); /* We need to enable the GPIO clock before we set any register in the GPIO * blocks. */ CLOCK_EnableClock(kCLOCK_Gpio); /* Disable the interrupts just in case this was already configured as an * interrupt pin. Note: this only disables the pin(s) that you write a 1 * to. */ base->INTENCLR = mask; /* pin_mode is the "or" of the three parts: function, mode and drive * strength. The mode is just the bits 4 and 5 of the gpio_mode_t and * corresponds to the IOCON_MODE_* values */ uint32_t pin_mode = IOCON_FUNC0 | /* FUNC0 is digital GPIO on all pins. */ (mode & 0x30) | IOCON_DRIVE_HIGH; gpio_init_mux(pin, pin_mode); if (mode & 2) { /* output mode */ /* Configure the open-drain variable for allowing setting the values * later. */ if (mode & 1) { /* open-drain enabled. */ gpio_open_drain[GPIO_T_PORT(pin)] |= mask; /* Starts with the pin set to "high" (open) in open-drain mode. * The DATAOUT value doesn't do anything if the output is not * enabled but we keep track of the current value in DATAOUT * anyway to allow gpio_toggle. */ base->OUTENCLR = mask; base->DATAOUT |= mask; } else { gpio_open_drain[GPIO_T_PORT(pin)] &= ~mask; /* Starts with the pin set to low on push-pull mode. */ base->DATAOUT &= ~mask; base->OUTENSET = mask; } } else { /* input mode */ gpio_open_drain[GPIO_T_PORT(pin)] &= ~mask; base->OUTENCLR = mask; } return 0; } #ifdef MODULE_PERIPH_GPIO_IRQ typedef struct { gpio_cb_t cb; void *arg; } gpio_isr_cb_state_t; /** * @brief The number of GPIO pins per port. */ #define PINS_PER_PORT (32) /** * @brief The total number of GPIO pins in the chip. */ #define TOTAL_GPIO_PINS (35) /* The IRQ number in the NVIC for each GPIO port. */ static const uint32_t gpio_nvic_irqs[GPIO_PORTS_NUMOF] = GPIO_IRQS; static gpio_isr_cb_state_t gpio_isr_state[TOTAL_GPIO_PINS] = {}; int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, gpio_cb_t cb, void *arg) { if (flank == GPIO_BOTH) { /* GPIO_BOTH is not supported. */ return -1; } uint8_t gpio_num = GPIO_T_PORT(pin) * PINS_PER_PORT + GPIO_T_PIN(pin); if (gpio_num >= TOTAL_GPIO_PINS) { return -1; } gpio_isr_state[gpio_num].cb = cb; gpio_isr_state[gpio_num].arg = arg; if (gpio_init(pin, mode) != 0) { return -1; } GPIO_Type *const base = GPIO_T_ADDR(pin); const uint32_t mask = 1u << GPIO_T_PIN(pin); switch (flank) { case GPIO_LOW: base->INTTYPECLR = mask; /* CLR = level */ base->INTPOLCLR = mask; /* CLR = low */ break; case GPIO_HIGH: base->INTTYPECLR = mask; /* CLR = level */ base->INTPOLSET = mask; /* SET = high */ break; case GPIO_FALLING: base->INTTYPESET = mask; /* SET = edge */ base->INTPOLCLR = mask; /* CLR = falling */ break; case GPIO_RISING: base->INTTYPESET = mask; /* SET = edge */ base->INTPOLSET = mask; /* SET = rising */ break; case GPIO_BOTH: /* Handled above */ break; } NVIC_EnableIRQ(gpio_nvic_irqs[GPIO_T_PORT(pin)]); gpio_irq_enable(pin); return 0; } void gpio_irq_enable(gpio_t pin) { GPIO_T_ADDR(pin)->INTENSET = 1u << GPIO_T_PIN(pin); } void gpio_irq_disable(gpio_t pin) { GPIO_T_ADDR(pin)->INTENCLR = 1u << GPIO_T_PIN(pin); } #endif /* defined(MODULE_PERIPH_GPIO_IRQ) */ int gpio_read(gpio_t pin) { return ((GPIO_T_ADDR(pin)->DATA) >> GPIO_T_PIN(pin)) & 1u; } void gpio_set(gpio_t pin) { GPIO_Type *const base = GPIO_T_ADDR(pin); const uint32_t mask = 1u << GPIO_T_PIN(pin); /* out_clr has only the pin bit set if this is an open-drain pin, which * means we need to disable the output. This needs to happen before changing * DATAOUT. */ const uint32_t out_clr = mask & gpio_open_drain[GPIO_T_PORT(pin)]; base->OUTENCLR = out_clr; base->DATAOUT |= mask; } void gpio_clear(gpio_t pin) { GPIO_Type *const base = GPIO_T_ADDR(pin); const uint32_t mask = 1u << GPIO_T_PIN(pin); base->DATAOUT &= ~mask; /* out_clr has only the pin bit set if this is an open-drain pin, which * means we need to enable the output. This needs to happen after changing * DATAOUT. */ const uint32_t out_clr = mask & gpio_open_drain[GPIO_T_PORT(pin)]; base->OUTENSET = out_clr; } void gpio_toggle(gpio_t pin) { GPIO_Type *const base = GPIO_T_ADDR(pin); const uint32_t mask = 1u << GPIO_T_PIN(pin); const uint32_t out_clr = mask & gpio_open_drain[GPIO_T_PORT(pin)]; const uint32_t dataout = base->DATAOUT; /* The output is disabled if the pin is an open-drain pin and DATAOUT is * not set for that pin. This avoids having if conditions. */ base->OUTENCLR = out_clr & ~dataout; base->DATAOUT ^= mask; /* The output is disabled if the pin is an open-drain and DATAOUT at the * beginning of the function was set. */ base->OUTENSET = out_clr & dataout; } void gpio_write(gpio_t pin, int value) { if (value) { gpio_set(pin); } else { gpio_clear(pin); } } #ifdef MODULE_PERIPH_GPIO_IRQ static inline void irq_handler(GPIO_Type *base, uint32_t port_num) { uint32_t status = base->INTSTATUS; while (status) { /* Clear all the flags immediately and process them in order. This gives * a chance to execute every pin's interrupt handler even if another pin * is always on. * Note: to *clear* the interrupt flag you write a 1 to that bit. */ base->INTSTATUS = status; while (status) { uint8_t pin; status = bitarithm_test_and_clear(status, &pin); uint32_t gpio_num = port_num * PINS_PER_PORT + pin; gpio_cb_t cb = gpio_isr_state[gpio_num].cb; if (cb) { cb(gpio_isr_state[gpio_num].arg); } } status = base->INTSTATUS; } } #ifdef GPIOA_BASE void isr_gpioa(void) { irq_handler(GPIOA, 0); cortexm_isr_end(); } #endif /* GPIOA_BASE */ #ifdef GPIOB_BASE void isr_gpiob(void) { irq_handler(GPIOB, 1); cortexm_isr_end(); } #endif /* GPIOB_BASE */ #endif /* MODULE_PERIPH_GPIO_IRQ */