Skip to content

Commit

Permalink
cpu/qn908x: Initial minimal support for NXP QN908x CPUs.
Browse files Browse the repository at this point in the history
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.

The header files under the vendor/ directories (both
cpu/qn908x/include/vendor/ and cpu/qn908x/vendor/) are part of NXP's
SDK for the QN908x family available for download from:
  https://mcuxpresso.nxp.com/en/builder
The files included in this vendor/ directory are released by NXP under
an Open Source license as described in each file, but only the files
used by this patch are included here.

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.
  • Loading branch information
iosabi committed Apr 14, 2020
1 parent b9fda56 commit 7af89e2
Show file tree
Hide file tree
Showing 26 changed files with 12,728 additions and 0 deletions.
10 changes: 10 additions & 0 deletions cpu/qn908x/Makefile
@@ -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 changes: 18 additions & 0 deletions cpu/qn908x/Makefile.dep
@@ -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 mux_common.h
USEMODULE += periph_mux_common

# 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
7 changes: 7 additions & 0 deletions cpu/qn908x/Makefile.features
@@ -0,0 +1,7 @@
CPU_MODEL ?= qn908x

FEATURES_PROVIDED += periph_cpuid
FEATURES_PROVIDED += periph_gpio periph_gpio_irq
FEATURES_PROVIDED += periph_wdt periph_wdt_cb

include $(RIOTCPU)/cortexm_common/Makefile.features
41 changes: 41 additions & 0 deletions cpu/qn908x/Makefile.include
@@ -0,0 +1,41 @@
CPU_ARCH = cortex-m4f

# 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
54 changes: 54 additions & 0 deletions cpu/qn908x/cpu.c
@@ -0,0 +1,54 @@
/*
* 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"

#ifndef MODULE_PERIPH_WDT
#include "vendor/drivers/fsl_clock.h"
#endif /* ndef MODULE_PERIPH_WDT */

/**
* @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 stdio prior to periph_init() to allow use of DEBUG() there */
stdio_init();
/* trigger static peripheral initialization */
periph_init();
}
68 changes: 68 additions & 0 deletions cpu/qn908x/doc.txt
@@ -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.

*/
181 changes: 181 additions & 0 deletions cpu/qn908x/include/cpu_conf.h
@@ -0,0 +1,181 @@
/*
* 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)
/** @} */

/**
* @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 */
/** @} */

0 comments on commit 7af89e2

Please sign in to comment.