[ion] Added bootloader device

This commit is contained in:
M4x1m3
2022-02-22 22:05:06 +03:00
parent 61673cd1b5
commit ffb4c39e04
51 changed files with 2719 additions and 34 deletions

View File

@@ -0,0 +1,3 @@
TOOLCHAIN ?= arm-gcc-m7f
ION_KEYBOARD_LAYOUT = layout_B3
PCB_LATEST = 343 # PCB version 3.43

View File

@@ -0,0 +1,30 @@
HANDY_TARGETS += test.external_flash.write test.external_flash.read
$(BUILD_DIR)/test.external_flash.%.$(EXE): LDSCRIPT = ion/test/device/n0110/external_flash_tests.ld
test_external_flash_src = $(ion_src) $(liba_src) $(libaxx_src) $(kandinsky_src) $(poincare_src) $(ion_device_dfu_relogated_src) $(runner_src)
$(BUILD_DIR)/test.external_flash.read.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_read_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_read_src))
$(BUILD_DIR)/test.external_flash.write.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_write_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_write_src))
.PHONY: %_flash
%_flash: $(BUILD_DIR)/%.dfu
@echo "DFU $@"
@echo "INFO About to flash your device. Please plug your device to your computer"
@echo " using an USB cable and press at the same time the 6 key and the RESET"
@echo " button on the back of your device."
$(Q) until $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11" > /dev/null 2>&1; do sleep 2;done
$(Q) $(PYTHON) build/device/dfu.py -u $(word 1,$^)
.PHONY: %.two_binaries
%.two_binaries: %.elf
@echo "Building an external binary for $<"
$(Q) $(OBJCOPY) -O binary $< $(basename $<).external.bin
@echo "Padding $(basename $<).external.bin"
$(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).external.bin
.PHONY: binpack
binpack: $(BUILD_DIR)/epsilon.onboarding.two_binaries
rm -rf $(BUILD_DIR)/binpack
mkdir -p $(BUILD_DIR)/binpack
cp $(BUILD_DIR)/epsilon.onboarding.external.bin $(BUILD_DIR)/binpack
cd $(BUILD_DIR) && for binary in epsilon.onboarding.external.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done
cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/*

View File

@@ -54,21 +54,3 @@ $(BUILD_DIR)/bench.ram.$(EXE): LDFLAGS += -Lion/src/$(PLATFORM)/bench
$(BUILD_DIR)/bench.ram.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld
$(BUILD_DIR)/bench.flash.$(EXE): $(call flavored_object_for,$(bench_src),consoleuart usbxip)
$(BUILD_DIR)/bench.flash.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/$(MODEL)/internal_flash.ld
.PHONY: %.two_binaries
%.two_binaries: %.elf
@echo "Building an internal and an external binary for $<"
$(Q) $(OBJCOPY) -O binary -j .text.external -j .rodata.external -j .exam_mode_buffer $< $(basename $<).external.bin
$(Q) $(OBJCOPY) -O binary -R .text.external -R .rodata.external -R .exam_mode_buffer $< $(basename $<).internal.bin
@echo "Padding $(basename $<).external.bin and $(basename $<).internal.bin"
$(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).external.bin
$(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).internal.bin
.PHONY: binpack
binpack: $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/epsilon.onboarding.two_binaries
rm -rf $(BUILD_DIR)/binpack
mkdir -p $(BUILD_DIR)/binpack
cp $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/binpack
cp $(BUILD_DIR)/epsilon.onboarding.internal.bin $(BUILD_DIR)/epsilon.onboarding.external.bin $(BUILD_DIR)/binpack
cd $(BUILD_DIR) && for binary in flasher.light.bin epsilon.onboarding.internal.bin epsilon.onboarding.external.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done
cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/*

View File

@@ -5,3 +5,18 @@
@echo " using an USB cable and press the RESET button the back of your device."
$(Q) until $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11" > /dev/null 2>&1; do sleep 2;done
$(Q) $(PYTHON) build/device/dfu.py -m -u $<
.PHONY: %.two_binaries
%.two_binaries: %.elf
@echo "Building an internal binary for $<"
$(Q) $(OBJCOPY) -O binary -R .text.external -R .rodata.external -R .exam_mode_buffer $< $(basename $<).internal.bin
@echo "Padding $(basename $<).internal.bin"
$(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).internal.bin
.PHONY: binpack
binpack: $(BUILD_DIR)/epsilon.onboarding.two_binaries
rm -rf $(BUILD_DIR)/binpack
mkdir -p $(BUILD_DIR)/binpack
cp $(BUILD_DIR)/epsilon.onboarding.internal.bin $(BUILD_DIR)/binpack
cd $(BUILD_DIR) && for binary in epsilon.onboarding.internal.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done
cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/*

View File

@@ -6,16 +6,28 @@ $(BUILD_DIR)/test.external_flash.read.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_ext
$(BUILD_DIR)/test.external_flash.write.$(EXE): $(BUILD_DIR)/quiz/src/test_ion_external_flash_write_symbols.o $(call object_for,$(test_external_flash_src) $(test_ion_external_flash_write_src))
.PHONY: %_flash
%_flash: $(BUILD_DIR)/%.dfu $(BUILD_DIR)/flasher.light.dfu
%_flash: $(BUILD_DIR)/%.dfu
@echo "DFU $@"
@echo "INFO About to flash your device. Please plug your device to your computer"
@echo " using an USB cable and press at the same time the 6 key and the RESET"
@echo " button on the back of your device."
$(Q) until $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11" > /dev/null 2>&1; do sleep 2;done
$(eval DFU_SLAVE := $(shell $(PYTHON) build/device/dfu.py -l | grep -E "0483:a291|0483:df11"))
$(Q) if expr "$(DFU_SLAVE)" : ".*0483:df11.*" > /dev/null; \
then \
$(PYTHON) build/device/dfu.py -u $(word 2,$^); \
sleep 2; \
fi
$(Q) $(PYTHON) build/device/dfu.py -u $(word 1,$^)
.PHONY: %.two_binaries
%.two_binaries: %.elf
@echo "Building an internal and an external binary for $<"
$(Q) $(OBJCOPY) -O binary -j .text.external -j .rodata.external -j .exam_mode_buffer $< $(basename $<).external.bin
$(Q) $(OBJCOPY) -O binary -R .text.external -R .rodata.external -R .exam_mode_buffer $< $(basename $<).internal.bin
@echo "Padding $(basename $<).external.bin and $(basename $<).internal.bin"
$(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).external.bin
$(Q) printf "\xFF\xFF\xFF\xFF" >> $(basename $<).internal.bin
.PHONY: binpack
binpack: $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/epsilon.onboarding.two_binaries
rm -rf $(BUILD_DIR)/binpack
mkdir -p $(BUILD_DIR)/binpack
cp $(BUILD_DIR)/flasher.light.bin $(BUILD_DIR)/binpack
cp $(BUILD_DIR)/epsilon.onboarding.internal.bin $(BUILD_DIR)/epsilon.onboarding.external.bin $(BUILD_DIR)/binpack
cd $(BUILD_DIR) && for binary in flasher.light.bin epsilon.onboarding.internal.bin epsilon.onboarding.external.bin; do shasum -a 256 -b binpack/$${binary} > binpack/$${binary}.sha256;done
cd $(BUILD_DIR) && tar cvfz binpack-$(MODEL)-`git rev-parse HEAD | head -c 7`.tgz binpack/*

View File

@@ -22,7 +22,7 @@ include ion/src/shared/tools/Makefile
# char test[4]= "ab"; is valid and should initialize test to 'a','b',0,0).
# Older versions of GCC are not conformant so we resort to an initializer list.
initializer_list = $(shell echo $(1) | sed "s/\(.\)/'\1',/g")0
$(call object_for,ion/src/shared/platform_info.cpp): SFLAGS += -DPATCH_LEVEL="$(call initializer_list,$(PATCH_LEVEL))" -DEPSILON_VERSION="$(call initializer_list,$(EPSILON_VERSION))" -DOMEGA_VERSION="$(call initializer_list,$(OMEGA_VERSION))" -DOMEGA_USERNAME="$(call initializer_list,$(OMEGA_USERNAME))"
$(call object_for,ion/src/simulator/platform_info.cpp ion/src/device/n0100/platform_info.cpp ion/src/device/n0110/platform_info.cpp ion/src/device/bootloader/platform_info.cpp): SFLAGS += -DPATCH_LEVEL="$(call initializer_list,$(PATCH_LEVEL))" -DEPSILON_VERSION="$(call initializer_list,$(EPSILON_VERSION))" -DOMEGA_VERSION="$(call initializer_list,$(OMEGA_VERSION))" -DOMEGA_USERNAME="$(call initializer_list,$(OMEGA_USERNAME))"
ion_src += $(addprefix ion/src/shared/, \
console_line.cpp \
@@ -31,7 +31,6 @@ ion_src += $(addprefix ion/src/shared/, \
events.cpp \
events_keyboard.cpp \
events_modifier.cpp \
platform_info.cpp \
rtc.cpp \
stack_position.cpp \
storage.cpp \

View File

@@ -4,7 +4,7 @@ include ion/src/device/bench/Makefile
include ion/src/device/flasher/Makefile
include ion/src/device/$(MODEL)/Makefile
$(call object_for,ion/src/shared/platform_info.cpp): SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))"
$(call object_for,ion/src/device/n0100/platform_info.cpp ion/src/device/n0110/platform_info.cpp ion/src/device/bootloader/platform_info.cpp): SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))"
ifeq ($(EPSILON_TELEMETRY),1)
ion_src += ion/src/shared/telemetry_console.cpp

View File

@@ -0,0 +1,31 @@
ion_device_src += $(addprefix ion/src/device/bootloader/drivers/, \
board.cpp \
cache.cpp \
external_flash.cpp \
led.cpp \
power.cpp \
reset.cpp \
usb.cpp \
)
ion_device_src += $(addprefix ion/src/device/bootloader/drivers/, \
board.cpp \
cache.cpp \
external_flash.cpp \
led.cpp \
power.cpp \
reset.cpp \
usb.cpp \
)
ion_device_src += $(addprefix ion/src/device/bootloader/boot/, \
rt0.cpp \
)
ion_device_src += $(addprefix ion/src/device/bootloader/, \
platform_info.cpp \
)
SLOT ?= A
LDSCRIPT ?= ion/src/device/bootloader/bootloader.$(SLOT).ld

View File

@@ -0,0 +1,136 @@
#include <stdint.h>
#include <string.h>
#include <ion.h>
#include <boot/isr.h>
#include <drivers/board.h>
#include <drivers/rtc.h>
#include <drivers/reset.h>
#include <drivers/timing.h>
typedef void (*cxx_constructor)();
extern "C" {
extern char _data_section_start_flash;
extern char _data_section_start_ram;
extern char _data_section_end_ram;
extern char _bss_section_start_ram;
extern char _bss_section_end_ram;
extern cxx_constructor _init_array_start;
extern cxx_constructor _init_array_end;
extern char _isr_vector_table_start_flash;
extern char _isr_vector_table_start_ram;
extern char _isr_vector_table_end_ram;
}
void __attribute__((noinline)) abort() {
#ifdef NDEBUG
Ion::Device::Reset::core();
#else
while (1) {
}
#endif
}
/* In order to ensure that this method is execute from the external flash, we
* forbid inlining it.*/
static void __attribute__((noinline)) external_flash_start() {
/* Init the peripherals. We do not initialize the backlight in case there is
* an on boarding app: indeed, we don't want the user to see the LCD tests
* happening during the on boarding app. The backlight will be initialized
* after the Power-On Self-Test if there is one or before switching to the
* home app otherwise. */
Ion::Device::Board::initPeripherals(false);
return ion_main(0, nullptr);
}
/* This additional function call 'jump_to_external_flash' serves two purposes:
* - By default, the compiler is free to inline any function call he wants. If
* the compiler decides to inline some functions that make use of VFP
* registers, it will need to push VFP them onto the stack in calling
* function's prologue.
* Problem: in start()'s prologue, we would never had a chance to enable the
* FPU since this function is the first thing called after reset.
* We can safely assume that neither memcpy, memset, nor any Ion::Device::init*
* method will use floating-point numbers, but ion_main very well can.
* To make sure ion_main's potential usage of VFP registers doesn't bubble-up to
* start(), we isolate it in its very own non-inlined function call.
* - To avoid jumping on the external flash when it is shut down, we ensure
* there is no symbol references from the internal flash to the external
* flash except this jump. In order to do that, we isolate this
* jump in a symbol that we link in a special section separated from the
* internal flash section. We can than forbid cross references from the
* internal flash to the external flash. */
static void __attribute__((noinline)) jump_to_external_flash() {
external_flash_start();
}
/* When 'start' is executed, the external flash is supposed to be shutdown. We
* thus forbid inlining to prevent executing this code from external flash
* (just in case 'start' was to be called from the external flash). */
void __attribute__((noinline)) start() {
/* This is where execution starts after reset.
* Many things are not initialized yet so the code here has to pay attention. */
/* Initialize the FPU as early as possible.
* For example, static C++ objects are very likely to manipulate float values */
Ion::Device::Board::initFPU();
/* Copy data section to RAM
* The data section is R/W but its initialization value matters. It's stored
* in Flash, but linked as if it were in RAM. Now's our opportunity to copy
* it. Note that until then the data section (e.g. global variables) contains
* garbage values and should not be used. */
size_t dataSectionLength = (&_data_section_end_ram - &_data_section_start_ram);
memcpy(&_data_section_start_ram, &_data_section_start_flash, dataSectionLength);
/* Zero-out the bss section in RAM
* Until we do, any uninitialized global variable will be unusable. */
size_t bssSectionLength = (&_bss_section_end_ram - &_bss_section_start_ram);
memset(&_bss_section_start_ram, 0, bssSectionLength);
/* Call static C++ object constructors
* The C++ compiler creates an initialization function for each static object.
* The linker then stores the address of each of those functions consecutively
* between _init_array_start and _init_array_end. So to initialize all C++
* static objects we just have to iterate between theses two addresses and
* call the pointed function. */
#define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0
#if SUPPORT_CPP_GLOBAL_CONSTRUCTORS
for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) {
(*c)();
}
#else
/* In practice, static initialized objects are a terrible idea. Since the init
* order is not specified, most often than not this yields the dreaded static
* init order fiasco. How about bypassing the issue altogether? */
if (&_init_array_start != &_init_array_end) {
abort();
}
#endif
/* Copy isr_vector_table section to RAM
* The isr table must be within the memory mapped by the microcontroller (it
* can't live in the external flash). */
size_t isrSectionLength = (&_isr_vector_table_end_ram - &_isr_vector_table_start_ram);
memcpy(&_isr_vector_table_start_ram, &_isr_vector_table_start_flash, isrSectionLength);
Ion::Device::Board::init();
/* At this point, we initialized clocks and the external flash but no other
* peripherals. */
jump_to_external_flash();
abort();
}
void __attribute__((interrupt, noinline)) isr_systick() {
auto t = Ion::Device::Timing::MillisElapsed;
t++;
Ion::Device::Timing::MillisElapsed = t;
}

View File

@@ -0,0 +1,144 @@
/* Linker script
* The role of this script is to take all the object files built by the compiler
* and produce a single binary suitable for execution.
* Without an explicit linker script, the linker will produce a binary file that
* would not match some of our requirements (for example, we want the code to be
* written at a specific address (in Flash ROM) and the data at another. */
/* Let's instruct the linker about our memory layout.
* This will let us use shortcuts such as ">FLASH" to ask for a given section to
* be stored in Flash. */
MEMORY {
SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K
FLASH (rx) : ORIGIN = 0x90000000, LENGTH = 4M
/*
ITCM (rwx) : ORIGIN = 0x00000000, LENGTH = 16K
DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
SRAM1 (rwx) : ORIGIN = 0x20010000, LENGTH = 176K
SRAM2 (rwx) : ORIGIN = 0x2003C000, LENGTH = 16K
*/
}
STACK_SIZE = 32K;
FIRST_FLASH_SECTOR_SIZE = 4K;
SIGNED_PAYLOAD_LENGTH = 8;
SECTIONS {
.signed_payload_prefix ORIGIN(FLASH) : {
FILL(0xFF);
BYTE(0xFF)
. = ORIGIN(FLASH) + SIGNED_PAYLOAD_LENGTH;
} >FLASH
.header : {
KEEP(*(.header))
} >FLASH
.isr_vector_table : AT(ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.header)) {
/* When booting, the STM32F412 fetches the content of address 0x0, and
* extracts from it various key infos: the initial value of the PC register
* (program counter), the initial value of the stack pointer, and various
* entry points to interrupt service routines. This data is called the ISR
* vector table.
*
* Note that address 0x0 is always an alias. It points to the beginning of
* Flash, SRAM, or integrated bootloader depending on the boot mode chosen.
* (This mode is chosen by setting the BOOTn pins on the chip).
*
* We're generating the ISR vector table in code because it's very
* convenient: using function pointers, we can easily point to the service
* routine for each interrupt. */
_isr_vector_table_start_flash = LOADADDR(.isr_vector_table);
_isr_vector_table_start_ram = .;
KEEP(*(.isr_vector_table))
_isr_vector_table_end_ram = .;
} >SRAM
.exam_mode_buffer ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.header) + SIZEOF(.isr_vector_table) : {
. = ALIGN(4K);
_exam_mode_buffer_start = .;
KEEP(*(.exam_mode_buffer))
/* Note: We don't increment "." here, we set it. */
. = . + FIRST_FLASH_SECTOR_SIZE;
_exam_mode_buffer_end = .;
} >FLASH
/* External flash memory */
.text ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.header) + SIZEOF(.isr_vector_table) + SIZEOF(.exam_mode_buffer) : {
. = ALIGN(4);
*(.text)
*(.text.*)
} >FLASH
.rodata : {
*(.rodata)
*(.rodata.*)
} >FLASH
.init_array : {
. = ALIGN(4);
_init_array_start = .;
KEEP (*(.init_array*))
_init_array_end = .;
} >FLASH
.data : {
/* The data section is written to Flash but linked as if it were in RAM.
*
* This is required because its initial value matters (so it has to be in
* persistant memory in the first place), but it is a R/W area of memory
* so it will have to live in RAM upon execution (in linker lingo, that
* translates to the data section having a LMA in Flash and a VMA in RAM).
*
* This means we'll have to copy it from Flash to RAM on initialization.
* To do this, we'll need to know the source location of the data section
* (in Flash), the target location (in RAM), and the size of the section.
* That's why we're defining three symbols that we'll use in the initial-
* -ization routine. */
. = ALIGN(4);
_data_section_start_flash = LOADADDR(.data);
_data_section_start_ram = .;
*(.data)
*(.data.*)
_data_section_end_ram = .;
} >SRAM AT> FLASH
.bss : {
/* The bss section contains data for all uninitialized variables
* So like the .data section, it will go in RAM, but unlike the data section
* we don't care at all about an initial value.
*
* Before execution, crt0 will erase that section of memory though, so we'll
* need pointers to the beginning and end of this section. */
. = ALIGN(4);
_bss_section_start_ram = .;
*(.bss)
*(.bss.*)
/* The compiler may choose to allocate uninitialized global variables as
* COMMON blocks. This can be disabled with -fno-common if needed. */
*(COMMON)
_bss_section_end_ram = .;
} >SRAM
.heap : {
_heap_start = .;
/* Note: We don't increment "." here, we set it. */
. = (ORIGIN(SRAM) + LENGTH(SRAM) - STACK_SIZE);
_heap_end = .;
} >SRAM
.stack : {
. = ALIGN(8);
_stack_end = .;
. += (STACK_SIZE - 8);
. = ALIGN(8);
_stack_start = .;
} >SRAM
/DISCARD/ : {
/* exidx and extab are needed for unwinding, which we don't use */
*(.ARM.exidx*)
*(.ARM.extab*)
}
}

View File

@@ -0,0 +1,144 @@
/* Linker script
* The role of this script is to take all the object files built by the compiler
* and produce a single binary suitable for execution.
* Without an explicit linker script, the linker will produce a binary file that
* would not match some of our requirements (for example, we want the code to be
* written at a specific address (in Flash ROM) and the data at another. */
/* Let's instruct the linker about our memory layout.
* This will let us use shortcuts such as ">FLASH" to ask for a given section to
* be stored in Flash. */
MEMORY {
SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K
FLASH (rx) : ORIGIN = 0x90400000, LENGTH = 4M
/*
ITCM (rwx) : ORIGIN = 0x00000000, LENGTH = 16K
DTCM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
SRAM1 (rwx) : ORIGIN = 0x20010000, LENGTH = 176K
SRAM2 (rwx) : ORIGIN = 0x2003C000, LENGTH = 16K
*/
}
STACK_SIZE = 32K;
FIRST_FLASH_SECTOR_SIZE = 4K;
SIGNED_PAYLOAD_LENGTH = 8;
SECTIONS {
.signed_payload_prefix ORIGIN(FLASH) : {
FILL(0xFF);
BYTE(0xFF)
. = ORIGIN(FLASH) + SIGNED_PAYLOAD_LENGTH;
} >FLASH
.header : {
KEEP(*(.header))
} >FLASH
.isr_vector_table : AT(ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.header)) {
/* When booting, the STM32F412 fetches the content of address 0x0, and
* extracts from it various key infos: the initial value of the PC register
* (program counter), the initial value of the stack pointer, and various
* entry points to interrupt service routines. This data is called the ISR
* vector table.
*
* Note that address 0x0 is always an alias. It points to the beginning of
* Flash, SRAM, or integrated bootloader depending on the boot mode chosen.
* (This mode is chosen by setting the BOOTn pins on the chip).
*
* We're generating the ISR vector table in code because it's very
* convenient: using function pointers, we can easily point to the service
* routine for each interrupt. */
_isr_vector_table_start_flash = LOADADDR(.isr_vector_table);
_isr_vector_table_start_ram = .;
KEEP(*(.isr_vector_table))
_isr_vector_table_end_ram = .;
} >SRAM
.exam_mode_buffer ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.header) + SIZEOF(.isr_vector_table) : {
. = ALIGN(4K);
_exam_mode_buffer_start = .;
KEEP(*(.exam_mode_buffer))
/* Note: We don't increment "." here, we set it. */
. = . + FIRST_FLASH_SECTOR_SIZE;
_exam_mode_buffer_end = .;
} >FLASH
/* External flash memory */
.text ORIGIN(FLASH) + SIZEOF(.signed_payload_prefix) + SIZEOF(.header) + SIZEOF(.isr_vector_table) + SIZEOF(.exam_mode_buffer) : {
. = ALIGN(4);
*(.text)
*(.text.*)
} >FLASH
.rodata : {
*(.rodata)
*(.rodata.*)
} >FLASH
.init_array : {
. = ALIGN(4);
_init_array_start = .;
KEEP (*(.init_array*))
_init_array_end = .;
} >FLASH
.data : {
/* The data section is written to Flash but linked as if it were in RAM.
*
* This is required because its initial value matters (so it has to be in
* persistant memory in the first place), but it is a R/W area of memory
* so it will have to live in RAM upon execution (in linker lingo, that
* translates to the data section having a LMA in Flash and a VMA in RAM).
*
* This means we'll have to copy it from Flash to RAM on initialization.
* To do this, we'll need to know the source location of the data section
* (in Flash), the target location (in RAM), and the size of the section.
* That's why we're defining three symbols that we'll use in the initial-
* -ization routine. */
. = ALIGN(4);
_data_section_start_flash = LOADADDR(.data);
_data_section_start_ram = .;
*(.data)
*(.data.*)
_data_section_end_ram = .;
} >SRAM AT> FLASH
.bss : {
/* The bss section contains data for all uninitialized variables
* So like the .data section, it will go in RAM, but unlike the data section
* we don't care at all about an initial value.
*
* Before execution, crt0 will erase that section of memory though, so we'll
* need pointers to the beginning and end of this section. */
. = ALIGN(4);
_bss_section_start_ram = .;
*(.bss)
*(.bss.*)
/* The compiler may choose to allocate uninitialized global variables as
* COMMON blocks. This can be disabled with -fno-common if needed. */
*(COMMON)
_bss_section_end_ram = .;
} >SRAM
.heap : {
_heap_start = .;
/* Note: We don't increment "." here, we set it. */
. = (ORIGIN(SRAM) + LENGTH(SRAM) - STACK_SIZE);
_heap_end = .;
} >SRAM
.stack : {
. = ALIGN(8);
_stack_end = .;
. += (STACK_SIZE - 8);
. = ALIGN(8);
_stack_start = .;
} >SRAM
/DISCARD/ : {
/* exidx and extab are needed for unwinding, which we don't use */
*(.ARM.exidx*)
*(.ARM.extab*)
}
}

View File

@@ -0,0 +1,402 @@
#include <drivers/board.h>
#include <drivers/cache.h>
#include <drivers/internal_flash.h>
#include <drivers/config/clocks.h>
#include <drivers/config/internal_flash.h>
#include <drivers/external_flash.h>
#include <regs/regs.h>
#include <ion.h>
typedef void(*ISR)(void);
extern ISR InitialisationVector[];
// Public Ion methods
const char * Ion::fccId() {
return "2ALWP-N0110";
}
// Private Ion::Device methods
namespace Ion {
namespace Device {
namespace Board {
using namespace Regs;
void initMPU() {
// 1. Disable the MPU
// 1.1 Memory barrier
Cache::dmb();
// 1.2 Disable fault exceptions
CORTEX.SHCRS()->setMEMFAULTENA(false);
// 1.3 Disable the MPU and clear the control register
MPU.CTRL()->setENABLE(false);
// 2. MPU settings
// 2.1 Configure a MPU region for the FMC memory area
/* This is needed for interfacing with the LCD
* We define the whole FMC memory bank 1 as strongly ordered, non-executable
* and not accessible. We define the FMC command and data addresses as
* writeable non-cachable, non-buffereable and non shareable. */
int sector = 0;
MPU.RNR()->setREGION(sector++);
MPU.RBAR()->setADDR(0x60000000);
MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB);
MPU.RASR()->setAP(MPU::RASR::AccessPermission::NoAccess);
MPU.RASR()->setXN(true);
MPU.RASR()->setTEX(2);
MPU.RASR()->setS(0);
MPU.RASR()->setC(0);
MPU.RASR()->setB(0);
MPU.RASR()->setENABLE(true);
MPU.RNR()->setREGION(sector++);
MPU.RBAR()->setADDR(0x60000000);
MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32B);
MPU.RASR()->setXN(true);
MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW);
MPU.RASR()->setTEX(2);
MPU.RASR()->setS(0);
MPU.RASR()->setC(0);
MPU.RASR()->setB(0);
MPU.RASR()->setENABLE(true);
MPU.RNR()->setREGION(sector++);
MPU.RBAR()->setADDR(0x60000000+0x20000);
MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32B);
MPU.RASR()->setXN(true);
MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW);
MPU.RASR()->setTEX(2);
MPU.RASR()->setS(0);
MPU.RASR()->setC(0);
MPU.RASR()->setB(0);
MPU.RASR()->setENABLE(true);
// 2.2 Configure MPU regions for the QUADSPI peripheral
/* L1 Cache can issue speculative reads to any memory address. But, when the
* Quad-SPI is in memory-mapped mode, if an access is made to an address
* outside of the range defined by FSIZE but still within the 256Mbytes range,
* then an AHB error is given (AN4760). To prevent this to happen, we
* configure the MPU to define the whole Quad-SPI addressable space as
* strongly ordered, non-executable and not accessible. Plus, we define the
* Quad-SPI region corresponding to the Expternal Chip as executable and
* fully accessible (AN4861). */
MPU.RNR()->setREGION(sector++);
MPU.RBAR()->setADDR(0x90000000);
MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB);
MPU.RASR()->setAP(MPU::RASR::AccessPermission::NoAccess);
MPU.RASR()->setXN(true);
MPU.RASR()->setTEX(0);
MPU.RASR()->setS(0);
MPU.RASR()->setC(0);
MPU.RASR()->setB(0);
MPU.RASR()->setENABLE(true);
MPU.RNR()->setREGION(sector++);
MPU.RBAR()->setADDR(0x90000000);
MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_8MB);
MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW);
MPU.RASR()->setXN(false);
MPU.RASR()->setTEX(0);
MPU.RASR()->setS(0);
MPU.RASR()->setC(1);
MPU.RASR()->setB(0);
MPU.RASR()->setENABLE(true);
// 2.3 Enable MPU
MPU.CTRL()->setPRIVDEFENA(true);
MPU.CTRL()->setENABLE(true);
// 3. Data/instruction synchronisation barriers to ensure that the new MPU configuration is used by subsequent instructions.
Cache::dsb();
Cache::isb();
}
void init() {
initMPU();
initClocks();
// Ensure right location of interrupt vectors
CORTEX.VTOR()->setVTOR((void*)&InitialisationVector);
// Initiate L1 cache after initiating the external flash
Cache::enable();
}
void initClocks() {
/* System clock
* Configure the CPU at 192 MHz and USB at 48 MHz. */
/* After reset, the device is using the high-speed internal oscillator (HSI)
* as a clock source, which runs at a fixed 16 MHz frequency. The HSI is not
* accurate enough for reliable USB operation, so we need to use the external
* high-speed oscillator (HSE). */
// Enable the HSI and wait for it to be ready
RCC.CR()->setHSION(true);
while(!RCC.CR()->getHSIRDY()) {
}
// Enable the HSE and wait for it to be ready
RCC.CR()->setHSEON(true);
while(!RCC.CR()->getHSERDY()) {
}
// Enable PWR peripheral clock
RCC.APB1ENR()->setPWREN(true);
/* To pass electromagnetic compatibility tests, we activate the Spread
* Spectrum clock generation, which adds jitter to the PLL clock in order to
* "lower peak-energy on the central frequency" and its harmonics.
* It must be done before enabling the PLL. */
class RCC::SSCGR sscgr(0); // Reset value
sscgr.setMODPER(Clocks::Config::SSCG_MODPER);
sscgr.setINCSTEP(Clocks::Config::SSCG_INCSTEP);
sscgr.setSPREADSEL(RCC::SSCGR::SPREADSEL::CenterSpread);
sscgr.setSSCGEN(true);
RCC.SSCGR()->set(sscgr);
/* Given the crystal used on our device, the HSE will oscillate at 8 MHz. By
* piping it through a phase-locked loop (PLL) we can derive other frequencies
* for use in different parts of the system. */
// Configure the PLL ratios and use HSE as a PLL input
RCC.PLLCFGR()->setPLLM(Clocks::Config::PLL_M);
RCC.PLLCFGR()->setPLLN(Clocks::Config::PLL_N);
RCC.PLLCFGR()->setPLLQ(Clocks::Config::PLL_Q);
RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE);
// Enable the PLL and wait for it to be ready
RCC.CR()->setPLLON(true);
// Enable Over-drive
PWR.CR()->setODEN(true);
while(!PWR.CSR()->getODRDY()) {
}
PWR.CR()->setODSWEN(true);
while(!PWR.CSR()->getODSWRDY()) {
}
// Choose Voltage scale 1
PWR.CR()->setVOS(PWR::CR::Voltage::Scale1);
while (!PWR.CSR()->getVOSRDY()) {}
/* After reset the Flash runs as fast as the CPU. When we clock the CPU faster
* the flash memory cannot follow and therefore flash memory accesses need to
* wait a little bit.
* The spec tells us that at 2.8V and over 210MHz the flash expects 7 WS. */
FLASH.ACR()->setLATENCY(7);
/* Enable prefetching flash instructions */
/* Fetching instructions increases slightly the power consumption but the
* increase is negligible compared to the screen consumption. */
FLASH.ACR()->setPRFTEN(true);
/* Enable the ART */
FLASH.ACR()->setARTEN(true);
// 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz
RCC.CFGR()->setPPRE1(Clocks::Config::APB1PrescalerReg);
// 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz
RCC.CFGR()->setPPRE2(Clocks::Config::APB2PrescalerReg);
while(!RCC.CR()->getPLLRDY()) {
}
// Use the PLL output as a SYSCLK source
RCC.CFGR()->setSW(RCC::CFGR::SW::PLL);
while (RCC.CFGR()->getSWS() != RCC::CFGR::SW::PLL) {
}
// Now that we don't need use it anymore, turn the HSI off
RCC.CR()->setHSION(false);
// Peripheral clocks
// AHB1 bus
// Our peripherals are using GPIO A, B, C, D and E.
// We're not using the CRC nor DMA engines.
class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value
ahb1enr.setGPIOAEN(true);
ahb1enr.setGPIOBEN(true);
ahb1enr.setGPIOCEN(true);
ahb1enr.setGPIODEN(true);
ahb1enr.setGPIOEEN(true);
ahb1enr.setDMA2EN(true);
RCC.AHB1ENR()->set(ahb1enr);
// AHB2 bus
RCC.AHB2ENR()->setOTGFSEN(true);
// AHB3 bus
RCC.AHB3ENR()->setFSMCEN(true);
// APB1 bus
// We're using TIM3 for the LEDs
RCC.APB1ENR()->setTIM3EN(true);
RCC.APB1ENR()->setPWREN(true);
RCC.APB1ENR()->setRTCAPB(true);
// APB2 bus
class RCC::APB2ENR apb2enr(0); // Reset value
apb2enr.setADC1EN(true);
apb2enr.setSYSCFGEN(true);
apb2enr.setUSART6EN(true); // TODO required if building bench target only?
RCC.APB2ENR()->set(apb2enr);
// Configure clocks in sleep mode
// AHB1 peripheral clock enable in low-power mode register
class RCC::AHB1LPENR ahb1lpenr(0x7EF7B7FF); // Reset value
ahb1lpenr.setGPIOALPEN(true); // Enable IO port A for Charging/USB plug/Keyboard pins
ahb1lpenr.setGPIOBLPEN(true); // Enable IO port B for LED pins
ahb1lpenr.setGPIOCLPEN(true); // Enable IO port C for LED/Keyboard pins
ahb1lpenr.setGPIODLPEN(false); // Disable IO port D (LCD...)
ahb1lpenr.setGPIOELPEN(true); // Enable IO port E for Keyboard/Battery pins
ahb1lpenr.setGPIOFLPEN(false); // Disable IO port F
ahb1lpenr.setGPIOGLPEN(false); // Disable IO port G
ahb1lpenr.setGPIOHLPEN(false); // Disable IO port H
ahb1lpenr.setGPIOILPEN(false); // Disable IO port I
ahb1lpenr.setCRCLPEN(false);
ahb1lpenr.setFLITFLPEN(false);
ahb1lpenr.setSRAM1LPEN(false);
ahb1lpenr.setDMA1LPEN(false);
ahb1lpenr.setDMA2LPEN(false);
ahb1lpenr.setAXILPEN(false);
ahb1lpenr.setSRAM2LPEN(false);
ahb1lpenr.setBKPSRAMLPEN(false);
ahb1lpenr.setDTCMLPEN(false);
ahb1lpenr.setOTGHSLPEN(false);
ahb1lpenr.setOTGHSULPILPEN(false);
RCC.AHB1LPENR()->set(ahb1lpenr);
// AHB2 peripheral clock enable in low-power mode register
class RCC::AHB2LPENR ahb2lpenr(0x000000F1); // Reset value
ahb2lpenr.setOTGFSLPEN(false);
ahb2lpenr.setRNGLPEN(false);
ahb2lpenr.setAESLPEN(false);
RCC.AHB2LPENR()->set(ahb2lpenr);
// AHB3 peripheral clock enable in low-power mode register
class RCC::AHB3LPENR ahb3lpenr(0x00000003); // Reset value
ahb3lpenr.setFMCLPEN(false);
ahb3lpenr.setQSPILPEN(false);
RCC.AHB3LPENR()->set(ahb3lpenr);
// APB1 peripheral clock enable in low-power mode register
class RCC::APB1LPENR apb1lpenr(0xFFFFCBFF); // Reset value
apb1lpenr.setTIM2LPEN(false);
apb1lpenr.setTIM3LPEN(true); // Enable TIM3 in sleep mode for LEDs
apb1lpenr.setTIM4LPEN(false);
apb1lpenr.setTIM5LPEN(false);
apb1lpenr.setTIM6LPEN(false);
apb1lpenr.setTIM7LPEN(false);
apb1lpenr.setTIM12LPEN(false);
apb1lpenr.setTIM13LPEN(false);
apb1lpenr.setTIM14LPEN(false);
apb1lpenr.setRTCAPBLPEN(false);
apb1lpenr.setWWDGLPEN(false);
apb1lpenr.setSPI2LPEN(false);
apb1lpenr.setSPI3LPEN(false);
apb1lpenr.setUSART2LPEN(false);
apb1lpenr.setUSART3LPEN(false);
apb1lpenr.setI2C1LPEN(false);
apb1lpenr.setI2C2LPEN(false);
apb1lpenr.setI2C3LPEN(false);
apb1lpenr.setCAN1LPEN(false);
apb1lpenr.setPWRLPEN(false);
apb1lpenr.setLPTIM1LPEN(false);
apb1lpenr.setUSART4LPEN(false);
apb1lpenr.setUSART5LPEN(false);
apb1lpenr.setOTGHSLPEN(false);
apb1lpenr.setOTGHSULPILPEN(false);
RCC.APB1LPENR()->set(apb1lpenr);
// APB2 peripheral clock enable in low-power mode register
class RCC::APB2LPENR apb2lpenr(0x04F77F33); // Reset value
apb2lpenr.setTIM1LPEN(false);
apb2lpenr.setTIM8LPEN(false);
apb2lpenr.setUSART1LPEN(false);
apb2lpenr.setUSART6LPEN(false);
apb2lpenr.setADC1LPEN(false);
apb2lpenr.setSPI1LPEN(false);
apb2lpenr.setSPI4LPEN(false);
apb2lpenr.setSYSCFGLPEN(false);
apb2lpenr.setTIM9LPEN(false);
apb2lpenr.setTIM10LPEN(false);
apb2lpenr.setTIM11LPEN(false);
apb2lpenr.setSPI5LPEN(false);
apb2lpenr.setSDMMC2LPEN(false);
apb2lpenr.setADC2LPEN(false);
apb2lpenr.setADC3LPEN(false);
apb2lpenr.setSAI1LPEN(false);
apb2lpenr.setSAI2LPEN(false);
RCC.APB2LPENR()->set(apb2lpenr);
}
void shutdownClocks(bool keepLEDAwake) {
// APB2 bus
RCC.APB2ENR()->set(0); // Reset value
// AHB2 bus
RCC.AHB2ENR()->set(0); // Reset value
// AHB3 bus
RCC.AHB3ENR()->set(0); // Reset value
// APB1
class RCC::APB1ENR apb1enr(0); // Reset value
// AHB1 bus
class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value
if (keepLEDAwake) {
apb1enr.setTIM3EN(true);
ahb1enr.setGPIOBEN(true);
}
RCC.APB1ENR()->set(apb1enr);
RCC.AHB1ENR()->set(ahb1enr);
}
constexpr int k_pcbVersionOTPIndex = 0;
/* As we want the PCB versions to be in ascending order chronologically, and
* because the OTP are initialized with 1s, we store the bitwise-not of the
* version number. This way, devices with blank OTP are considered version 0. */
PCBVersion pcbVersion() {
#if IN_FACTORY
/* When flashing for the first time, we want all systems that depend on the
* PCB version to function correctly before flashing the PCB version. This
* way, flashing the PCB version can be done last. */
return PCB_LATEST;
#else
PCBVersion version = readPCBVersionInMemory();
return (version == k_alternateBlankVersion ? 0 : version);
#endif
}
PCBVersion readPCBVersionInMemory() {
return ~(*reinterpret_cast<const PCBVersion *>(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex)));
}
void writePCBVersion(PCBVersion version) {
uint8_t * destination = reinterpret_cast<uint8_t *>(InternalFlash::Config::OTPAddress(k_pcbVersionOTPIndex));
PCBVersion formattedVersion = ~version;
InternalFlash::WriteMemory(destination, reinterpret_cast<uint8_t *>(&formattedVersion), sizeof(formattedVersion));
}
void lockPCBVersion() {
uint8_t * destination = reinterpret_cast<uint8_t *>(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex));
uint8_t zero = 0;
InternalFlash::WriteMemory(destination, &zero, sizeof(zero));
}
bool pcbVersionIsLocked() {
return *reinterpret_cast<const uint8_t *>(InternalFlash::Config::OTPLockAddress(k_pcbVersionOTPIndex)) == 0;
}
}
}
}

View File

@@ -0,0 +1,104 @@
#include "cache.h"
namespace Ion {
namespace Device {
namespace Cache {
using namespace Regs;
void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disable) {
// Select Level 1 data cache
CORTEX.CSSELR()->set(0);
dsb();
// Disable D-Cache
if (disable) {
CORTEX.CCR()->setDC(false);
dsb();
}
// Pick the right DC??SW register according to invalidate/disable parameters
volatile CORTEX::DCSW * target = nullptr;
if (clean && invalidate) {
target = CORTEX.DCCISW();
} else if (clean) {
target = CORTEX.DCCSW();
} else {
assert(invalidate);
target = CORTEX.DCISW();
}
class CORTEX::CCSIDR ccsidr = CORTEX.CCSIDR()->get();
uint32_t sets = ccsidr.getNUMSETS();
uint32_t ways = ccsidr.getASSOCIATIVITY();
for (int set = sets; set >= 0; set--) {
for (int way = ways; way >= 0; way--) {
class CORTEX::DCSW dcsw;
dcsw.setSET(set);
dcsw.setWAY(way);
target->set(dcsw);
}
}
dsb();
isb();
}
void enable() {
enableICache();
enableDCache();
}
void disable() {
disableICache();
disableDCache();
}
void invalidateDCache() {
privateCleanInvalidateDisableDCache(false, true, false);
}
void cleanDCache() {
privateCleanInvalidateDisableDCache(true, false, false);
}
void enableDCache() {
invalidateDCache();
CORTEX.CCR()->setDC(true); // Enable D-cache
dsb();
isb();
}
void disableDCache() {
privateCleanInvalidateDisableDCache(true, true, true);
}
void invalidateICache() {
dsb();
isb();
CORTEX.ICIALLU()->set(0); // Invalidate I-cache
dsb();
isb();
}
void enableICache() {
invalidateICache();
CORTEX.CCR()->setIC(true); // Enable I-cache
dsb();
isb();
}
void disableICache() {
dsb();
isb();
CORTEX.CCR()->setIC(false); // Disable I-cache
CORTEX.ICIALLU()->set(0); // Invalidate I-cache
dsb();
isb();
}
}
}
}

View File

@@ -0,0 +1,46 @@
#ifndef ION_DEVICE_N0110_CACHE_H
#define ION_DEVICE_N0110_CACHE_H
#include <regs/regs.h>
namespace Ion {
namespace Device {
namespace Cache {
/* Data memory barrier
* Ensures that all explicit memory accesses that appear in program order before
* the DMB instruction are observed before any explicit memory accesses that
* appear in program order after the DMB instruction */
inline void dmb() {
asm volatile("dmb 0xF":::"memory");
}
/* Data synchronisation barrier
* Ensures that the processor stalls until the memory write is complete */
inline void dsb() {
asm volatile("dsb 0xF":::"memory");
}
/* Instructions synchronisation barrier
* Ensures that the subsequent instructions are loaded in the new context */
inline void isb() {
asm volatile("isb 0xF":::"memory");
}
void enable();
void disable();
void invalidateDCache();
void cleanDCache();
void enableDCache();
void disableDCache();
void invalidateICache();
void enableICache();
void disableICache();
}
}
}
#endif

View File

@@ -0,0 +1,25 @@
#ifndef ION_DEVICE_N0110_CONFIG_BACKLIGHT_H
#define ION_DEVICE_N0110_CONFIG_BACKLIGHT_H
#include <regs/regs.h>
/* Pin | Role | Mode | Function
* -----+-------------------+-----------------------+----------
* PE0 | Backlight Enable | Output |
*/
namespace Ion {
namespace Device {
namespace Backlight {
namespace Config {
using namespace Regs;
constexpr static GPIOPin BacklightPin = GPIOPin(GPIOE, 0);
}
}
}
}
#endif

View File

@@ -0,0 +1,30 @@
#ifndef ION_DEVICE_N0110_CONFIG_BATTERY_H
#define ION_DEVICE_N0110_CONFIG_BATTERY_H
#include <regs/regs.h>
/* Pin | Role | Mode | Function
* -----+-------------------+-----------------------+----------
* PE3 | BAT_CHRG | Input, pulled up | Low = charging, high = full
* PB1 | VBAT_SNS | Analog | ADC1_1
*/
namespace Ion {
namespace Device {
namespace Battery {
namespace Config {
using namespace Regs;
constexpr static GPIOPin ChargingPin = GPIOPin(GPIOE, 3);
constexpr static GPIOPin ADCPin = GPIOPin(GPIOB, 1);
constexpr uint8_t ADCChannel = 9;
constexpr float ADCReferenceVoltage = 2.8f;
constexpr float ADCDividerBridgeRatio = 2.0f;
}
}
}
}
#endif

View File

@@ -0,0 +1,68 @@
#ifndef ION_DEVICE_N0110_CONFIG_CLOCKS_H
#define ION_DEVICE_N0110_CONFIG_CLOCKS_H
#include <regs/regs.h>
namespace Ion {
namespace Device {
namespace Clocks {
namespace Config {
/* If you want to considerably slow down the whole machine uniformely, which
* can be very useful to diagnose performance issues, change the PLL
* configuration to:
* PLL_M = 8
* PLL_N = 192
* PLL_P_Reg = Regs::RCC::PLLCFGR::PLLP::PLLP8
* PLL_Q = 4
*
* SYSCLK and HCLK will be set to 24 MHz.
* Note that even booting takes a few seconds, so don't be surprised
* if the screen is black for a short while upon booting. */
constexpr static int HSE = 8;
constexpr static int PLL_M = 8;
constexpr static int PLL_N = 384;
constexpr static Regs::RCC::PLLCFGR::PLLP PLL_P_Reg = Regs::RCC::PLLCFGR::PLLP::PLLP2;
constexpr static int PLL_P = ((int)PLL_P_Reg | 1) << 1;
constexpr static int PLL_Q = 8;
constexpr static int SYSCLKFrequency = ((HSE/PLL_M)*PLL_N)/PLL_P;
constexpr static int AHBPrescaler = 1;
/* To slow down the whole system, we prescale the AHB clock.
* We could divide the system clock by 512. However, the HCLK clock
* frequency must be >= 14.2MHz and <=216 MHz which forces the
* AHBPrescaler to be below 192MHz/14.2MHz~13.5. */
constexpr static Regs::RCC::CFGR::AHBPrescaler AHBLowFrequencyPrescalerReg = Regs::RCC::CFGR::AHBPrescaler::SysClkDividedBy8;
constexpr static int AHBLowFrequencyPrescaler = 8;
constexpr static int HCLKFrequency = SYSCLKFrequency/AHBPrescaler;
static_assert(HCLKFrequency == 192, "HCLK frequency changed!");
constexpr static int HCLKLowFrequency = SYSCLKFrequency/AHBLowFrequencyPrescaler;
constexpr static int AHBFrequency = HCLKFrequency;
//constexpr static int AHBLowFrequency = HCLKLowFrequency;
constexpr static Regs::RCC::CFGR::APBPrescaler APB1PrescalerReg = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy4;
constexpr static int APB1Prescaler = 4;
//constexpr static int APB1Frequency = HCLKFrequency/APB1Prescaler;
constexpr static int APB1LowFrequency = HCLKLowFrequency/APB1Prescaler;
//constexpr static int APB1TimerFrequency = 2*APB1Frequency;
constexpr static int APB1TimerLowFrequency = 2*APB1LowFrequency;
constexpr static Regs::RCC::CFGR::APBPrescaler APB2PrescalerReg = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy2;
/* According to AN4850 about Spread Spectrum clock generation
* MODPER = round[HSE/(4 x fMOD)] with fMOD the target modulation frequency. */
constexpr static int fMod = 8; // in KHz. Must be <= 10KHz
constexpr static uint32_t SSCG_MODPER = HSE*1000/(4*fMod); // *1000 to put HSE in KHz
/* According to the USB specification 2, "For full-speed only functions, the
* required data-rate when transmitting (TFDRATE) is 12.000 Mb/s ±0.25%". */
constexpr static double modulationDepth = 0.25; // Must be (0.25% <= md <= 2%)
// INCSTEP = round[(2^15 -1)xmdxPLLN)/(100x5xMODPER)
constexpr static uint32_t SSCG_INCSTEP = (32767*modulationDepth*PLL_N)/(1.0*100*5*SSCG_MODPER);
static_assert(SSCG_MODPER == 250, "SSCG_MODPER changed");
static_assert(SSCG_INCSTEP == 25, "SSCG_INCSTEP changed");
static_assert(SSCG_INCSTEP * SSCG_MODPER < 32767, "Wrong values for the Spread spectrun clock generator");
}
}
}
}
#endif

View File

@@ -0,0 +1,32 @@
#ifndef ION_DEVICE_N0110_CONFIG_CONSOLE_H
#define ION_DEVICE_N0110_CONFIG_CONSOLE_H
#include <regs/regs.h>
namespace Ion {
namespace Device {
namespace Console {
namespace Config {
using namespace Regs;
constexpr static USART Port = USART(6);
constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 7);
constexpr static GPIOPin TxPin = GPIOPin(GPIOC, 6);
constexpr static GPIO::AFR::AlternateFunction AlternateFunction = GPIO::AFR::AlternateFunction::AF8;
/* The baud rate of the UART is set by the following equation:
* BaudRate = f/USARTDIV, where f is the clock frequency and USARTDIV a divider.
* In other words, USARTDIV = f/BaudRate. All frequencies in Hz.
*
* In our case, we configure the minicom to use a 115200 BaudRate and
* f = fAPB2 = 96 MHz, so USARTDIV = 833.333 */
constexpr static int USARTDIVValue = 833;
}
}
}
}
#endif

View File

@@ -0,0 +1,38 @@
#ifndef ION_DEVICE_N0110_CONFIG_DISPLAY_H
#define ION_DEVICE_N0110_CONFIG_DISPLAY_H
#include <regs/regs.h>
namespace Ion {
namespace Device {
namespace Display {
namespace Config {
using namespace Regs;
constexpr static GPIOPin FSMCPins[] = {
GPIOPin(GPIOD, 0), GPIOPin(GPIOD, 1), GPIOPin(GPIOD, 4), GPIOPin(GPIOD, 5),
GPIOPin(GPIOD, 7), GPIOPin(GPIOD, 8), GPIOPin(GPIOD, 9), GPIOPin(GPIOD, 10),
GPIOPin(GPIOD, 11), GPIOPin(GPIOD, 14), GPIOPin(GPIOD, 15), GPIOPin(GPIOE, 7),
GPIOPin(GPIOE, 8), GPIOPin(GPIOE, 9), GPIOPin(GPIOE, 10), GPIOPin(GPIOE, 11),
GPIOPin(GPIOE, 12), GPIOPin(GPIOE, 13), GPIOPin(GPIOE, 14), GPIOPin(GPIOE, 15),
};
constexpr static GPIOPin PowerPin = GPIOPin(GPIOC, 8);
constexpr static GPIOPin ResetPin = GPIOPin(GPIOE, 1);
constexpr static GPIOPin ExtendedCommandPin = GPIOPin(GPIOD, 6);
constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 11);
constexpr static DMA DMAEngine = DMA2;
constexpr static int DMAStream = 0;
constexpr static int HCLKFrequencyInMHz = 192;
constexpr static bool DisplayInversion = true;
}
}
}
}
#endif

View File

@@ -0,0 +1,30 @@
#ifndef ION_DEVICE_N0110_CONFIG_EXAM_MODE_H
#define ION_DEVICE_N0110_CONFIG_EXAM_MODE_H
namespace Ion {
namespace ExamMode {
namespace Config {
// TODO: factorize the macro with equivalent macro on N100
#define byte4 0xFF, 0xFF, 0xFF, 0xFF
#define byte8 byte4, byte4
#define byte16 byte8, byte8
#define byte32 byte16, byte16
#define byte64 byte32, byte32
#define byte128 byte64, byte64
#define byte256 byte128, byte128
#define byte512 byte256, byte256
#define byte1K byte512, byte512
#define byte2K byte1K, byte1K
#define byte4K byte2K, byte2K
#define EXAM_BUFFER_CONTENT byte4K
constexpr static int ExamModeBufferSize = 4*1024;
}
}
}
#endif

View File

@@ -0,0 +1,45 @@
#ifndef ION_DEVICE_N0110_CONFIG_EXTERNAL_FLASH_H
#define ION_DEVICE_N0110_CONFIG_EXTERNAL_FLASH_H
#include <regs/regs.h>
/* Pin | Role | Mode | Function
* -----+----------------------+-----------------------+-----------------
* PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK
* PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS
* PE2 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI_BK1_IO2
* PC9 | QUADSPI BK1_IO0/SO | Alternate Function 9 | QUADSPI_BK1_IO0
* PD12 | QUADSPI BK1_IO1/SI | Alternate Function 9 | QUADSPI_BK1_IO1
* PD13 | QUADSPI BK1_IO3/HOLD | Alternate Function 9 | QUADSPI_BK1_IO3
*/
namespace Ion {
namespace Device {
namespace ExternalFlash {
namespace Config {
using namespace Regs;
constexpr static uint32_t StartAddress = 0x90000000;
constexpr static uint32_t EndAddress = 0x90800000;
constexpr static int NumberOf4KSectors = 8;
constexpr static int NumberOf32KSectors = 1;
constexpr static int NumberOf64KSectors = 128 - 1;
constexpr static int NumberOfSectors = NumberOf4KSectors + NumberOf32KSectors + NumberOf64KSectors;
constexpr static AFGPIOPin Pins[] = {
AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast),
AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast),
AFGPIOPin(GPIOC, 9, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast),
AFGPIOPin(GPIOD, 12, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast),
AFGPIOPin(GPIOD, 13, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast),
AFGPIOPin(GPIOE, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast),
};
}
}
}
}
#endif

View File

@@ -0,0 +1,31 @@
#ifndef ION_DEVICE_N0110_CONFIG_INTERNAL_FLASH_H
#define ION_DEVICE_N0110_CONFIG_INTERNAL_FLASH_H
#include <regs/regs.h>
namespace Ion {
namespace Device {
namespace InternalFlash {
namespace Config {
constexpr static uint32_t StartAddress = 0x08000000;
constexpr static uint32_t EndAddress = 0x08010000;
constexpr static int NumberOfSectors = 4;
constexpr static uint32_t SectorAddresses[NumberOfSectors+1] = {
0x08000000, 0x08004000, 0x08008000, 0x0800C000,
0x08010000
};
constexpr static uint32_t OTPStartAddress = 0x1FF07800;
constexpr static uint32_t OTPLocksAddress = 0x1FF07A00;
constexpr static int NumberOfOTPBlocks = 16;
constexpr static uint32_t OTPBlockSize = 0x20;
constexpr uint32_t OTPAddress(int block) { return OTPStartAddress + block * OTPBlockSize; };
constexpr uint32_t OTPLockAddress(int block) { return OTPLocksAddress + block; }
}
}
}
}
#endif

View File

@@ -0,0 +1,75 @@
#ifndef ION_DEVICE_N0110_CONFIG_KEYBOARD_H
#define ION_DEVICE_N0110_CONFIG_KEYBOARD_H
#include <regs/regs.h>
#include <ion/keyboard.h>
/* Pin | Role | Mode
* -----+-------------------+--------------------
* PC0 | Keyboard column 1 | Input, pulled up
* PC1 | Keyboard column 2 | Input, pulled up
* PC2 | Keyboard column 3 | Input, pulled up
* PC3 | Keyboard column 4 | Input, pulled up
* PC4 | Keyboard column 5 | Input, pulled up
* PC5 | Keyboard column 6 | Input, pulled up
* PA1 | Keyboard row A | Output, open drain
* PA0 | Keyboard row B | Output, open drain
* PA2 | Keyboard row C | Output, open drain
* PA3 | Keyboard row D | Output, open drain
* PA4 | Keyboard row E | Output, open drain
* PA5 | Keyboard row F | Output, open drain
* PA6 | Keyboard row G | Output, open drain
* PA7 | Keyboard row H | Output, open drain
* PA8 | Keyboard row I | Output, open drain
*
* The keyboard is a matrix that is laid out as follow:
*
* -+------+------+------+------+------+------+
* | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 |
* -+------+------+------+------+------+------+
* | K_B1 | | K_B3 | | | |
* -+------+------+------+------+------+------+
* | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 |
* -+------+------+------+------+------+------+
* | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 |
* -+------+------+------+------+------+------+
* | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 |
* -+------+------+------+------+------+------+
* | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | |
* -+------+------+------+------+------+------+
* | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | |
* -+------+------+------+------+------+------+
* | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | |
* -+------+------+------+------+------+------+
* | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | |
* -+------+------+------+------+------+------|
*/
namespace Ion {
namespace Device {
namespace Keyboard {
namespace Config {
using namespace Regs;
constexpr GPIO RowGPIO = GPIOA;
constexpr uint8_t numberOfRows = 9;
constexpr uint8_t RowPins[numberOfRows] = {1, 0, 2, 3, 4, 5, 6, 7, 8};
constexpr GPIO ColumnGPIO = GPIOC;
constexpr uint8_t numberOfColumns = 6;
constexpr uint8_t ColumnPins[numberOfColumns] = {0, 1, 2, 3, 4, 5};
/* Undefined keys numbers are: 7, 9, 10, 11, 35, 41, 47 and 53
* Therefore we want to make sure those bits are forced to zero in
* whatever value we return. */
inline uint64_t ValidKeys(uint64_t state) {
return state & 0x1F7DF7FFFFF17F;
}
}
}
}
}
#endif

View File

@@ -0,0 +1,28 @@
#ifndef ION_DEVICE_N0110_CONFIG_LED_H
#define ION_DEVICE_N0110_CONFIG_LED_H
#include <regs/regs.h>
namespace Ion {
namespace Device {
namespace LED {
namespace Config {
using namespace Regs;
static constexpr int RedChannel = 1;
static constexpr int GreenChannel = 2;
static constexpr int BlueChannel = 3;
constexpr static AFGPIOPin RGBPins[] = {
AFGPIOPin(GPIOB, 4, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Low), // RED
AFGPIOPin(GPIOB, 5, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Low), // GREEN
AFGPIOPin(GPIOB, 0, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Low) // BLUE
};
}
}
}
}
#endif

View File

@@ -0,0 +1,18 @@
#ifndef ION_DEVICE_N0110_CONFIG_SERIAL_NUMBER_H
#define ION_DEVICE_N0110_CONFIG_SERIAL_NUMBER_H
#include <regs/regs.h>
namespace Ion {
namespace Device {
namespace SerialNumber {
namespace Config {
constexpr uint32_t UniqueDeviceIDAddress = 0x1FF07A10;
}
}
}
}
#endif

View File

@@ -0,0 +1,24 @@
#ifndef ION_DEVICE_N0110_CONFIG_SWD_H
#define ION_DEVICE_N0110_CONFIG_SWD_H
#include <regs/regs.h>
namespace Ion {
namespace Device {
namespace SWD {
namespace Config {
using namespace Regs;
constexpr static AFGPIOPin Pins[] = {
AFGPIOPin(GPIOA, 13, GPIO::AFR::AlternateFunction::AF0, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High),
AFGPIOPin(GPIOA, 14, GPIO::AFR::AlternateFunction::AF0, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High),
AFGPIOPin(GPIOB, 3, GPIO::AFR::AlternateFunction::AF0, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High),
};
}
}
}
}
#endif

View File

@@ -0,0 +1,19 @@
#ifndef ION_DEVICE_N0110_CONFIG_TIMING_H
#define ION_DEVICE_N0110_CONFIG_TIMING_H
#include <regs/regs.h>
namespace Ion {
namespace Device {
namespace Timing {
namespace Config {
constexpr static int LoopsPerMillisecond = 4811;
constexpr static int LoopsPerMicrosecond = 38;
}
}
}
}
#endif

View File

@@ -0,0 +1,31 @@
#ifndef ION_DEVICE_N0110_CONFIG_USB_H
#define ION_DEVICE_N0110_CONFIG_USB_H
#include <regs/regs.h>
namespace Ion {
namespace Device {
namespace USB {
namespace Config {
using namespace Regs;
/* On the STM32F730, PA9 does not actually support alternate function 10.
* However, because of the wiring of the USB connector on old N0110, detection
* of when the device is plugged required the use of this undocumented setting.
* After the revision of the USB connector and ESD protection, we can now
* follow the specification and configure the Vbus pin as a floating-input GPIO.
*/
constexpr static AFGPIOPin VbusPin = AFGPIOPin(GPIOA, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast);
constexpr static AFGPIOPin DmPin = AFGPIOPin(GPIOA, 11, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast);
constexpr static AFGPIOPin DpPin = AFGPIOPin(GPIOA, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::Fast);
constexpr static const char * InterfaceStringDescriptor = "@Flash/0x08000000/04*016Kg/0x90000000/08*004Kg,01*032Kg,63*064Kg,64*064Kg";
}
}
}
}
#endif

View File

@@ -0,0 +1,520 @@
#include <drivers/external_flash.h>
#include <drivers/cache.h>
#include <drivers/config/external_flash.h>
#include <drivers/config/clocks.h>
#include <ion/timing.h>
namespace Ion {
namespace Device {
namespace ExternalFlash {
using namespace Regs;
/* The external flash and the Quad-SPI peripheral support several operating
* modes, corresponding to different numbers of signals used to communicate
* during each phase of the command sequence.
*
* Mode name for | Number of signals used during each phase:
* external flash | Instruction | Address | Alt. bytes | Data
* ----------------+-------------+---------+------------+------
* Standard SPI | 1 | 1 | 1 | 1
* Dual-Output SPI | 1 | 1 | 1 | 2
* Dual-I/O SPI | 1 | 2 | 2 | 2
* Quad-Output SPI | 1 | 1 | 1 | 4
* Quad-I/O SPI | 1 | 4 | 4 | 4
* QPI | 4 | 4 | 4 | 4
*
* The external flash supports clock frequencies up to 104MHz for all
* instructions, except for Read Data (0x03) which is supported up to 50Mhz.
*
*
* Quad-SPI block diagram
*
* +----------------------+ +------------+
* | Quad-SPI | | |
* | peripheral | | External |
* | | read | flash |
* AHB <-- | data <-- 32-byte | <-- | memory |
* matrix --> | register --> FIFO | --> | |
* +----------------------+ write +------------+
*
* Any data transmitted to or from the external flash memory go through a
* 32-byte FIFO.
*
* Read or write operations are performed in burst mode, that is, after any data
* byte is transmitted between the Quad-SPI and the flash memory, the latter
* automatically increments the specified address and the next byte to read or
* write is respectively pushed in or popped from the FIFO.
* And so on, as long as the clock continues.
*
* If the FIFO gets full in a read operation or
* if the FIFO gets empty in a write operation,
* the operation stalls and CLK stays low until firmware services the FIFO.
*
* If the FIFO gets full in a write operation, the operation is stalled until
* the FIFO has enough space to accept the amount of data being written.
* If the FIFO does not have as many bytes as requested by the read operation
* and if BUSY=1, the operation is stalled until enough data is present or until
* the transfer is complete, whichever happens first. */
enum class Command : uint8_t {
WriteStatusRegister = 0x01,
PageProgram = 0x02, // Program previously erased memory areas as being "0"
ReadData = 0x03,
ReadStatusRegister1 = 0x05,
WriteEnable = 0x06,
Erase4KbyteBlock = 0x20,
WriteStatusRegister2 = 0x31,
QuadPageProgramW25Q64JV = 0x32,
QuadPageProgramAT25F641 = 0x33,
ReadStatusRegister2 = 0x35,
Erase32KbyteBlock = 0x52,
EnableReset = 0x66,
Reset = 0x99,
ReadJEDECID = 0x9F,
ReleaseDeepPowerDown = 0xAB,
DeepPowerDown = 0xB9,
ChipErase = 0xC7, // Erase the whole chip or a 64-Kbyte block as being "1"
Erase64KbyteBlock = 0xD8,
FastReadQuadIO = 0xEB
};
static constexpr uint8_t NumberOfAddressBitsIn64KbyteBlock = 16;
static constexpr uint8_t NumberOfAddressBitsIn32KbyteBlock = 15;
static constexpr uint8_t NumberOfAddressBitsIn4KbyteBlock = 12;
class ExternalFlashStatusRegister {
public:
class StatusRegister1 : public Register8 {
public:
using Register8::Register8;
REGS_BOOL_FIELD_R(BUSY, 0);
};
class StatusRegister2 : public Register8 {
public:
using Register8::Register8;
REGS_BOOL_FIELD(QE, 1);
};
};
class OperatingModes {
public:
constexpr OperatingModes(
QUADSPI::CCR::OperatingMode instruction,
QUADSPI::CCR::OperatingMode address,
QUADSPI::CCR::OperatingMode data) :
m_instructionOperatingMode(instruction),
m_addressOperatingMode(address),
m_dataOperatingMode(data)
{}
QUADSPI::CCR::OperatingMode instructionOperatingMode() const { return m_instructionOperatingMode; }
QUADSPI::CCR::OperatingMode addressOperatingMode() const { return m_addressOperatingMode; }
QUADSPI::CCR::OperatingMode dataOperatingMode() const { return m_dataOperatingMode; }
private:
QUADSPI::CCR::OperatingMode m_instructionOperatingMode;
QUADSPI::CCR::OperatingMode m_addressOperatingMode;
QUADSPI::CCR::OperatingMode m_dataOperatingMode;
};
/* W25Q64JV does not implement QPI-4-4-4, so we always send the instructions on
* one wire only.*/
static constexpr OperatingModes sOperatingModes100(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData, QUADSPI::CCR::OperatingMode::NoData);
static constexpr OperatingModes sOperatingModes101(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData, QUADSPI::CCR::OperatingMode::Single);
static constexpr OperatingModes sOperatingModes110(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::NoData);
static constexpr OperatingModes sOperatingModes111(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single);
static constexpr OperatingModes sOperatingModes114(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Quad);
static constexpr OperatingModes sOperatingModes144(QUADSPI::CCR::OperatingMode::Single, QUADSPI::CCR::OperatingMode::Quad, QUADSPI::CCR::OperatingMode::Quad);
static QUADSPI::CCR::OperatingMode sOperatingMode = QUADSPI::CCR::OperatingMode::Single;
static constexpr int ClockFrequencyDivisor = 2; // F(QUADSPI) = F(AHB) / ClockFrequencyDivisor
static constexpr int FastReadQuadIODummyCycles = 4; // Must be 4 for W25Q64JV (Fig 24.A page 34) and for AT25F641 (table 7.19 page 28)
/* According to datasheets, the CS signal should stay high (deselect the device)
* for t_SHSL = 50ns at least.
* -> Max of 30ns (see AT25F641 Sections 8.7 and 8.8),
* 10ns and 50ns (see W25Q64JV Section 9.6). */
static constexpr float ChipSelectHighTimeInNanoSeconds = 50.0f;
static void send_command_full(
QUADSPI::CCR::FunctionalMode functionalMode,
OperatingModes operatingModes,
Command c,
uint8_t * address,
uint32_t altBytes,
size_t numberOfAltBytes,
uint8_t dummyCycles,
uint8_t * data,
size_t dataLength);
static inline void send_command(Command c) {
send_command_full(
QUADSPI::CCR::FunctionalMode::IndirectWrite,
sOperatingModes100,
c,
reinterpret_cast<uint8_t *>(FlashAddressSpaceSize),
0, 0,
0,
nullptr, 0);
}
static inline void send_write_command(Command c, uint8_t * address, const uint8_t * data, size_t dataLength, OperatingModes operatingModes) {
send_command_full(
QUADSPI::CCR::FunctionalMode::IndirectWrite,
operatingModes,
c,
address,
0, 0,
0,
const_cast<uint8_t *>(data), dataLength);
}
static inline void send_read_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength) {
send_command_full(
QUADSPI::CCR::FunctionalMode::IndirectRead,
sOperatingModes101,
c,
address,
0, 0,
0,
data, dataLength);
}
static inline void wait() {
/* The DSB instruction guarantees the completion of a write operation before
* polling the status register. */
Cache::dsb();
ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0);
do {
send_read_command(Command::ReadStatusRegister1, reinterpret_cast<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(&statusRegister1), sizeof(statusRegister1));
} while (statusRegister1.getBUSY());
}
static void set_as_memory_mapped() {
/* In memory-mapped mode, all AHB masters may access the external flash memory as an internal one:
* the programmed instruction is sent automatically whenever an AHB master reads in the Quad-SPI flash bank area.
* (The QUADSPI_DLR register has no meaning and any access to QUADSPI_DR returns zero.)
*
* To anticipate sequential reads, the nCS signal is maintained low so as to
* keep the read operation active and prefetch the subsequent bytes in the FIFO.
*
* It goes low, only if the low-power timeout counter is enabled.
* (Flash memories tend to consume more when nCS is held low.) */
send_command_full(
QUADSPI::CCR::FunctionalMode::MemoryMapped,
sOperatingModes144,
Command::FastReadQuadIO,
reinterpret_cast<uint8_t *>(FlashAddressSpaceSize),
0xA0, 1,
FastReadQuadIODummyCycles,
nullptr, 0
);
}
static void unset_memory_mapped_mode() {
/* Reset Continuous Read Mode Bits before issuing normal instructions. */
uint8_t dummyData;
send_command_full(
QUADSPI::CCR::FunctionalMode::IndirectRead,
sOperatingModes144,
Command::FastReadQuadIO,
0,
~(0xA0), 1,
FastReadQuadIODummyCycles,
&dummyData, 1
);
}
static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, OperatingModes operatingModes, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) {
/* According to ST's Errata Sheet ES0360, "Wrong data can be read in
* memory-mapped after an indirect mode operation". This is the workaround. */
if (functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) {
QUADSPI::CCR::FunctionalMode previousMode = QUADSPI.CCR()->getFMODE();
if (previousMode == QUADSPI::CCR::FunctionalMode::IndirectWrite || previousMode == QUADSPI::CCR::FunctionalMode::IndirectRead) {
// Reset the address register
QUADSPI.AR()->set(0); // No write to DR should be done after this
if (previousMode == QUADSPI::CCR::FunctionalMode::IndirectRead) {
// Make an abort request to stop the reading and clear the busy bit
QUADSPI.CR()->setABORT(true);
while (QUADSPI.CR()->getABORT()) {
}
}
}
} else if (QUADSPI.CCR()->getFMODE() == QUADSPI::CCR::FunctionalMode::MemoryMapped) {
/* "BUSY goes high as soon as the first memory-mapped access occurs. Because
* of the prefetch operations, BUSY does not fall until there is a timeout,
* there is an abort, or the peripheral is disabled". (From the Reference
* Manual)
* If we are leaving memory-mapped mode, we send an abort to clear BUSY. */
QUADSPI.CR()->setABORT(true);
while (QUADSPI.CR()->getABORT()) {
}
}
assert(QUADSPI.CCR()->getFMODE() != QUADSPI::CCR::FunctionalMode::MemoryMapped || QUADSPI.SR()->getBUSY() == 0);
class QUADSPI::CCR ccr(0);
ccr.setFMODE(functionalMode);
if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) {
ccr.setDMODE(operatingModes.dataOperatingMode());
}
if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) {
QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0);
}
ccr.setDCYC(dummyCycles);
if (numberOfAltBytes > 0) {
ccr.setABMODE(operatingModes.addressOperatingMode()); // Seems to always be the same as address mode
ccr.setABSIZE(static_cast<QUADSPI::CCR::Size>(numberOfAltBytes - 1));
QUADSPI.ABR()->set(altBytes);
}
if (address != reinterpret_cast<uint8_t *>(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) {
ccr.setADMODE(operatingModes.addressOperatingMode());
ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes);
}
ccr.setIMODE(operatingModes.instructionOperatingMode());
ccr.setINSTRUCTION(static_cast<uint8_t>(c));
if (functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) {
ccr.setSIOO(true);
/* If the SIOO bit is set, the instruction is sent only for the first command following a write to QUADSPI_CCR.
* Subsequent command sequences skip the instruction phase, until there is a write to QUADSPI_CCR. */
}
QUADSPI.CCR()->set(ccr);
if (address != reinterpret_cast<uint8_t *>(FlashAddressSpaceSize)) {
QUADSPI.AR()->set(reinterpret_cast<uint32_t>(address));
}
if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) {
for (size_t i=0; i<dataLength; i++) {
QUADSPI.DR()->set(data[i]);
}
} else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) {
for (size_t i=0; i<dataLength; i++) {
data[i] = QUADSPI.DR()->get();
}
}
/* Wait for the command to be sent.
* "When configured in memory-mapped mode, because of the prefetch operations,
* BUSY does not fall until there is a timeout, there is an abort, or the
* peripheral is disabled.", so we do not wait if the device is in
* memory-mapped mode. */
if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) {
while (QUADSPI.SR()->getBUSY()) {
}
}
}
static void initGPIO() {
for(const AFGPIOPin & p : Config::Pins) {
p.init();
}
}
static void initQSPI() {
// Enable QUADSPI AHB3 peripheral clock
RCC.AHB3ENR()->setQSPIEN(true);
// Configure controller for target device
class QUADSPI::DCR dcr(0);
dcr.setFSIZE(NumberOfAddressBitsInChip - 1);
constexpr int ChipSelectHighTimeCycles = (ChipSelectHighTimeInNanoSeconds * static_cast<float>(Clocks::Config::AHBFrequency)) / (static_cast<float>(ClockFrequencyDivisor) * 1000.0f) + 1.0f;
dcr.setCSHT(ChipSelectHighTimeCycles - 1);
dcr.setCKMODE(true);
QUADSPI.DCR()->set(dcr);
class QUADSPI::CR cr(0);
cr.setPRESCALER(ClockFrequencyDivisor - 1);
cr.setEN(true);
QUADSPI.CR()->set(cr);
}
static void initChip() {
// Release sleep deep
send_command(Command::ReleaseDeepPowerDown);
Timing::usleep(3);
/* The chip initially expects commands in SPI mode. We need to use SPI to tell
* it to switch to QuadSPI/QPI. */
if (sOperatingMode == QUADSPI::CCR::OperatingMode::Single) {
send_command(Command::WriteEnable);
ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0);
statusRegister2.setQE(true);
wait();
send_write_command(Command::WriteStatusRegister2, reinterpret_cast<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(&statusRegister2), sizeof(statusRegister2), sOperatingModes101);
wait();
sOperatingMode = QUADSPI::CCR::OperatingMode::Quad;
}
set_as_memory_mapped();
}
void init() {
if (Config::NumberOfSectors == 0) {
return;
}
initGPIO();
initQSPI();
initChip();
}
static void shutdownGPIO() {
for(const AFGPIOPin & p : Config::Pins) {
p.group().OSPEEDR()->setOutputSpeed(p.pin(), GPIO::OSPEEDR::OutputSpeed::Low);
p.group().MODER()->setMode(p.pin(), GPIO::MODER::Mode::Analog);
p.group().PUPDR()->setPull(p.pin(), GPIO::PUPDR::Pull::None);
}
}
static void shutdownChip() {
unset_memory_mapped_mode();
// Reset
send_command(Command::EnableReset);
send_command(Command::Reset);
sOperatingMode = QUADSPI::CCR::OperatingMode::Single;
Timing::usleep(30);
// Sleep deep
send_command(Command::DeepPowerDown);
Timing::usleep(3);
}
static void shutdownQSPI() {
// Reset the controller
RCC.AHB3RSTR()->setQSPIRST(true);
RCC.AHB3RSTR()->setQSPIRST(false);
RCC.AHB3ENR()->setQSPIEN(false); // TODO: move in Device::shutdownClocks
}
void shutdown() {
if (Config::NumberOfSectors == 0) {
return;
}
shutdownChip();
shutdownQSPI();
shutdownGPIO();
}
int SectorAtAddress(uint32_t address) {
/* WARNING: this code assumes that the flash sectors are of increasing size:
* first all 4K sectors, then all 32K sectors, and finally all 64K sectors. */
int i = address >> NumberOfAddressBitsIn64KbyteBlock;
if (i > Config::NumberOf64KSectors) {
return -1;
}
if (i >= 1) {
return Config::NumberOf4KSectors + Config::NumberOf32KSectors + i - 1;
}
i = address >> NumberOfAddressBitsIn32KbyteBlock;
if (i >= 1) {
i = Config::NumberOf4KSectors + i - 1;
assert(i >= 0 && i <= Config::NumberOf32KSectors);
return i;
}
i = address >> NumberOfAddressBitsIn4KbyteBlock;
assert(i <= Config::NumberOf4KSectors);
return i;
}
void unlockFlash() {
// Warning: unset_memory_mapped_mode must be called before
send_command(Command::WriteEnable);
wait();
ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0);
ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0);
ExternalFlashStatusRegister::StatusRegister2 currentStatusRegister2(0);
send_read_command(Command::ReadStatusRegister2, reinterpret_cast<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(&currentStatusRegister2), sizeof(currentStatusRegister2));
statusRegister2.setQE(currentStatusRegister2.getQE());
uint8_t registers[] = {statusRegister1.get(), statusRegister2.get()};
send_write_command(Command::WriteStatusRegister, reinterpret_cast<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(registers), sizeof(registers), sOperatingModes101);
wait();
}
void MassErase() {
if (Config::NumberOfSectors == 0) {
return;
}
unset_memory_mapped_mode();
unlockFlash();
send_command(Command::WriteEnable);
wait();
send_command(Command::ChipErase);
wait();
set_as_memory_mapped();
}
void __attribute__((noinline)) EraseSector(int i) {
assert(i >= 0 && i < Config::NumberOfSectors);
unset_memory_mapped_mode();
unlockFlash();
send_command(Command::WriteEnable);
wait();
/* WARNING: this code assumes that the flash sectors are of increasing size:
* first all 4K sectors, then all 32K sectors, and finally all 64K sectors. */
if (i < Config::NumberOf4KSectors) {
send_write_command(Command::Erase4KbyteBlock, reinterpret_cast<uint8_t *>(i << NumberOfAddressBitsIn4KbyteBlock), nullptr, 0, sOperatingModes110);
} else if (i < Config::NumberOf4KSectors + Config::NumberOf32KSectors) {
/* If the sector is the number Config::NumberOf4KSectors, we want to write
* at the address 1 << NumberOfAddressBitsIn32KbyteBlock, hence the formula
* (i - Config::NumberOf4KSectors + 1). */
send_write_command(Command::Erase32KbyteBlock, reinterpret_cast<uint8_t *>((i - Config::NumberOf4KSectors + 1) << NumberOfAddressBitsIn32KbyteBlock), nullptr, 0, sOperatingModes110);
} else {
/* If the sector is the number
* Config::NumberOf4KSectors - Config::NumberOf32KSectors, we want to write
* at the address 1 << NumberOfAddressBitsIn32KbyteBlock, hence the formula
* (i - Config::NumberOf4KSectors - Config::NumberOf32KSectors + 1). */
send_write_command(Command::Erase64KbyteBlock, reinterpret_cast<uint8_t *>((i - Config::NumberOf4KSectors - Config::NumberOf32KSectors + 1) << NumberOfAddressBitsIn64KbyteBlock), nullptr, 0, sOperatingModes110);
}
wait();
set_as_memory_mapped();
}
void __attribute__((noinline)) WriteMemory(uint8_t * destination, const uint8_t * source, size_t length) {
if (Config::NumberOfSectors == 0) {
return;
}
unset_memory_mapped_mode();
/* Each 256-byte page of the external flash memory (contained in a previously erased area)
* may be programmed in burst mode with a single Page Program instruction.
* However, when the end of a page is reached, the addressing wraps to the beginning.
* Hence a Page Program instruction must be issued for each page. */
static constexpr size_t PageSize = 256;
uint8_t offset = reinterpret_cast<uint32_t>(destination) & (PageSize - 1);
size_t lengthThatFitsInPage = PageSize - offset;
while (length > 0) {
if (lengthThatFitsInPage > length) {
lengthThatFitsInPage = length;
}
send_command(Command::WriteEnable);
wait();
/* Some chips implement 0x32 only, others 0x33 only, we call both. This does
* not seem to affect the writing. */
send_write_command(Command::QuadPageProgramAT25F641, destination, source, lengthThatFitsInPage, sOperatingModes144);
send_write_command(Command::QuadPageProgramW25Q64JV, destination, source, lengthThatFitsInPage, sOperatingModes114);
length -= lengthThatFitsInPage;
destination += lengthThatFitsInPage;
source += lengthThatFitsInPage;
lengthThatFitsInPage = PageSize;
wait();
}
set_as_memory_mapped();
}
void JDECid(uint8_t * manufacturerID, uint8_t * memoryType, uint8_t * capacityType) {
unset_memory_mapped_mode();
struct JEDECId {
uint8_t manufacturerID;
uint8_t memoryType;
uint8_t capacityType;
};
JEDECId id;
send_read_command(Command::ReadJEDECID, reinterpret_cast<uint8_t *>(FlashAddressSpaceSize), reinterpret_cast<uint8_t *>(&id), sizeof(id));
*manufacturerID = id.manufacturerID;
*memoryType = id.memoryType;
*capacityType = id.capacityType;
set_as_memory_mapped();
}
}
}
}

View File

@@ -0,0 +1,23 @@
#include <ion/led.h>
#include <ion/battery.h>
#include <ion/usb.h>
#include <ion/exam_mode.h>
namespace Ion {
namespace LED {
KDColor updateColorWithPlugAndCharge() {
KDColor ledColor = getColor();
if (ExamMode::FetchExamMode() == 0) { // If exam mode is on, we do not update the LED with the plugged/charging state
if (USB::isPlugged()) {
ledColor = Battery::isCharging() ? KDColorOrange : KDColorGreen;
} else {
ledColor = KDColorBlack;
}
setColor(ledColor);
}
return ledColor;
}
}
}

View File

@@ -0,0 +1,61 @@
#include <drivers/board.h>
#include <drivers/power.h>
#include <drivers/keyboard.h>
#include <drivers/wakeup.h>
#include <regs/regs.h>
namespace Ion {
namespace Power {
/* We isolate the standby code that needs to be executed from the internal
* flash (because the external flash is then shut down). We forbid inlining to
* avoid inlining these instructions in the external flash. */
void standby() {
Device::Power::waitUntilOnOffKeyReleased();
Device::Power::standbyConfiguration();
Device::Board::shutdownPeripherals();
Device::Power::internalFlashStandby();
}
}
}
namespace Ion {
namespace Device {
namespace Power {
void configWakeUp() {
Device::WakeUp::onOnOffKeyDown();
Device::WakeUp::onUSBPlugging();
Device::WakeUp::onChargingEvent();
}
// Public Power methods
using namespace Device::Regs;
void standbyConfiguration() {
PWR.CR()->setPPDS(true); // Select standby when the CPU enters deepsleep
PWR.CR()->setCSBF(true); // Clear Standby flag
PWR.CSR()->setBRE(false); // Unable back up RAM (lower power consumption in standby)
PWR.CSR()->setEIWUP(false); // Unable RTC (lower power consumption in standby)
/* The pin A0 is about to be configured as a wakeup pin. However, the matrix
* keyboard connects pin A0 (row B) with other pins (column 1, column 3...).
* We thus shutdown this pins to avoid the potential pull-up on pin A0 due to
* a keyboard event. For example, if the "Home" key is down, pin A0 is
* pulled-up so enabling it as the wake up pin would trigger a wake up flag
* instantly. */
Device::Keyboard::shutdown();
#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS
PWR.CSR2()->setEWUP1(true); // Enable PA0 as wakeup pin
PWR.CR2()->setWUPP1(false); // Define PA0 (wakeup) pin polarity (rising edge)
PWR.CR2()->setCWUPF1(true); // Clear wakeup pin flag for PA0 (if device has already been in standby and woke up)
#endif
CORTEX.SCR()->setSLEEPDEEP(true); // Allow Cortex-M7 deepsleep state
}
}
}
}

View File

@@ -0,0 +1,16 @@
#ifndef ION_DEVICE_N0110_POWER_H
#define ION_DEVICE_N0110_POWER_H
#include <ion/src/device/shared/drivers/power.h>
namespace Ion {
namespace Device {
namespace Power {
void standbyConfiguration();
}
}
}
#endif

View File

@@ -0,0 +1,13 @@
#include <drivers/reset.h>
namespace Ion {
namespace Device {
namespace Reset {
void coreWhilePlugged() {
core();
}
}
}
}

View File

@@ -0,0 +1,27 @@
#include <drivers/usb.h>
#include <drivers/board.h>
#include <drivers/config/usb.h>
namespace Ion {
namespace Device {
using namespace Regs;
namespace USB {
bool useAlternateFunctionVbus() {
return Board::pcbVersion() == 0;
}
void initVbus() {
if (useAlternateFunctionVbus()) {
Config::VbusPin.init();
} else {
Config::VbusPin.group().MODER()->setMode(Config::VbusPin.pin(), GPIO::MODER::Mode::Input);
Config::VbusPin.group().PUPDR()->setPull(Config::VbusPin.pin(), GPIO::PUPDR::Pull::None);
}
}
}
}
}

View File

@@ -0,0 +1,74 @@
#include <ion.h>
#include <assert.h>
#ifndef PATCH_LEVEL
#error This file expects PATCH_LEVEL to be defined
#endif
#ifndef EPSILON_VERSION
#error This file expects EPSILON_VERSION to be defined
#endif
#ifndef OMEGA_VERSION
#error This file expects OMEGA_VERSION to be defined
#endif
#ifndef HEADER_SECTION
#define HEADER_SECTION
#endif
namespace Ion {
extern char staticStorageArea[];
}
constexpr void * storageAddress = &(Ion::staticStorageArea);
class PlatformInfo {
public:
constexpr PlatformInfo() :
m_header(Magic),
m_version{EPSILON_VERSION},
m_patchLevel{PATCH_LEVEL},
m_footer(Magic) { }
const char * version() const {
assert(m_header == Magic);
assert(m_footer == Magic);
return m_version;
}
const char * patchLevel() const {
assert(m_header == Magic);
assert(m_footer == Magic);
return m_patchLevel;
}
private:
constexpr static uint32_t Magic = 0xDEC00DF0;
constexpr static uint32_t OmegaMagic = 0xEFBEADDE;
uint32_t m_header;
const char m_version[8];
const char m_patchLevel[8];
uint32_t m_footer;
};
const PlatformInfo HEADER_SECTION platform_infos;
const char * Ion::softwareVersion() {
return platform_infos.version();
}
static const char s_omegaVersion[16] = {OMEGA_VERSION};
#ifdef OMEGA_USERNAME
static const char s_username[16] = {OMEGA_USERNAME};
#else
static const char s_username[16] = {"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"};
#endif
const char * Ion::omegaVersion() {
return s_omegaVersion;
}
const volatile char * Ion::username() {
return s_username;
}
const char * Ion::patchLevel() {
return platform_infos.patchLevel();
}

View File

@@ -0,0 +1,6 @@
#ifndef ION_DEVICE_N0110_REGS_CONFIG_CORTEX_H
#define ION_DEVICE_N0110_REGS_CONFIG_CORTEX_H
#define REGS_CORTEX_CONFIG_CACHE 1
#endif

View File

@@ -0,0 +1,6 @@
#ifndef ION_DEVICE_N0110_REGS_CONFIG_CRC_H
#define ION_DEVICE_N0110_REGS_CONFIG_CRC_H
#define REGS_CRC_CONFIG_BYTE_ACCESS 1
#endif

View File

@@ -0,0 +1,6 @@
#ifndef ION_DEVICE_N0110_REGS_CONFIG_FLASH_H
#define ION_DEVICE_N0110_REGS_CONFIG_FLASH_H
#define REGS_FLASH_CONFIG_ART 1
#endif

View File

@@ -0,0 +1,6 @@
#ifndef ION_DEVICE_N0110_REGS_CONFIG_PWR_H
#define ION_DEVICE_N0110_REGS_CONFIG_PWR_H
#define REGS_PWR_CONFIG_ADDITIONAL_FIELDS 1
#endif

View File

@@ -0,0 +1,7 @@
#ifndef ION_DEVICE_N0110_REGS_CONFIG_RCC_H
#define ION_DEVICE_N0110_REGS_CONFIG_RCC_H
#define REGS_RCC_CONFIG_F730 1
#define REGS_RCC_CONFIG_F412 0
#endif

View File

@@ -0,0 +1,6 @@
#ifndef ION_DEVICE_N0110_REGS_CONFIG_SYSCFG_H
#define ION_DEVICE_N0110_REGS_CONFIG_SYSCFG_H
#define REGS_SYSCFG_CONFIG_F412 0
#endif

View File

@@ -0,0 +1,12 @@
#ifndef ION_DEVICE_N0110_REGS_CONFIG_USART_H
#define ION_DEVICE_N0110_REGS_CONFIG_USART_H
#define REGS_USART_SR_OFFSET 0x1C
#define REGS_USART_RDR_OFFSET 0x24
#define REGS_USART_TDR_OFFSET 0x28
#define REGS_USART_BRR_OFFSET 0x0C
#define REGS_USART_CR1_OFFSET 0x00
#define REGS_USART_CR1_UE_BIT 0
#endif

View File

@@ -7,4 +7,12 @@ ion_device_src += $(addprefix ion/src/device/n0100/drivers/, \
usb.cpp \
)
ion_device_src += $(addprefix ion/src/device/n0100/boot/, \
rt0.cpp \
)
ion_device_src += $(addprefix ion/src/device/n0100/, \
platform_info.cpp \
)
LDSCRIPT ?= ion/src/device/n0100/flash.ld

View File

@@ -1,11 +1,11 @@
#include "isr.h"
#include <stdint.h>
#include <string.h>
#include <ion.h>
#include "../drivers/board.h"
#include "../drivers/rtc.h"
#include "../drivers/reset.h"
#include "../drivers/timing.h"
#include <boot/isr.h>
#include <drivers/board.h>
#include <drivers/rtc.h>
#include <drivers/reset.h>
#include <drivers/timing.h>
typedef void (*cxx_constructor)();

View File

@@ -8,4 +8,12 @@ ion_device_src += $(addprefix ion/src/device/n0110/drivers/, \
usb.cpp \
)
ion_device_src += $(addprefix ion/src/device/n0110/boot/, \
rt0.cpp \
)
ion_device_src += $(addprefix ion/src/device/n0110/, \
platform_info.cpp \
)
LDSCRIPT ?= ion/src/device/n0110/flash.ld

View File

@@ -0,0 +1,126 @@
#include <stdint.h>
#include <string.h>
#include <ion.h>
#include <boot/isr.h>
#include <drivers/board.h>
#include <drivers/rtc.h>
#include <drivers/reset.h>
#include <drivers/timing.h>
typedef void (*cxx_constructor)();
extern "C" {
extern char _data_section_start_flash;
extern char _data_section_start_ram;
extern char _data_section_end_ram;
extern char _bss_section_start_ram;
extern char _bss_section_end_ram;
extern cxx_constructor _init_array_start;
extern cxx_constructor _init_array_end;
}
void __attribute__((noinline)) abort() {
#ifdef NDEBUG
Ion::Device::Reset::core();
#else
while (1) {
}
#endif
}
/* In order to ensure that this method is execute from the external flash, we
* forbid inlining it.*/
static void __attribute__((noinline)) external_flash_start() {
/* Init the peripherals. We do not initialize the backlight in case there is
* an on boarding app: indeed, we don't want the user to see the LCD tests
* happening during the on boarding app. The backlight will be initialized
* after the Power-On Self-Test if there is one or before switching to the
* home app otherwise. */
Ion::Device::Board::initPeripherals(false);
return ion_main(0, nullptr);
}
/* This additional function call 'jump_to_external_flash' serves two purposes:
* - By default, the compiler is free to inline any function call he wants. If
* the compiler decides to inline some functions that make use of VFP
* registers, it will need to push VFP them onto the stack in calling
* function's prologue.
* Problem: in start()'s prologue, we would never had a chance to enable the
* FPU since this function is the first thing called after reset.
* We can safely assume that neither memcpy, memset, nor any Ion::Device::init*
* method will use floating-point numbers, but ion_main very well can.
* To make sure ion_main's potential usage of VFP registers doesn't bubble-up to
* start(), we isolate it in its very own non-inlined function call.
* - To avoid jumping on the external flash when it is shut down, we ensure
* there is no symbol references from the internal flash to the external
* flash except this jump. In order to do that, we isolate this
* jump in a symbol that we link in a special section separated from the
* internal flash section. We can than forbid cross references from the
* internal flash to the external flash. */
static void __attribute__((noinline)) jump_to_external_flash() {
external_flash_start();
}
/* When 'start' is executed, the external flash is supposed to be shutdown. We
* thus forbid inlining to prevent executing this code from external flash
* (just in case 'start' was to be called from the external flash). */
void __attribute__((noinline)) start() {
/* This is where execution starts after reset.
* Many things are not initialized yet so the code here has to pay attention. */
/* Copy data section to RAM
* The data section is R/W but its initialization value matters. It's stored
* in Flash, but linked as if it were in RAM. Now's our opportunity to copy
* it. Note that until then the data section (e.g. global variables) contains
* garbage values and should not be used. */
size_t dataSectionLength = (&_data_section_end_ram - &_data_section_start_ram);
memcpy(&_data_section_start_ram, &_data_section_start_flash, dataSectionLength);
/* Zero-out the bss section in RAM
* Until we do, any uninitialized global variable will be unusable. */
size_t bssSectionLength = (&_bss_section_end_ram - &_bss_section_start_ram);
memset(&_bss_section_start_ram, 0, bssSectionLength);
/* Initialize the FPU as early as possible.
* For example, static C++ objects are very likely to manipulate float values */
Ion::Device::Board::initFPU();
/* Call static C++ object constructors
* The C++ compiler creates an initialization function for each static object.
* The linker then stores the address of each of those functions consecutively
* between _init_array_start and _init_array_end. So to initialize all C++
* static objects we just have to iterate between theses two addresses and
* call the pointed function. */
#define SUPPORT_CPP_GLOBAL_CONSTRUCTORS 0
#if SUPPORT_CPP_GLOBAL_CONSTRUCTORS
for (cxx_constructor * c = &_init_array_start; c<&_init_array_end; c++) {
(*c)();
}
#else
/* In practice, static initialized objects are a terrible idea. Since the init
* order is not specified, most often than not this yields the dreaded static
* init order fiasco. How about bypassing the issue altogether? */
if (&_init_array_start != &_init_array_end) {
abort();
}
#endif
Ion::Device::Board::init();
/* At this point, we initialized clocks and the external flash but no other
* peripherals. */
jump_to_external_flash();
abort();
}
void __attribute__((interrupt, noinline)) isr_systick() {
auto t = Ion::Device::Timing::MillisElapsed;
t++;
Ion::Device::Timing::MillisElapsed = t;
}

View File

@@ -0,0 +1,109 @@
#include <ion.h>
#include <assert.h>
#ifndef PATCH_LEVEL
#error This file expects PATCH_LEVEL to be defined
#endif
#ifndef EPSILON_VERSION
#error This file expects EPSILON_VERSION to be defined
#endif
#ifndef OMEGA_VERSION
#error This file expects OMEGA_VERSION to be defined
#endif
#ifndef HEADER_SECTION
#define HEADER_SECTION
#endif
namespace Ion {
extern char staticStorageArea[];
}
constexpr void * storageAddress = &(Ion::staticStorageArea);
class PlatformInfo {
public:
constexpr PlatformInfo() :
m_header(Magic),
m_version{EPSILON_VERSION},
m_patchLevel{PATCH_LEVEL},
m_storageAddress(storageAddress),
m_storageSize(Ion::Storage::k_storageSize),
m_footer(Magic),
m_ohm_header(OmegaMagic),
m_omegaVersion{OMEGA_VERSION},
#ifdef OMEGA_USERNAME
m_username{OMEGA_USERNAME},
#else
m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"},
#endif
m_ohm_footer(OmegaMagic) { }
const char * version() const {
assert(m_storageAddress != nullptr);
assert(m_storageSize != 0);
assert(m_header == Magic);
assert(m_footer == Magic);
assert(m_ohm_header == OmegaMagic);
assert(m_ohm_footer == OmegaMagic);
return m_version;
}
const char * omegaVersion() const {
assert(m_storageAddress != nullptr);
assert(m_storageSize != 0);
assert(m_header == Magic);
assert(m_footer == Magic);
assert(m_ohm_header == OmegaMagic);
assert(m_ohm_footer == OmegaMagic);
return m_omegaVersion;
}
const volatile char * username() const volatile {
assert(m_storageAddress != nullptr);
assert(m_storageSize != 0);
assert(m_header == Magic);
assert(m_footer == Magic);
assert(m_ohm_header == OmegaMagic);
assert(m_ohm_footer == OmegaMagic);
return m_username;
}
const char * patchLevel() const {
assert(m_storageAddress != nullptr);
assert(m_storageSize != 0);
assert(m_header == Magic);
assert(m_footer == Magic);
assert(m_ohm_header == OmegaMagic);
assert(m_ohm_footer == OmegaMagic);
return m_patchLevel;
}
private:
constexpr static uint32_t Magic = 0xDEC00DF0;
constexpr static uint32_t OmegaMagic = 0xEFBEADDE;
uint32_t m_header;
const char m_version[8];
const char m_patchLevel[8];
void * m_storageAddress;
size_t m_storageSize;
uint32_t m_footer;
uint32_t m_ohm_header;
const char m_omegaVersion[16];
const volatile char m_username[16];
uint32_t m_ohm_footer;
};
const PlatformInfo HEADER_SECTION platform_infos;
const char * Ion::softwareVersion() {
return platform_infos.version();
}
const char * Ion::omegaVersion() {
return platform_infos.omegaVersion();
}
const volatile char * Ion::username() {
return platform_infos.username();
}
const char * Ion::patchLevel() {
return platform_infos.patchLevel();
}

View File

@@ -1,4 +1,3 @@
ion_device_src += $(addprefix ion/src/device/shared/boot/, \
isr.c \
rt0.cpp \
)

View File

@@ -25,6 +25,7 @@ ion_src += $(addprefix ion/src/simulator/shared/, \
keyboard.cpp \
layout.cpp \
main.cpp \
platform_info.cpp \
random.cpp \
timing.cpp \
window.cpp \

View File

@@ -0,0 +1,109 @@
#include <ion.h>
#include <assert.h>
#ifndef PATCH_LEVEL
#error This file expects PATCH_LEVEL to be defined
#endif
#ifndef EPSILON_VERSION
#error This file expects EPSILON_VERSION to be defined
#endif
#ifndef OMEGA_VERSION
#error This file expects OMEGA_VERSION to be defined
#endif
#ifndef HEADER_SECTION
#define HEADER_SECTION
#endif
namespace Ion {
extern char staticStorageArea[];
}
constexpr void * storageAddress = &(Ion::staticStorageArea);
class PlatformInfo {
public:
constexpr PlatformInfo() :
m_header(Magic),
m_version{EPSILON_VERSION},
m_patchLevel{PATCH_LEVEL},
m_storageAddress(storageAddress),
m_storageSize(Ion::Storage::k_storageSize),
m_footer(Magic),
m_ohm_header(OmegaMagic),
m_omegaVersion{OMEGA_VERSION},
#ifdef OMEGA_USERNAME
m_username{OMEGA_USERNAME},
#else
m_username{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"},
#endif
m_ohm_footer(OmegaMagic) { }
const char * version() const {
assert(m_storageAddress != nullptr);
assert(m_storageSize != 0);
assert(m_header == Magic);
assert(m_footer == Magic);
assert(m_ohm_header == OmegaMagic);
assert(m_ohm_footer == OmegaMagic);
return m_version;
}
const char * omegaVersion() const {
assert(m_storageAddress != nullptr);
assert(m_storageSize != 0);
assert(m_header == Magic);
assert(m_footer == Magic);
assert(m_ohm_header == OmegaMagic);
assert(m_ohm_footer == OmegaMagic);
return m_omegaVersion;
}
const volatile char * username() const volatile {
assert(m_storageAddress != nullptr);
assert(m_storageSize != 0);
assert(m_header == Magic);
assert(m_footer == Magic);
assert(m_ohm_header == OmegaMagic);
assert(m_ohm_footer == OmegaMagic);
return m_username;
}
const char * patchLevel() const {
assert(m_storageAddress != nullptr);
assert(m_storageSize != 0);
assert(m_header == Magic);
assert(m_footer == Magic);
assert(m_ohm_header == OmegaMagic);
assert(m_ohm_footer == OmegaMagic);
return m_patchLevel;
}
private:
constexpr static uint32_t Magic = 0xDEC00DF0;
constexpr static uint32_t OmegaMagic = 0xEFBEADDE;
uint32_t m_header;
const char m_version[8];
const char m_patchLevel[8];
void * m_storageAddress;
size_t m_storageSize;
uint32_t m_footer;
uint32_t m_ohm_header;
const char m_omegaVersion[16];
const volatile char m_username[16];
uint32_t m_ohm_footer;
};
const PlatformInfo HEADER_SECTION platform_infos;
const char * Ion::softwareVersion() {
return platform_infos.version();
}
const char * Ion::omegaVersion() {
return platform_infos.omegaVersion();
}
const volatile char * Ion::username() {
return platform_infos.username();
}
const char * Ion::patchLevel() {
return platform_infos.patchLevel();
}