1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/qn908x/periph/gpio.c
Marian Buschsieweke c2c2cc8592
drivers/periph_gpio: let gpio_read() return bool
Since https://github.com/RIOT-OS/RIOT/pull/20935 gpio_write()
uses a `bool` instead of an `int`. This does the same treatment for
`gpio_read()`.

This does indeed add an instruction to `gpio_read()` implementations.
However, users caring about an instruction more are better served with
`gpio_ll_read()` anyway. And `gpio_read() == 1` is often seen in
newcomer's code, which would now work as expected.
2024-10-23 13:24:09 +02:00

288 lines
7.8 KiB
C

/*
* 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 <iosabi@protonmail.com>
*
* @}
*/
#include <stddef.h>
#include <stdint.h>
#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) */
bool 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, bool 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 */