1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

cpu/qn908x: Initial minimal support for NXP QN908x CPUs.

The NXP QN908x CPU family is a Cortex-M4F CPU with integrated USB,
Bluetooth Low Energy and in some variants NFC. This patch implements the
first steps for having support for this CPU.

While the QN908x can be considered the successor of similar chips from
NXP like the KW41Z when looking at the feature set, the internal
architecture, boot image format and CPU peripherals don't match those
in the Kinetis line. Therefore, this patch creates a new directory for
just the QN908x chip under cpu/qn908x.

The minimal set of peripherals are implemented in this patch to allow
the device to boot and enable a GPIO: the gpio and wdt peripheral
modules only.

The wdt driver is required to boot and disable the wdt. On reset, the
wdt is disabled by the chip, however the QN908x bootloader stored in
the internal ROM enables the wdt and sets a timer to reboot after 10
seconds, therefore it is needed to disable the wdt in RIOT OS soon
after booting. This patch sets it up such that when no periph_wdt module
is used the Watchdog is disabled, but if the periph_wdt is used it must
be configured (initialized) within the first 10 seconds.

Tests performed:
Defined a custom board for this CPU and compiled a simple application
that blinks some LEDs. Manually tested with periph_wdt and with
periph_wdt_cb as well.
This commit is contained in:
iosabi 2020-04-10 12:26:17 +00:00
parent 21fd1f9258
commit cde8ac6093
22 changed files with 2002 additions and 0 deletions

51
cpu/qn908x/Kconfig Normal file
View File

@ -0,0 +1,51 @@
# Copyright (c) 2020 iosabi
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
#
config CPU_FAM_QN908X
bool
select CPU_CORE_CORTEX_M4F
select HAS_CORTEXM_MPU
select HAS_CPU_QN908X
select HAS_PERIPH_CPUID
select HAS_PERIPH_GPIO
select HAS_PERIPH_GPIO_IRQ
select HAS_PERIPH_WDT
select HAS_PERIPH_WDT_CB
## CPU Models
# For cpus QN9080CHN (revision C) and QN9080DHN (revision D)
config CPU_MODEL_QN9080XHN
bool
select CPU_FAM_QN908X
# For the smaller package for the same die, with 28 GPIOs instead of 35.
# cpus QN9083CUK (revision C) and QN9083DUK (revision D)
config CPU_MODEL_QN9083XUK
bool
select CPU_FAM_QN908X
## CPU common symbols
config CPU_FAM
default "qn908x" if CPU_FAM_QN908X
config CPU_MODEL
default "qn9080xhn" if CPU_MODEL_QN9080XHN
default "qn9083xhk" if CPU_MODEL_QN9083XUK
config CPU
default "qn908x" if CPU_FAM_QN908X
## Definition of specific features
config HAS_CPU_QN908X
bool
help
Indicates that the current cpu is 'qn908x'.
# Other cpu configuration
rsource "Kconfig.clk"
source "$(RIOTCPU)/cortexm_common/Kconfig"

120
cpu/qn908x/Kconfig.clk Normal file
View File

@ -0,0 +1,120 @@
# Copyright (c) 2020 iosabi
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
#
menu "QN908x clock configuration"
depends on CPU_FAM_QN908X
config BOARD_HAS_XTAL32K
bool
help
Indicates that the board has an external low frequency 32.786 KHz
crystal oscillator connected to the XTAL32_IN / XTAL32_OUT pins.
This should only be set from board definition.
choice
prompt "32K low frequency clock selector"
default CPU_CLK_32K_XTAL if BOARD_HAS_XTAL32K
default CPU_CLK_32K_RCO
help
The "32K" clock bus runs at either 32 KHz from the internal RCO or
32.768 KHz from an external crystal oscillator. This clock can be used
to drive the "System clock" for a very low power operation, but it can
independently also be used for the watchdog timer (WDT) and other low
frequency system timers like a real time clock.
config CPU_CLK_32K_XTAL
bool "External 32.768 KHz crystal"
depends on BOARD_HAS_XTAL32K
config CPU_CLK_32K_RCO
bool "Internal 32 KHz oscillator"
endchoice
config BOARD_HAS_XTAL
bool
help
Indicates that the board has an external high frequency crystal
oscillator connected to the XTAL_IN / XTAL_OUT pins.
This should only be set from board definition.
config BOARD_HAS_XTAL_16M
bool
imply BOARD_HAS_XTAL
depends on !BOARD_HAS_XTAL_32M
help
Indicates that the external high frequency crystal oscillator is a
16 MHz crystal. This should only be set from board definition.
config BOARD_HAS_XTAL_32M
bool
imply BOARD_HAS_XTAL
help
Indicates that the external high frequency crystal oscillator is a
32 MHz crystal. This should only be set from board definition.
config CPU_CLK_OSC32M_DIV
bool "Internal OSC32M clock input /2 divider"
help
Selecting this option will set the high-speed internal oscillator
divider to /2, making it a 16 MHz clock source. See "System clock
configuration selector" for selecting this source.
config CPU_CLK_XTAL_DIV
bool "External XTAL 32 MHz clock input /2 divider"
depends on BOARD_HAS_XTAL_32M
help
Selecting this option will set the high-speed external crystal
oscillator divider to /2. This option is only available when the
external oscillator is a 32 MHz one. See "System clock
configuration selector" for selecting this source.
choice
prompt "System clock configuration selector"
default CPU_CLK_SYS_XTAL if BOARD_HAS_XTAL
default CPU_CLK_SYS_OSC32M
help
The System clock is used to derive the AHB clock, which drives the ARM
core and most peripherals.
config CPU_CLK_SYS_XTAL
bool "External 16/32 MHz crystal source (with optional divider)"
depends on BOARD_HAS_XTAL
config CPU_CLK_SYS_OSC32M
bool "Internal 32 MHz oscillator source (with optional divider)"
config CPU_CLK_SYS_32K
bool "Low frequency clock source (32 or 32.768 KHz)"
endchoice
config CPU_CLK_AHB_DIV
int "AHB clock divider"
default 1
range 1 8192
help
The AHB clock is derived from the System clock using this divider value,
between 1 and 8192, and serves as a clock source for ARM core, FSP, SCT,
Quad-SPI, Flexcomm (UART, SPI, I2C), GPIO, BLE_AHB and DMA.
Note: When BLE is enabled, the AHB clock must be at least the BLE clock
(either 8 or 16 MHz) limiting the range of allowed values for this
divider so that the AHB clock is 8, 16 or 32 MHz.
config CPU_CLK_APB_DIV
int "APB clock divider"
default 1
range 1 16
help
The APB clock is derived from the AHB clock using this divide value,
between 1 and 16, and serves as the clock source for several
peripherals, such as the RTC, ADC, DAC, Capacitive Sense (CS) and
optionally the WDT.
# TODO: Add USB PLL and BLE clock selectors.
endmenu

10
cpu/qn908x/Makefile Normal file
View File

@ -0,0 +1,10 @@
# define the module that is build
MODULE = cpu
# add a list of subdirectories that should also be built
DIRS = periph $(RIOTCPU)/cortexm_common vendor
# (file triggers compiler bug. see #5775)
SRC_NOLTO += vectors.c
include $(RIOTBASE)/Makefile.base

18
cpu/qn908x/Makefile.dep Normal file
View File

@ -0,0 +1,18 @@
# In some cases, peripheral modules use vendor provided driver modules such as
# the vendor_fsl_clock. This file defines the dependencies between these periph
# modules and the vendor modules.
USEMODULE += vendor
# The clock functionality is used by most modules, including cpu.c even when
# no peripheral module is being used.
USEMODULE += vendor_fsl_clock
# All peripherals use gpio_mux.h
USEMODULE += periph_gpio_mux
# This cpu modules doesn't support UART peripherals yet, so we need to include
# stdio_null.
# TODO: Remove stdio_null once periph_uart is implemented in this module.
USEMODULE += stdio_null
include $(RIOTCPU)/cortexm_common/Makefile.dep

View File

@ -0,0 +1,9 @@
CPU_CORE = cortex-m4f
CPU_FAM = qn908x
FEATURES_PROVIDED += cortexm_mpu
FEATURES_PROVIDED += periph_cpuid
FEATURES_PROVIDED += periph_gpio periph_gpio_irq
FEATURES_PROVIDED += periph_wdt periph_wdt_cb
include $(RIOTCPU)/cortexm_common/Makefile.features

View File

@ -0,0 +1,39 @@
# Add search path for linker scripts
LINKFLAGS += -L$(RIOTCPU)/$(CPU)/ldscripts
LINKER_SCRIPT = qn908x.ld
# Internal FLASH memory is located at address 0x0100000, aliased to address
# 0x2100000 and can also be aliased to address 0, which is done by the
# pre_startup() function in cpu/qn908x/isr_qn908x.c. The address 0 can be also
# be remapped to RAM instead, and the FLASH can be turned completely off to save
# power, thus linking all the code based on address 0 could make it easier in
# the future to provide a low-power mode where portions of the code execute
# from RAM only during this low-power mode. However, linking all the code at
# address 0 makes it more difficult to attach gdb after a 'reset halt' but
# before the FLASH is mapped to 0 by pre_startup() since it can't place a
# breakpoint at any function in the FLASH alias at 0 until it is mapped.
# This default value of 0x01000000 makes it possible to place breakpoints across
# reboots, but it can be override from the board if needed. When setting
# ROM_START_ADDR to 0 the IMAGE_OFFSET must be set to 0x01000000 to allow
# flashing at the right location.
ROM_START_ADDR ?= 0x01000000
# SRAM is actually at 0x04000000 but it is also aliased to 0x20000000.
RAM_BASE_ADDR = 0x20000000
RAM_START_ADDR = $(RAM_BASE_ADDR)
# The only QN908x chips available have 512K flash, although it seems possible to
# have 256K versions.
ROM_LEN ?= 512K
RAM_LEN ?= 128K
CFLAGS += \
-DQN908X_ROM_START_ADDR=$(ROM_START_ADDR)
#
# Vendor submodules are all bundled in the vendor module, and they include
# some files from the include/vendor directory directly so we need to add that
# include path here.
PSEUDOMODULES += vendor_%
INCLUDES += -I$(RIOTCPU)/$(CPU)/include/vendor
include $(RIOTMAKE)/arch/cortexm.inc.mk

125
cpu/qn908x/cpu.c Normal file
View File

@ -0,0 +1,125 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_qn908x
* @{
*
* @file
* @brief QN908x CPU initialization
*
* @author iosabi <iosabi@protonmail.com>
* @}
*/
#include "cpu.h"
#include "periph/init.h"
#include "stdio_base.h"
#include "vendor/drivers/fsl_clock.h"
static void cpu_clock_init(void);
/**
* @brief Initialize the CPU
*/
void cpu_init(void)
{
/* initialize the Cortex-M core */
cortexm_init();
#ifndef MODULE_PERIPH_WDT
/* If the `periph_wdt` is *not* being used (because the user does not care
* about that feature) we need to disable the Watchdog and continue running
* without it. Otherwise the CPU will reboot after about 10 seconds.
*/
CLOCK_DisableClock(kCLOCK_Wdt);
#endif /* ndef MODULE_PERIPH_WDT */
/* TODO: It would be good to move the VTOR to SRAM to allow execution from
* RAM with the FLASH memory off to allow for ultra low power operation on
* sleep mode. This needs to be done after cortexm_init() since it sets the
* VTOR to _isr_vectors which is the address on FLASH.
*/
/* initialize the clocks */
cpu_clock_init();
/* initialize stdio prior to periph_init() to allow use of DEBUG() there */
stdio_init();
/* trigger static peripheral initialization */
periph_init();
}
/* Set up clock speed configuration. See cpu_conf.h for details about the
* different clock options. */
void cpu_clock_init(void)
{
/* Set up external clock frequency. */
#if CONFIG_BOARD_HAS_XTAL
#if CONFIG_BOARD_HAS_XTAL_32M
CLOCK_AttachClk(k32M_to_XTAL_CLK); /* Switch XTAL_CLK to 32M */
#elif CONFIG_BOARD_HAS_XTAL_16M
CLOCK_AttachClk(k16M_to_XTAL_CLK); /* Switch XTAL_CLK to 16M */
#else
#error "One of the CONFIG_BOARD_XTAL_* must be set."
#endif
#endif /* CONFIG_BOARD_HAS_XTAL */
/* Set up 32K clock source. */
#if CONFIG_CPU_CLK_32K_XTAL
CLOCK_AttachClk(kXTAL32K_to_32K_CLK); /* Switch 32K_CLK to XTAL32K */
#elif CONFIG_CPU_CLK_32K_RCO
CLOCK_AttachClk(kRCO32K_to_32K_CLK); /* Switch 32K_CLK to RCO32K */
#else
#error "One of the CONFIG_CPU_CLK_32K_* must be set."
#endif
/* Set up System clock source. */
#if CONFIG_CPU_CLK_SYS_XTAL
CLOCK_AttachClk(kXTAL_to_SYS_CLK); /* Switch SYS_CLK to XTAL */
#elif CONFIG_CPU_CLK_SYS_OSC32M
CLOCK_AttachClk(kOSC32M_to_SYS_CLK); /* Switch SYS_CLK to OSM32M */
#elif CONFIG_CPU_CLK_SYS_32K
CLOCK_AttachClk(k32K_to_SYS_CLK); /* Switch SYS_CLK to 32K source */
#else
#error "One of the CONFIG_CPU_CLK_SYS_* must be set."
#endif
/* Run the WDT from the APB always. */
CLOCK_AttachClk(kAPB_to_WDT_CLK);
/* Set up dividers */
/* Set OSC32M_DIV divider */
#if CONFIG_CPU_CLK_OSC32M_DIV != 0 && CONFIG_CPU_CLK_OSC32M_DIV != 1
#error "Invalid CONFIG_CPU_CLK_OSC32M_DIV value"
#endif
/* Note: The denominator is set to (CONFIG_CPU_CLK_OSC32M_DIV + 1), so /2
* when the macro is enabled. */
CLOCK_SetClkDiv(kCLOCK_DivOsc32mClk, CONFIG_CPU_CLK_OSC32M_DIV);
/* Set XTAL_DIV divider */
#if CONFIG_CPU_CLK_XTAL_DIV != 0 && CONFIG_CPU_CLK_XTAL_DIV != 1
#error "Invalid CONFIG_CPU_CLK_XTAL_DIV value"
#endif
CLOCK_SetClkDiv(kCLOCK_DivXtalClk, CONFIG_CPU_CLK_XTAL_DIV);
/* Set AHB_DIV divider. */
#if CONFIG_CPU_CLK_AHB_DIV < 1 || CONFIG_CPU_CLK_AHB_DIV > 8192
#error "Invalid CONFIG_CPU_CLK_AHB_DIV"
#endif
CLOCK_SetClkDiv(kCLOCK_DivAhbClk, CONFIG_CPU_CLK_AHB_DIV - 1u);
/* Set APB_DIV divider. */
#if CONFIG_CPU_CLK_APB_DIV < 1 || CONFIG_CPU_CLK_APB_DIV > 16
#error "Invalid CONFIG_CPU_CLK_APB_DIV"
#endif
CLOCK_SetClkDiv(kCLOCK_DivApbClk, CONFIG_CPU_CLK_APB_DIV - 1u);
}

68
cpu/qn908x/doc.txt Normal file
View File

@ -0,0 +1,68 @@
/* NXP QN908x specific information for the `periph` drivers */
/**
@defgroup cpu_qn908x NXP QN908x
@ingroup cpu
@brief NXP QN908x BLE-enabled Cortex-M4F MCU specific implementation
The NXP QN908x family of chips such as the QN9080 feature a Cortex-M4F,
Bluetooth Low Energy, USB 2.0 and in some SKUs like the QN9080SIP NFC as well.
The CPU is designed to be ultra-low-power and high-performance, allowing
applications with small battery capacity. It includes an optional DC-DC and LDO,
low power sleep timers, I2C, SPI, ADC, SPIFI and several other peripherals.
@defgroup cpu_qn908x_cpuid NXP QN908x CPUID
@ingroup cpu_qn908x
@brief NXP QN908x CPUID driver
No configuration is necessary. The CPUID value is based on the factory assigned
default Bluetooth address in the read-only flash section which may not be the
Bluetooth address used by the Bluetooth module if a different one was programmed
there.
@defgroup cpu_qn908x_gpio NXP QN908x GPIO
@ingroup cpu_qn908x
@brief NXP QN908x GPIO driver
The GPIO driver uses the @ref GPIO_PIN(port, pin) macro to declare pins.
No configuration is necessary.
@defgroup cpu_qn908x_wdt NXP QN908x Watchdog timer (WDT)
@ingroup cpu_qn908x
@brief NXP QN908x Watchdog timer (WDT)
The Watchdog timer in the NXP QN908x starts disabled on reset: the clock bit
`CLK_WDT_EN` is enabled in the `CLK_EN` register on reset so the timer is
running but the interrupt and reset functions are disabled. However, after the
read-only bootloader ROM in the QN908x transfer the control flow to the user
application (the RIOT kernel) the Watchdog is enabled with a timeout of 10
seconds.
If your board does not include the `periph_wdt` module, the Watchdog will be
disabled at `cpu_init()` time and there's no configuration necessary. However,
if your board or application does include it, the Watchdog will be left
configured with the 10 second timeout set by the Bootloader and you need to
call `wdt_setup_reboot()` or `wdt_setup_reboot_with_callback()` within the first
10 seconds.
The WDT block supports different clock sources which would be configured by the
board since they depend on whether the optional crystals are populated in your
board. Nevertheless, the millisecond values passed to `wdt_setup_reboot*` are
internally converted to clock ticks using the clock configured at the time the
function was called. `wdt_setup_reboot*()` can be called multiple times to
change the WDT parameters or after changing the WDT clock source, but in any
case `wdt_start()` must be called after it to start the WDT operation.
Once the WDT triggers, it is not possible to avoid the device reboot and calling
wdt_kick() from the WDT callback (if any) or after the callback was called will
not have any effect. Note that, however, if the WDT callback returns before the
configured CONFIG_WDT_WARNING_PERIOD the CPU will continue executing the code
before the WDT interrupt occurred. If this is not desired, an infinite loop at
the end of the WDT callback, after the safety operations have been performed is
advisable.
*/

View File

@ -0,0 +1,332 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_qn908x
* @{
*
* @file
* @brief Implementation specific CPU configuration options
*
* @author iosabi <iosabi@protonmail.com>
*/
#ifndef CPU_CONF_H
#define CPU_CONF_H
#include "cpu_conf_common.h"
#include "vendor/QN908XC.h"
#include "vendor/QN908XC_features.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name ARM Cortex-M specific CPU configuration
* @{
*/
#define CPU_DEFAULT_IRQ_PRIO (1U)
/**
* NUMBER_OF_INT_VECTORS in the QN908XC.h is defined as including the standard
* ARM interrupt vectors and headers, however CPU_IRQ_NUMOF does not include
* the first 15 interrupt values and the stack pointer.
*/
#define CPU_IRQ_NUMOF (NUMBER_OF_INT_VECTORS - 16)
/**
* The flash is aliased at several addresses in the memory range. In particular,
* address 0 can be mapped to RAM or flash, so it is possible to run from
* address 0 from flash, or even turn off the flash altogether and run from RAM
* to save power. This setting uses the ROM_START_ADDR value set in the
* Makefile.
*/
#define CPU_FLASH_BASE (QN908X_ROM_START_ADDR)
/**
* @brief Bit-Band configuration
*/
#define CPU_HAS_BITBAND 1
/** @} */
/**
* @name Clocks configuration
* @{
* @brief External and internal clocks configuration.
*
* The QN908x has an internal 32 MHz RCO for the high frequency clock source and
* a 32 KHz RCO for the low frequency clock source, as well as external
* connections for a crystal oscillator (XTAL) of either 16 MHz or 32 MHz for
* the high frequency clock source and another connection for a 32.768 KHz XTAL
* for the low frequency clock normally used for accurate Bluetooth timing.
* Note that the "32 KHz" clock source is not exactly the same frequency whether
* you use the internal or external one.
*/
/**
* @brief Whether the board has a 32.768 KHz crystal in XTAL32_IN / XTAL32_OUT.
**/
#if !defined(CONFIG_BOARD_HAS_XTAL32K) || DOXYGEN
#define CONFIG_BOARD_HAS_XTAL32K 0
#endif
/**
* @name 32K low frequency clock selector
* @{
*/
#ifdef DOXYGEN
/**
* @brief Enabled when the 32K low frequency uses the external crystal.
**/
#define CONFIG_CPU_CLK_32K_XTAL
/**
* @brief Enabled when the 32K low frequency uses the internal oscillator.
**/
#define CONFIG_CPU_CLK_32K_RCO
#endif /* def DOXYGEN */
/** @} */
/* Default 32K clock selector config. */
#if !defined(CONFIG_CPU_CLK_32K_XTAL) && !defined(CONFIG_CPU_CLK_32K_RCO)
#if CONFIG_BOARD_HAS_XTAL32K
#define CONFIG_CPU_CLK_32K_XTAL 1
#else
#define CONFIG_CPU_CLK_32K_RCO 1
#endif
#endif
/**
* @brief Whether the board has a 16 or 32 MHz crystal in XTAL_IN / XTAL_OUT.
* @{
**/
#ifndef CONFIG_BOARD_HAS_XTAL
#define CONFIG_BOARD_HAS_XTAL 0
#endif
/**
* @name External high frequency "XTAL" crystal frequency
*/
#ifdef DOXYGEN
/**
* @brief Enabled when the external XTAL is a 16 MHz one.
**/
#define CONFIG_CPU_CLK_XTAL_16M
/**
* @brief Enabled when the external XTAL is a 32 MHz one.
**/
#define CONFIG_CPU_CLK_XTAL_32M
#endif /* def DOXYGEN */
/** @} */
/* Default XTAL setting. */
#if CONFIG_BOARD_HAS_XTAL && \
!defined(CONFIG_BOARD_HAS_XTAL_16M) && !defined(CONFIG_BOARD_HAS_XTAL_32M)
#define CONFIG_BOARD_HAS_XTAL_32M 1
#endif
/**
* @brief Internal OSC32M clock input /2 divider enabled
**/
#ifndef CONFIG_CPU_CLK_OSC32M_DIV
#define CONFIG_CPU_CLK_OSC32M_DIV 0
#endif
/**
* @brief External XTAL 32 MHz clock input /2 divider enabled
**/
#ifndef CONFIG_CPU_CLK_XTAL_DIV
#define CONFIG_CPU_CLK_XTAL_DIV 0
#endif
/**
* @name System clock configuration selector
* @{
*/
#ifdef DOXYGEN
/**
* @brief System clock is external crystal source (including divider).
**/
#define CONFIG_CPU_CLK_SYS_XTAL
/**
* @brief System clock is internal 32 MHz oscillator source (including divider).
**/
#define CONFIG_CPU_CLK_SYS_OSC32M
/**
* @brief System clock is the low frequency clock (32 or 32.768 KHz)
**/
#define CONFIG_CPU_CLK_SYS_32K
#endif /* def DOXYGEN */
/** @} */
/* Default system clock configuration selector */
#if !defined(CONFIG_CPU_CLK_SYS_XTAL) && !defined(CONFIG_CPU_CLK_SYS_OSC32M) && \
!defined(CONFIG_CPU_CLK_SYS_32K)
#if CONFIG_BOARD_HAS_XTAL
#define CONFIG_CPU_CLK_SYS_XTAL 1
#else
#define CONFIG_CPU_CLK_SYS_OSC32M 1
#endif
#endif
/**
* @brief AHB clock divider
*
* The AHB clock is derived from the System clock using this divider value,
* between 1 and 8192, and serves as a clock source for ARM core, FSP, SCT,
* Quad-SPI, Flexcomm (UART, SPI, I2C), GPIO, BLE_AHB and DMA.
* Note: When BLE is enabled, the AHB clock must be at least the BLE clock
* (either 8 or 16 MHz) limiting the range of allowed values for this
* divider so that the AHB clock is 8, 16 or 32 MHz.
**/
#ifndef CONFIG_CPU_CLK_AHB_DIV
#define CONFIG_CPU_CLK_AHB_DIV 1u
#endif
/**
* @brief APB clock divider
*
* The APB clock is derived from the AHB clock using this divide value,
* between 1 and 16, and serves as the clock source for several
* peripherals, such as the RTC, ADC, DAC, Capacitive Sense (CS) and
* optionally the WDT.
**/
#ifndef CONFIG_CPU_CLK_APB_DIV
#define CONFIG_CPU_CLK_APB_DIV 1u
#endif
/** @} */
/**
* @name Code Read Protection
* @{
* @brief Image "Code Read Protection" field definitions.
*
* The Code Read Protection (CRP) is a 32-bit field stored in one of the
* reserved fields in the Cortex-M interrupt vector and therefore part of the
* image. It allows to enable or disable access to the flash from the In-System
* Programming (ISP) interface to read, erase or write flash pages, as well as
* external SWD access for debugging or programming the flash. Not all the CRP
* values are valid and an invalid value may render the flash inaccessible and
* effectively brick the device.
*
* To select the access level define the @ref QN908X_CRP macro from the global
* compile options, otherwise the default value in this module will be used
* (allowing everything). The value of the uint32_t CRP field in the Image
* vector table should be the "or" of the following QN908X_CRP_* macros. Every
* field must be either enabled or disabled, otherwise it would result in an
* invalid CRP value.
*/
/**
* @brief Number of pages to protect (0 to 255).
*
* This defines the number of pages to protect starting from 0. A value of 0
* in this macro means that no page is protected. The maximum number allowed to
* be passed to this macro is 255, however there are 256 pages in the flash. The
* last page is protected if any other page is protected.
*
* Protected pages can't be erased or written to by the ISP.
*/
#define QN908X_CRP_PROTECT_PAGES(X) (255 - (X))
/**
* @brief Mass erase from ISP allowed.
*/
#define QN908X_CRP_MASS_ERASE_ALLOW (0x800)
/**
* @brief Mass erase from ISP not allowed.
*/
#define QN908X_CRP_MASS_ERASE_DISALLOW (0x400)
/**
* @brief Page erase/write from ISP (for unprotected pages) allowed.
*/
#define QN908X_CRP_PAGE_ERASE_WRITE_ALLOW (0x2000)
/**
* @brief Page erase/write from ISP (for unprotected pages) not allowed.
*/
#define QN908X_CRP_PAGE_ERASE_WRITE_DISALLOW (0x1000)
/**
* @brief Flash read (for unprotected pages) from ISP allowed or not.
*/
#define QN908X_CRP_FLASH_READ_ALLOW (0x8000)
/**
* @brief Flash read (for unprotected pages) from ISP not allowed.
*/
#define QN908X_CRP_FLASH_READ_DISALLOW (0x4000)
/**
* @brief ISP entry is allowed (via CHIP_MODE pin).
*/
#define QN908X_CRP_ISP_ENTRY_ALLOW (0x20000)
/**
* @brief ISP entry via CHIP_MODE pin is not allowed.
*/
#define QN908X_CRP_ISP_ENTRY_DISALLOW (0x10000)
/**
* @brief External access is allowed (including SWD interface).
*/
#define QN908X_CRP_EXTERNAL_ACCESS_ALLOW (0x80000)
/**
* @brief External access is not allowed (including SWD interface).
*/
#define QN908X_CRP_EXTERNAL_ACCESS_DISALLOW (0x40000)
/** @} */
/**
* @brief Default "Code Read Protection" allows everything.
*/
#ifndef QN908X_CRP
#define QN908X_CRP \
(QN908X_CRP_PROTECT_PAGES(0) \
| QN908X_CRP_MASS_ERASE_ALLOW \
| QN908X_CRP_PAGE_ERASE_WRITE_ALLOW \
| QN908X_CRP_FLASH_READ_ALLOW \
| QN908X_CRP_ISP_ENTRY_ALLOW \
| QN908X_CRP_EXTERNAL_ACCESS_ALLOW)
#endif /* QN908X_CRP */
/**
* @brief The "Code Read Protection" is stored at the offset 0x20.
*
* To modify the CRP field define the macro @ref QN908X_CRP.
*/
#define CORTEXM_VECTOR_RESERVED_0X20 QN908X_CRP
/* Safety checks that the QN908X_CRP value is valid. */
#if !(QN908X_CRP & QN908X_CRP_MASS_ERASE_ALLOW) == \
!(QN908X_CRP & QN908X_CRP_MASS_ERASE_DISALLOW)
#error "Must select exactly one of QN908X_CRP_MASS_ERASE_* in the QN908X_CRP"
#endif
#if !(QN908X_CRP & QN908X_CRP_PAGE_ERASE_WRITE_ALLOW) == \
!(QN908X_CRP & QN908X_CRP_PAGE_ERASE_WRITE_DISALLOW)
#error \
"Must select exactly one of QN908X_CRP_PAGE_ERASE_WRITE_* in the QN908X_CRP"
#endif
#if !(QN908X_CRP & QN908X_CRP_FLASH_READ_ALLOW) == \
!(QN908X_CRP & QN908X_CRP_FLASH_READ_DISALLOW)
#error "Must select exactly one of QN908X_CRP_FLASH_READ_* in the QN908X_CRP"
#endif
#if !(QN908X_CRP & QN908X_CRP_ISP_ENTRY_ALLOW) == \
!(QN908X_CRP & QN908X_CRP_ISP_ENTRY_DISALLOW)
#error "Must select exactly one of QN908X_CRP_ISP_ENTRY_* in the QN908X_CRP"
#endif
#if !(QN908X_CRP & QN908X_CRP_EXTERNAL_ACCESS_ALLOW) == \
!(QN908X_CRP & QN908X_CRP_EXTERNAL_ACCESS_DISALLOW)
#error \
"Must select exactly one of QN908X_CRP_EXTERNAL_ACCESS_* in the QN908X_CRP"
#endif
#ifdef __cplusplus
}
#endif
#endif /* CPU_CONF_H */
/** @} */

View File

@ -0,0 +1,71 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_qn908x
*
* @{
*
* @file
* @brief Common Pin MUX functions.
*
* The pins in this CPU are multiplexed to several different function. This
* module allows to configure the pin multiplexer (MUX) from peripheral drivers.
*
* @author iosabi <iosabi@protonmail.com>
*/
#ifndef GPIO_MUX_H
#define GPIO_MUX_H
#include <stdint.h>
#include "periph_cpu.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Obtain the GPIO_BASE address from a GPIO_PIN(x, y) value.
*/
#define GPIO_T_ADDR_BASE(pin) (GPIOA_BASE + ((pin) & 0xf000u))
/**
* @brief Obtain the GPIO_BASE GPIO_Type* pointer from a GPIO_PIN(x, y) value.
*/
#define GPIO_T_ADDR(pin) ((GPIO_Type *)(GPIO_T_ADDR_BASE(pin)))
/**
* @brief Obtain the "x" port number (0 based) from a GPIO_PIN(x, y) value.
*
* This macro needs to be kept in sync with the definition of GPIO_PIN.
*/
#define GPIO_T_PORT(pin) ((gpio_t)(pin) >> 12u)
/**
* @brief Obtain the pin number "y" from a GPIO_PIN(x, y) value.
*/
#define GPIO_T_PIN(pin) ((pin) & 0x00ffu)
#if defined(GPIOB_BASE) && (GPIO_T_ADDR_BASE(GPIO_PIN(1, 1)) != GPIOB_BASE)
#error "GPIO_T_ADDR(GPIO_PIN(1, x)) must be the GPIOB address"
#endif
/**
* @brief Configure the pin mux to the given function.
*
* The meaning of the function value will depend on the gpio pin.
*/
void gpio_init_mux(gpio_t pin, uint32_t func);
#ifdef __cplusplus
}
#endif
#endif /* GPIO_MUX_H */
/** @} */

View File

@ -0,0 +1,148 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_qn908x
* @{
*
* @file
* @brief CPU specific definitions for internal peripheral handling
*
* @author iosabi <iosabi@protonmail.com>
*/
#ifndef PERIPH_CPU_H
#define PERIPH_CPU_H
#include <stdint.h>
#include <stdbool.h>
#include "cpu.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name CPU specific gpio_t type definition
* @{
*/
#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.
*
* This generates the GPIO port base address with a mask of the GPIO_PIN value
* to avoid a memory access. The value 12 here is selected as an optimization
* to be able to derive the GPIO port address with a simple mask of the GPIO_PIN
* value.
*/
#define GPIO_PIN(x, y) (((x) << 12u) | (y))
/* QN908x has a unique default Bluetooth address in the Flash Information Page
* descriptor. This value is set in the factory and cannot be modified by
* users. However, the actual Bluetooth address used by the stack may be
* different, this is just the default. */
/**
* @brief Starting offset of CPU_ID
*/
#define CPUID_ADDR (FSL_FEATURE_FLASH_ADDR_OF_VENDOR_BD_ADDR)
/**
* @brief Length of the CPU_ID in octets
*/
#define CPUID_LEN (6U)
/**
* @brief Watchdog clock can be stopped independently of other clocks.
*/
#define WDT_HAS_STOP (1)
/**
* @name WDT upper and lower bound times in ms
* @{
*/
/** The WDT clock can run up to 16MHz (via CLK_APB) and the WDT counter is
* 32-bit so the maximum value in ms we can wait is ((1 << 32) - 1) / 16000.
* TODO: A much larger limit (~1.5 days) can be set if the WDT runs from the
* 32 KHz clock. However, this is likely decided by the board and depends on the
* clocks installed on the board. Figure out a way to configure this limit based
* on the clock used.
*/
#define NWDT_TIME_LOWER_LIMIT (0)
#define NWDT_TIME_UPPER_LIMIT (268435U)
#define WWDT_TIME_LOWER_LIMIT (0)
#define WWDT_TIME_UPPER_LIMIT (268435U)
/** @} */
/**
* @brief Generate GPIO mode bitfields
*
* The GPIO_MODE has the following structure:
* - bit 0: open-drain: 1 for enabled (open-drain mode) and 0 for disabled.
* - bit 1: output-enabled: 1 output mode, 0 input mode.
* - bit 4-5: pull_mode: 0 for hi-z (no pull-up or down), 1 for pull-down and 2
* for pull-up. These correspond to the IOCON_MODE macros.
*/
#define GPIO_MODE(open_drain, out_enabled, pull_mode) \
((open_drain) | ((out_enabled) << 1) | ((pull_mode) << 4))
#ifndef DOXYGEN
/**
* @name GPIO pin modes
* @{
*/
#define HAVE_GPIO_MODE_T
typedef enum {
GPIO_IN = GPIO_MODE(0, 0, 0), /**< IN */
GPIO_IN_PD = GPIO_MODE(0, 0, 1), /**< IN with pull-down */
GPIO_IN_PU = GPIO_MODE(0, 0, 2), /**< IN with pull-up */
GPIO_OUT = GPIO_MODE(0, 1, 0), /**< OUT (push-pull) */
GPIO_OD = GPIO_MODE(1, 1, 0), /**< OD */
GPIO_OD_PU = GPIO_MODE(1, 1, 2), /**< OD with pull-up */
} gpio_mode_t;
/** @} */
#endif /* ndef DOXYGEN */
#ifndef DOXYGEN
/**
* @name GPIO flank configuration values
* @{
*/
#define HAVE_GPIO_FLANK_T
typedef enum {
GPIO_LOW = 0, /**< emit interrupt when the value is low */
GPIO_HIGH = 1, /**< emit interrupt when the value is high */
GPIO_RISING = 2, /**< emit interrupt on rising flank */
GPIO_FALLING = 3, /**< emit interrupt on falling flank */
} gpio_flank_t;
/** @} */
#endif /* ndef DOXYGEN */
/**
* @brief Available ports on the QN908x.
*/
enum {
PORT_A = 0, /**< port A */
PORT_B = 1, /**< port B */
GPIO_PORTS_NUMOF /**< overall number of available ports */
};
#ifdef __cplusplus
}
#endif
#endif /* PERIPH_CPU_H */
/** @} */

View File

@ -0,0 +1,118 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_qn908x
* @{
*
* @file
* @brief Interrupt service routine declarations NXP QN908x MCUs
*
* @author iosabi <iosabi@protonmail.com>
*
* @}
*/
#ifndef VECTORS_QN908X_H
#define VECTORS_QN908X_H
#include <stdint.h>
#include "vectors_cortexm.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Dummy handler
*/
void dummy_handler(void);
/* Device specific interrupt vectors */
void isr_ext_gpio_wakeup(void); /**< Ext GPIO wakeup */
void isr_osc(void); /**< BLE wakeup */
void isr_acmp0(void); /**< Analog comparator0 */
void isr_acmp1(void); /**< Analog comparator1 */
void isr_rtc_sec(void); /**< RTC second */
void isr_rtc_fr(void); /**< RTC free running */
void isr_cs_wakeup(void); /**< Capacitive sense wakeup */
void isr_cs(void); /**< Capacitive sense */
void isr_gpioa(void); /**< GPIO group A */
void isr_gpiob(void); /**< GPIO group B */
void isr_dma0(void); /**< DMA controller */
void isr_pin_int0(void); /**< pin or pattern match engine slice 0 */
void isr_pin_int1(void); /**< pin or pattern match engine slice 1 */
void isr_pin_int2(void); /**< pin or pattern match engine slice 2 */
void isr_pin_int3(void); /**< pin or pattern match engine slice 3 */
void isr_osc_int_low(void); /**< Inverse of OSC */
void isr_usb0(void); /**< USB device */
void isr_flexcomm0(void); /**< Flexcomm Interface 0 (USART) */
void isr_flexcomm1(void); /**< Flexcomm Interface 1 (USART, I2C) */
void isr_flexcomm2(void); /**< Flexcomm Interface 2 (SPI, I2C) */
void isr_flexcomm3(void); /**< Flexcomm Interface 3 (SPI) */
void isr_ble(void); /**< BLE interrupts */
void isr_fsp(void); /**< FSP */
void isr_qdec0(void); /**< QDEC0 */
void isr_qdec1(void); /**< QDEC1 */
void isr_ctimer0(void); /**< Standard counter/timer CTIMER0 */
void isr_ctimer1(void); /**< Standard counter/timer CTIMER1 */
void isr_ctimer2(void); /**< Standard counter/timer CTIMER2 */
void isr_ctimer3(void); /**< Standard counter/timer CTIMER3 */
void isr_wdt(void); /**< Watch dog timer */
void isr_adc(void); /**< ADC */
void isr_dac(void); /**< DAC */
void isr_xtal_ready(void); /**< High frequency crystal ready */
void isr_flash(void); /**< Flash */
void isr_spifi0(void); /**< SPI flash interface */
void isr_sct0(void); /**< SCTimer/PWM */
void isr_rng(void); /**< Random number generator */
void isr_calib(void); /**< Calibration */
void isr_ble_tx(void); /**< ble tx flag */
void isr_ble_rx(void); /**< ble rx flag */
void isr_ble_freq_hop(void); /**< ble frequency hop */
void isr_bod(void); /**< Brown out detect */
/**
* @{
* @brief In-System Programming configuration field
*
* After the interrupt vectors, at address 0x110 there's a ISP
* (In-System Programming) configuration field. Define isp_configuration to
* any | combination of the following `QN908X_ISP_*` values to change the ISP
* allowed options.
*/
__attribute__((section(".vectors.100")))
extern const uint32_t isp_configuration;
/**
* @brief SMART write enabled to ISP
*/
#define QN908X_ISP_SMART_EN 0x80
/**
* @brief SMART USB disabled to ISP
*/
#define QN908X_ISP_USB_DIS 0x20
/**
* @brief SMART UART disabled to ISP
*/
#define QN908X_ISP_UART_DIS 0x08
/**
* @brief SMART SPI disabled to ISP
*/
#define QN908X_ISP_SPI_DIS 0x02
/** @} */
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* VECTORS_QN908X_H */

52
cpu/qn908x/include/vendor/fsl_common.h vendored Normal file
View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup cpu_qn908x
* @{
*
* @file
* @brief Wrapper header for SDK drivers.
*
* Vendor SDK drivers include "fsl_common.h" with many definitions and extra
* dependencies on more headers that are not used in the RIOT-OS port. This
* header is a wrapper intended to keep the SDK headers unchanged and provide
* the essential definitions needed by other SDK modules.
*
* @author iosabi <iosabi@protonmail.com>
*/
#ifndef FSL_COMMON_H
#define FSL_COMMON_H
#include <stdint.h>
#include <stdbool.h>
#include <assert.h>
#include "irq.h"
#include "vectors_cortexm.h"
#include "QN908XC.h"
#include "QN908XC_features.h"
#ifdef __cplusplus
extern "C" {
#endif
/* RIOT-OS equivalent functions. */
#define DisableGlobalIRQ irq_disable
#define EnableGlobalIRQ irq_restore
#define __Vectors cortex_vector_base
#ifdef __cplusplus
}
#endif
#endif /* FSL_COMMON_H */
/** @} */

129
cpu/qn908x/isr_qn908x.c Normal file
View File

@ -0,0 +1,129 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_qn908x
* @{
*
* @file
* @brief Default interrupt service routine definitions for NXP QN908x
*
* These weak default definitions act as a fallback definition if no driver
* defines a ISR for the specific interrupt.
*
* @author iosabi <iosabi@protonmail.com>
*
* @}
*/
#include "cpu.h"
#include "vectors_cortexm.h"
#include "vectors_qn908x.h"
/* These are defined in vectors_cortexm.c. */
extern void reset_handler_default(void);
extern uint32_t _estack; /* Exception stack pointer. */
/**
* @brief Jump to the reset_handle_default handler with the exception stack.
*/
__attribute__((noreturn)) static inline void cpu_restart(void)
{
/* Reset the stack pointer to the beginning again and jump to the reset
* handler at the expected address.
*/
__asm volatile ("msr msp, %[estack]\n"
"mov pc, %[entry]\n"
:
: [ estack ] "r" (&_estack),
[ entry ] "r" (reset_handler_default)
: "memory");
/* This function doesn't return anyway. */
while (1) {}
}
/**
* @brief Remap the flash to address 0 on start.
*
* The bootloader will jump to the flash at address 0x21000000 which is aliased
* to the flash on this CPU. However, our program is linked to run as if the
* flash is mapped at address 0 which is the common case. The range starting at
* address 0 can be mapped to flash, RAM or ROM via the SYS_MODE_CTRL register,
* but on reset the default value (0x0) means that it is mapped to ROM.
* We need to remap the flash and change the program counter to be running from
* the right address range (0).
*/
void pre_startup(void)
{
register unsigned int pc;
/* Disable interrupts */
__disable_irq();
/* Check whether we are running from the 0x21000000 range. If that's the
* case we need to remap the flash to the address 0 in SYS_MODE_CTRL and
* jump back to the reset_handler_default so everything starts as running
* from the address 0x0 instead.
*/
__asm volatile ("mov %0, pc" : "=r" (pc));
if ((pc & 0x21000000) == 0x21000000) {
SYSCON->SYS_MODE_CTRL |= 1;
cpu_restart();
}
}
/* QN908x interrupt service routines */
WEAK_DEFAULT void isr_ext_gpio_wakeup(void);
WEAK_DEFAULT void isr_osc(void);
WEAK_DEFAULT void isr_acmp0(void);
WEAK_DEFAULT void isr_acmp1(void);
WEAK_DEFAULT void isr_rtc_sec(void);
WEAK_DEFAULT void isr_rtc_fr(void);
WEAK_DEFAULT void isr_cs_wakeup(void);
WEAK_DEFAULT void isr_cs(void);
WEAK_DEFAULT void isr_gpioa(void);
WEAK_DEFAULT void isr_gpiob(void);
WEAK_DEFAULT void isr_dma0(void);
WEAK_DEFAULT void isr_pin_int0(void);
WEAK_DEFAULT void isr_pin_int1(void);
WEAK_DEFAULT void isr_pin_int2(void);
WEAK_DEFAULT void isr_pin_int3(void);
WEAK_DEFAULT void isr_osc_int_low(void);
WEAK_DEFAULT void isr_usb0(void);
WEAK_DEFAULT void isr_flexcomm0(void);
WEAK_DEFAULT void isr_flexcomm1(void);
WEAK_DEFAULT void isr_flexcomm2(void);
WEAK_DEFAULT void isr_flexcomm3(void);
WEAK_DEFAULT void isr_ble(void);
WEAK_DEFAULT void isr_fsp(void);
WEAK_DEFAULT void isr_qdec0(void);
WEAK_DEFAULT void isr_qdec1(void);
WEAK_DEFAULT void isr_ctimer0(void);
WEAK_DEFAULT void isr_ctimer1(void);
WEAK_DEFAULT void isr_ctimer2(void);
WEAK_DEFAULT void isr_ctimer3(void);
WEAK_DEFAULT void isr_wdt(void);
WEAK_DEFAULT void isr_adc(void);
WEAK_DEFAULT void isr_dac(void);
WEAK_DEFAULT void isr_xtal_ready(void);
WEAK_DEFAULT void isr_flash(void);
WEAK_DEFAULT void isr_spifi0(void);
WEAK_DEFAULT void isr_sct0(void);
WEAK_DEFAULT void isr_rng(void);
WEAK_DEFAULT void isr_calib(void);
WEAK_DEFAULT void isr_ble_tx(void);
WEAK_DEFAULT void isr_ble_rx(void);
WEAK_DEFAULT void isr_ble_freq_hop(void);
WEAK_DEFAULT void isr_bod(void);
void dummy_handler(void)
{
dummy_handler_default();
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @addtogroup cpu_qn908x
* @{
*
* @file
* @brief Sections definitions for the NXP QN908x MCUs
*
* @author iosabi <iosabi@protonmail.com>
*
* This linker script organizes the flash headers to generate a "Legacy" image
* as described in the "Boot Process" section of the QN908x user manual. A
* legacy image contains an "Image vector table" which is the standard ARM
* vector table with some special values in the reserved fields. In particular,
* this needs a to have a valid checksum at address 0x1c to be considered a
* valid image by the bootloader, which is not set by the build process.
*
* @}
*/
OUTPUT_FORMAT ("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
_vectors_length = 0x114;
/* The Flash lock and protect descriptor occupies the last flash page of 0x800
* bytes. See "Flash lock and protection" protection section. */
_flash_lock_length = 0x800;
INCLUDE cortexm_rom_offset.ld
MEMORY
{
/* Note: What we call "rom" here is the flash region for consistency with
* the rest of the RIOT build system naming. There is a 256 kB ROM memory in
* the QN908x holding the bootloader and Bluetooth stack that can't be
* modified by the user.
*/
vectors : ORIGIN = _rom_start_addr + _rom_offset, LENGTH = _vectors_length
rom (rx) : ORIGIN = _rom_start_addr + _rom_offset + _vectors_length, LENGTH = _fw_rom_length - _vectors_length - _flash_lock_length
ram (!rx) : ORIGIN = _ram_start_addr, LENGTH = _ram_length
}
SECTIONS
{
/* "Image vector table" 0x000-0x114, defined in the "Boot process" section,
* must have exactly this size, otherwise we configured something wrong.
*/
.vectors :
{
PROVIDE(_isr_vectors = .);
KEEP(*(SORT(.vector*)))
} > vectors
ASSERT (SIZEOF(.vectors) == _vectors_length,
"Image vector table size mismatch.")
ASSERT (ADDR(.vectors) == _rom_start_addr + _rom_offset,
"Image vector table must start at the beginning of the flash")
ASSERT (LOADADDR(.vectors) == _rom_start_addr + _rom_offset,
"Image vector table must start at the beginning of the flash")
}
INCLUDE cortexm_base.ld

View File

@ -0,0 +1 @@
include $(RIOTMAKE)/periph.mk

276
cpu/qn908x/periph/gpio.c Normal file
View File

@ -0,0 +1,276 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_qn908x
* @ingroup drivers_periph_gpio
*
* @{
*
* @file
* @brief Low-level GPIO driver implementation
*
* @author iosabi <iosabi@protonmail.com>
*
* @}
*/
#include <stddef.h>
#include <stdint.h>
#include "cpu.h"
#include "bitarithm.h"
#include "periph/gpio.h"
#include "vectors_qn908x.h"
#include "gpio_mux.h"
#include "vendor/drivers/fsl_clock.h"
#include "vendor/drivers/fsl_iocon.h"
/* The pull-up / pull-down / high-z mode in the gpio_mode_t enum matches the
* values in the IOCON_PinMuxSet() function.
*/
#if (GPIO_MODE(0, 0, 0) & 0x30) != IOCON_MODE_HIGHZ
#error "GPIO_MODE(x, y, 0) must be High-Z mode"
#endif
#if (GPIO_MODE(0, 0, 1) & 0x30) != IOCON_MODE_PULLDOWN
#error "GPIO_MODE(x, y, 0) must be pull-down mode"
#endif
#if (GPIO_MODE(0, 0, 2) & 0x30) != IOCON_MODE_PULLUP
#error "GPIO_MODE(x, y, 0) must be pull-up mode"
#endif
/* Bit mask indicating if a GPIO is set to open_drain. */
static uint32_t gpio_open_drain[GPIO_PORTS_NUMOF] = {};
int gpio_init(gpio_t pin, gpio_mode_t mode)
{
GPIO_Type *const base = GPIO_T_ADDR(pin);
const uint32_t mask = 1u << GPIO_T_PIN(pin);
/* We need to enable the GPIO clock before we set any register in the GPIO
* blocks. */
CLOCK_EnableClock(kCLOCK_Gpio);
/* Disable the interrupts just in case this was already configured as an
* interrupt pin. Note: this only disables the pin(s) that you write a 1
* to. */
base->INTENCLR = mask;
/* pin_mode is the "or" of the three parts: function, mode and drive
* strength. The mode is just the bits 4 and 5 of the gpio_mode_t and
* corresponds to the IOCON_MODE_* values */
uint32_t pin_mode =
IOCON_FUNC0 | /* FUNC0 is digital GPIO on all pins. */
(mode & 0x30) | IOCON_DRIVE_HIGH;
gpio_init_mux(pin, pin_mode);
if (mode & 2) {
/* output mode */
/* Configure the open-drain variable for allowing setting the values
* later. */
if (mode & 1) {
/* open-drain enabled. */
gpio_open_drain[GPIO_T_PORT(pin)] |= mask;
/* Starts with the pin set to "high" (open) in open-drain mode.
* The DATAOUT value doesn't do anything if the output is not
* enabled but we keep track of the current value in DATAOUT
* anyway to allow gpio_toggle. */
base->OUTENCLR = mask;
base->DATAOUT |= mask;
}
else {
gpio_open_drain[GPIO_T_PORT(pin)] &= ~mask;
/* Starts with the pin set to low on push-pull mode. */
base->DATAOUT &= ~mask;
base->OUTENSET = mask;
}
}
else {
/* input mode */
gpio_open_drain[GPIO_T_PORT(pin)] &= ~mask;
base->OUTENCLR = mask;
}
return 0;
}
#ifdef MODULE_PERIPH_GPIO_IRQ
typedef struct {
gpio_cb_t cb;
void *arg;
} gpio_isr_cb_state_t;
/**
* @brief The number of GPIO pins per port.
*/
#define PINS_PER_PORT (32)
/**
* @brief The total number of GPIO pins in the chip.
*/
#define TOTAL_GPIO_PINS (35)
static gpio_isr_cb_state_t gpio_isr_state[TOTAL_GPIO_PINS] = {};
int gpio_init_int(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank,
gpio_cb_t cb, void *arg)
{
uint8_t gpio_num = GPIO_T_PORT(pin) * PINS_PER_PORT + GPIO_T_PIN(pin);
if (gpio_num >= TOTAL_GPIO_PINS) {
return -1;
}
gpio_isr_state[gpio_num].cb = cb;
gpio_isr_state[gpio_num].arg = arg;
if (gpio_init(pin, mode) != 0) {
return -1;
}
GPIO_Type *const base = GPIO_T_ADDR(pin);
const uint32_t mask = 1u << GPIO_T_PIN(pin);
switch (flank) {
case GPIO_LOW:
base->INTTYPECLR = mask; /* CLR = level */
base->INTPOLCLR = mask; /* CLR = low */
break;
case GPIO_HIGH:
base->INTTYPECLR = mask; /* CLR = level */
base->INTPOLSET = mask; /* SET = high */
break;
case GPIO_FALLING:
base->INTTYPESET = mask; /* SET = edge */
base->INTPOLCLR = mask; /* CLR = falling */
break;
case GPIO_RISING:
base->INTTYPESET = mask; /* SET = edge */
base->INTPOLSET = mask; /* SET = rising */
break;
}
gpio_irq_enable(pin);
return 0;
}
void gpio_irq_enable(gpio_t pin)
{
GPIO_T_ADDR(pin)->INTENSET = 1u << GPIO_T_PIN(pin);
}
void gpio_irq_disable(gpio_t pin)
{
GPIO_T_ADDR(pin)->INTENCLR = 1u << GPIO_T_PIN(pin);
}
#endif /* defined(MODULE_PERIPH_GPIO_IRQ) */
int gpio_read(gpio_t pin)
{
return ((GPIO_T_ADDR(pin)->DATA) >> GPIO_T_PIN(pin)) & 1u;
}
void gpio_set(gpio_t pin)
{
GPIO_Type *const base = GPIO_T_ADDR(pin);
const uint32_t mask = 1u << GPIO_T_PIN(pin);
/* out_clr has only the pin bit set if this is an open-drain pin, which
* means we need to disable the output. This needs to happen before changing
* DATAOUT. */
const uint32_t out_clr = mask & gpio_open_drain[GPIO_T_PORT(pin)];
base->OUTENCLR = out_clr;
base->DATAOUT |= mask;
}
void gpio_clear(gpio_t pin)
{
GPIO_Type *const base = GPIO_T_ADDR(pin);
const uint32_t mask = 1u << GPIO_T_PIN(pin);
base->DATAOUT &= ~mask;
/* out_clr has only the pin bit set if this is an open-drain pin, which
* means we need to enable the output. This needs to happen after changing
* DATAOUT. */
const uint32_t out_clr = mask & gpio_open_drain[GPIO_T_PORT(pin)];
base->OUTENSET = out_clr;
}
void gpio_toggle(gpio_t pin)
{
GPIO_Type *const base = GPIO_T_ADDR(pin);
const uint32_t mask = 1u << GPIO_T_PIN(pin);
const uint32_t out_clr = mask & gpio_open_drain[GPIO_T_PORT(pin)];
const uint32_t dataout = base->DATAOUT;
/* The output is disabled if the pin is an open-drain pin and DATAOUT is
* not set for that pin. This avoids having if conditions. */
base->OUTENCLR = out_clr & ~dataout;
base->DATAOUT ^= mask;
/* The output is disabled if the pin is an open-drain and DATAOUT at the
* beginning of the function was set. */
base->OUTENSET = out_clr & dataout;
}
void gpio_write(gpio_t pin, int value)
{
if (value) {
gpio_set(pin);
}
else {
gpio_clear(pin);
}
}
#ifdef MODULE_PERIPH_GPIO_IRQ
static inline void irq_handler(GPIO_Type *base, uint32_t port_num)
{
uint32_t status = base->INTSTATUS;
while (status) {
/* Clear all the flags immediately and process them in order. This gives
* a chance to execute every pin's interrupt handler even if another pin
* is always on.
* Note: to *clear* the interrupt flag you write a 1 to that bit.
*/
base->INTSTATUS = status;
while (status) {
uint8_t pin;
status = bitarithm_test_and_clear(status, &pin);
uint32_t gpio_num = port_num * PINS_PER_PORT + pin;
gpio_cb_t cb = gpio_isr_state[gpio_num].cb;
if (cb) {
cb(gpio_isr_state[gpio_num].arg);
}
}
status = base->INTSTATUS;
}
}
#ifdef GPIOA_BASE
void isr_gpioa(void)
{
irq_handler(GPIOA, 0);
cortexm_isr_end();
}
#endif /* GPIOA_BASE */
#ifdef GPIOB_BASE
void isr_gpiob(void)
{
irq_handler(GPIOB, 1);
cortexm_isr_end();
}
#endif /* GPIOB_BASE */
#endif /* MODULE_PERIPH_GPIO_IRQ */

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_qn908x
*
* @{
*
* @file
* @brief Common Pin MUX functions.
*
* @author iosabi <iosabi@protonmail.com>
*
* @}
*/
#include "gpio_mux.h"
#include "vendor/drivers/fsl_iocon.h"
void gpio_init_mux(gpio_t pin, uint32_t func)
{
if (pin == GPIO_UNDEF) {
return;
}
IOCON_PinMuxSet(IOCON, GPIO_T_PORT(pin), GPIO_T_PIN(pin), func);
}

191
cpu/qn908x/periph/wdt.c Normal file
View File

@ -0,0 +1,191 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_qn908x
* @ingroup cpu_qn908x_wdt
*
* @{
*
* @file
* @brief Low-level WDOG driver implementation
*
* @author iosabi <iosabi@protonmail.com>
*
* @}
*/
#include "periph/wdt.h"
#include <stdint.h>
#include <stdbool.h>
#include "vendor/drivers/fsl_clock.h"
#include "debug.h"
/* The number of cycles to refresh the WDT with when kicked. */
static uint32_t wdt_load_value = 0xffffffff;
/* The maximum value the WDT counter could have when kicked. The WDT counter
* always decrements starting from wdt_load_value, so when used in WINDOW mode
* a counter value larger than this means that WDT was kicked before the lower
* bound of the window.
*/
static uint32_t wdt_load_window_value = 0xffffffff;
/* The value that will be loaded in the WDT counter after the first interrupt
* triggers. The WDT doesn't not reset the device automatically once the WDT
* counter reaches 0, instead it first triggers an interrupt and restarts the
* count again. This value will be loaded by the ISR after it triggers. A value
* of 0 means to reset immediately after the ISR triggers, which is used if no
* callback is provided. */
static uint32_t wdt_load_after_isr = 0;
#ifdef MODULE_PERIPH_WDT_CB
static wdt_cb_t wdt_cb;
static void *wdt_arg;
static bool wdt_kick_disabled = false;
#endif /* MODULE_PERIPH_WDT_CB */
/**
* Before making any change to the WDT it is required to "unlock" it by writing
* this value to the LOCK register. Call @ref _wdt_lock() to lock it again.
*/
static void _wdt_unlock(void)
{
WDT->LOCK = 0x1acce551;
}
/**
* @brief Lock the WDT block to prevent accidental changes.
*/
static void _wdt_lock(void)
{
WDT->LOCK = 0x10c1ced; /* Any other value is as good. */
}
void wdt_start(void)
{
CLOCK_EnableClock(kCLOCK_Wdt);
}
void wdt_stop(void)
{
/* This only stops the clock of the watchdog and therefore the counter
* stops, but leaves it otherwise configured. */
CLOCK_DisableClock(kCLOCK_Wdt);
}
void wdt_kick(void)
{
#ifdef MODULE_PERIPH_WDT_CB
if (wdt_kick_disabled) {
return;
}
#endif /* MODULE_PERIPH_WDT_CB */
if (WDT->VALUE > wdt_load_window_value) {
DEBUG("wdt_kick() called before the minimum window time.");
/* In this condition we simulate the WDT triggering immediately by
* setting its LOAD value to 0. This will cause the ISR (and the
* callback if there is one) if it wasn't called yet, which will update
* the LOAD value with the time derived from CONFIG_WDT_WARNING_PERIOD
* if needed and reset the device afterwards. */
_wdt_unlock();
WDT->LOAD = 0;
_wdt_lock();
while (true) {}
}
_wdt_unlock();
WDT->LOAD = wdt_load_value;
_wdt_lock();
}
static void wdt_setup(uint32_t min_time, uint32_t max_time, uint32_t isr_time)
{
/* Check reset time limit */
assert((max_time > NWDT_TIME_LOWER_LIMIT)
&& (max_time < NWDT_TIME_UPPER_LIMIT));
assert(min_time <= max_time);
/* The clock is stopped after setup and calling wdt_start() is required. */
wdt_stop();
const uint32_t tick_per_ms = CLOCK_GetFreq(kCLOCK_WdtClk) / 1000;
wdt_load_value = tick_per_ms * max_time;
wdt_load_window_value = tick_per_ms * (max_time - min_time);
wdt_load_after_isr = tick_per_ms * isr_time;
/* The ISR is always called as it is needed to trigger the reset after the
* appropriate delay even in the no callback case. */
_wdt_unlock();
WDT->LOAD = wdt_load_value;
WDT->CTRL = WDT_CTRL_RESEN_MASK | WDT_CTRL_INTEN_MASK;
_wdt_lock();
NVIC_EnableIRQ(WDT_IRQn);
}
void wdt_setup_reboot(uint32_t min_time, uint32_t max_time)
{
#ifdef MODULE_PERIPH_WDT_CB
wdt_cb = NULL;
wdt_arg = NULL;
#endif /* MODULE_PERIPH_WDT_CB */
wdt_setup(min_time, max_time, 0);
}
#ifdef MODULE_PERIPH_WDT_CB
void wdt_setup_reboot_with_callback(uint32_t min_time, uint32_t max_time,
wdt_cb_t cb, void *arg)
{
wdt_cb = cb;
wdt_arg = arg;
assert(max_time >= CONFIG_WDT_WARNING_PERIOD);
/* We don't support having a min_time that falls within the
* CONFIG_WDT_WARNING_PERIOD since that would mean that you can't call
* wdt_kick() until some time after the callback is called which is pretty
* useless considering that the purpose of the callback is to perform
* "specific safety operations of data logging before the actual reboot."
* before the reboot happens. After the callback is called the reboot is
* inevitable and calling wdt_kick() has no effect. This code moves the
* min_time back to at least the point where the callback is called which is
* a similar behavior. */
if (max_time - CONFIG_WDT_WARNING_PERIOD < min_time) {
min_time = max_time - CONFIG_WDT_WARNING_PERIOD;
}
wdt_setup(min_time, max_time - CONFIG_WDT_WARNING_PERIOD,
CONFIG_WDT_WARNING_PERIOD);
}
#endif /* MODULE_PERIPH_WDT_CB */
void isr_wdt(void)
{
DEBUG("[wdt] isr_wdt with LOAD=%" PRIu32 "\n", WDT->LOAD);
/* Set the timer to reset the device after CONFIG_WDT_WARNING_PERIOD but not
* clear the interrupt bit in the WDT->INT_CLR register. This will cause the
* WDT to reset the device the next time the counter reaches 0, which now
* will happen again in CONFIG_WDT_WARNING_PERIOD ms.
* Since the wdt_cb may return before the new WDT counter triggers, which is
* the normal case if we expect the callback to do some short safety
* operations, we would exit the ISR without clearing the interrupt bit in
* WDT->INT_CLR which would cause the interrupt to be called again. To avoid
* this situation, we disable the IRQ source in the NVIC.
* After this ISR triggers, further wdt_kicks() are ignored to prevent the
* software from kicking the WDT during the CONFIG_WDT_WARNING_PERIOD. */
NVIC_DisableIRQ(WDT_IRQn);
_wdt_unlock();
WDT->LOAD = wdt_load_after_isr;
_wdt_lock();
#ifdef MODULE_PERIPH_WDT_CB
wdt_kick_disabled = true;
if (wdt_cb) {
wdt_cb(wdt_arg);
}
#endif /* MODULE_PERIPH_WDT_CB */
cortexm_isr_end();
}

52
cpu/qn908x/system.c Normal file
View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_qn908x
* @{
*
* @file
* @brief CMSIS system setup wrapper functions for NXP QN908x
*
* The system_QN908XC.h header is used by several vendor headers (including
* QN908XC.h which defines all the registers and some drivers). The
* system_QN908XC.c file in the vendor SDK implements the system initialization
* via the SystemInit() function and provides the current system clock
* frequency, however part of the system initialization is more appropriate in
* the board module (in `board_init`) or in the cpu module (in `cpu_init`) and
* some values like the XTAL or BUCK configuration depend on the actual board
* used while in the SDK code they are set to the QN9080DK developer module.
* Because of this we can't include that source file here.
*
* This file implements the minimum required to make some of the drivers in the
* vendor code work by providing the global SystemCoreClock variable and a
* function to update its value with the current clock configuration.
*
* @author iosabi <iosabi@protonmail.com>
*
* @}
*/
#include <stdint.h>
#include "cpu.h"
#include "vendor/drivers/fsl_clock.h"
uint32_t SystemCoreClock = DEFAULT_SYSTEM_CLOCK;
void SystemInit(void)
{
/* Do nothing here. The system initialization is done in board_init() and
* cpu_init() as needed. This function shouldn't be called anyway.
*/
}
void SystemCoreClockUpdate(void)
{
SystemCoreClock = CLOCK_GetFreq(kCLOCK_CoreSysClk);
}

77
cpu/qn908x/vectors.c Normal file
View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2020 iosabi
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_qn908x
* @{
*
* @file
*
* @brief Interrupt vector for NXP QN908x MCUs
*
* @author iosabi <iosabi@protonmail.com>
*/
/**
* @name Interrupt vector definition
* @{
*/
#include "board.h"
#include "vectors_cortexm.h"
#include "vectors_qn908x.h"
/* CPU specific interrupt vector table */
ISR_VECTOR(1) const isr_t vector_cpu[CPU_IRQ_NUMOF] = {
[EXT_GPIO_WAKEUP_IRQn] = isr_ext_gpio_wakeup, /* Ext GPIO wakeup */
[OSC_IRQn ] = isr_osc, /* BLE wakeup */
[ACMP0_IRQn ] = isr_acmp0, /* Analog comparator0 */
[ACMP1_IRQn ] = isr_acmp1, /* Analog comparator1 */
[RTC_SEC_IRQn ] = isr_rtc_sec, /* RTC second */
[RTC_FR_IRQn ] = isr_rtc_fr, /* RTC free running */
[CS_WAKEUP_IRQn ] = isr_cs_wakeup, /* Capacitive sense wakeup */
[CS_IRQn ] = isr_cs, /* Capacitive sense */
[GPIOA_IRQn ] = isr_gpioa, /* GPIO group A */
[GPIOB_IRQn ] = isr_gpiob, /* GPIO group B */
[DMA0_IRQn ] = isr_dma0, /* DMA controller */
[PIN_INT0_IRQn ] = isr_pin_int0, /* pin or pattern match engine slice 0 */
[PIN_INT1_IRQn ] = isr_pin_int1, /* pin or pattern match engine slice 1 */
[PIN_INT2_IRQn ] = isr_pin_int2, /* pin or pattern match engine slice 2 */
[PIN_INT3_IRQn ] = isr_pin_int3, /* pin or pattern match engine slice 3 */
[OSC_INT_LOW_IRQn ] = isr_osc_int_low, /* Inverse of OSC */
[USB0_IRQn ] = isr_usb0, /* USB device */
[FLEXCOMM0_IRQn ] = isr_flexcomm0, /* Flexcomm Interface 0 (USART) */
[FLEXCOMM1_IRQn ] = isr_flexcomm1, /* Flexcomm Interface 1 (USART, I2C) */
[FLEXCOMM2_IRQn ] = isr_flexcomm2, /* Flexcomm Interface 2 (SPI, I2C) */
[FLEXCOMM3_IRQn ] = isr_flexcomm3, /* Flexcomm Interface 3 (SPI) */
[BLE_IRQn ] = isr_ble, /* BLE interrupts */
[FSP_IRQn ] = isr_fsp, /* FSP */
[QDEC0_IRQn ] = isr_qdec0, /* QDEC0 */
[QDEC1_IRQn ] = isr_qdec1, /* QDEC1 */
[CTIMER0_IRQn ] = isr_ctimer0, /* Standard counter/timer CTIMER0 */
[CTIMER1_IRQn ] = isr_ctimer1, /* Standard counter/timer CTIMER1 */
[CTIMER2_IRQn ] = isr_ctimer2, /* Standard counter/timer CTIMER2 */
[CTIMER3_IRQn ] = isr_ctimer3, /* Standard counter/timer CTIMER3 */
[WDT_IRQn ] = isr_wdt, /* Watch dog timer */
[ADC_IRQn ] = isr_adc, /* ADC */
[DAC_IRQn ] = isr_dac, /* DAC */
[XTAL_READY_IRQn ] = isr_xtal_ready, /* High frequency crystal ready */
[FLASH_IRQn ] = isr_flash, /* Flash */
[SPIFI0_IRQn ] = isr_spifi0, /* SPI flash interface */
[SCT0_IRQn ] = isr_sct0, /* SCTimer/PWM */
[RNG_IRQn ] = isr_rng, /* Random number generator */
[CALIB_IRQn ] = isr_calib, /* Calibration */
[BLE_TX_IRQn ] = isr_ble_tx, /* ble tx flag */
[BLE_RX_IRQn ] = isr_ble_rx, /* ble rx flag */
[BLE_FREQ_HOP_IRQn] = isr_ble_freq_hop, /* ble frequency hop */
[BOD_IRQn ] = isr_bod, /* Brown out detect */
};
__attribute__((weak)) const uint32_t isp_configuration = 0;
/** @} */

14
cpu/qn908x/vendor/Makefile vendored Normal file
View File

@ -0,0 +1,14 @@
MODULE := vendor
# Enable submodules, although these are all compiled into the same "vendor.a"
# library since vendor_% is marked as a pseudomodule.
SUBMODULES := 1
SUBMODULES_NOFORCE := 1
CFLAGS += -Wno-unused-parameter -Wno-sign-compare
INCLUDES += \
-I$(RIOTCPU)/$(CPU)/include/vendor/ \
-I$(RIOTCPU)/$(CPU)/include/vendor/drivers/ \
#
include $(RIOTBASE)/Makefile.base