mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
04ab5a74f3
Co-authored-by: Gunar Schorcht <gunar@schorcht.net> Co-authored-by: Alexandre Abadie <alexandre.abadie@inria.fr>
295 lines
6.9 KiB
C
295 lines
6.9 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.
|
|
*/
|
|
|
|
#ifdef PORTH
|
|
#define GPIO_PORT(num) \
|
|
((num >= PORT_H) ? \
|
|
(ATMEGA_GPIO_BASE_H + ((num) - PORT_H) * ATMEGA_GPIO_SIZE) : \
|
|
(ATMEGA_GPIO_BASE_A + (num) * ATMEGA_GPIO_SIZE))
|
|
#define GPIO_PORT_NUM(port) \
|
|
(((port) >= ATMEGA_GPIO_BASE_H) ? \
|
|
(((port) - ATMEGA_GPIO_BASE_H) / ATMEGA_GPIO_SIZE + PORT_H) : \
|
|
(((port) - ATMEGA_GPIO_BASE_A) / ATMEGA_GPIO_SIZE))
|
|
#else
|
|
#define GPIO_PORT(num) (ATMEGA_GPIO_BASE_A + (num) * ATMEGA_GPIO_SIZE)
|
|
#define GPIO_PORT_NUM(port) (((port) - ATMEGA_GPIO_BASE_A) / ATMEGA_GPIO_SIZE)
|
|
#endif
|
|
|
|
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 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 */
|
|
/** @} */
|