From 1a88f0bad612e276cda1906f9d3ed979f0229560 Mon Sep 17 00:00:00 2001 From: Gerson Fernando Budke Date: Thu, 7 Jan 2021 13:05:50 -0300 Subject: [PATCH] cpu: Introduce Atmel xmega cpu Add ATxmega common files and cpu definitions. This works was originally developed by @Josar. The 2018 version were port to 2021 mainline. This version changes original port to have only the atxmega CPU definition. With that, all family can be accomodated. Signed-off-by: Gerson Fernando Budke --- cpu/atxmega/Kconfig | 82 +++++ cpu/atxmega/Kconfig.XMEGAA | 105 ++++++ cpu/atxmega/Kconfig.XMEGAB | 25 ++ cpu/atxmega/Kconfig.XMEGAC | 45 +++ cpu/atxmega/Kconfig.XMEGAD | 55 ++++ cpu/atxmega/Kconfig.XMEGAE | 17 + cpu/atxmega/Makefile | 7 + cpu/atxmega/Makefile.dep | 11 + cpu/atxmega/Makefile.features | 11 + cpu/atxmega/Makefile.include | 38 +++ cpu/atxmega/atxmega_cpu.c | 132 ++++++++ cpu/atxmega/doc.txt | 10 + cpu/atxmega/include/cpu_clock.h | 75 +++++ cpu/atxmega/include/cpu_conf.h | 76 +++++ cpu/atxmega/include/cpu_nvm.h | 65 ++++ cpu/atxmega/include/periph_cpu.h | 247 ++++++++++++++ cpu/atxmega/periph/Makefile | 3 + cpu/atxmega/periph/cpuid.c | 64 ++++ cpu/atxmega/periph/gpio.c | 546 +++++++++++++++++++++++++++++++ cpu/atxmega/periph/nvm.c | 78 +++++ cpu/atxmega/periph/pm.c | 80 +++++ cpu/atxmega/periph/timer.c | 533 ++++++++++++++++++++++++++++++ cpu/atxmega/periph/uart.c | 425 ++++++++++++++++++++++++ 23 files changed, 2730 insertions(+) create mode 100644 cpu/atxmega/Kconfig create mode 100644 cpu/atxmega/Kconfig.XMEGAA create mode 100644 cpu/atxmega/Kconfig.XMEGAB create mode 100644 cpu/atxmega/Kconfig.XMEGAC create mode 100644 cpu/atxmega/Kconfig.XMEGAD create mode 100644 cpu/atxmega/Kconfig.XMEGAE create mode 100644 cpu/atxmega/Makefile create mode 100644 cpu/atxmega/Makefile.dep create mode 100644 cpu/atxmega/Makefile.features create mode 100644 cpu/atxmega/Makefile.include create mode 100644 cpu/atxmega/atxmega_cpu.c create mode 100644 cpu/atxmega/doc.txt create mode 100644 cpu/atxmega/include/cpu_clock.h create mode 100644 cpu/atxmega/include/cpu_conf.h create mode 100644 cpu/atxmega/include/cpu_nvm.h create mode 100644 cpu/atxmega/include/periph_cpu.h create mode 100644 cpu/atxmega/periph/Makefile create mode 100644 cpu/atxmega/periph/cpuid.c create mode 100644 cpu/atxmega/periph/gpio.c create mode 100644 cpu/atxmega/periph/nvm.c create mode 100644 cpu/atxmega/periph/pm.c create mode 100644 cpu/atxmega/periph/timer.c create mode 100644 cpu/atxmega/periph/uart.c diff --git a/cpu/atxmega/Kconfig b/cpu/atxmega/Kconfig new file mode 100644 index 0000000000..450978477f --- /dev/null +++ b/cpu/atxmega/Kconfig @@ -0,0 +1,82 @@ +# Copyright (c) 2020 HAW Hamburg +# Copyright (c) 2021 Gerson Fernando Budke +# +# 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. +# + +config CPU_COMMON_ATXMEGA + bool + select CPU_ARCH_AVR8 + select CPU_CORE_AVR + select HAS_CPU_ATXMEGA + select HAS_CPP + select HAS_PERIPH_CPUID + select HAS_PERIPH_GPIO + select HAS_PERIPH_GPIO_IRQ + select HAS_PERIPH_NVM + select HAS_PERIPH_PM + select HAS_PERIPH_TIMER + select HAS_PERIPH_TIMER_PERIODIC + select HAS_PERIPH_UART + +config CPU_CORE_ATXMEGA_A1 + bool + select CPU_COMMON_ATXMEGA + +config CPU_CORE_ATXMEGA_A3 + bool + select CPU_COMMON_ATXMEGA + +config CPU_CORE_ATXMEGA_A4 + bool + select CPU_COMMON_ATXMEGA + +config CPU_CORE_ATXMEGA_B1 + bool + select CPU_COMMON_ATXMEGA + +config CPU_CORE_ATXMEGA_B3 + bool + select CPU_COMMON_ATXMEGA + +config CPU_CORE_ATXMEGA_C3 + bool + select CPU_COMMON_ATXMEGA + +config CPU_CORE_ATXMEGA_C4 + bool + select CPU_COMMON_ATXMEGA + +config CPU_CORE_ATXMEGA_D3 + bool + select CPU_COMMON_ATXMEGA + +config CPU_CORE_ATXMEGA_D4 + bool + select CPU_COMMON_ATXMEGA + +config CPU_CORE_ATXMEGA_E5 + bool + select CPU_COMMON_ATXMEGA + +config CPU + default "atxmega" if CPU_COMMON_ATXMEGA + +source "$(RIOTCPU)/atxmega/Kconfig.XMEGAA" +source "$(RIOTCPU)/atxmega/Kconfig.XMEGAB" +source "$(RIOTCPU)/atxmega/Kconfig.XMEGAC" +source "$(RIOTCPU)/atxmega/Kconfig.XMEGAD" +source "$(RIOTCPU)/atxmega/Kconfig.XMEGAE" + +## Declaration of specific features +config HAS_CPU_ATXMEGA + bool + +config HAS_PERIPH_NVM + bool + help + Indicates that the Non Volatile Memory controller is present. + +source "$(RIOTCPU)/avr8_common/Kconfig" diff --git a/cpu/atxmega/Kconfig.XMEGAA b/cpu/atxmega/Kconfig.XMEGAA new file mode 100644 index 0000000000..0fb05fc837 --- /dev/null +++ b/cpu/atxmega/Kconfig.XMEGAA @@ -0,0 +1,105 @@ +## CPU Models +# XMEGA - A1/A1U +config CPU_MODEL_XMEGA64A1 + bool + select CPU_CORE_ATXMEGA_A1 + +config CPU_MODEL_XMEGA128A1 + bool + select CPU_CORE_ATXMEGA_A1 + +config CPU_MODEL_XMEGA64A1U + bool + select CPU_CORE_ATXMEGA_A1 + +config CPU_MODEL_XMEGA128A1U + bool + select CPU_CORE_ATXMEGA_A1 + +# XMEGA - A3/A3U/A3BU +config CPU_MODEL_XMEGA64A3 + bool + select CPU_CORE_ATXMEGA_A3 + +config CPU_MODEL_XMEGA128A3 + bool + select CPU_CORE_ATXMEGA_A3 + +config CPU_MODEL_XMEGA192A3 + bool + select CPU_CORE_ATXMEGA_A3 + +config CPU_MODEL_XMEGA256A3 + bool + select CPU_CORE_ATXMEGA_A3 + +config CPU_MODEL_XMEGA64A3U + bool + select CPU_CORE_ATXMEGA_A3 + +config CPU_MODEL_XMEGA128A3U + bool + select CPU_CORE_ATXMEGA_A3 + +config CPU_MODEL_XMEGA192A3U + bool + select CPU_CORE_ATXMEGA_A3 + +config CPU_MODEL_XMEGA256A3U + bool + select CPU_CORE_ATXMEGA_A3 + +config CPU_MODEL_XMEGA256A3BU + bool + select CPU_CORE_ATXMEGA_A3 + +# XMEGA - A4/A4U +config CPU_MODEL_XMEGA16A4 + bool + select CPU_CORE_ATXMEGA_A4 + +config CPU_MODEL_XMEGA32A4 + bool + select CPU_CORE_ATXMEGA_A4 + +config CPU_MODEL_XMEGA16A4U + bool + select CPU_CORE_ATXMEGA_A4 + +config CPU_MODEL_XMEGA32A4U + bool + select CPU_CORE_ATXMEGA_A4 + +config CPU_MODEL_XMEGA64A4U + bool + select CPU_CORE_ATXMEGA_A4 + +config CPU_MODEL_XMEGA128A4U + bool + select CPU_CORE_ATXMEGA_A4 + +config CPU_MODEL + string + default "atxmega64a1" if CPU_MODEL_XMEGA64A1 + default "atxmega128a1" if CPU_MODEL_XMEGA128A1 + default "atxmega192a1" if CPU_MODEL_XMEGA192A1 + default "atxmega256a1" if CPU_MODEL_XMEGA256A1 + default "atxmega64a1u" if CPU_MODEL_XMEGA64A1U + default "atxmega128a1u" if CPU_MODEL_XMEGA128A1U + + default "atxmega64a3" if CPU_MODEL_XMEGA64A3 + default "atxmega128a3" if CPU_MODEL_XMEGA128A3 + default "atxmega192a3" if CPU_MODEL_XMEGA192A3 + default "atxmega256a3" if CPU_MODEL_XMEGA256A3 + default "atxmega64a3u" if CPU_MODEL_XMEGA64A3U + default "atxmega128a3u" if CPU_MODEL_XMEGA128A3U + default "atxmega192a3u" if CPU_MODEL_XMEGA192A3U + default "atxmega256a3u" if CPU_MODEL_XMEGA256A3U + default "atxmega256a3bu" if CPU_MODEL_XMEGA256A3BU + + default "atxmega16a4" if CPU_MODEL_XMEGA16A4 + default "atxmega32a4" if CPU_MODEL_XMEGA32A4 + default "atxmega16a4u" if CPU_MODEL_XMEGA16A4U + default "atxmega32a4u" if CPU_MODEL_XMEGA32A4U + default "atxmega64a4u" if CPU_MODEL_XMEGA64A4U + default "atxmega128a4u" if CPU_MODEL_XMEGA128A4U diff --git a/cpu/atxmega/Kconfig.XMEGAB b/cpu/atxmega/Kconfig.XMEGAB new file mode 100644 index 0000000000..8070567dfb --- /dev/null +++ b/cpu/atxmega/Kconfig.XMEGAB @@ -0,0 +1,25 @@ +## CPU Models +# XMEGA - B1 +config CPU_MODEL_XMEGA64B1 + bool + select CPU_CORE_ATXMEGA_B1 + +config CPU_MODEL_XMEGA128B1 + bool + select CPU_CORE_ATXMEGA_B1 + +# XMEGA - B3 +config CPU_MODEL_XMEGA64B3 + bool + select CPU_CORE_ATXMEGA_B3 + +config CPU_MODEL_XMEGA128B3 + bool + select CPU_CORE_ATXMEGA_B3 + +config CPU_MODEL + default "atxmega64b3" if CPU_MODEL_XMEGA64B3 + default "atxmega128b3" if CPU_MODEL_XMEGA128B3 + + default "atxmega64b1" if CPU_MODEL_XMEGA64B1 + default "atxmega128b1" if CPU_MODEL_XMEGA128B1 diff --git a/cpu/atxmega/Kconfig.XMEGAC b/cpu/atxmega/Kconfig.XMEGAC new file mode 100644 index 0000000000..d6fa5e7a34 --- /dev/null +++ b/cpu/atxmega/Kconfig.XMEGAC @@ -0,0 +1,45 @@ +## CPU Models +# XMEGA - C3 +config CPU_MODEL_XMEGA32C3 + bool + select CPU_CORE_ATXMEGA_C3 + +config CPU_MODEL_XMEGA64C3 + bool + select CPU_CORE_ATXMEGA_C3 + +config CPU_MODEL_XMEGC128C3 + bool + select CPU_CORE_ATXMEGA_C3 + +config CPU_MODEL_XMEGC192C3 + bool + select CPU_CORE_ATXMEGA_C3 + +config CPU_MODEL_XMEGA256C3 + bool + select CPU_CORE_ATXMEGA_C3 + +config CPU_MODEL_XMEGA384C3 + bool + select CPU_CORE_ATXMEGA_C3 + +# XMEGA - C4 +config CPU_MODEL_XMEGA16C4 + bool + select CPU_CORE_ATXMEGA_C4 + +config CPU_MODEL_XMEGA32C4 + bool + select CPU_CORE_ATXMEGA_C4 + +config CPU_MODEL + default "atxmega32c3" if CPU_MODEL_XMEGA32C3 + default "atxmega64c3" if CPU_MODEL_XMEGA64C3 + default "atxmega128c3" if CPU_MODEL_XMEGC128C3 + default "atxmega192c3" if CPU_MODEL_XMEGC192C3 + default "atxmega256c3" if CPU_MODEL_XMEGA256C3 + default "atxmega384c3" if CPU_MODEL_XMEGA384C3 + + default "atxmega16c4" if CPU_MODEL_XMEGA16C4 + default "atxmega32c4" if CPU_MODEL_XMEGA32C4 diff --git a/cpu/atxmega/Kconfig.XMEGAD b/cpu/atxmega/Kconfig.XMEGAD new file mode 100644 index 0000000000..af0dcf12d5 --- /dev/null +++ b/cpu/atxmega/Kconfig.XMEGAD @@ -0,0 +1,55 @@ +## CPU Models +# XMEGA - D3 +config CPU_MODEL_XMEGA32D3 + bool + select CPU_CORE_ATXMEGA_D3 + +config CPU_MODEL_XMEGA64D3 + bool + select CPU_CORE_ATXMEGA_D3 + +config CPU_MODEL_XMEGC128D3 + bool + select CPU_CORE_ATXMEGA_D3 + +config CPU_MODEL_XMEGC192D3 + bool + select CPU_CORE_ATXMEGA_D3 + +config CPU_MODEL_XMEGA256D3 + bool + select CPU_CORE_ATXMEGA_D3 + +config CPU_MODEL_XMEGA384D3 + bool + select CPU_CORE_ATXMEGA_D3 + +# XMEGA - D4 +config CPU_MODEL_XMEGA16D4 + bool + select CPU_CORE_ATXMEGA_D4 + +config CPU_MODEL_XMEGA32D4 + bool + select CPU_CORE_ATXMEGA_D4 + +config CPU_MODEL_XMEGA64D4 + bool + select CPU_CORE_ATXMEGA_D4 + +config CPU_MODEL_XMEGA128D4 + bool + select CPU_CORE_ATXMEGA_D4 + +config CPU_MODEL + default "atxmega32d3" if CPU_MODEL_XMEGA32D3 + default "atxmega64d3" if CPU_MODEL_XMEGA64D3 + default "atxmega128d3" if CPU_MODEL_XMEGC128D3 + default "atxmega192d3" if CPU_MODEL_XMEGC192D3 + default "atxmega256d3" if CPU_MODEL_XMEGA256D3 + default "atxmega384d3" if CPU_MODEL_XMEGA384D3 + + default "atxmega16d4" if CPU_MODEL_XMEGA16D4 + default "atxmega32d4" if CPU_MODEL_XMEGA32D4 + default "atxmega64d4" if CPU_MODEL_XMEGA64D4 + default "atxmega128d4" if CPU_MODEL_XMEGA128D4 diff --git a/cpu/atxmega/Kconfig.XMEGAE b/cpu/atxmega/Kconfig.XMEGAE new file mode 100644 index 0000000000..6363d632f3 --- /dev/null +++ b/cpu/atxmega/Kconfig.XMEGAE @@ -0,0 +1,17 @@ +## CPU Models +config CPU_MODEL_XMEGA8E5 + bool + select CPU_CORE_ATXMEGA_E5 + +config CPU_MODEL_XMEGA16E5 + bool + select CPU_CORE_ATXMEGA_E5 + +config CPU_MODEL_XMEGA32E5 + bool + select CPU_CORE_ATXMEGA_E5 + +config CPU_MODEL + default "atxmega8e5" if CPU_MODEL_XMEGA8E5 + default "atxmega16e5" if CPU_MODEL_XMEGA16E5 + default "atxmega32e5" if CPU_MODEL_XMEGA32E5 diff --git a/cpu/atxmega/Makefile b/cpu/atxmega/Makefile new file mode 100644 index 0000000000..4e5fcf7a8c --- /dev/null +++ b/cpu/atxmega/Makefile @@ -0,0 +1,7 @@ +# define the module that is build +MODULE = cpu + +# add a list of subdirectories, that should also be build +DIRS = periph $(RIOTCPU)/avr8_common/ + +include $(RIOTBASE)/Makefile.base diff --git a/cpu/atxmega/Makefile.dep b/cpu/atxmega/Makefile.dep new file mode 100644 index 0000000000..562b7c960d --- /dev/null +++ b/cpu/atxmega/Makefile.dep @@ -0,0 +1,11 @@ +# peripheral drivers are linked into the final binary +USEMODULE += atxmega_periph + +# All ATxmega based CPUs provide PM +USEMODULE += pm_layered + +ifeq (,$(filter cpuid,$(USEMODULE))) + USEMODULE += periph_nvm +endif + +include $(RIOTCPU)/avr8_common/Makefile.dep diff --git a/cpu/atxmega/Makefile.features b/cpu/atxmega/Makefile.features new file mode 100644 index 0000000000..c20ff1a9dd --- /dev/null +++ b/cpu/atxmega/Makefile.features @@ -0,0 +1,11 @@ +include $(RIOTCPU)/avr8_common/Makefile.features + +# common feature are defined in avr8_common/Makefile.features +# Only add Additional features + +FEATURES_PROVIDED += periph_cpuid +FEATURES_PROVIDED += periph_gpio periph_gpio_irq +FEATURES_PROVIDED += periph_nvm +FEATURES_PROVIDED += periph_pm +FEATURES_PROVIDED += periph_timer periph_timer_periodic +FEATURES_PROVIDED += periph_uart diff --git a/cpu/atxmega/Makefile.include b/cpu/atxmega/Makefile.include new file mode 100644 index 0000000000..e8365a18e4 --- /dev/null +++ b/cpu/atxmega/Makefile.include @@ -0,0 +1,38 @@ +export CPU_ATXMEGA 1 + +# CPU ROM/RAM +ifneq (,$(findstring atxmega8,$(CPU_MODEL))) + RAM_LEN = 1K + ROM_LEN = 8K +endif +ifneq (,$(findstring atxmega16,$(CPU_MODEL))) + RAM_LEN = 2K + ROM_LEN = 16K +endif +ifneq (,$(findstring atxmega32,$(CPU_MODEL))) + RAM_LEN = 4K + ROM_LEN = 32K +endif +ifneq (,$(findstring atxmega64,$(CPU_MODEL))) + RAM_LEN ?= 4K + ROM_LEN = 64K +endif +#ifneq (,$(findstring atxmega128,$(CPU_MODEL))) + RAM_LEN ?= 8K + ROM_LEN = 128K +#endif +ifneq (,$(findstring atxmega192,$(CPU_MODEL))) + RAM_LEN = 16K + ROM_LEN = 192K +endif +ifneq (,$(findstring atxmega256,$(CPU_MODEL))) + RAM_LEN = 16K + ROM_LEN = 256K +endif +ifneq (,$(findstring atxmega384,$(CPU_MODEL))) + RAM_LEN = 32K + ROM_LEN = 384K +endif + +# CPU depends on the avr8 common module, so include it +include $(RIOTCPU)/avr8_common/Makefile.include diff --git a/cpu/atxmega/atxmega_cpu.c b/cpu/atxmega/atxmega_cpu.c new file mode 100644 index 0000000000..433b0f7f18 --- /dev/null +++ b/cpu/atxmega/atxmega_cpu.c @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @{ + * + * @file + * @brief Implementation of the CPU initialization + * + * @author Gerson Fernando Budke + + * @} + */ + +#include + +#include "cpu.h" +#include "cpu_clock.h" +#include "panic.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#ifndef CPU_ATXMEGA_CLK_SCALE_INIT +#define CPU_ATXMEGA_CLK_SCALE_INIT CPU_ATXMEGA_CLK_SCALE_DIV1 +#endif +#ifndef CPU_ATXMEGA_BUS_SCALE_INIT +#define CPU_ATXMEGA_BUS_SCALE_INIT CPU_ATXMEGA_BUS_SCALE_DIV1_1 +#endif + +extern uint8_t mcusr_mirror; + +void avr8_reset_cause(void) +{ + if (mcusr_mirror & (1 << RST_PORF_bp)) { + DEBUG("Power-on reset.\n"); + } + if (mcusr_mirror & (1 << RST_EXTRF_bp)) { + DEBUG("External reset!\n"); + } + if (mcusr_mirror & (1 << RST_BORF_bp)) { + DEBUG("Brown-out reset!\n"); + } + if (mcusr_mirror & (1 << RST_WDRF_bp)) { + DEBUG("Watchdog reset!\n"); + } + if (mcusr_mirror & (1 << RST_PDIRF_bp)) { + DEBUG("Programming and Debug Interface reset!\n"); + } + if (mcusr_mirror & (1 << RST_SRF_bp)) { + DEBUG("Software reset!\n"); + } + if (mcusr_mirror & (1 << RST_SDRF_bp)) { + DEBUG("Spike Detection reset!\n"); + } +} + +void __attribute__((weak)) avr8_clk_init(void) +{ + volatile uint8_t *reg = (uint8_t *)&PR.PRGEN; + uint8_t i; + + /* Turn off all peripheral clocks that can be turned off. */ + for (i = 0; i <= 7; i++) { + reg[i] = 0xff; + } + + /* Turn on all peripheral clocks that can be turned on. */ + for (i = 0; i <= 7; i++) { + reg[i] = 0x00; + } + + /* XMEGA A3U [DATASHEET] p.23 After reset, the device starts up running + * from the 2MHz internal oscillator. The other clock sources, DFLLs + * and PLL, are turned off by default. + * + * Configure clock to 32MHz with calibration + * application note AVR1003 + * + * From errata http://www.avrfreaks.net/forum/xmega-dfll-does-it-work + * In order to use the automatic runtime calibration for the 2 MHz or + * the 32 MHz internal oscillators, the DFLL for both oscillators and + * both oscillators has to be enabled for one to work. + */ + OSC.PLLCTRL = 0; + + /* Enable the internal PLL & 32MHz & 32KHz oscillators */ + OSC.CTRL |= OSC_PLLEN_bm | OSC_RC32MEN_bm | OSC_RC32KEN_bm; + + /* Wait for 32Khz and 32MHz oscillator to stabilize */ + while ((OSC.STATUS & (OSC_RC32KRDY_bm | OSC_RC32MRDY_bm)) + != (OSC_RC32KRDY_bm | OSC_RC32MRDY_bm)) {} + + /* Enable DFLL - defaults to calibrate against internal 32Khz clock */ + DFLLRC32M.CTRL = DFLL_ENABLE_bm; + + /* Enable DFLL - defaults to calibrate against internal 32Khz clock */ + DFLLRC2M.CTRL = DFLL_ENABLE_bm; + + atxmega_set_prescaler(CPU_ATXMEGA_CLK_SCALE_INIT, + CPU_ATXMEGA_BUS_SCALE_INIT); + + /* Disable CCP for Protected IO register and set new value*/ + /* Switch to 32MHz clock */ + _PROTECTED_WRITE(CLK.CTRL, CLK_SCLKSEL_RC32M_gc); +} + +/* This is a vector which is aliased to __vector_default, + * the vector executed when an ISR fires with no accompanying + * ISR handler. This may be used along with the ISR() macro to + * create a catch-all for undefined but used ISRs for debugging + * purposes. + */ +ISR(BADISR_vect) +{ + avr8_reset_cause(); + +#ifdef LED_PANIC + /* Use LED light to signal ERROR. */ + LED_PANIC; +#endif + + core_panic(PANIC_GENERAL_ERROR, + PSTR("FATAL ERROR: BADISR_vect called, unprocessed Interrupt.\n" + "STOP Execution.\n")); +} diff --git a/cpu/atxmega/doc.txt b/cpu/atxmega/doc.txt new file mode 100644 index 0000000000..8da7e0301c --- /dev/null +++ b/cpu/atxmega/doc.txt @@ -0,0 +1,10 @@ +/** + * @defgroup cpu_atxmega Atmel ATxmega MCU + * @ingroup cpu + * @brief Implementation of Atmel's ATxmega MCU + */ + +/** + * @defgroup cpu_atxmega_periph Atmel ATxmega MCU Peripherals + * @ingroup cpu_atxmega + */ diff --git a/cpu/atxmega/include/cpu_clock.h b/cpu/atxmega/include/cpu_clock.h new file mode 100644 index 0000000000..f65100c9cf --- /dev/null +++ b/cpu/atxmega/include/cpu_clock.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @brief Common implementations and headers for ATxmega family based micro-controllers + * @{ + * + * @file + * @brief Basic definitions for the ATxmega common clock module + * + * When ever you want to do something hardware related, that is accessing MCUs registers directly, + * just include this file. It will then make sure that the MCU specific headers are included. + * + * @author Gerson Fernando Budke + * + */ + +#ifndef CPU_CLOCK_H +#define CPU_CLOCK_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ATxmega system clock prescaler settings + * + * Some CPUs may not support the highest prescaler settings + */ +enum { + CPU_ATXMEGA_CLK_SCALE_DIV1 = 0, + CPU_ATXMEGA_CLK_SCALE_DIV2 = 1, + CPU_ATXMEGA_CLK_SCALE_DIV4 = 3, + CPU_ATXMEGA_CLK_SCALE_DIV8 = 5, + CPU_ATXMEGA_CLK_SCALE_DIV16 = 7, + CPU_ATXMEGA_CLK_SCALE_DIV32 = 9, + CPU_ATXMEGA_CLK_SCALE_DIV64 = 11, + CPU_ATXMEGA_CLK_SCALE_DIV128 = 13, + CPU_ATXMEGA_CLK_SCALE_DIV256 = 15, + CPU_ATXMEGA_CLK_SCALE_DIV512 = 17, +}; + +enum { + CPU_ATXMEGA_BUS_SCALE_DIV1_1 = 0, + CPU_ATXMEGA_BUS_SCALE_DIV1_2 = 1, + CPU_ATXMEGA_BUS_SCALE_DIV4_1 = 2, + CPU_ATXMEGA_BUS_SCALE_DIV2_2 = 3, +}; + +/** + * @brief Initializes system clock prescaler + */ +static inline void atxmega_set_prescaler(uint8_t clk_scale, uint8_t bus_scale) +{ + /* Disable CCP for Protected IO register and set new value + * Set system clock prescalers to zero. PSCTRL contains A Prescaler + * Value and one value for and B and C Prescaler + */ + _PROTECTED_WRITE(CLK.PSCTRL, clk_scale | bus_scale); +} + +#ifdef __cplusplus +} +#endif + +#endif /* CPU_CLOCK_H */ +/** @} */ diff --git a/cpu/atxmega/include/cpu_conf.h b/cpu/atxmega/include/cpu_conf.h new file mode 100644 index 0000000000..132d149bf5 --- /dev/null +++ b/cpu/atxmega/include/cpu_conf.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @{ + * + * @file + * @brief Implementation specific CPU configuration options + * + * @author Gerson Fernando Budke + */ + +#ifndef CPU_CONF_H +#define CPU_CONF_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define THREAD_EXTRA_STACKSIZE_PRINTF (128) + +/** + * @name Kernel configuration + * + * Since printf seems to get memory allocated by the + * linker/avr-libc the stack size tested successfully + * even with pretty small stacks. + * @{ + */ +#ifndef THREAD_STACKSIZE_DEFAULT +#define THREAD_STACKSIZE_DEFAULT (512) +#endif + +/* keep THREAD_STACKSIZE_IDLE > THREAD_EXTRA_STACKSIZE_PRINTF + * to avoid not printing of debug in interrupts + */ +#ifndef THREAD_STACKSIZE_IDLE +#ifdef MODULE_XTIMER +/* xtimer's 64 bit arithmetic doesn't perform well on 8 bit archs. In order to + * prevent a stack overflow when an timer triggers while the idle thread is + * running, we have to increase the stack size then + */ +#define THREAD_STACKSIZE_IDLE (384) +#else +#define THREAD_STACKSIZE_IDLE (192) +#endif +#endif +/** @} */ + +/** + * @brief Declare the heap_stats function as available + */ +#define HAVE_HEAP_STATS + +/** + * @brief This arch uses the inlined IRQ API. + */ +#define IRQ_API_INLINED (1) + +/** + * @brief This arch require special clock initialization. + */ +#define CPU_AVR8_HAS_CLOCK_INIT 1 + +#ifdef __cplusplus +} +#endif + +#endif /* CPU_CONF_H */ +/** @} */ diff --git a/cpu/atxmega/include/cpu_nvm.h b/cpu/atxmega/include/cpu_nvm.h new file mode 100644 index 0000000000..f3fb592ff4 --- /dev/null +++ b/cpu/atxmega/include/cpu_nvm.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @brief Non Volatile Memory (NVM) internal API + * @{ + * + * @author Gerson Fernando Budke + * + */ + +#ifndef CPU_NVM_H +#define CPU_NVM_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get offset of calibration bytes in the production signature row + * + * @note In some distributions may require most recent vendor headers, even + * more recent than the upstream avr-libc. The headers can be download + * directly from Microchip web site. In general, Microchip have a + * dedicated page at Tools and Software / AVR and SAM Downloads Archive. + * The most recent version is AVR 8-bit Toolchain (3.4.4) 6.2.0.334. + * http://ww1.microchip.com/downloads/archive/avr8-headers-6.2.0.334.zip + * + * The official RIOT-OS docker container, Debian and Ubuntu are + * distributions that already had updated versions of those files. + * + * @param regname Name of register within the production signature row + * @retval Offset of register into the production signature row + */ +#define nvm_get_production_signature_row_offset(regname) \ + offsetof(NVM_PROD_SIGNATURES_t, regname) + +/** + * @brief Read one byte from the production signature row + * + * This function reads one byte from the production signature row of the device + * at the given address. + * + * @note This function is modifying the NVM.CMD register. + * If the application are using program space access in interrupts + * (__flash pointers in IAR EW or pgm_read_byte in GCC) interrupts + * needs to be disabled when running EEPROM access functions. If not + * the program space reads will be corrupted. + * + * @param address Byte offset into the signature row + */ +uint8_t nvm_read_production_signature_row(uint8_t address); + +#ifdef __cplusplus +} +#endif + +#endif /* CPU_NVM_H */ +/** @} */ diff --git a/cpu/atxmega/include/periph_cpu.h b/cpu/atxmega/include/periph_cpu.h new file mode 100644 index 0000000000..2597917d18 --- /dev/null +++ b/cpu/atxmega/include/periph_cpu.h @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @{ + * + * @file + * @brief CPU specific definitions for internal peripheral handling + * + * @author Gerson Fernando Budke + */ + +#ifndef PERIPH_CPU_H +#define PERIPH_CPU_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Length of the CPU_ID in octets + * @{ + */ +#define CPUID_LEN (11U) +/** @} */ + +/** + * @name Interrupt level used to control nested interrupts + * @{ + */ +typedef enum { + CPU_INT_LVL_OFF, /**< Interrupt Disabled */ + CPU_INT_LVL_LOW, /**< Interrupt Low Level */ + CPU_INT_LVL_MID, /**< Interrupt Medium Level */ + CPU_INT_LVL_HIGH, /**< Interrupt High Level */ +} cpu_int_lvl_t; +/** @} */ + +/** + * @brief Available ports on the ATxmega family + */ +enum { + PORT_A, /**< port A - 600 - 0 */ + PORT_B, /**< port B - 620 - 1 */ + PORT_C, /**< port C - 640 - 2 */ + PORT_D, /**< port D - 660 - 3 */ + PORT_E, /**< port E - 680 - 4 */ + PORT_F, /**< port F - 6A0 - 5 */ + PORT_G, /**< port G - 6C0 - 6 */ + PORT_H, /**< port H - 6E0 - 7 */ + PORT_J, /**< port J - 700 - 8 */ + PORT_K, /**< port K - 720 - 9 */ + PORT_L, /**< port L - 740 - A */ + PORT_M, /**< port M - 760 - B */ + PORT_N, /**< port N - 780 - C */ + PORT_P, /**< port P - 7A0 - D */ + PORT_Q, /**< port Q - 7C0 - E */ + PORT_R, /**< port R - 7E0 - F */ + /* ... */ + PORT_MAX, +}; + +/** + * @name Power management configuration + * @{ + */ +#define PM_NUM_MODES (4) +/** @} */ + +/** + * @brief Define the number of GPIO interrupts vectors for ATxmega CPU. + * @{ + */ +#define GPIO_EXT_INT_NUMOF (2 * PORT_MAX) +/** @} */ + +/** + * @brief Override GPIO type + * @{ + */ +#define HAVE_GPIO_T +typedef uint16_t gpio_t; +/** @} */ + +/** + * @brief Definition of a fitting UNDEF value + */ +#define GPIO_UNDEF (0xffff) + +/** + * @brief Define a CPU specific GPIO pin generator macro + * + * The ATxmega internally uses pin mask to manipulate all gpio functions. + * This allows simultaneous pin actions at any method call. ATxmega specific + * applications can use ATXMEGA_GPIO_PIN macro to define pins and generic + * RIOT-OS application should continue to use GPIO_PIN API for compatibility. + * + * @{ + */ +#define ATXMEGA_GPIO_PIN(x, y) (((x & 0x0f) << 8) | (y & 0xff)) +#define GPIO_PIN(x, y) ATXMEGA_GPIO_PIN(x, (1U << (y & 0x07))) +/** @} */ + +/** + * @brief Available pin modes + * + * Generally, a pin can be configured to be input or output. In output mode, a + * pin can further be put into push-pull or open drain configuration. Though + * this is supported by most platforms, this is not always the case, so driver + * implementations may return an error code if a mode is not supported. + * @{ + */ +#define HAVE_GPIO_MODE_T +typedef enum GPIO_MODE { + GPIO_SLEW_RATE = (1 << 7), /**< enable slew rate */ + GPIO_INVERTED = (1 << 6), /**< enable inverted signal */ + + GPIO_OPC_TOTEN = (0 << 3), /**< select no pull resistor (TOTEM) */ + GPIO_OPC_BSKPR = (1 << 3), /**< push-pull mode (BUSKEEPER) */ + GPIO_OPC_PD = (2 << 3), /**< pull-down resistor */ + GPIO_OPC_PU = (3 << 3), /**< pull-up resistor */ + GPIO_OPC_WRD_OR = (4 << 3), /**< enable wired OR */ + GPIO_OPC_WRD_AND = (5 << 3), /**< enable wired AND */ + GPIO_OPC_WRD_OR_PULL = (6 << 3), /**< enable wired OR and pull-down resistor */ + GPIO_OPC_WRD_AND_PULL = (7 << 3), /**< enable wired AND and pull-up resistor */ + + GPIO_ANALOG = (1 << 1), /**< select GPIO for analog function */ + + GPIO_IN = (0 << 0), /**< select GPIO MASK as input */ + GPIO_OUT = (1 << 0), /**< select GPIO MASK as output */ + + /* Compatibility Mode */ + GPIO_IN_PU = GPIO_IN | GPIO_OPC_PU, + GPIO_IN_PD = GPIO_IN | GPIO_OPC_PD, + GPIO_OD = GPIO_OUT | GPIO_OPC_WRD_OR, + GPIO_OD_PU = GPIO_OUT | GPIO_OPC_WRD_OR_PULL, +} gpio_mode_t; +/** @} */ + +/** + * @brief Definition of possible active flanks for external interrupt mode + * @{ + */ +#define HAVE_GPIO_FLANK_T +typedef enum { + GPIO_ISC_BOTH = (0 << 4), /**< emit interrupt on both flanks (default) */ + GPIO_ISC_RISING = (1 << 4), /**< emit interrupt on rising flank */ + GPIO_ISC_FALLING = (2 << 4), /**< emit interrupt on falling flank */ + GPIO_ISC_LOW_LEVEL = (3 << 4), /**< emit interrupt on low level */ + + GPIO_INT_DISABLED_ALL = (1 << 3), /**< disable all interrupts */ + + GPIO_INT0_VCT = (0 << 2), /**< enable interrupt on Vector 0 (default) */ + GPIO_INT1_VCT = (1 << 2), /**< enable interrupt on Vector 1 */ + + GPIO_LVL_OFF = (0 << 0), /**< interrupt disabled (default) */ + GPIO_LVL_LOW = (1 << 0), /**< interrupt low level */ + GPIO_LVL_MID = (2 << 0), /**< interrupt medium level */ + GPIO_LVL_HIGH = (3 << 0), /**< interrupt higher */ + + /* Compatibility Mode */ + GPIO_FALLING = GPIO_ISC_FALLING | GPIO_LVL_LOW, + GPIO_RISING = GPIO_ISC_RISING | GPIO_LVL_LOW, + GPIO_BOTH = GPIO_ISC_BOTH | GPIO_LVL_LOW, +} gpio_flank_t; +/** @} */ + +/** + * @brief Max number of available UARTs + */ +#define UART_MAX_NUMOF (7) + +/** + * @brief Size of the UART TX buffer for non-blocking mode. + */ +#ifndef UART_TXBUF_SIZE +#define UART_TXBUF_SIZE (64) +#endif + +/** + * @brief UART device configuration + */ +typedef struct { + USART_t *dev; /**< pointer to the used UART device */ + gpio_t rx_pin; /**< pin used for RX */ + gpio_t tx_pin; /**< pin used for TX */ +#ifdef MODULE_PERIPH_UART_HW_FC + gpio_t rts_pin; /**< RTS pin */ + gpio_t cts_pin; /**< CTS pin */ +#endif + cpu_int_lvl_t rx_int_lvl; /**< RX Complete Interrupt Level */ + cpu_int_lvl_t tx_int_lvl; /**< TX Complete Interrupt Level */ + cpu_int_lvl_t dre_int_lvl; /**< Data Registry Empty Interrupt Level */ +} uart_conf_t; + +/** + * @brief Max number of available timer channels + */ +#define TIMER_CH_MAX_NUMOF (4) + +/** + * @brief A low-level timer_set() implementation is provided + */ +#define PERIPH_TIMER_PROVIDES_SET + +/** + * @brief Timer Type + * + * Timer Type 1 is equal to Type 0 (two channels instead four) + * Timer Type 2 is Type 0 configured as two 8 bit timers instead one 16 bit + * Timer Type 2 won't be available as a standard timer + * Timer Type 5 is equal to Type 4 (two channels instead four) + */ +typedef enum { + TC_TYPE_0 = 0, + TC_TYPE_1 = 1, + TC_TYPE_2 = 2, + TC_TYPE_4 = 4, + TC_TYPE_5 = 5, +} timer_type_t; + +/** + * @brief Timer device configuration + * + * All timers can be derived from TC0_t struct. Need check at runtime the + * type and number of channels to perform all operations. + */ +typedef struct { + TC0_t *dev; /**< Pointer to the used as Timer device */ + timer_type_t type; /**< Timer Type */ + cpu_int_lvl_t int_lvl[TIMER_CH_MAX_NUMOF]; /**< Interrupt channels level */ +} timer_conf_t; + +#ifdef __cplusplus +} +#endif + +#endif /* PERIPH_CPU_H */ +/** @} */ diff --git a/cpu/atxmega/periph/Makefile b/cpu/atxmega/periph/Makefile new file mode 100644 index 0000000000..f71f88d5c1 --- /dev/null +++ b/cpu/atxmega/periph/Makefile @@ -0,0 +1,3 @@ +MODULE = atxmega_periph + +include $(RIOTMAKE)/periph.mk diff --git a/cpu/atxmega/periph/cpuid.c b/cpu/atxmega/periph/cpuid.c new file mode 100644 index 0000000000..0e15a0420b --- /dev/null +++ b/cpu/atxmega/periph/cpuid.c @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @ingroup cpu_atxmega_periph + * @{ + * + * @file + * @brief Low-level CPUID driver implementation + * + * @author Gerson Fernando Budke + * + * @} + */ + +#include "periph_cpu.h" +#include "cpu_nvm.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +void cpuid_get(void *id) +{ + uint8_t *addr = id; + + addr[0x0] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(LOTNUM0)); + addr[0x1] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(LOTNUM1)); + addr[0x2] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(LOTNUM2)); + addr[0x3] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(LOTNUM3)); + addr[0x4] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(LOTNUM4)); + addr[0x5] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(LOTNUM5)); + + addr[0x6] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(WAFNUM)); + + addr[0x7] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(COORDX0)); + addr[0x8] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(COORDX1)); + addr[0x9] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(COORDY0)); + addr[0xa] = nvm_read_production_signature_row( + nvm_get_production_signature_row_offset(COORDY1)); + + if (IS_ACTIVE(ENABLE_DEBUG)) { + DEBUG("CPUID: "); + for (uint8_t i = 0; i < CPUID_LEN; i++) { + DEBUG("%02x ", addr[i]); + } + DEBUG("\n"); + } +} diff --git a/cpu/atxmega/periph/gpio.c b/cpu/atxmega/periph/gpio.c new file mode 100644 index 0000000000..ffe92209d4 --- /dev/null +++ b/cpu/atxmega/periph/gpio.c @@ -0,0 +1,546 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @ingroup cpu_atxmega_periph + * @{ + * + * @file + * @brief Low-level GPIO driver implementation + * + * @author Gerson Fernando Budke + * + * @} + */ + +#include +#include + +#include "cpu.h" +#include "periph_conf.h" +#include "periph/gpio.h" +#include "bitarithm.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/** + * @brief GPIO port base + * + * GPIO_PORT_BASE resides in the IO address space and must be 16 bits. + */ +#define GPIO_PORT_BASE ((uint16_t)&PORTA) + +/** + * @brief GPIO port structure offset + * + * The PORT_t struct it is not complete filled and it is necessary define the + * address offset manually. + */ +#define GPIO_PORT_OFFSET (0x20) + +static gpio_isr_ctx_t config_ctx[GPIO_EXT_INT_NUMOF]; +static uint8_t config_irq[GPIO_EXT_INT_NUMOF]; + +/** + * @brief Extract the pin number of the given pin + */ +static inline uint8_t _pin_mask(gpio_t pin) +{ + return (pin & 0xff); +} + +/** + * @brief Extract the port number of the given pin + */ +static inline uint8_t _port_num(gpio_t pin) +{ + return (pin >> 8) & 0x0f; +} + +/** + * @brief Generate the PORTx address of the give pin in the IO address space + */ +static inline PORT_t *_port_addr(gpio_t pin) +{ + uint8_t port_num = _port_num(pin); + uint16_t port_addr = GPIO_PORT_BASE + (port_num * GPIO_PORT_OFFSET); + + return (PORT_t *) port_addr; +} + +static inline void _print_config(gpio_t pin) +{ + PORT_t *port = _port_addr(pin); + uint8_t pin_mask = _pin_mask(pin); + volatile uint8_t *pin_ctrl = &port->PIN0CTRL; + + DEBUG("PORT: 0x%04x, PIN: 0x%02x\n", (uint16_t)port, pin_mask); + DEBUG("DIR: 0x%02x, IN: 0x%02x, OUT: 0x%02x\n", port->DIR, port->IN, port->OUT); + DEBUG("INTCTRL: 0x%02x\nINTFLAGS: 0x%02x\n", port->INTCTRL, port->INTFLAGS); + DEBUG("INT0MASK: 0x%02x\nINT1MASK: 0x%02x\n", port->INT0MASK, port->INT1MASK); + + for (uint8_t p = 0; p < 8; p++) { + DEBUG("PIN%dCTRL: 0x%02x\n", p, pin_ctrl[p]); + } +} + +static inline void _gpio_pinctrl_set(gpio_t pin, gpio_mode_t mode, + gpio_flank_t flank, gpio_cb_t cb, + void *arg) +{ + uint8_t pin_mask = _pin_mask(pin); + uint8_t port_num = _port_num(pin); + PORT_t *port = _port_addr(pin); + volatile uint8_t *pin_ctrl = &port->PIN0CTRL; + uint8_t irq_state; + uint8_t in_sense_cfg; + uint8_t pins; + uint8_t pin_idx; + + in_sense_cfg = (mode & ~PORT_ISC_gm); + in_sense_cfg |= (mode & GPIO_ANALOG) + ? PORT_ISC_INPUT_DISABLE_gc + : ((flank >> 4) & PORT_ISC_gm); + + pins = pin_mask; + pin_idx = 0; + + if (mode & GPIO_OUT) { + port->DIRSET = pin_mask; + } + else { + port->DIRCLR = pin_mask; + } + + irq_state = irq_disable(); + + while (pins) { + pins = bitarithm_test_and_clear(pins, &pin_idx); + pin_ctrl[pin_idx] = in_sense_cfg; + } + + if (flank & GPIO_INT_DISABLED_ALL) { + config_ctx[port_num + PORT_MAX].cb = NULL; + config_ctx[port_num + PORT_MAX].arg = NULL; + config_irq[port_num + PORT_MAX] = 0; + config_ctx[port_num].cb = NULL; + config_ctx[port_num].arg = NULL; + config_irq[port_num] = 0; + + port->INTCTRL = 0; + port->INT1MASK = 0; + port->INT0MASK = 0; + port->INTFLAGS = PORT_INT1IF_bm | PORT_INT0IF_bm; + } + else if (flank & GPIO_INT1_VCT) { + config_ctx[port_num + PORT_MAX].cb = cb; + config_ctx[port_num + PORT_MAX].arg = arg; + config_irq[port_num + PORT_MAX] = (flank & GPIO_LVL_HIGH) + << PORT_INT1LVL_gp; + + if ((flank & GPIO_LVL_HIGH) == GPIO_LVL_OFF) { + port->INT1MASK &= ~pin_mask; + } + else { + port->INT1MASK |= pin_mask; + } + + port->INTFLAGS = PORT_INT1IF_bm; + + /* Get mask from INT 0 and apply new INT 1 mask */ + port->INTCTRL = (port->INTCTRL & PORT_INT0LVL_gm) + | config_irq[port_num + PORT_MAX]; + } + else { + config_ctx[port_num].cb = cb; + config_ctx[port_num].arg = arg; + config_irq[port_num] = (flank & GPIO_LVL_HIGH) + << PORT_INT0LVL_gp; + + if ((flank & GPIO_LVL_HIGH) == GPIO_LVL_OFF) { + port->INT0MASK &= ~pin_mask; + } + else { + port->INT0MASK |= pin_mask; + } + + port->INTFLAGS = PORT_INT0IF_bm; + + /* Get mask from INT 1 and apply new INT 0 mask */ + port->INTCTRL = (port->INTCTRL & PORT_INT1LVL_gm) + | config_irq[port_num]; + } + + irq_restore(irq_state); +} + +int gpio_init(gpio_t pin, gpio_mode_t mode) +{ + DEBUG("gpio_init pin = 0x%02x mode = 0x%02x\n", pin, mode); + + _gpio_pinctrl_set(pin, mode, GPIO_INT_DISABLED_ALL, NULL, NULL); + + return 0; +} + +int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank, + gpio_cb_t cb, void *arg) +{ + DEBUG("gpio_init_int pin = 0x%02x mode = 0x%02x flank = 0x%02x\n", pin, + mode, flank); + + if (mode & GPIO_ANALOG) { + DEBUG("Pin can't be ANALOG (input buffer disabled) and INT at same time.\n"); + return -1; + } + + _gpio_pinctrl_set(pin, mode, flank, cb, arg); + + if (IS_ACTIVE(ENABLE_DEBUG)) { + _print_config(pin); + } + + return 0; +} + +void gpio_irq_enable(gpio_t pin) +{ + DEBUG("gpio_irq_enable pin = 0x%04x \n", pin); + + uint8_t pin_mask = _pin_mask(pin); + uint8_t port_num = _port_num(pin); + PORT_t *port = _port_addr(pin); + + if (port->INT1MASK & pin_mask) { + port->INTFLAGS = PORT_INT1IF_bm; + port->INTCTRL |= config_irq[port_num + PORT_MAX]; + } + if (port->INT0MASK & pin_mask) { + port->INTFLAGS = PORT_INT0IF_bm; + port->INTCTRL |= config_irq[port_num]; + } + + if (IS_ACTIVE(ENABLE_DEBUG)) { + _print_config(pin); + } +} + +void gpio_irq_disable(gpio_t pin) +{ + DEBUG("gpio_irq_disable pin = 0x%04x \n", pin); + + uint8_t pin_mask = _pin_mask(pin); + PORT_t *port = _port_addr(pin); + + if (port->INT1MASK & pin_mask) { + port->INTCTRL &= ~PORT_INT1LVL_gm; + } + if (port->INT0MASK & pin_mask) { + port->INTCTRL &= ~PORT_INT0LVL_gm; + } + + if (IS_ACTIVE(ENABLE_DEBUG)) { + _print_config(pin); + } +} + +int gpio_read(gpio_t pin) +{ + PORT_t *port = _port_addr(pin); + uint8_t pin_mask = _pin_mask(pin); + + if (IS_ACTIVE(ENABLE_DEBUG)) { + _print_config(pin); + } + + return port->IN & pin_mask; +} + +void gpio_set(gpio_t pin) +{ + DEBUG("gpio_set pin = 0x%04x \n", pin); + + PORT_t *port = _port_addr(pin); + uint8_t pin_mask = _pin_mask(pin); + + port->OUTSET = pin_mask; + + if (IS_ACTIVE(ENABLE_DEBUG)) { + _print_config(pin); + } +} + +void gpio_clear(gpio_t pin) +{ + DEBUG("gpio_clear pin = 0x%04x \n", pin); + + PORT_t *port = _port_addr(pin); + uint8_t pin_mask = _pin_mask(pin); + + port->OUTCLR = pin_mask; + + if (IS_ACTIVE(ENABLE_DEBUG)) { + _print_config(pin); + } +} + +void gpio_toggle(gpio_t pin) +{ + DEBUG("gpio_toggle pin = 0x%04x \n", pin); + + PORT_t *port = _port_addr(pin); + uint8_t pin_mask = _pin_mask(pin); + + port->OUTTGL = pin_mask; + + if (IS_ACTIVE(ENABLE_DEBUG)) { + _print_config(pin); + } +} + +void gpio_write(gpio_t pin, int value) +{ + DEBUG("gpio_write pin = 0x%04x, value = 0x%02x \n", pin, value); + + if (value) { + gpio_set(pin); + } + else { + gpio_clear(pin); + } +} + +static inline void irq_handler(uint8_t port_num, uint8_t isr_vct_num) +{ + avr8_enter_isr(); + + DEBUG("irq_handler port = 0x%02x, vct_num = %d \n", port_num, isr_vct_num); + + if (isr_vct_num) { + port_num += PORT_MAX; + } + + if (config_ctx[port_num].cb) { + config_ctx[port_num].cb(config_ctx[port_num].arg); + } + else { + DEBUG("WARNING! irq_handler without callback\n"); + } + + avr8_exit_isr(); +} + +#if defined(PORTA_INT0_vect) +ISR(PORTA_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_A, 0); +} +#endif +#if defined(PORTA_INT1_vect) +ISR(PORTA_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_A, 1); +} +#endif + +#if defined(PORTB_INT0_vect) +ISR(PORTB_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_B, 0); +} +#endif +#if defined(PORTB_INT1_vect) +ISR(PORTB_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_B, 1); +} +#endif + +#if defined(PORTC_INT0_vect) +ISR(PORTC_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_C, 0); +} +#endif +#if defined(PORTC_INT1_vect) +ISR(PORTC_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_C, 1); +} +#endif + +#if defined(PORTD_INT0_vect) +ISR(PORTD_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_D, 0); +} +#endif +#if defined(PORTD_INT1_vect) +ISR(PORTD_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_D, 1); +} +#endif + +#if defined(PORTE_INT0_vect) +ISR(PORTE_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_E, 0); +} +#endif +#if defined(PORTE_INT1_vect) +ISR(PORTE_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_E, 1); +} +#endif + +#if defined(PORTF_INT0_vect) +ISR(PORTF_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_F, 0); +} +#endif +#if defined(PORTF_INT1_vect) +ISR(PORTF_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_F, 1); +} +#endif + +#if defined(PORTG_INT0_vect) +ISR(PORTG_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_G, 0); +} +#endif +#if defined(PORTG_INT1_vect) +ISR(PORTG_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_G, 1); +} +#endif + +#if defined(PORTH_INT0_vect) +ISR(PORTH_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_H, 0); +} +#endif +#if defined(PORTH_INT1_vect) +ISR(PORTH_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_H, 1); +} +#endif + +#if defined(PORTJ_INT0_vect) +ISR(PORTJ_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_J, 0); +} +#endif +#if defined(PORTJ_INT1_vect) +ISR(PORTJ_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_J, 1); +} +#endif + +#if defined(PORTK_INT0_vect) +ISR(PORTK_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_K, 0); +} +#endif +#if defined(PORTK_INT1_vect) +ISR(PORTK_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_K, 1); +} +#endif + +#if defined(PORTL_INT0_vect) +ISR(PORTL_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_L, 0); +} +#endif +#if defined(PORTL_INT1_vect) +ISR(PORTL_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_L, 1); +} +#endif + +#if defined(PORTM_INT0_vect) +ISR(PORTM_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_M, 0); +} +#endif +#if defined(PORTM_INT1_vect) +ISR(PORTM_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_M, 1); +} +#endif + +#if defined(PORTN_INT0_vect) +ISR(PORTN_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_N, 0); +} +#endif +#if defined(PORTN_INT1_vect) +ISR(PORTN_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_N, 1); +} +#endif + +#if defined(PORTP_INT0_vect) +ISR(PORTP_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_P, 0); +} +#endif +#if defined(PORTP_INT1_vect) +ISR(PORTP_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_P, 1); +} +#endif + +#if defined(PORTQ_INT0_vect) +ISR(PORTQ_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_Q, 0); +} +#endif +#if defined(PORTQ_INT1_vect) +ISR(PORTQ_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_Q, 1); +} +#endif + +#if defined(PORTR_INT0_vect) +ISR(PORTR_INT0_vect, ISR_BLOCK) +{ + irq_handler(PORT_R, 0); +} +#endif +#if defined(PORTR_INT1_vect) +ISR(PORTR_INT1_vect, ISR_BLOCK) +{ + irq_handler(PORT_R, 1); +} +#endif diff --git a/cpu/atxmega/periph/nvm.c b/cpu/atxmega/periph/nvm.c new file mode 100644 index 0000000000..88dc389e4c --- /dev/null +++ b/cpu/atxmega/periph/nvm.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @ingroup cpu_atxmega_periph + * @{ + * + * @file + * @brief Low-level NVM driver implementation + * + * @author Gerson Fernando Budke + * + * @} + */ +#include + +#include "cpu_nvm.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/** + * @brief Read one byte using the Load Program Memory (LPM) instruction + * @internal + * + * This function sets the specified NVM_CMD, reads one byte using at the + * specified byte address with the LPM instruction. NVM_CMD is restored after + * use. + * + * @note This will disable interrupts during execution. + * + * @param nvm_cmd NVM command to load before running LPM + * @param address Byte offset into the signature row + */ +static inline uint8_t _nvm_read_byte(uint8_t nvm_cmd, uint16_t address) +{ + uint8_t result; + + __asm__ volatile ( + "in __tmp_reg__, __SREG__ \n\t" + "cli \n\t" + "lds __zero_reg__, %3 \n\t" + "sts %3, %1 \n\t" + "lpm %0, %a2 \n\t" + "sts %3, __zero_reg__ \n\t" + "clr __zero_reg__ \n\t" + "out __SREG__, __tmp_reg__ \n\t" + : + "=&r" (result) + : + "r" (nvm_cmd), "e" (address), "m" (NVM_CMD) + : /* no clobbers */ + ); + + return result; +} + +/** + * @brief Read one byte from the production signature row + * + * This function reads one byte from the production signature row of the device + * at the given address. + * + * @note The execution can take some time. It is recommended not call this + * inside an interrupt service routine. + * + * @param address Byte offset into the signature row + */ +uint8_t nvm_read_production_signature_row(uint8_t address) +{ + return _nvm_read_byte(NVM_CMD_READ_CALIB_ROW_gc, address); +} diff --git a/cpu/atxmega/periph/pm.c b/cpu/atxmega/periph/pm.c new file mode 100644 index 0000000000..08875134e4 --- /dev/null +++ b/cpu/atxmega/periph/pm.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @ingroup cpu_atxmega_periph + * @{ + * + * @file + * @brief Low-level PM driver implementation + * + * @author Gerson Fernando Budke + * + * @} + */ + +#include + +#include "periph_conf.h" +#include "periph/pm.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +void pm_reboot(void) +{ + DEBUG("Reboot Software Reset\n" ); + + /* XMEGA AU [MANUAL] p. 116 CTRL->Control register + * page 13 3.12.1 Sequence for write operation to protected I/O registers + * page 15 3.14.1 CCP – Configuration Change Protection register + */ + + /* Disable CCP for Protected IO registerand set new value*/ + _PROTECTED_WRITE(RST_CTRL, RST_SWRST_bm); + while (1) {} +} + +/* + * DEBUG may affect this routine. + * + * --- Do NOT add DEBUG macro here --- + * + */ +void pm_set(unsigned mode) +{ + unsigned irq_state = irq_disable(); + + if (avr8_is_uart_tx_pending() && mode < 4) { + irq_restore(irq_state); + return; + } + + switch (mode) { + case 0: + set_sleep_mode(SLEEP_SMODE_PDOWN_gc); + break; + case 1: + set_sleep_mode(SLEEP_SMODE_PSAVE_gc); + break; + case 2: + set_sleep_mode(SLEEP_SMODE_STDBY_gc); + break; + case 3: + set_sleep_mode(SLEEP_SMODE_ESTDBY_gc); + break; + default: + set_sleep_mode(SLEEP_SMODE_IDLE_gc); + } + sleep_enable(); + sei(); + sleep_cpu(); + sleep_disable(); + irq_restore(irq_state); +} diff --git a/cpu/atxmega/periph/timer.c b/cpu/atxmega/periph/timer.c new file mode 100644 index 0000000000..f49fbfd510 --- /dev/null +++ b/cpu/atxmega/periph/timer.c @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @ingroup cpu_atxmega_periph + * @{ + * + * @file + * @brief Low-level TIMER driver implementation + * + * @author Gerson Fernando Budke + * + * @} + */ + +#include + +#include + +#include "cpu.h" +#include "thread.h" + +#include "periph/timer.h" + +#include "board.h" +#include "periph_conf.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/** + * @brief We have 7 possible prescaler values + */ +#define PRESCALE_NUMOF (7U) + +/** + * @brief Possible prescaler values, encoded as 2 ^ val + */ +static const uint8_t prescalers[] = { 0, 1, 2, 3, 6, 8, 10 }; + +/** + * @brief Timer state context + */ +typedef struct { + timer_cb_t cb; /**< interrupt callback */ + void *arg; /**< interrupt callback argument */ + uint8_t prescaler; /**< remember the prescaler value */ + uint8_t channels; /**< number of channels */ +} ctx_t; + +/** + * @brief Allocate memory for saving the device states + * @{ + */ +#ifdef TIMER_NUMOF +static ctx_t ctx[TIMER_NUMOF] = { { NULL } }; +#else +/* fallback if no timer is configured */ +static ctx_t *ctx[] = { { NULL } }; +#endif +/** @} */ + +static uint32_t _oneshot; + +static inline void set_oneshot(tim_t tim, int chan) +{ + _oneshot |= (1 << chan) << (TIMER_CH_MAX_NUMOF * tim); +} + +static inline void clear_oneshot(tim_t tim, int chan) +{ + _oneshot &= ~((1 << chan) << (TIMER_CH_MAX_NUMOF * tim)); +} + +static inline bool is_oneshot(tim_t tim, int chan) +{ + return _oneshot & ((1 << chan) << (TIMER_CH_MAX_NUMOF * tim)); +} + +/** + * @brief Setup the given timer + */ +int timer_init(tim_t tim, unsigned long freq, timer_cb_t cb, void *arg) +{ + DEBUG("timer.c: freq = %ld, Core Clock = %ld\n", freq, CLOCK_CORECLOCK); + + TC0_t *dev; + uint8_t pre; + uint8_t ch; + + assert(TIMER_CH_MAX_NUMOF * TIMER_NUMOF < 32); + + /* make sure given device is valid */ + if (tim >= TIMER_NUMOF) { + return -1; + } + + /* figure out if freq is applicable */ + for (pre = 0; pre < PRESCALE_NUMOF; pre++) { + if ((CLOCK_CORECLOCK >> prescalers[pre]) == freq) { + break; + } + } + if (pre == PRESCALE_NUMOF) { + DEBUG("timer.c: prescaling failed!\n"); + return -1; + } + + dev = timer_config[tim].dev; + + /* stop and reset timer */ + dev->CTRLA = 0; /* Stop */ + dev->CTRLFSET = TC_CMD_RESET_gc; /* Force Reset */ + + /* save interrupt context and timer Prescaler */ + ctx[tim].cb = cb; + ctx[tim].arg = arg; + ctx[tim].prescaler = (0x07 & (pre + 1)); + + /* Check enabled channels */ + ctx[tim].channels = 0; + for (ch = 0; ch < TIMER_CH_MAX_NUMOF; ch++) { + if (timer_config[tim].int_lvl[ch] != CPU_INT_LVL_OFF) { + ctx[tim].channels++; + } + } + + if (timer_config[tim].type != TC_TYPE_0 + && timer_config[tim].type != TC_TYPE_4) { + if (ctx[tim].channels > 2) { + DEBUG("timer.c: wrong number of channels. max value is 2.\n"); + return -1; + } + } + + if (timer_config[tim].type == TC_TYPE_2) { + DEBUG("timer.c: Timer version %d is current not supported.\n", + timer_config[tim].type); + return -1; + } + + /* Normal Counter with rollover */ + dev->CTRLB = TC_WGMODE_NORMAL_gc; + + /* Compare or Capture disable all channels */ + dev->INTCTRLB = 0; + + /* Free running counter */ + dev->PER = 0xFFFF; + + DEBUG("timer.c: prescaler set to %d \n", ctx[tim].prescaler); + dev->CTRLA = ctx[tim].prescaler; + + return 0; +} + +int timer_set_absolute(tim_t tim, int channel, unsigned int value) +{ + if (tim >= TIMER_NUMOF) { + return -1; + } + + if (channel >= ctx[tim].channels) { + return -1; + } + + DEBUG("Setting timer %i channel %i to %04x\n", tim, channel, value); + + TC0_t *dev = timer_config[tim].dev; + + dev->INTCTRLB &= ~(TC0_CCAINTLVL_gm << (channel * 2)); + dev->INTFLAGS |= TC0_CCAIF_bm << channel; + + uint8_t irq_state = irq_disable(); + + *(((uint16_t *)(&dev->CCA)) + channel) = (uint16_t)value; + + irq_restore(irq_state); + set_oneshot(tim, channel); + + dev->INTCTRLB |= (timer_config[tim].int_lvl[channel] << (channel * 2)); + + return 0; +} + +int timer_set_periodic(tim_t tim, int channel, unsigned int value, uint8_t flags) +{ + (void)flags; + + if (tim >= TIMER_NUMOF) { + return -1; + } + + if (channel > 0 || ctx[tim].channels != 1) { + DEBUG("Only channel 0 can be set as periodic and channels must be 1\n"); + + return -1; + } + + DEBUG("Setting timer %i channel 0 to %i and flags %i (repeating)\n", + tim, value, flags); + + TC0_t *dev = timer_config[tim].dev; + uint8_t irq_state = irq_disable(); + + dev->CTRLA = 0; + dev->CTRLFSET = TC_CMD_RESET_gc; + dev->CTRLB = TC_WGMODE_FRQ_gc; + dev->INTCTRLB = 0; + dev->INTFLAGS |= TC0_CCAIF_bm; + dev->CCA = (uint16_t)value; + dev->INTCTRLB = timer_config[tim].int_lvl[0]; + dev->CTRLA = ctx[tim].prescaler; + + clear_oneshot(tim, channel); + irq_restore(irq_state); + + return 0; +} + +int timer_clear(tim_t tim, int channel) +{ + if (tim >= TIMER_NUMOF) { + return -1; + } + + if (channel >= ctx[tim].channels) { + return -1; + } + + DEBUG("timer_clear channel %d\n", channel ); + + TC0_t *dev = timer_config[tim].dev; + + /* Compare or Capture Disable */ + dev->INTCTRLB &= ~(TC0_CCAINTLVL_gm << (channel * 2)); + + /* Clear Interrupt Flag + * The CCxIF is automatically cleared when the corresponding + * interrupt vector is executed.*/ + dev->INTFLAGS |= TC0_CCAIF_bm << channel; + + return 0; +} + +int timer_set(tim_t tim, int channel, unsigned int timeout) +{ + if (tim >= TIMER_NUMOF) { + return -1; + } + + if (channel >= ctx[tim].channels) { + return -1; + } + + TC0_t *dev = timer_config[tim].dev; + + /* Compare or Capture Disable */ + dev->INTCTRLB &= ~(TC0_CCAINTLVL_gm << (channel * 2)); + + /* Clear Interrupt Flag */ + dev->INTFLAGS |= TC0_CCAIF_bm << channel; + + uint8_t irq_state = irq_disable(); + + /* set value to compare with rollover */ + uint16_t absolute = dev->CNT + timeout; + *(((uint16_t *)(&dev->CCA)) + channel) = absolute; + + irq_restore(irq_state); + set_oneshot(tim, channel); + + /* Compare or Capture Enable */ + dev->INTCTRLB |= (timer_config[tim].int_lvl[channel] << (channel * 2)); + + return 0; +} + +unsigned int timer_read(tim_t tim) +{ + if (tim >= TIMER_NUMOF) { + return -1; + } + + DEBUG("timer_read\n"); + return (unsigned int)timer_config[tim].dev->CNT; +} + +void timer_stop(tim_t tim) +{ + if (tim >= TIMER_NUMOF) { + return; + } + + DEBUG("timer_stop\n"); + timer_config[tim].dev->CTRLA = 0; + timer_config[tim].dev->CTRLFSET = TC_CMD_RESTART_gc; +} + +void timer_start(tim_t tim) +{ + if (tim >= TIMER_NUMOF) { + return; + } + + DEBUG("timer_start\n"); + timer_config[tim].dev->CTRLA = ctx[tim].prescaler; +} + +#ifdef TIMER_NUMOF +static inline void _isr(tim_t tim, int channel) +{ + avr8_enter_isr(); + + DEBUG("timer %d _isr channel %d\n", tim, channel); + + if (is_oneshot(tim, channel)) { + timer_config[tim].dev->INTCTRLB &= ~(TC0_CCAINTLVL_gm << (channel * 2)); + } + + if (ctx[tim].cb) { + ctx[tim].cb(ctx[tim].arg, channel); + } + + avr8_exit_isr(); +} +#endif + +#ifdef TIMER_0_ISRA +ISR(TIMER_0_ISRA, ISR_BLOCK) +{ + _isr(0, 0); +} +#endif +#ifdef TIMER_0_ISRB +ISR(TIMER_0_ISRB, ISR_BLOCK) +{ + _isr(0, 1); +} +#endif +#ifdef TIMER_0_ISRC +ISR(TIMER_0_ISRC, ISR_BLOCK) +{ + _isr(0, 2); +} +#endif +#ifdef TIMER_0_ISRD +ISR(TIMER_0_ISRD, ISR_BLOCK) +{ + _isr(0, 3); +} +#endif /* TIMER_0 */ + +#ifdef TIMER_1_ISRA +ISR(TIMER_1_ISRA, ISR_BLOCK) +{ + _isr(1, 0); +} +#endif +#ifdef TIMER_1_ISRB +ISR(TIMER_1_ISRB, ISR_BLOCK) +{ + _isr(1, 1); +} +#endif +#ifdef TIMER_1_ISRC +ISR(TIMER_1_ISRC, ISR_BLOCK) +{ + _isr(1, 2); +} +#endif +#ifdef TIMER_1_ISRD +ISR(TIMER_1_ISRD, ISR_BLOCK) +{ + _isr(1, 3); +} +#endif /* TIMER_1 */ + +#ifdef TIMER_2_ISRA +ISR(TIMER_2_ISRA, ISR_BLOCK) +{ + _isr(2, 0); +} +#endif +#ifdef TIMER_2_ISRB +ISR(TIMER_2_ISRB, ISR_BLOCK) +{ + _isr(2, 1); +} +#endif +#ifdef TIMER_2_ISRC +ISR(TIMER_2_ISRC, ISR_BLOCK) +{ + _isr(2, 2); +} +#endif +#ifdef TIMER_2_ISRD +ISR(TIMER_2_ISRD, ISR_BLOCK) +{ + _isr(2, 3); +} +#endif /* TIMER_2 */ + +#ifdef TIMER_3_ISRA +ISR(TIMER_3_ISRA, ISR_BLOCK) +{ + _isr(3, 0); +} +#endif +#ifdef TIMER_3_ISRB +ISR(TIMER_3_ISRB, ISR_BLOCK) +{ + _isr(3, 1); +} +#endif +#ifdef TIMER_3_ISRC +ISR(TIMER_3_ISRC, ISR_BLOCK) +{ + _isr(3, 2); +} +#endif +#ifdef TIMER_3_ISRD +ISR(TIMER_3_ISRD, ISR_BLOCK) +{ + _isr(3, 3); +} +#endif /* TIMER_3 */ + +#ifdef TIMER_4_ISRA +ISR(TIMER_4_ISRA, ISR_BLOCK) +{ + _isr(4, 0); +} +#endif +#ifdef TIMER_4_ISRB +ISR(TIMER_4_ISRB, ISR_BLOCK) +{ + _isr(4, 1); +} +#endif +#ifdef TIMER_4_ISRC +ISR(TIMER_4_ISRC, ISR_BLOCK) +{ + _isr(4, 2); +} +#endif +#ifdef TIMER_4_ISRD +ISR(TIMER_4_ISRD, ISR_BLOCK) +{ + _isr(4, 3); +} +#endif /* TIMER_4 */ + +#ifdef TIMER_5_ISRA +ISR(TIMER_5_ISRA, ISR_BLOCK) +{ + _isr(5, 0); +} +#endif +#ifdef TIMER_5_ISRB +ISR(TIMER_5_ISRB, ISR_BLOCK) +{ + _isr(5, 1); +} +#endif +#ifdef TIMER_5_ISRC +ISR(TIMER_5_ISRC, ISR_BLOCK) +{ + _isr(5, 2); +} +#endif +#ifdef TIMER_5_ISRD +ISR(TIMER_5_ISRD, ISR_BLOCK) +{ + _isr(5, 3); +} +#endif /* TIMER_5 */ + +#ifdef TIMER_6_ISRA +ISR(TIMER_6_ISRA, ISR_BLOCK) +{ + _isr(6, 0); +} +#endif +#ifdef TIMER_6_ISRB +ISR(TIMER_6_ISRB, ISR_BLOCK) +{ + _isr(6, 1); +} +#endif +#ifdef TIMER_6_ISRC +ISR(TIMER_6_ISRC, ISR_BLOCK) +{ + _isr(6, 2); +} +#endif +#ifdef TIMER_6_ISRD +ISR(TIMER_6_ISRD, ISR_BLOCK) +{ + _isr(6, 3); +} +#endif /* TIMER_6 */ + +#ifdef TIMER_7_ISRA +ISR(TIMER_7_ISRA, ISR_BLOCK) +{ + _isr(7, 0); +} +#endif +#ifdef TIMER_7_ISRB +ISR(TIMER_7_ISRB, ISR_BLOCK) +{ + _isr(7, 1); +} +#endif +#ifdef TIMER_7_ISRC +ISR(TIMER_7_ISRC, ISR_BLOCK) +{ + _isr(7, 2); +} +#endif +#ifdef TIMER_7_ISRB +ISR(TIMER_7_ISRD, ISR_BLOCK) +{ + _isr(7, 3); +} +#endif /* TIMER_7 */ diff --git a/cpu/atxmega/periph/uart.c b/cpu/atxmega/periph/uart.c new file mode 100644 index 0000000000..aeaebcda3b --- /dev/null +++ b/cpu/atxmega/periph/uart.c @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2021 Gerson Fernando Budke + * + * 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_atxmega + * @ingroup cpu_atxmega_periph + * @{ + * + * @file + * @brief Low-level UART driver implementation + * + * @author Gerson Fernando Budke + * + * + * Supports runtime calculation of the BSEL and BSCALE register values. + * Supports reconfiguring UART after a clock change. + * + * @} + */ +#include + +#include +#include + +#include "board.h" +#include "cpu.h" +#include "sched.h" +#include "thread.h" +#include "periph/uart.h" +#include "periph/gpio.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +/** + * @brief UART Baudrate Tolerance + * + * The tolerance is expressed as baud rate percentage multiplied by 100. + * For bigger tolerances UART double frequency can be avoided, thus saving + * power. + * + * The default baud tolerance is 2%. + */ +#ifndef BAUD_TOL +#define BAUD_TOL 2 * 100 +#endif + +/** + * @brief Allocate memory to store the callback functions. + */ +static uart_isr_ctx_t isr_ctx[UART_NUMOF]; + +/** + * @brief Get the pointer to the base register of the given USART device + * + * @param[in] dev USART device identifier + * + * @return base register address + */ +static inline USART_t *dev(uart_t dev) +{ + return uart_config[dev].dev; +} + +static inline int8_t _check_bsel(uint32_t baud, uint32_t calcbaud, + int16_t *precision) +{ + /* Avoid negative values and with precision of two positions + * after the decimal point for the deviation in percent + * + * result is the absolute deviation eg. 10 001 + * + * MAX BAUD fper/2 = 16MHz a shift of 8 can be used to increase + * accuracy + */ + uint16_t pre; + + if (baud < calcbaud) { + pre = (uint16_t)((calcbaud * 10000ULL) / baud); + } + else { + pre = (uint16_t)((baud * 10000ULL) / calcbaud); + } + + if (pre < ((uint16_t)*precision)) { + *precision = pre; + return 1; + } + + return 0; +} + +static inline int16_t _xmega_bsel_bscale(uint32_t *fper, uint32_t *baud, + uint8_t clk2x, uint16_t *bsel, + int8_t *bscale) +{ + uint32_t calcbaud = 0; + uint32_t deno = 0; + int16_t precision = UINT_MAX; + uint16_t loc_bsel = 0; + int8_t loc_bscale; + + /* Some explanation for equation transformation + * bsel = ( (fper / (2^bscale*16*baud) ) - 1 ) + * = ( (fper / (2^bscale*(16-8*clk2x)*baud) ) - 1 ) + * = ( fper - (2^bscale*(16-8*clk2x)*baud) ) + * / (2^bscale*(16-8*clk2x)*baud) + * + * deno = ( baud * (16-8*clk2x) * 2^bscale ) + * = ( baud * (1<<(4-clk2x)) * (1<= 4095) { + continue; + } + + /* Omit division by 16 get higher accuracy at small baudrates*/ + calcbaud = (*fper >> loc_bscale) / ((loc_bsel + 1) << (4 - clk2x)); + + if (_check_bsel(*baud, calcbaud, &precision)) { + *bsel = loc_bsel; + *bscale = loc_bscale; + } + } + + /* More math for the negative equation + * bscale is negative so 1/2^bscale = 2^|bscale| which is a factor and + * not a division again runding the result before division with + * 0.5 * denominator + * + * bsel = 1/2^bscale *( fcpu / ( (16*baud)-1) ) + * = ( fcpu*2^|bscale| - (16*baud)*2^|bscale| )/(16*baud) + * = ( fcpu*2^|bscale| - (16*baud)*2^|bscale| + 0.5*(16*baud) )/(16*baud) + * + * deno = (16/2*baud) = (baud<<(3-clk2x)) + * + * bsel = ( (fcpu - (deno<<(1))<<|bscale|) + deno )/(deno<<1) + * + * Baud = (fper*1/2^bscale)/ (16*(bsel+1/2^bscale)) + */ + for (loc_bscale = -1; loc_bscale >= -7; loc_bscale--) { + uint32_t num = 0; + + deno = (*baud << (3 - clk2x)); + num = ((*fper - (deno << 1)) << (-loc_bscale)) + deno; + num = (num / (deno << 1)); + + if (num >= 4095) { + break; + } + + loc_bsel = (uint16_t)(num & 0xFFF); + + /* Omit division by 16 get higher accuracy at small baudrates */ + calcbaud = (*fper << (-loc_bscale)) + / ((loc_bsel + (1 << (-loc_bscale))) << (4 - clk2x)); + + if (_check_bsel(*baud, calcbaud, &precision)) { + *bsel = loc_bsel; + *bscale = loc_bscale; + } + } + + return precision; +} + +/** + * @brief Calculates bsel and bscale for a given periphery clock and baudrate. + * Limitation are the periphery clock maximum is 32MHz, unsigned int + * overflows if clock is bigger. And the periphery clock has to be not + * smaller then 1 when divided by 128. + * + * fper/128 !=0 must be divide able by 128 + * fper*128 != uint23_max => 32MHz max fper + */ +static inline int16_t _xmega_calculate_bsel_bscale(uint32_t fcpu, uint32_t baud, + uint8_t *clk2x, + uint16_t *bsel, + int8_t *bscale) +{ + int16_t precision = 0; + + precision = _xmega_bsel_bscale(&fcpu, &baud, 0, bsel, bscale ); + + /* default 2% precision, required precision is at least 2% */ + if (precision <= (10000 + BAUD_TOL)) { + return (precision - 10000); + } + + /* Precision goal was not met calculate baudrate with uart frequency doubled */ + precision = _xmega_bsel_bscale(&fcpu, &baud, 1, bsel, bscale ); + *clk2x = 1; + + return (precision - 10000); +} + +static inline void _configure_pins(uart_t uart) +{ + /* configure RX pin */ + if (gpio_is_valid(uart_config[uart].rx_pin)) { + gpio_init(uart_config[uart].rx_pin, GPIO_IN); + } + + /* configure TX pin */ + if (gpio_is_valid(uart_config[uart].tx_pin)) { + gpio_set(uart_config[uart].tx_pin); + gpio_init(uart_config[uart].tx_pin, GPIO_OUT); + } + +#ifdef MODULE_PERIPH_UART_HW_FC + /* TODO */ +#endif +} + +int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg) +{ + int8_t bscale; + uint8_t clk2x; + uint16_t bsel; + + /* make sure the given device is valid */ + if (uart >= UART_NUMOF) { + return UART_NODEV; + } + + uint16_t count = UINT16_MAX; + while (avr8_is_uart_tx_pending() && count--) {} + + /* register interrupt context */ + isr_ctx[uart].rx_cb = rx_cb; + isr_ctx[uart].arg = arg; + + /* disable and reset UART */ + dev(uart)->CTRLA = 0; + dev(uart)->CTRLB = 0; + dev(uart)->CTRLC = 0; + + _configure_pins(uart); + + /* configure UART to 8N1 mode */ + dev(uart)->CTRLC = USART_CMODE_ASYNCHRONOUS_gc + | USART_PMODE_DISABLED_gc + | USART_CHSIZE_8BIT_gc; + + /* set clock divider */ + _xmega_calculate_bsel_bscale(CLOCK_CORECLOCK, baudrate, &clk2x, + &bsel, &bscale); + + dev(uart)->BAUDCTRLA = (uint8_t)(bsel & 0x00ff); + dev(uart)->BAUDCTRLB = (bscale << USART_BSCALE_gp) + | ((uint8_t)((bsel & 0x0fff) >> 8)); + if (clk2x == 1) { + dev(uart)->CTRLB |= USART_CLK2X_bm; + } + + /* enable RX and TX Interrupts and set level*/ + if (rx_cb) { + dev(uart)->CTRLA = (uart_config[uart].rx_int_lvl << USART_RXCINTLVL_gp) + | (uart_config[uart].tx_int_lvl << USART_TXCINTLVL_gp) + | (uart_config[uart].dre_int_lvl << USART_DREINTLVL_gp); + dev(uart)->CTRLB = USART_RXEN_bm | USART_TXEN_bm; + } + else { + /* only transmit */ + dev(uart)->CTRLB = USART_TXEN_bm; + } + + DEBUG("Set clk2x %" PRIu8 " bsel %" PRIu16 "bscale %" PRIi8 "\n", + clk2x, bsel, bscale); + + return UART_OK; +} + +void uart_write(uart_t uart, const uint8_t *data, size_t len) +{ + for (size_t i = 0; i < len; i++) { + while (!(dev(uart)->STATUS & USART_DREIF_bm)) {} + + /* start of TX won't finish until no data in DATAn and transmit shift + register is empty */ + uint8_t irq_state = irq_disable(); + avr8_state |= AVR8_STATE_FLAG_UART_TX(uart); + irq_restore(irq_state); + + dev(uart)->DATA = data[i]; + } +} + +void uart_poweron(uart_t uart) +{ + (void)uart; + /* not implemented (yet) */ +} + +void uart_poweroff(uart_t uart) +{ + (void)uart; + /* not implemented (yet) */ +} + +static inline void _rx_isr_handler(int num) +{ + avr8_enter_isr(); + + if (isr_ctx[num].rx_cb) { + isr_ctx[num].rx_cb(isr_ctx[num].arg, dev(num)->DATA); + } + + avr8_exit_isr(); +} + +static inline void _tx_isr_handler(int num) +{ + avr8_enter_isr(); + + /* entire frame in the Transmit Shift Register has been shifted out and + there are no new data currently present in the transmit buffer */ + avr8_state &= ~AVR8_STATE_FLAG_UART_TX(num); + + avr8_exit_isr(); +} + +#ifdef UART_0_RXC_ISR +ISR(UART_0_RXC_ISR, ISR_BLOCK) +{ + _rx_isr_handler(0); +} +ISR(UART_0_TXC_ISR, ISR_BLOCK) +{ + _tx_isr_handler(0); +} +#endif /* UART_0_ISR */ + +#ifdef UART_1_RXC_ISR +ISR(UART_1_RXC_ISR, ISR_BLOCK) +{ + _rx_isr_handler(1); +} +ISR(UART_1_TXC_ISR, ISR_BLOCK) +{ + _tx_isr_handler(1); +} +#endif /* UART_1_ISR */ + +#ifdef UART_2_RXC_ISR +ISR(UART_2_RXC_ISR, ISR_BLOCK) +{ + _rx_isr_handler(2); +} +ISR(UART_2_TXC_ISR, ISR_BLOCK) +{ + _tx_isr_handler(2); +} +#endif /* UART_2_ISR */ + +#ifdef UART_3_RXC_ISR +ISR(UART_3_RXC_ISR, ISR_BLOCK) +{ + _rx_isr_handler(3); +} +ISR(UART_3_TXC_ISR, ISR_BLOCK) +{ + _tx_isr_handler(3); +} +#endif /* UART_3_ISR */ + +#ifdef UART_4_RXC_ISR +ISR(UART_4_RXC_ISR, ISR_BLOCK) +{ + _rx_isr_handler(4); +} +ISR(UART_4_TXC_ISR, ISR_BLOCK) +{ + _tx_isr_handler(4); +} +#endif /* UART_4_ISR */ + +#ifdef UART_5_RXC_ISR +ISR(UART_5_RXC_ISR, ISR_BLOCK) +{ + _rx_isr_handler(5); +} +ISR(UART_5_TXC_ISR, ISR_BLOCK) +{ + _tx_isr_handler(5); +} +#endif /* UART_5_ISR */ + +#ifdef UART_6_RXC_ISR +ISR(UART_6_RXC_ISR, ISR_BLOCK) +{ + _rx_isr_handler(6); +} +ISR(UART_6_TXC_ISR, ISR_BLOCK) +{ + _tx_isr_handler(6); +} +#endif /* UART_6_ISR */