1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/cpu/stm32/periph/gpio_ll.c
Marian Buschsieweke 0dfd83938f
{cpu,drivers}/periph_gpio_ll: add missing include
For `gpio_ll_print_conf()` we need to include `<stdio.h>`, when not
using `fmt.h`.
2024-11-27 09:06:56 +01:00

453 lines
14 KiB
C

/*
* Copyright (C) 2014-2015 Freie Universität Berlin
* 2015 Hamburg University of Applied Sciences
* 2017-2020 Inria
* 2017 OTA keys S.A.
* 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_stm32
* @ingroup drivers_periph_gpio_ll
* @{
*
* @file
* @brief GPIO Low-level API implementation for the STM32 GPIO peripheral
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Fabian Nack <nack@inf.fu-berlin.de>
* @author Alexandre Abadie <alexandre.abadie@inria.fr>
* @author Katja Kirstein <katja.kirstein@haw-hamburg.de>
* @author Vincent Dupont <vincent@otakeys.com>
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <errno.h>
#include "cpu.h"
#include "periph/gpio_ll.h"
#ifdef MODULE_FMT
# include "fmt.h"
#else
# include <stdio.h>
static inline void print_str(const char *str)
{
fputs(str, stdout);
}
#endif
#ifdef RCC_AHBENR_GPIOAEN
# define GPIO_BUS AHB
# define GPIOAEN RCC_AHBENR_GPIOAEN
#endif
#ifdef RCC_AHB1ENR_GPIOAEN
# define GPIO_BUS AHB1
# define GPIOAEN RCC_AHB1ENR_GPIOAEN
#endif
#ifdef RCC_AHB2ENR1_GPIOAEN
# define GPIO_BUS AHB2
# define GPIOAEN RCC_AHB2ENR1_GPIOAEN
#endif
#ifdef RCC_AHB2ENR_GPIOAEN
# define GPIO_BUS AHB2
# define GPIOAEN RCC_AHB2ENR_GPIOAEN
#endif
#ifdef RCC_MC_AHB4ENSETR_GPIOAEN
# define GPIO_BUS AHB4
# define GPIOAEN RCC_MC_AHB4ENSETR_GPIOAEN
#endif
#ifdef RCC_IOPENR_GPIOAEN
# define GPIO_BUS IOP
# define GPIOAEN RCC_IOPENR_GPIOAEN
#endif
#ifdef RCC_APB2ENR_IOPAEN
# define GPIO_BUS APB2
# define GPIOAEN RCC_APB2ENR_IOPAEN
#endif
/* Bitmask to extract a mode field of the mode register "MODER".
* Note: Some families provide both, hence #elif */
#ifdef GPIO_MODER_MODER0_Msk
# define MODE_Msk GPIO_MODER_MODER0_Msk
#elif GPIO_MODER_MODE0_Msk
# define MODE_Msk GPIO_MODER_MODE0_Msk
#endif
/* Number of bits a mode field in the mode register "MODER" is wide.
* Note: Some families provide both, hence #elif */
#ifdef GPIO_MODER_MODER1_Pos
# define MODE_BITS GPIO_MODER_MODER1_Pos
#elif GPIO_MODER_MODE1_Pos
# define MODE_BITS GPIO_MODER_MODE1_Pos
#endif
static void _init_clock(gpio_port_t port)
{
periph_clk_en(GPIO_BUS, (GPIOAEN << gpio_port_num(port)));
#ifdef PORTG_REQUIRES_EXTERNAL_POWER
if (port == (uintptr_t)GPIOG) {
/* Port G requires external power supply */
periph_clk_en(APB1, RCC_APB1ENR1_PWREN);
PWR->CR2 |= PWR_CR2_IOSV;
}
#endif
}
#if defined(GPIO_MODER_MODER0) || defined(GPIO_MODER_MODE0)
static void _set_mode(gpio_port_t port, uint8_t pin, gpiox_moder_t mode)
{
GPIO_TypeDef *p = (void *)port;
uint32_t tmp = p->MODER;
tmp &= ~(MODE_Msk << (MODE_BITS * pin));
tmp |= (unsigned)mode << (MODE_BITS * pin);
p->MODER = tmp;
}
#endif
#if (defined(GPIO_MODER_MODER0) || defined(GPIO_MODER_MODE0)) \
&& (defined(GPIO_OTYPER_OT0) || defined(GPIO_OTYPER_OT_0))
static gpio_state_t _get_state(gpio_port_t port, uint8_t pin)
{
GPIO_TypeDef *p = (void *)port;
gpiox_moder_t moder = (p->MODER >> (MODE_BITS * pin)) & MODE_Msk;
switch (moder) {
case GPIOX_MODER_INPUT:
return GPIO_INPUT;
case GPIOX_MODER_OUTPUT:
return ((p->OTYPER >> pin) & 0x1UL) ? GPIO_OUTPUT_OPEN_DRAIN
: GPIO_OUTPUT_PUSH_PULL;
case GPIOX_MODER_ANALOG:
return GPIO_DISCONNECT;
default:
case GPIOX_MODER_AF:
return GPIO_USED_BY_PERIPHERAL;
}
}
#endif
#if defined(GPIO_OTYPER_OT0) || defined(GPIO_OTYPER_OT_0)
static void _set_output_type(gpio_port_t port, uint8_t pin, bool open_drain)
{
GPIO_TypeDef *p = (void *)port;
if (open_drain) {
p->OTYPER |= 1UL << pin;
}
else {
p->OTYPER &= ~(1UL << pin);
}
}
#endif
#if defined(GPIO_PUPDR_PUPDR0) || defined(GPIO_PUPDR_PUPD0)
static void _set_pull_config(gpio_port_t port, uint8_t pin, gpio_pull_t pull)
{
GPIO_TypeDef *p = (void *)port;
/* being more verbose here so that compiler doesn't generate two loads and stores when accessing
* volatile variable */
uint32_t pupdr = p->PUPDR;
pupdr &= ~(0x3UL << (2 * pin));
pupdr |= (uint32_t)pull << (2 * pin);
p->PUPDR = pupdr;
}
static gpio_pull_t _get_pull_config(gpio_port_t port, uint8_t pin)
{
GPIO_TypeDef *p = (void *)port;
uint32_t pupdr = (p->PUPDR >> (2 * pin)) & 0x3UL;
return (gpio_pull_t)pupdr;
}
#endif
#if defined(GPIO_OSPEEDR_OSPEED0) || defined(GPIO_OSPEEDER_OSPEEDR0) \
|| defined(GPIO_OSPEEDER_OSPEED0) || defined(GPIO_OSPEEDR_OSPEEDR0)
static void _set_slew_rate(gpio_port_t port, uint8_t pin, gpio_slew_t slew_rate)
{
GPIO_TypeDef *p = (void *)port;
/* being more verbose here so that compiler doesn't generate two loads and
* stores when accessing volatile variable */
uint32_t ospeedr = p->OSPEEDR;
ospeedr &= ~(3UL << (2 * pin));
ospeedr |= (uint32_t)slew_rate << (2 * pin);
p->OSPEEDR = ospeedr;
}
static gpio_slew_t _get_slew_rate(gpio_port_t port, uint8_t pin)
{
GPIO_TypeDef *p = (void *)port;
uint32_t ospeedr = (p->OSPEEDR >> (2 * pin)) & 0x3UL;
return (gpio_slew_t)ospeedr;
}
#endif
#ifdef GPIO_CRL_MODE
static void _set_legacy_f1_config(gpio_port_t port, uint8_t pin,
gpio_conf_t conf)
{
/* STM32F1 style config register mix output mode and slew rate into the
* same field. This look up table can be used to look up the correct
* output mode by slew rate */
static const uint8_t output_mode_by_slew_rate[] = {
[GPIO_SLEW_SLOWEST] = GPIO_CRL_MODE0_OUTPUT_2MHZ,
[GPIO_SLEW_FAST] = GPIO_CRL_MODE0_OUTPUT_10MHZ,
[GPIO_SLEW_FASTEST] = GPIO_CRL_MODE0_OUTPUT_50MHZ,
};
GPIO_TypeDef *p = (void *)port;
/* There is low control register (CRL) for pins 0-7, and a high control
* register (CRH) for pins 8-15. `offset` is the offset within the
* registers, `high_reg` is true if CRH is to be used */
unsigned offset = (pin & 0x7U) << 2;
bool high_reg = pin > 7;
uint32_t control = high_reg ? p->CRH : p -> CRL;
assert((unsigned)conf.slew_rate < ARRAY_SIZE(output_mode_by_slew_rate));
/* prepare bis in cnf and mode fields for given pin */
uint32_t cnf_mode = 0;
switch (conf.state) {
default:
case GPIO_DISCONNECT:
/* Keeping GPIO in analog mode is said to reduce power consumption.
* This is plausible, as the Schmitt trigger and the input buffer could
* be disabled. */
cnf_mode = GPIO_CRL_MODE0_INPUT | GPIO_CRL_CNF0_INPUT_ANALOG;
break;
case GPIO_INPUT:
switch (conf.pull) {
default:
case GPIO_FLOATING:
cnf_mode = GPIO_CRL_MODE0_INPUT | GPIO_CRL_CNF0_INPUT_FLOATING;
break;
case GPIO_PULL_UP:
cnf_mode = GPIO_CRL_MODE0_INPUT | GPIO_CRL_CNF0_INPUT_PULL;
/* ODR controls pull resistor in input mode. We access ODR via
* BSRR to atomically set the bit (mostly to safe ROM and CPU
* cycles, IRQs are disabled anyway) */
p->BSRR = 1U << pin;
break;
case GPIO_PULL_DOWN:
cnf_mode = GPIO_CRL_MODE0_INPUT | GPIO_CRL_CNF0_INPUT_PULL;
/* ODR controls pull resistor in input mode. We access ODR via
* BSRR to atomically clear the bit (mostly to safe ROM and CPU
* cycles, IRQs are disabled anyway) */
p->BSRR = 1U << (pin | 0x10);
}
break;
case GPIO_OUTPUT_PUSH_PULL:
cnf_mode = GPIO_CRL_CNF0_OUTPUT_PUSH_PULL
| output_mode_by_slew_rate[conf.slew_rate];
break;
case GPIO_OUTPUT_OPEN_DRAIN:
cnf_mode = GPIO_CRL_CNF0_OUTPUT_OPEN_DRAIN
| output_mode_by_slew_rate[conf.slew_rate];
}
/* clear old values of cnf and mode fields in config reg */
control &= ~(0xFU << offset);
/* apply new values of cnf and mode fields in config reg */
control |= cnf_mode << offset;
if (high_reg) {
p->CRH = control;
}
else {
p->CRL = control;
}
}
static gpio_conf_t _get_legacy_f1_config(gpio_port_t port, uint8_t pin)
{
gpio_conf_t result = { 0 };
GPIO_TypeDef *p = (void *)port;
unsigned offset = (pin & 0x7U) << 2;
bool high_reg = pin > 7;
uint32_t control = high_reg ? p->CRH : p ->CRL;
uint32_t cnf_mode = control >> offset;
uint32_t cnf = cnf_mode & GPIO_CRL_CNF0_Msk;
uint32_t mode = cnf_mode & GPIO_CRL_MODE0_Msk;
switch (mode) {
default:
case GPIO_CRL_MODE0_INPUT:
switch (cnf) {
default:
case GPIO_CRL_CNF0_INPUT_ANALOG:
result.state = GPIO_DISCONNECT;
break;
case GPIO_CRL_CNF0_INPUT_FLOATING:
result.state = GPIO_INPUT;
break;
case GPIO_CRL_CNF0_INPUT_PULL:
result.state = GPIO_INPUT;
result.pull = GPIO_PULL_DOWN;
if (p->ODR & (1U << pin)) {
result.pull = GPIO_PULL_UP;
}
}
return result;
case GPIO_CRL_MODE0_OUTPUT_2MHZ:
result.slew_rate = GPIO_SLEW_SLOWEST;
break;
case GPIO_CRL_MODE0_OUTPUT_10MHZ:
result.slew_rate = GPIO_SLEW_FAST;
break;
case GPIO_CRL_MODE0_OUTPUT_50MHZ:
result.slew_rate = GPIO_SLEW_FASTEST;
break;
}
switch (cnf) {
case GPIO_CRL_CNF0_OUTPUT_PUSH_PULL:
result.state = GPIO_OUTPUT_PUSH_PULL;
break;
case GPIO_CRL_CNF0_OUTPUT_OPEN_DRAIN:
result.state = GPIO_OUTPUT_OPEN_DRAIN;
break;
default:
case GPIO_CRL_CNF0_AF_PUSH_PULL:
case GPIO_CRL_CNF0_AF_OPEN_DRAIN:
result.state = GPIO_USED_BY_PERIPHERAL;
}
return result;
}
#endif
int gpio_ll_init(gpio_port_t port, uint8_t pin, gpio_conf_t conf)
{
if ((conf.pull == GPIO_PULL_KEEP) || (conf.state == GPIO_OUTPUT_OPEN_SOURCE)) {
return -ENOTSUP;
}
#ifndef GPIO_PUPDR_PUPDR0
/* without dedicated pull up / pull down register, pull resistors can only
* be used with input pins */
if ((conf.state == GPIO_OUTPUT_OPEN_DRAIN) && (conf.pull != GPIO_FLOATING)) {
return -ENOTSUP;
}
#endif
unsigned state = irq_disable();
_init_clock(port);
if (conf.initial_value) {
gpio_ll_set(port, 1UL << pin);
}
else {
gpio_ll_clear(port, 1UL << pin);
}
#ifdef GPIO_CRL_MODE
/* old STM32F1 style GPIO configuration register layout */
_set_legacy_f1_config(port, pin, conf);
#else
/* modern STM32 style GPIO configuration register layout */
_set_output_type(port, pin, conf.state & GPIO_STATE_T_OPEN_DRAIN_FLAG);
_set_pull_config(port, pin, conf.pull);
_set_slew_rate(port, pin, conf.slew_rate);
_set_mode(port, pin, conf.state & GPIO_STATE_T_MODER_Msk);
#endif
irq_restore(state);
return 0;
}
uword_t gpio_ll_prepare_switch_dir_impl(uword_t mask)
{
/* Mask contains a bitmask containing the pins needed to change
* the direction of. E.g. for pins 0 to 3 it looks like:
*
* 3 2 1 0
* +----+----+----+----+
* | P3 | P2 | P1 | P0 |
* +----+----+----+----+
*
* We need to update the GPIOX->MODER register, which for pins 0 to 3
* looks like this:
*
* 7 6 5 4 3 2 1 0
* +---------+---------+---------+---------+
* | MODE3 | MODE2 | MODE1 | MODE0 |
* +---------+---------+---------+---------+
*
* Where each mode field will have the value `0b00` for input or `0b01`
* for output (the others two values are for alternate function mode and
* analog mode, which are both not relevant here). So, we need a way to
* efficiently set and clear every second bit. Specifically, a bitmask
* that looks like this is our goal:
*
* 7 6 5 4 3 2 1 0
* +----+----+----+----+----+----+----+----+
* | 0 | P3 | 0 | P2 | 0 | P1 | 0 | P0 |
* +----+----+----+----+----+----+----+----+
*
* This is what below bit magic magic does (but for 16 pins instead of
* 4).
*/
uword_t output = mask & 0xFFFF;
output |= output << 8;
output &= 0x00FF00FF;
output |= output << 4;
output &= 0x0F0F0F0F;
output |= output << 2;
output &= 0x33333333;
output |= output << 1;
output &= 0x55555555;
return output;
}
gpio_conf_t gpio_ll_query_conf(gpio_port_t port, uint8_t pin)
{
gpio_conf_t result = { 0 };
unsigned state = irq_disable();
#ifdef GPIO_CRL_MODE
/* old STM32F1 style GPIO configuration register layout */
result = _get_legacy_f1_config(port, pin);
#else
/* modern STM32 style GPIO configuration register layout */
result.state = _get_state(port, pin);
result.pull = _get_pull_config(port, pin);
result.slew_rate = _get_slew_rate(port, pin);
#endif
if (result.state == GPIO_INPUT) {
result.initial_value = (gpio_ll_read(port) >> pin) & 1UL;
}
else {
result.initial_value = (gpio_ll_read_output(port) >> pin) & 1UL;
}
irq_restore(state);
return result;
}
void gpio_ll_print_conf(gpio_conf_t conf)
{
static const char *slew_strs[] = {
[GPIO_SLEW_SLOWEST] = "slowest",
#if STM32_HAS_OSPEED
[GPIO_SLEW_SLOW] = "slow",
#endif
[GPIO_SLEW_FAST] = "fast",
[GPIO_SLEW_FASTEST] = "fastest",
/* If only three slew rates are supported, a fourth value would be
* representable with the two-bit field. Let's be rather safe than sorry */
#if !STM32_HAS_OSPEED
"invalid"
#endif
};
gpio_ll_print_conf_common(conf);
print_str(", slew: ");
print_str(slew_strs[conf.slew_rate]);
}