mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
36e8526046
The API was based on the assumption that GPIO ports are mapped in memory sanely, so that a `GPIO_PORT(num)` macro would work allow for constant folding when `num` is known and still be efficient when it is not. Some MCUs, however, will need a look up tables to efficiently translate GPIO port numbers to the port's base address. This will prevent the use of such a `GPIO_PORT(num)` macro in constant initializers. As a result, we rather provide `GPIO_PORT_0`, `GPIO_PORT_1`, etc. macros for each GPIO port present (regardless of MCU naming scheme), as well as `GPIO_PORT_A`, `GPIO_PORT_B`, etc. macros if (and only if) the MCU port naming scheme uses letters rather than numbers. These can be defined as macros to the peripheral base address even when those are randomly mapped into the address space. In addition, a C function `gpio_port()` replaces the role of the `GPIO_PORT()` and `gpio_port_num()` the `GPIO_PORT_NUM()` macro. Those functions will still be implemented as efficient as possible and will allow constant folding where it was formerly possible. Hence, there is no downside for MCUs with sane peripheral memory mapping, but it is highly beneficial for the crazy ones. There are also two benefits for the non-crazy MCUs: 1. We can now test for valid port numbers with `#ifdef GPIO_PORT_<NUM>` - This directly benefits the test in `tests/periph/gpio_ll`, which can now provide a valid GPIO port for each and every board - Writing to invalid memory mapped I/O addresses was treated as triggering undefined behavior by the compiler and used as a optimization opportunity 2. We can now detect at compile time if the naming scheme of the MCU uses letters or numbers, and produce more user friendly output. - This is directly applied in the test app
377 lines
8.7 KiB
C
377 lines
8.7 KiB
C
/*
|
|
* Copyright (C) 2015 Freie Universität Berlin
|
|
*
|
|
* 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_ll
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief CPU specific part of the Peripheral GPIO Low-Level API
|
|
*
|
|
* @note This GPIO driver implementation supports only one pin to be
|
|
* defined as external interrupt.
|
|
*
|
|
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
|
|
*/
|
|
|
|
#ifndef GPIO_LL_ARCH_H
|
|
#define GPIO_LL_ARCH_H
|
|
|
|
#include <assert.h>
|
|
|
|
#include "cpu.h"
|
|
#include "irq.h"
|
|
#include "kernel_defines.h"
|
|
#include "periph_cpu.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#ifndef DOXYGEN /* hide implementation specific details from Doxygen */
|
|
|
|
/* AVR generally requires two CPU cycles for loads from and stores to memory.
|
|
* However, there are special single cycle instructions, which however require
|
|
* the target address to be given as an 5 bit immediate. So only a 64 byte sized
|
|
* part of the data address space can be accessed this way, which is called
|
|
* the I/O memory (as (almost) only memory mapped I/O is mapped there).
|
|
*
|
|
* GPIO ports up to G are part of the I/O mapped area, but starting from port H
|
|
* the GPIO registers are only accessible via regular load and store operations
|
|
* and mapped slightly different in the data address space. For some reason,
|
|
* there is a gap between the GPIO memory regions for port A to G and H and
|
|
* above that results in special handling for GPIO ports H and above.
|
|
*
|
|
* Note that this implementation always uses the data address way to access GPIO
|
|
* registers and never the single cycle instructions. However, GCC converts the
|
|
* instructions into semantically equivalent single CPU cycle instructions
|
|
* whenever the target address is known at compile time (so can be expressed as
|
|
* immediate) and also mapped into the I/O address space. We rely on this
|
|
* optimization to claim single cycle GPIO accesses for GPIO ports below H,
|
|
* whenever the port number is known at compile time.
|
|
*/
|
|
|
|
#define GPIO_PORT_NUMBERING_ALPHABETIC 1
|
|
|
|
#ifdef PINA
|
|
/* We sadly cannot use PINA, as PINA is technically (in terms of C spec lingo)
|
|
* not constant and would trigger:
|
|
* initializer element is not constant
|
|
* Hence, the defines are a bit more involved to yield proper constants
|
|
* suitable for initializers.
|
|
*/
|
|
# define GPIO_PORT_0 (ATMEGA_GPIO_BASE_A)
|
|
#endif
|
|
|
|
#ifdef PINB
|
|
# define GPIO_PORT_1 (ATMEGA_GPIO_BASE_A + 1 * ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
#ifdef PINC
|
|
# define GPIO_PORT_2 (ATMEGA_GPIO_BASE_A + 2 * ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
#ifdef PIND
|
|
# define GPIO_PORT_3 (ATMEGA_GPIO_BASE_A + 3 * ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
#ifdef PINE
|
|
# define GPIO_PORT_4 (ATMEGA_GPIO_BASE_A + 4 * ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
#ifdef PINF
|
|
# define GPIO_PORT_5 (ATMEGA_GPIO_BASE_A + 5 * ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
#ifdef PING
|
|
# define GPIO_PORT_6 (ATMEGA_GPIO_BASE_A + 6 * ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
/* There is a larger gap between PING and PINH to allow other peripherals to
|
|
* also be mapped into the fast I/O memory area. */
|
|
#ifdef PINH
|
|
# define GPIO_PORT_7 (ATMEGA_GPIO_BASE_H)
|
|
#endif
|
|
|
|
#ifdef PINI
|
|
# define GPIO_PORT_8 (ATMEGA_GPIO_BASE_H + 1 * ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
#ifdef PINJ
|
|
# define GPIO_PORT_9 (ATMEGA_GPIO_BASE_H + 2 * ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
#ifdef PINK
|
|
# define GPIO_PORT_10 (ATMEGA_GPIO_BASE_H + 3 * ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
#ifdef PINL
|
|
# define GPIO_PORT_11 (ATMEGA_GPIO_BASE_H + 4 * ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
static inline gpio_port_t gpio_port(uword_t num)
|
|
{
|
|
#ifdef PINH
|
|
if (num >= PORT_H) {
|
|
return ATMEGA_GPIO_BASE_H + ((num - PORT_H) * ATMEGA_GPIO_SIZE);
|
|
}
|
|
#endif
|
|
|
|
return ATMEGA_GPIO_BASE_A + (num * ATMEGA_GPIO_SIZE);
|
|
}
|
|
|
|
static inline uword_t gpio_port_num(gpio_port_t port)
|
|
{
|
|
#ifdef PINH
|
|
if ((port) >= ATMEGA_GPIO_BASE_H) {
|
|
return (port - ATMEGA_GPIO_BASE_H) / ATMEGA_GPIO_SIZE + PORT_H;
|
|
}
|
|
#endif
|
|
|
|
return (port - ATMEGA_GPIO_BASE_A) / ATMEGA_GPIO_SIZE;
|
|
}
|
|
|
|
static inline uword_t gpio_ll_read(gpio_port_t port)
|
|
{
|
|
atmega_gpio_port_t *p = (void *)port;
|
|
return p->pin;
|
|
}
|
|
|
|
static inline uword_t gpio_ll_read_output(gpio_port_t port)
|
|
{
|
|
atmega_gpio_port_t *p = (void *)port;
|
|
return p->port;
|
|
}
|
|
|
|
/**
|
|
* @brief Check if a set/clear operation with the on the given port with the
|
|
* given mask can be done in a single-cycle bit-set / bit-clear
|
|
* instruction.
|
|
*
|
|
* @param port GPIO port to set/clear bits on
|
|
* @param mask Bitmask specifying the bits to set/clear
|
|
*
|
|
* The compiler can optimize a clear/set operation if all of the following is
|
|
* true:
|
|
*
|
|
* 1. The GPIO port number is known at compile time, so that it can be used as
|
|
* immediate
|
|
* 2. The GPIO port is alphabetically smaller than H, otherwise its registers
|
|
* are not within the I/O space (and cannot fit the 5 bit immediate field)
|
|
* 3. The bitmask is known at compile time
|
|
* 4. Exactly one bit is set in the bitmask
|
|
*/
|
|
static inline bool _can_bitwise_access(gpio_port_t port, uword_t mask)
|
|
{
|
|
if (IS_CT_CONSTANT(port)
|
|
#ifdef ATMEGA_GPIO_BASE_H
|
|
&& IS_CT_CONSTANT(port < ATMEGA_GPIO_BASE_H)
|
|
&& (port < ATMEGA_GPIO_BASE_H)
|
|
#endif
|
|
&& IS_CT_CONSTANT(mask)
|
|
&& IS_CT_CONSTANT(__builtin_popcount(mask) == 1)) {
|
|
return __builtin_popcount(mask) == 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void gpio_ll_set(gpio_port_t port, uword_t mask)
|
|
{
|
|
atmega_gpio_port_t *p = (void *)port;
|
|
if (_can_bitwise_access(port, mask)) {
|
|
p->port |= mask;
|
|
}
|
|
else {
|
|
unsigned state = irq_disable();
|
|
p->port |= mask;
|
|
irq_restore(state);
|
|
}
|
|
}
|
|
|
|
static inline void gpio_ll_clear(gpio_port_t port, uword_t mask)
|
|
{
|
|
atmega_gpio_port_t *p = (void *)port;
|
|
if (_can_bitwise_access(port, mask)) {
|
|
p->port &= ~mask;
|
|
}
|
|
else {
|
|
unsigned state = irq_disable();
|
|
p->port &= ~mask;
|
|
irq_restore(state);
|
|
}
|
|
}
|
|
|
|
static inline void gpio_ll_toggle(gpio_port_t port, uword_t mask)
|
|
{
|
|
atmega_gpio_port_t *p = (void *)port;
|
|
/* this is equivalent to `p->port ^= mask`, but faster and inherently
|
|
* atomically */
|
|
p->pin = mask;
|
|
}
|
|
|
|
static inline void gpio_ll_write(gpio_port_t port, uword_t value)
|
|
{
|
|
atmega_gpio_port_t *p = (void *)port;
|
|
p->port = value;
|
|
}
|
|
|
|
static inline gpio_port_t gpio_get_port(gpio_t pin)
|
|
{
|
|
return gpio_port(pin >> 4);
|
|
}
|
|
|
|
static inline uint8_t gpio_get_pin_num(gpio_t pin)
|
|
{
|
|
return pin & 0x0f;
|
|
}
|
|
|
|
static inline uword_t gpio_ll_prepare_write_all_outputs(gpio_port_t port,
|
|
uword_t value)
|
|
{
|
|
atmega_gpio_port_t *p = (void *)port;
|
|
uword_t result = (gpio_ll_read_output(port) & (~p->ddr)) | value;
|
|
return result;
|
|
}
|
|
|
|
static inline uword_t gpio_ll_prepare_write(gpio_port_t port, uword_t mask,
|
|
uword_t value)
|
|
{
|
|
atmega_gpio_port_t *p = (void *)port;
|
|
uword_t result = gpio_ll_read_output(port);
|
|
result &= (~p->ddr) | (~mask);
|
|
result |= value;
|
|
return result;
|
|
}
|
|
|
|
static inline void gpio_ll_switch_dir_output(gpio_port_t port, uword_t outputs)
|
|
{
|
|
unsigned irq_state = irq_disable();
|
|
atmega_gpio_port_t *p = (void *)port;
|
|
p->ddr |= outputs;
|
|
irq_restore(irq_state);
|
|
}
|
|
|
|
static inline void gpio_ll_switch_dir_input(gpio_port_t port, uword_t inputs)
|
|
{
|
|
unsigned irq_state = irq_disable();
|
|
atmega_gpio_port_t *p = (void *)port;
|
|
p->ddr &= ~(inputs);
|
|
irq_restore(irq_state);
|
|
}
|
|
|
|
static inline gpio_port_t gpio_port_pack_addr(void *addr)
|
|
{
|
|
return (gpio_port_t)addr;
|
|
}
|
|
|
|
static inline void * gpio_port_unpack_addr(gpio_port_t port)
|
|
{
|
|
if (port < RAMSTART) {
|
|
return NULL;
|
|
}
|
|
|
|
return (void *)port;
|
|
}
|
|
|
|
static inline bool is_gpio_port_num_valid(uint_fast8_t num)
|
|
{
|
|
switch (num) {
|
|
default:
|
|
return false;
|
|
#ifdef DDRA
|
|
case 0:
|
|
#endif
|
|
#ifdef DDRB
|
|
case 1:
|
|
#endif
|
|
#ifdef DDRC
|
|
case 2:
|
|
#endif
|
|
#ifdef DDRD
|
|
case 3:
|
|
#endif
|
|
#ifdef DDRE
|
|
case 4:
|
|
#endif
|
|
#ifdef DDRF
|
|
case 5:
|
|
#endif
|
|
#ifdef DDRG
|
|
case 6:
|
|
#endif
|
|
#ifdef DDRH
|
|
case 7:
|
|
#endif
|
|
#ifdef DDRI
|
|
case 8:
|
|
#endif
|
|
#ifdef DDRJ
|
|
case 9:
|
|
#endif
|
|
#ifdef DDRK
|
|
case 10:
|
|
#endif
|
|
#ifdef DDRL
|
|
case 11:
|
|
#endif
|
|
#ifdef DDRM
|
|
case 12:
|
|
#endif
|
|
#ifdef DDRN
|
|
case 13:
|
|
#endif
|
|
#ifdef DDRO
|
|
case 14:
|
|
#endif
|
|
#ifdef DDRP
|
|
case 15:
|
|
#endif
|
|
#ifdef DDRQ
|
|
case 16:
|
|
#endif
|
|
#ifdef DDRR
|
|
case 17:
|
|
#endif
|
|
#ifdef DDRS
|
|
case 18:
|
|
#endif
|
|
#ifdef DDRT
|
|
case 19:
|
|
#endif
|
|
#ifdef DDRU
|
|
case 20:
|
|
#endif
|
|
#ifdef DDRV
|
|
case 21:
|
|
#endif
|
|
#ifdef DDRW
|
|
case 22:
|
|
#endif
|
|
#ifdef DDRX
|
|
case 23:
|
|
#endif
|
|
#ifdef DDRY
|
|
case 24:
|
|
#endif
|
|
#ifdef DDRZ
|
|
case 25:
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#endif /* DOXYGEN */
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* GPIO_LL_ARCH_H */
|
|
/** @} */
|