From 927763bc31532e2f67f17a8bf496ff1be0dcc286 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:01:28 +0100 Subject: [PATCH 0001/1750] [ion] Initial add of the f730 folder --- ion/src/f730/Makefile | 51 +++ ion/src/f730/backlight.cpp | 85 ++++ ion/src/f730/backlight.h | 28 ++ ion/src/f730/base64.cpp | 48 +++ ion/src/f730/base64.h | 5 + ion/src/f730/battery.cpp | 88 ++++ ion/src/f730/battery.h | 35 ++ ion/src/f730/bench/Makefile | 21 + ion/src/f730/bench/bench.cpp | 46 +++ ion/src/f730/bench/bench.h | 14 + ion/src/f730/bench/command/adc.cpp | 27 ++ ion/src/f730/bench/command/backlight.cpp | 34 ++ ion/src/f730/bench/command/charge.cpp | 24 ++ ion/src/f730/bench/command/command.cpp | 47 +++ ion/src/f730/bench/command/command.h | 42 ++ ion/src/f730/bench/command/display.cpp | 74 ++++ ion/src/f730/bench/command/exit.cpp | 19 + ion/src/f730/bench/command/keyboard.cpp | 26 ++ ion/src/f730/bench/command/led.cpp | 35 ++ ion/src/f730/bench/command/mcu_serial.cpp | 23 ++ ion/src/f730/bench/command/ping.cpp | 19 + ion/src/f730/bench/command/print.cpp | 30 ++ ion/src/f730/bench/command/suspend.cpp | 22 + ion/src/f730/bench/command/vblank.cpp | 26 ++ ion/src/f730/bench/command_handler.cpp | 44 ++ ion/src/f730/bench/command_handler.h | 27 ++ ion/src/f730/bench/command_list.cpp | 22 + ion/src/f730/bench/command_list.h | 22 + ion/src/f730/boot/Makefile | 2 + ion/src/f730/boot/flash.ld | 112 ++++++ ion/src/f730/boot/isr.c | 129 ++++++ ion/src/f730/boot/isr.h | 16 + ion/src/f730/boot/rt0.cpp | 97 +++++ ion/src/f730/console.cpp | 76 ++++ ion/src/f730/console.h | 30 ++ ion/src/f730/device.cpp | 325 +++++++++++++++ ion/src/f730/device.h | 36 ++ ion/src/f730/display.cpp | 376 ++++++++++++++++++ ion/src/f730/display.h | 110 +++++ ion/src/f730/events.cpp | 93 +++++ ion/src/f730/flash.cpp | 233 +++++++++++ ion/src/f730/flash.h | 28 ++ ion/src/f730/keyboard.cpp | 112 ++++++ ion/src/f730/keyboard.h | 70 ++++ ion/src/f730/led.cpp | 133 +++++++ ion/src/f730/led.h | 48 +++ ion/src/f730/log.cpp | 15 + ion/src/f730/power.cpp | 69 ++++ ion/src/f730/regs/adc.h | 73 ++++ ion/src/f730/regs/cm4.h | 81 ++++ ion/src/f730/regs/crc.h | 27 ++ ion/src/f730/regs/dma.h | 63 +++ ion/src/f730/regs/exti.h | 34 ++ ion/src/f730/regs/flash.h | 56 +++ ion/src/f730/regs/fsmc.h | 79 ++++ ion/src/f730/regs/gpio.h | 104 +++++ ion/src/f730/regs/itm.h | 32 ++ ion/src/f730/regs/mpu.h | 71 ++++ ion/src/f730/regs/nvic.h | 37 ++ ion/src/f730/regs/otg.h | 195 +++++++++ ion/src/f730/regs/pwr.h | 25 ++ ion/src/f730/regs/rcc.h | 149 +++++++ ion/src/f730/regs/register.h | 61 +++ ion/src/f730/regs/regs.h | 25 ++ ion/src/f730/regs/rng.h | 33 ++ ion/src/f730/regs/sdio.h | 112 ++++++ ion/src/f730/regs/spi.h | 45 +++ ion/src/f730/regs/syscfg.h | 44 ++ ion/src/f730/regs/tim.h | 100 +++++ ion/src/f730/regs/usart.h | 50 +++ ion/src/f730/sd_card.cpp | 95 +++++ ion/src/f730/sd_card.h | 33 ++ ion/src/f730/stack.cpp | 10 + ion/src/f730/swd.cpp | 24 ++ ion/src/f730/swd.h | 28 ++ ion/src/f730/timing.cpp | 36 ++ ion/src/f730/timing.h | 16 + ion/src/f730/usb.cpp | 187 +++++++++ ion/src/f730/usb.h | 34 ++ ion/src/f730/usb/Makefile | 67 ++++ ion/src/f730/usb/boot.cpp | 2 + ion/src/f730/usb/calculator.cpp | 94 +++++ ion/src/f730/usb/calculator.h | 166 ++++++++ ion/src/f730/usb/dfu.ld | 50 +++ ion/src/f730/usb/dfu_interface.cpp | 278 +++++++++++++ ion/src/f730/usb/dfu_interface.h | 172 ++++++++ ion/src/f730/usb/dfu_relocated.cpp | 80 ++++ ion/src/f730/usb/dfu_xip.cpp | 12 + ion/src/f730/usb/flasher.cpp | 13 + ion/src/f730/usb/flasher.ld | 77 ++++ .../usb/stack/descriptor/bos_descriptor.cpp | 22 + .../usb/stack/descriptor/bos_descriptor.h | 36 ++ .../descriptor/configuration_descriptor.cpp | 26 ++ .../descriptor/configuration_descriptor.h | 48 +++ .../f730/usb/stack/descriptor/descriptor.cpp | 15 + .../f730/usb/stack/descriptor/descriptor.h | 32 ++ .../device_capability_descriptor.cpp | 18 + .../descriptor/device_capability_descriptor.h | 31 ++ .../stack/descriptor/device_descriptor.cpp | 29 ++ .../usb/stack/descriptor/device_descriptor.h | 62 +++ .../descriptor/dfu_functional_descriptor.cpp | 21 + .../descriptor/dfu_functional_descriptor.h | 38 ++ .../extended_compat_id_descriptor.cpp | 61 +++ .../extended_compat_id_descriptor.h | 43 ++ .../stack/descriptor/interface_descriptor.cpp | 27 ++ .../stack/descriptor/interface_descriptor.h | 55 +++ .../language_id_string_descriptor.cpp | 19 + .../language_id_string_descriptor.h | 25 ++ .../microsoft_os_string_descriptor.cpp | 19 + .../microsoft_os_string_descriptor.h | 30 ++ .../platform_device_capability_descriptor.cpp | 21 + .../platform_device_capability_descriptor.h | 47 +++ .../stack/descriptor/string_descriptor.cpp | 25 ++ .../usb/stack/descriptor/string_descriptor.h | 28 ++ .../usb/stack/descriptor/url_descriptor.cpp | 25 ++ .../usb/stack/descriptor/url_descriptor.h | 36 ++ .../descriptor/webusb_platform_descriptor.cpp | 22 + .../descriptor/webusb_platform_descriptor.h | 37 ++ ion/src/f730/usb/stack/device.cpp | 156 ++++++++ ion/src/f730/usb/stack/device.h | 66 +++ ion/src/f730/usb/stack/endpoint0.cpp | 344 ++++++++++++++++ ion/src/f730/usb/stack/endpoint0.h | 79 ++++ ion/src/f730/usb/stack/interface.cpp | 66 +++ ion/src/f730/usb/stack/interface.h | 42 ++ ion/src/f730/usb/stack/request_recipient.cpp | 32 ++ ion/src/f730/usb/stack/request_recipient.h | 29 ++ ion/src/f730/usb/stack/setup_packet.cpp | 38 ++ ion/src/f730/usb/stack/setup_packet.h | 65 +++ ion/src/f730/usb/stack/streamable.cpp | 15 + ion/src/f730/usb/stack/streamable.h | 44 ++ ion/src/f730/wakeup.cpp | 69 ++++ ion/src/f730/wakeup.h | 41 ++ 132 files changed, 8048 insertions(+) create mode 100644 ion/src/f730/Makefile create mode 100644 ion/src/f730/backlight.cpp create mode 100644 ion/src/f730/backlight.h create mode 100644 ion/src/f730/base64.cpp create mode 100644 ion/src/f730/base64.h create mode 100644 ion/src/f730/battery.cpp create mode 100644 ion/src/f730/battery.h create mode 100644 ion/src/f730/bench/Makefile create mode 100644 ion/src/f730/bench/bench.cpp create mode 100644 ion/src/f730/bench/bench.h create mode 100644 ion/src/f730/bench/command/adc.cpp create mode 100644 ion/src/f730/bench/command/backlight.cpp create mode 100644 ion/src/f730/bench/command/charge.cpp create mode 100644 ion/src/f730/bench/command/command.cpp create mode 100644 ion/src/f730/bench/command/command.h create mode 100644 ion/src/f730/bench/command/display.cpp create mode 100644 ion/src/f730/bench/command/exit.cpp create mode 100644 ion/src/f730/bench/command/keyboard.cpp create mode 100644 ion/src/f730/bench/command/led.cpp create mode 100644 ion/src/f730/bench/command/mcu_serial.cpp create mode 100644 ion/src/f730/bench/command/ping.cpp create mode 100644 ion/src/f730/bench/command/print.cpp create mode 100644 ion/src/f730/bench/command/suspend.cpp create mode 100644 ion/src/f730/bench/command/vblank.cpp create mode 100644 ion/src/f730/bench/command_handler.cpp create mode 100644 ion/src/f730/bench/command_handler.h create mode 100644 ion/src/f730/bench/command_list.cpp create mode 100644 ion/src/f730/bench/command_list.h create mode 100644 ion/src/f730/boot/Makefile create mode 100644 ion/src/f730/boot/flash.ld create mode 100644 ion/src/f730/boot/isr.c create mode 100644 ion/src/f730/boot/isr.h create mode 100644 ion/src/f730/boot/rt0.cpp create mode 100644 ion/src/f730/console.cpp create mode 100644 ion/src/f730/console.h create mode 100644 ion/src/f730/device.cpp create mode 100644 ion/src/f730/device.h create mode 100644 ion/src/f730/display.cpp create mode 100644 ion/src/f730/display.h create mode 100644 ion/src/f730/events.cpp create mode 100644 ion/src/f730/flash.cpp create mode 100644 ion/src/f730/flash.h create mode 100644 ion/src/f730/keyboard.cpp create mode 100644 ion/src/f730/keyboard.h create mode 100644 ion/src/f730/led.cpp create mode 100644 ion/src/f730/led.h create mode 100644 ion/src/f730/log.cpp create mode 100644 ion/src/f730/power.cpp create mode 100644 ion/src/f730/regs/adc.h create mode 100644 ion/src/f730/regs/cm4.h create mode 100644 ion/src/f730/regs/crc.h create mode 100644 ion/src/f730/regs/dma.h create mode 100644 ion/src/f730/regs/exti.h create mode 100644 ion/src/f730/regs/flash.h create mode 100644 ion/src/f730/regs/fsmc.h create mode 100644 ion/src/f730/regs/gpio.h create mode 100644 ion/src/f730/regs/itm.h create mode 100644 ion/src/f730/regs/mpu.h create mode 100644 ion/src/f730/regs/nvic.h create mode 100644 ion/src/f730/regs/otg.h create mode 100644 ion/src/f730/regs/pwr.h create mode 100644 ion/src/f730/regs/rcc.h create mode 100644 ion/src/f730/regs/register.h create mode 100644 ion/src/f730/regs/regs.h create mode 100644 ion/src/f730/regs/rng.h create mode 100644 ion/src/f730/regs/sdio.h create mode 100644 ion/src/f730/regs/spi.h create mode 100644 ion/src/f730/regs/syscfg.h create mode 100644 ion/src/f730/regs/tim.h create mode 100644 ion/src/f730/regs/usart.h create mode 100644 ion/src/f730/sd_card.cpp create mode 100644 ion/src/f730/sd_card.h create mode 100644 ion/src/f730/stack.cpp create mode 100644 ion/src/f730/swd.cpp create mode 100644 ion/src/f730/swd.h create mode 100644 ion/src/f730/timing.cpp create mode 100644 ion/src/f730/timing.h create mode 100644 ion/src/f730/usb.cpp create mode 100644 ion/src/f730/usb.h create mode 100644 ion/src/f730/usb/Makefile create mode 100644 ion/src/f730/usb/boot.cpp create mode 100644 ion/src/f730/usb/calculator.cpp create mode 100644 ion/src/f730/usb/calculator.h create mode 100644 ion/src/f730/usb/dfu.ld create mode 100644 ion/src/f730/usb/dfu_interface.cpp create mode 100644 ion/src/f730/usb/dfu_interface.h create mode 100644 ion/src/f730/usb/dfu_relocated.cpp create mode 100644 ion/src/f730/usb/dfu_xip.cpp create mode 100644 ion/src/f730/usb/flasher.cpp create mode 100644 ion/src/f730/usb/flasher.ld create mode 100644 ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/bos_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/configuration_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/device_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/device_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/interface_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/string_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/string_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/url_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/url_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h create mode 100644 ion/src/f730/usb/stack/device.cpp create mode 100644 ion/src/f730/usb/stack/device.h create mode 100644 ion/src/f730/usb/stack/endpoint0.cpp create mode 100644 ion/src/f730/usb/stack/endpoint0.h create mode 100644 ion/src/f730/usb/stack/interface.cpp create mode 100644 ion/src/f730/usb/stack/interface.h create mode 100644 ion/src/f730/usb/stack/request_recipient.cpp create mode 100644 ion/src/f730/usb/stack/request_recipient.h create mode 100644 ion/src/f730/usb/stack/setup_packet.cpp create mode 100644 ion/src/f730/usb/stack/setup_packet.h create mode 100644 ion/src/f730/usb/stack/streamable.cpp create mode 100644 ion/src/f730/usb/stack/streamable.h create mode 100644 ion/src/f730/wakeup.cpp create mode 100644 ion/src/f730/wakeup.h diff --git a/ion/src/f730/Makefile b/ion/src/f730/Makefile new file mode 100644 index 000000000..3d243bd47 --- /dev/null +++ b/ion/src/f730/Makefile @@ -0,0 +1,51 @@ +include ion/src/device/boot/Makefile +include ion/src/device/bench/Makefile +include ion/src/device/usb/Makefile + +ion/src/shared/platform_info.o: SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" + +objs += $(addprefix ion/src/shared/, \ + console_line.o \ + crc32_padded.o\ + events_modifier.o \ +) + +# If you need to profile execution, you can replace events_keyboard with +# events_replay.o and dummy/events_modifier.o + +objs += $(addprefix ion/src/device/, \ + backlight.o \ + battery.o\ + base64.o\ + console.o \ + device.o\ + display.o\ + events.o\ + flash.o\ + keyboard.o\ + led.o\ + power.o\ + sd_card.o\ + stack.o\ + swd.o \ + timing.o \ + usb.o \ + wakeup.o \ +) + +# When using the register.h C++ file in production mode, we expect the compiler +# to completely inline all bit manipulations. For some reason, if we build using +# the -Os optimization flag, GCC doesn't inline everything and and ends up +# emitting calls to aeabi_llsl for 64-bits registers. This is very sub-optimal +# so we're enforcing -O3 for this specific file. + +ifneq ($(DEBUG),1) +ifneq ($(COMPILER),llvm) +ion/src/device/led.o: SFLAGS+=-O3 +ion/src/device/console.o: SFLAGS+=-O3 +ion/src/device/display.o: SFLAGS+=-O3 +ion/src/device/swd.o: SFLAGS+=-O3 +endif +endif + +#objs += $(addprefix ion/src/device/keyboard/, keyboard.o) diff --git a/ion/src/f730/backlight.cpp b/ion/src/f730/backlight.cpp new file mode 100644 index 000000000..efcd49b9d --- /dev/null +++ b/ion/src/f730/backlight.cpp @@ -0,0 +1,85 @@ +#include +#include "regs/regs.h" +#include "backlight.h" + +/* This driver controls the RT9365 LED driver. + * This chip allows the brightness to be set to 16 different values. It starts + * at full brightness on power on. Applying a pulse on the EN pin will select + * the next value in decreasing order. Once it reaches the minimal value, the + * next pulse will loop back to full brightness. */ + +// Public Ion::Backlight methods + +namespace Ion { +namespace Backlight { + +void setBrightness(uint8_t b) { + Device::setLevel(b >> 4); +} + +uint8_t brightness() { + return Device::level() << 4; +} + +} +} + +// Private Ion::Backlight::Device methods + +namespace Ion { +namespace Backlight { +namespace Device { + +static uint8_t sLevel; + +void init() { + GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Output); + sLevel = 0xF; + resume(); +} + +void shutdown() { + GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Analog); + GPIOC.PUPDR()->setPull(6, GPIO::PUPDR::Pull::None); +} + +void suspend() { + GPIOC.ODR()->set(6, false); + Timing::msleep(3); // Might not need to be blocking +} + +void resume() { + GPIOC.ODR()->set(6, true); + Timing::usleep(50); + uint8_t level = sLevel; + sLevel = 0xF; + setLevel(level); +} + +void setLevel(uint8_t level) { + // From sLevel = 12 to level 7 : 5 pulses + // From sLevel = 5 to level 9 : 12 pulses (5 to go to level 16, and 7 to 9) + if (sLevel < level) { + sendPulses(16 + sLevel - level); + } else { + sendPulses(sLevel - level); + } + sLevel = level; +} + +uint8_t level() { + return sLevel; +} + +void sendPulses(int n) { + for (int i=0; iset(6, false); + Timing::usleep(20); + GPIOC.ODR()->set(6, true); + Timing::usleep(20); + } +} + +} +} +} diff --git a/ion/src/f730/backlight.h b/ion/src/f730/backlight.h new file mode 100644 index 000000000..15eab4fb0 --- /dev/null +++ b/ion/src/f730/backlight.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_BACKLIGHT_H +#define ION_DEVICE_BACKLIGHT_H + +#include + +namespace Ion { +namespace Backlight { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PC6 | Backlight Enable | Output | + */ + +void init(); +void shutdown(); +void suspend(); +void resume(); +void setLevel(uint8_t level); +uint8_t level(); + +void sendPulses(int n); + +} +} +} + +#endif diff --git a/ion/src/f730/base64.cpp b/ion/src/f730/base64.cpp new file mode 100644 index 000000000..6b4bb950c --- /dev/null +++ b/ion/src/f730/base64.cpp @@ -0,0 +1,48 @@ +namespace Base64 { + +static constexpr char encodeTable[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', +}; + +constexpr char Padding = '='; + +void encode(const unsigned char * input, unsigned int inputLength, char * output) { + unsigned int i, j; + for (i = j = 0; i < inputLength; i++) { + int s = i % 3; /* from 6/gcd(6, 8) */ + + switch (s) { + case 0: + output[j++] = encodeTable[(input[i] >> 2) & 0x3F]; + continue; + case 1: + output[j++] = encodeTable[((input[i-1] & 0x3) << 4) + ((input[i] >> 4) & 0xF)]; + continue; + case 2: + output[j++] = encodeTable[((input[i-1] & 0xF) << 2) + ((input[i] >> 6) & 0x3)]; + output[j++] = encodeTable[input[i] & 0x3F]; + } + } + + /* move back */ + i -= 1; + + /* check the last and add padding */ + if ((i % 3) == 0) { + output[j++] = encodeTable[(input[i] & 0x3) << 4]; + output[j++] = Padding; + output[j++] = Padding; + } else if ((i % 3) == 1) { + output[j++] = encodeTable[(input[i] & 0xF) << 2]; + output[j++] = Padding; + } +} + +} diff --git a/ion/src/f730/base64.h b/ion/src/f730/base64.h new file mode 100644 index 000000000..b9a4f2852 --- /dev/null +++ b/ion/src/f730/base64.h @@ -0,0 +1,5 @@ +namespace Base64 { + +void encode(const unsigned char * input, unsigned int inputLength, char * output); + +} diff --git a/ion/src/f730/battery.cpp b/ion/src/f730/battery.cpp new file mode 100644 index 000000000..f3ed16364 --- /dev/null +++ b/ion/src/f730/battery.cpp @@ -0,0 +1,88 @@ +#include +#include "battery.h" +#include "regs/regs.h" + +/* To measure the battery voltage, we're using the internal ADC. The ADC works + * by comparing the input voltage to a reference voltage. The only fixed voltage + * we have around is 2.8V, so that's the one we're using as a refrence. However, + * and ADC can only measure voltage that is lower than the reference voltage. So + * we need to use a voltage divider before sampling Vbat. + * To avoid draining the battery, we're using an high-impedence voltage divider, + * so we need to be careful when sampling the ADC. See AN2834 for more info. */ + +namespace Ion { +namespace Battery { + +bool isCharging() { + return !Device::ChargingGPIO.IDR()->get(Device::ChargingPin); +} + +Charge level() { + if (voltage() < 3.2f) { + return Charge::EMPTY; + } + if (voltage() < 3.5f) { + return Charge::LOW; + } + if (voltage() < 3.8f) { + return Charge::SOMEWHERE_INBETWEEN; + } + return Charge::FULL; +} + +float voltage() { + ADC.CR2()->setSWSTART(true); + while (ADC.SR()->getEOC() != true) { + } + uint16_t value = ADC.DR()->get(); + + // The ADC is 12 bits by default + return Device::ADCDividerBridgeRatio*(Device::ADCReferenceVoltage * value)/0xFFF; +} + +} +} + +namespace Ion { +namespace Battery { +namespace Device { + +void init() { + initGPIO(); + + /* The BAT_SNS pin is connected to Vbat through a divider bridge. It therefore + * has a voltage of Vbat/2. We'll measure this using ADC channel 0. */ + ADCGPIO.MODER()->setMode(ADCPin, GPIO::MODER::Mode::Analog); + + // Step 2 - Enable the ADC + RCC.APB2ENR()->setADC1EN(true); + ADC.CR2()->setADON(true); + + // Configure the ADC channel + ADC.SQR1()->setL(0); // Always sample the same channel + ADC.SQR3()->setSQ1(ADCChannel); + ADC.SMPR()->setSamplingTime(ADCChannel, ADC::SMPR::SamplingTime::Cycles480); // Use the max sampling time +} + +void initGPIO() { + /* Step 1 - Configure the GPIOs + * The BAT_CHRG pin is connected to the Li-Po charging IC. That pin uses an + * open-drain output. Open-drain output are either connected to ground or left + * floating. To interact with such an output, our input must therefore be + * pulled up. */ + ChargingGPIO.MODER()->setMode(ChargingPin, GPIO::MODER::Mode::Input); + ChargingGPIO.PUPDR()->setPull(ChargingPin, GPIO::PUPDR::Pull::Up); +} + +void shutdown() { + ChargingGPIO.MODER()->setMode(ChargingPin, GPIO::MODER::Mode::Analog); + ChargingGPIO.PUPDR()->setPull(ChargingPin, GPIO::PUPDR::Pull::None); + + // Disable the ADC + ADC.CR2()->setADON(false); + RCC.APB2ENR()->setADC1EN(false); +} + +} +} +} diff --git a/ion/src/f730/battery.h b/ion/src/f730/battery.h new file mode 100644 index 000000000..ef558dc86 --- /dev/null +++ b/ion/src/f730/battery.h @@ -0,0 +1,35 @@ +#ifndef ION_DEVICE_BATTERY_H +#define ION_DEVICE_BATTERY_H + +#include "regs/regs.h" + +namespace Ion { +namespace Battery { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PA0 | BAT_CHRG | Input, pulled up | Low = charging, high = full + * PA1 | VBAT_SNS | Analog | ADC1_1 + */ + +void init(); +void shutdown(); +void initGPIO(); +void initADC(); + +constexpr GPIO ChargingGPIO = GPIOA; +constexpr uint8_t ChargingPin = 0; + +constexpr GPIO ADCGPIO = GPIOA; +constexpr uint8_t ADCPin = 1; +constexpr uint8_t ADCChannel = 1; + +constexpr float ADCReferenceVoltage = 2.8f; +constexpr float ADCDividerBridgeRatio = 2.0f; + +} +} +} + +#endif diff --git a/ion/src/f730/bench/Makefile b/ion/src/f730/bench/Makefile new file mode 100644 index 000000000..90b0c82b6 --- /dev/null +++ b/ion/src/f730/bench/Makefile @@ -0,0 +1,21 @@ +objs += $(addprefix ion/src/device/bench/, \ + bench.o \ + command_handler.o \ + command_list.o \ +) + +objs += $(addprefix ion/src/device/bench/command/, \ + command.o \ + adc.o \ + backlight.o \ + charge.o \ + display.o \ + exit.o \ + keyboard.o \ + led.o \ + mcu_serial.o \ + ping.o \ + print.o \ + suspend.o \ + vblank.o \ +) diff --git a/ion/src/f730/bench/bench.cpp b/ion/src/f730/bench/bench.cpp new file mode 100644 index 000000000..5458111bc --- /dev/null +++ b/ion/src/f730/bench/bench.cpp @@ -0,0 +1,46 @@ +#include "bench.h" +#include +#include +#include "command_list.h" + +namespace Ion { +namespace Device { +namespace Bench { + +constexpr CommandHandler handles[] = { + CommandHandler("ADC", Command::ADC), + CommandHandler("BACKLIGHT", Command::Backlight), + CommandHandler("CHARGE", Command::Charge), + CommandHandler("DISPLAY", Command::Display), + CommandHandler("EXIT", Command::Exit), + CommandHandler("KEYBOARD", Command::Keyboard), + CommandHandler("LED", Command::LED), + CommandHandler("MCU_SERIAL", Command::MCUSerial), + CommandHandler("PING", Command::Ping), + CommandHandler("PRINT", Command::Print), + CommandHandler("SUSPEND", Command::Suspend), + CommandHandler("VBLANK", Command::VBlank), + CommandHandler(nullptr, nullptr) +}; + +constexpr const CommandList sCommandList = CommandList(handles); + +constexpr int kMaxCommandLength = 255; + +void run() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColorWhite); + ctx->drawString("BENCH", KDPoint((320-50)/2, (240-18)/2)); + char command[kMaxCommandLength]; + while (true) { + Ion::Console::readLine(command, kMaxCommandLength); + const CommandHandler * ch = sCommandList.dispatch(command); + if (ch != nullptr && ch->function() == Command::Exit) { + break; + } + } +} + +} +} +} diff --git a/ion/src/f730/bench/bench.h b/ion/src/f730/bench/bench.h new file mode 100644 index 000000000..de999cd37 --- /dev/null +++ b/ion/src/f730/bench/bench.h @@ -0,0 +1,14 @@ +#ifndef ION_DEVICE_BENCH_BENCH_H +#define ION_DEVICE_BENCH_BENCH_H + +namespace Ion { +namespace Device { +namespace Bench { + +void run(); + +} +} +} + +#endif diff --git a/ion/src/f730/bench/command/adc.cpp b/ion/src/f730/bench/command/adc.cpp new file mode 100644 index 000000000..8c3e677a4 --- /dev/null +++ b/ion/src/f730/bench/command/adc.cpp @@ -0,0 +1,27 @@ +#include "command.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void ADC(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + float result = Ion::Battery::voltage(); + constexpr int precision = 8; + constexpr int bufferSize = Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(precision); + char responseBuffer[bufferSize+4] = {'A', 'D', 'C', '='}; // ADC= + Poincare::PrintFloat::convertFloatToText(result, responseBuffer+4, bufferSize, precision, Poincare::Preferences::PrintFloatMode::Decimal); + reply(responseBuffer); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/backlight.cpp b/ion/src/f730/bench/command/backlight.cpp new file mode 100644 index 000000000..b4a7f2d9f --- /dev/null +++ b/ion/src/f730/bench/command/backlight.cpp @@ -0,0 +1,34 @@ +#include "command.h" +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Backlight(const char * input) { + // Input must be of the form "0xAA" or "ON" or "OFF" + if (strcmp(input, sON) == 0) { + Ion::Backlight::Device::init(); + reply(sOK); + return; + } + if (strcmp(input, sOFF) == 0) { + Ion::Backlight::Device::shutdown(); + reply(sOK); + return; + } + if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || input[4] != NULL) { + reply(sSyntaxError); + return; + } + uint32_t brightness = hexNumber(input+2); + Ion::Backlight::setBrightness(brightness); + reply(sOK); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/charge.cpp b/ion/src/f730/bench/command/charge.cpp new file mode 100644 index 000000000..8aa14f536 --- /dev/null +++ b/ion/src/f730/bench/command/charge.cpp @@ -0,0 +1,24 @@ +#include "command.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Charge(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + if (Ion::Battery::isCharging()) { + reply("CHARGE=ON"); + } else { + reply("CHARGE=OFF"); + } +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/command.cpp b/ion/src/f730/bench/command/command.cpp new file mode 100644 index 000000000..645b224d9 --- /dev/null +++ b/ion/src/f730/bench/command/command.cpp @@ -0,0 +1,47 @@ +#include "command.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +const char * const sOK = "OK"; +const char * const sKO = "KO"; +const char * const sSyntaxError = "SYNTAX_ERROR"; +const char * const sON = "ON"; +const char * const sOFF = "OFF"; + +void reply(const char * s) { + Console::writeLine(s); +} + +int8_t hexChar(char c) { + if (c >= '0' && c <= '9') { + return (c - '0'); + } + if (c >= 'A' && c <= 'F') { + return (c - 'A') + 0xA; + } + return -1; +} + +bool isHex(char c) { + return hexChar(c) >= 0; +} + +uint32_t hexNumber(const char * s, int maxLength) { + uint32_t result = 0; + int index = 0; + while ((maxLength < 0 || index < maxLength) && s[index] != NULL) { + result = (result << 4) | hexChar(s[index]); + index++; + } + return result; +} + + +} +} +} +} diff --git a/ion/src/f730/bench/command/command.h b/ion/src/f730/bench/command/command.h new file mode 100644 index 000000000..c749d3728 --- /dev/null +++ b/ion/src/f730/bench/command/command.h @@ -0,0 +1,42 @@ +#ifndef ION_DEVICE_BENCH_COMMAND_COMMAND_H +#define ION_DEVICE_BENCH_COMMAND_COMMAND_H + +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +typedef void (*Function)(const char * input); + +void ADC(const char * input); +void Backlight(const char * input); +void Charge(const char * input); +void Display(const char * input); +void Exit(const char * input); +void Keyboard(const char * input); +void LED(const char * input); +void MCUSerial(const char * input); +void Ping(const char * input); +void Print(const char * input); +void Suspend(const char * input); +void VBlank(const char * input); + +extern const char * const sOK; +extern const char * const sKO; +extern const char * const sSyntaxError; +extern const char * const sON; +extern const char * const sOFF; + +void reply(const char * s); +int8_t hexChar(char c); +bool isHex(char c); +uint32_t hexNumber(const char * s, int maxLength = -1); + +} +} +} +} + +#endif diff --git a/ion/src/f730/bench/command/display.cpp b/ion/src/f730/bench/command/display.cpp new file mode 100644 index 000000000..553e6e54a --- /dev/null +++ b/ion/src/f730/bench/command/display.cpp @@ -0,0 +1,74 @@ +#include "command.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +// Input must be of the form "0xAABBCC" or "ON" or "OFF" +void Display(const char * input) { + + if (strcmp(input, sON) == 0) { + Ion::Display::Device::init(); + reply(sOK); + return; + } + if (strcmp(input, sOFF) == 0) { + Ion::Display::Device::shutdown(); + reply(sOK); + return; + } + if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || !isHex(input[4]) || !isHex(input[5]) || !isHex(input[6]) || !isHex(input[7]) || input[8] != NULL) { + reply(sSyntaxError); + return; + } + + /* We fill the screen with a color and return OK if we read that color back everywhere. */ + + KDColor c = KDColor::RGB24(hexNumber(input)); + + constexpr int stampHeight = 10; + constexpr int stampWidth = 10; + static_assert(Ion::Display::Width % stampWidth == 0, "Stamps must tesselate the display"); + static_assert(Ion::Display::Height % stampHeight == 0, "Stamps must tesselate the display"); + static_assert(stampHeight % 2 == 0 || stampWidth % 2 == 0, "Even number of XOR needed."); + + KDColor stamp[stampWidth*stampHeight]; + for (int i=0;i +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +// Input must be of the form "0xAABBCC" or "ON" or "OFF" +void LED(const char * input) { + if (strcmp(input, sON) == 0) { + Ion::LED::Device::init(); + Ion::Console::writeLine(sOK); + return; + } + if (strcmp(input, sOFF) == 0) { + Ion::LED::Device::shutdown(); + Ion::Console::writeLine(sOK); + return; + } + if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || !isHex(input[4]) || !isHex(input[5]) || !isHex(input[6]) || !isHex(input[7]) || input[8] != NULL) { + Ion::Console::writeLine(sSyntaxError); + return; + } + uint32_t hexColor = hexNumber(input+2); + KDColor ledColor = KDColor::RGB24(hexColor); + Ion::LED::setColor(ledColor); + Ion::Console::writeLine(sOK); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/mcu_serial.cpp b/ion/src/f730/bench/command/mcu_serial.cpp new file mode 100644 index 000000000..b6703085b --- /dev/null +++ b/ion/src/f730/bench/command/mcu_serial.cpp @@ -0,0 +1,23 @@ +#include "command.h" +#include +#include "../../device.h" + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void MCUSerial(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + char response[11 + Ion::Device::SerialNumberLength + 1] = {'M', 'C', 'U', '_', 'S', 'E', 'R', 'I', 'A', 'L', '=', 0}; + Ion::Device::copySerialNumber(response + 11); + reply(response); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/ping.cpp b/ion/src/f730/bench/command/ping.cpp new file mode 100644 index 000000000..23649a7dc --- /dev/null +++ b/ion/src/f730/bench/command/ping.cpp @@ -0,0 +1,19 @@ +#include "command.h" + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Ping(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + reply("PONG"); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/print.cpp b/ion/src/f730/bench/command/print.cpp new file mode 100644 index 000000000..8b5eb3bfc --- /dev/null +++ b/ion/src/f730/bench/command/print.cpp @@ -0,0 +1,30 @@ +#include "command.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +// Input must be of the form "XX,YY,STRING" +void Print(const char * input) { + if (input == nullptr || !isHex(input[0]) || !isHex(input[1]) || input[2] != ',' || !isHex(input[3]) || !isHex(input[4]) || input[5] != ',') { + reply(sKO); + return; + } + + char x = hexNumber(input, 2); + char y = hexNumber(input+3, 2); + + KDContext * ctx = KDIonContext::sharedContext(); + ctx->drawString(input+6, KDPoint(x, y)); + + reply(sOK); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/suspend.cpp b/ion/src/f730/bench/command/suspend.cpp new file mode 100644 index 000000000..a494501c0 --- /dev/null +++ b/ion/src/f730/bench/command/suspend.cpp @@ -0,0 +1,22 @@ +#include "command.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Suspend(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + reply(sOK); + Ion::Timing::msleep(100); + Ion::Power::suspend(); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/vblank.cpp b/ion/src/f730/bench/command/vblank.cpp new file mode 100644 index 000000000..3c6971061 --- /dev/null +++ b/ion/src/f730/bench/command/vblank.cpp @@ -0,0 +1,26 @@ +#include "command.h" +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void VBlank(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + + for (int i=0; i<6; i++) { + Ion::Display::waitForVBlank(); + } + + reply(sOK); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command_handler.cpp b/ion/src/f730/bench/command_handler.cpp new file mode 100644 index 000000000..082d229a0 --- /dev/null +++ b/ion/src/f730/bench/command_handler.cpp @@ -0,0 +1,44 @@ +#include "command_handler.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { + +bool CommandHandler::valid() const { + return (m_name != nullptr && m_function != nullptr); +} + +bool CommandHandler::handle(const char * command) const { + if (matches(command)) { + size_t nameLength = strlen(m_name); + if (command[nameLength] == '=') { + m_function(command+nameLength+1); // Skip the "Equal character" + } else { + m_function(nullptr); + } + return true; + } + return false; +} + +bool CommandHandler::matches(const char * command) const { + const char * c = command; + const char * n = m_name; + while (true) { + if (*n == NULL) { + if (*c == NULL || *c == '=') { + return true; + } + } + if (*c != *n) { + return false; + } + c++; + n++; + } +} + +} +} +} diff --git a/ion/src/f730/bench/command_handler.h b/ion/src/f730/bench/command_handler.h new file mode 100644 index 000000000..be453e80c --- /dev/null +++ b/ion/src/f730/bench/command_handler.h @@ -0,0 +1,27 @@ +#ifndef ION_DEVICE_BENCH_COMMAND_HANDLER_H +#define ION_DEVICE_BENCH_COMMAND_HANDLER_H + +#include "command/command.h" + +namespace Ion { +namespace Device { +namespace Bench { + +class CommandHandler { +public: + constexpr CommandHandler(const char * name, Command::Function function) : + m_name(name), m_function(function) {} + bool valid() const; + bool handle(const char * command) const; + Command::Function function() const { return m_function; } +private: + bool matches(const char * command) const; + const char * m_name; + Command::Function m_function; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/bench/command_list.cpp b/ion/src/f730/bench/command_list.cpp new file mode 100644 index 000000000..917f54dfb --- /dev/null +++ b/ion/src/f730/bench/command_list.cpp @@ -0,0 +1,22 @@ +#include "command_list.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { + +const CommandHandler * CommandList::dispatch(const char * command) const { + const CommandHandler * handler = m_handlers; + while (handler->valid()) { + if (handler->handle(command)) { + return handler; + } + handler++; + } + Console::writeLine("NOT_FOUND"); + return nullptr; +} + +} +} +} diff --git a/ion/src/f730/bench/command_list.h b/ion/src/f730/bench/command_list.h new file mode 100644 index 000000000..2b47b88b3 --- /dev/null +++ b/ion/src/f730/bench/command_list.h @@ -0,0 +1,22 @@ +#ifndef ION_DEVICE_BENCH_COMMAND_LIST_H +#define ION_DEVICE_BENCH_COMMAND_LIST_H + +#include "command_handler.h" + +namespace Ion { +namespace Device { +namespace Bench { + +class CommandList { +public: + constexpr CommandList(const CommandHandler * handlers) : m_handlers(handlers) {} + const CommandHandler * dispatch(const char * command) const; +private: + const CommandHandler * m_handlers; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/boot/Makefile b/ion/src/f730/boot/Makefile new file mode 100644 index 000000000..ded572acd --- /dev/null +++ b/ion/src/f730/boot/Makefile @@ -0,0 +1,2 @@ +objs += $(addprefix ion/src/device/boot/, isr.o rt0.o) +LDSCRIPT = ion/src/device/boot/flash.ld diff --git a/ion/src/f730/boot/flash.ld b/ion/src/f730/boot/flash.ld new file mode 100644 index 000000000..e5ddcc9e4 --- /dev/null +++ b/ion/src/f730/boot/flash.ld @@ -0,0 +1,112 @@ +/* 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 { + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K +} + +STACK_SIZE = 32K; + +SECTIONS { + .isr_vector_table ORIGIN(FLASH) : { + /* 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. */ + + KEEP(*(.isr_vector_table)) + } >FLASH + + .header : { + KEEP(*(.header)) + } >FLASH + + .text : { + . = ALIGN(4); + *(.text) + *(.text.*) + } >FLASH + + .init_array : { + . = ALIGN(4); + _init_array_start = .; + KEEP (*(.init_array*)) + _init_array_end = .; + } >FLASH + + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata.*) + } >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 +} diff --git a/ion/src/f730/boot/isr.c b/ion/src/f730/boot/isr.c new file mode 100644 index 000000000..5c201aa67 --- /dev/null +++ b/ion/src/f730/boot/isr.c @@ -0,0 +1,129 @@ +#include "isr.h" +extern const void * _stack_start; + +/* Interrupt Service Routines are void->void functions */ +typedef void(*ISR)(void); + +/* Notice: The Cortex-M4 expects all jumps to be made at an odd address when + * jumping to Thumb code. For example, if you want to execute Thumb code at + * address 0x100, you'll have to jump to 0x101. Luckily, this idiosyncrasy is + * properly handled by the C compiler that will generate proper addresses when + * using function pointers. */ + +#define INITIALISATION_VECTOR_SIZE 0x71 + +ISR InitialisationVector[INITIALISATION_VECTOR_SIZE] + __attribute__((section(".isr_vector_table"))) + __attribute__((used)) + = { + (ISR)&_stack_start, // Stack start + start, // Reset service routine, + 0, // NMI service routine, + abort, // HardFault service routine, + 0, // MemManage service routine, + 0, // BusFault service routine, + 0, // UsageFault service routine, + 0, 0, 0, 0, // Reserved + 0, // SVCall service routine, + 0, // DebugMonitor service routine, + 0, // Reserved + 0, // PendSV service routine, + isr_systick, // SysTick service routine + 0, // WWDG service routine + 0, // PVD service routine + 0, // TampStamp service routine + 0, // RtcWakeup service routine + 0, // Flash service routine + 0, // RCC service routine + 0, // EXTI0 service routine + 0, // EXTI1 service routine + 0, // EXTI2 service routine + 0, // EXTI3 service routine + 0, // EXTI4 service routine + 0, // DMA1Stream0 service routine + 0, // DMA1Stream1 service routine + 0, // DMA1Stream2 service routine + 0, // DMA1Stream3 service routine + 0, // DMA1Stream4 service routine + 0, // DMA1Stream5 service routine + 0, // DMA1Stream6 service routine + 0, // ADC1 global interrupt + 0, // CAN1 TX interrupt + 0, // CAN1 RX0 interrupt + 0, // CAN1 RX1 interrupt + 0, // CAN1 SCE interrupt + 0, // EXTI Line[9:5] interrupts + 0, // TIM1 Break interrupt and TIM9 global interrupt + 0, // TIM1 update interrupt and TIM10 global interrupt + 0, // TIM1 Trigger & Commutation interrupts and TIM11 global interrupt + 0, // TIM1 Capture Compare interrupt + 0, // TIM2 global interrupt + 0, // TIM3 global interrupt + 0, // TIM4 global interrupt + 0, // I2C1 global event interrupt + 0, // I2C1 global error interrupt + 0, // I2C2 global event interrupt + 0, // I2C2 global error interrupt + 0, // SPI1 global interrupt + 0, // SPI2 global interrupt + 0, // USART1 global interrupt + 0, // USART2 global interrupt + 0, // USART3 global interrupt + 0, // EXTI Line[15:10] interrupts + 0, // EXTI Line 17 interrupt RTC Alarms (A and B) through EXTI line interrupt + 0, // EXTI Line 18 interrupt / USB On-The-Go FS Wakeup through EXTI line interrupt + 0, // TIM8 Break interrupt TIM12 global interrupt + 0, // TIM8 Update interrupt TIM13 global interrupt + 0, // TIM8 Trigger & Commutation interrupt TIM14 global interrupt + 0, // TIM8 Cap/Com interrupt + 0, // DMA1 global interrupt Channel 7 + 0, // FSMC global interrupt + 0, // SDIO global interrupt + 0, // TIM5 global interrupt + 0, // SPI3 global interrupt + 0, // ? + 0, // ? + 0, // TIM6 global interrupt + 0, // TIM7 global interrupt + 0, // DMA2 Stream0 global interrupt + 0, // DMA2 Stream1 global interrupt + 0, // DMA2 Stream2 global interrupt + 0, // DMA2 Stream3 global interrupt + 0, // DMA2 Stream4 global interrupt + 0, // SD filter0 global interrupt + 0, // SD filter1 global interrupt + 0, // CAN2 TX interrupt + 0, // BXCAN2 RX0 interrupt + 0, // BXCAN2 RX1 interrupt + 0, // CAN2 SCE interrupt + 0, // USB On The Go FS global interrupt + 0, // DMA2 Stream5 global interrupts + 0, // DMA2 Stream6 global interrupt + 0, // DMA2 Stream7 global interrupt + 0, // USART6 global interrupt + 0, // I2C3 event interrupt + 0, // I2C3 error interrupt + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // RNG global interrupt + 0, // FPU global interrupt + 0, // ? + 0, // ? + 0, // SPI4 global interrupt + 0, // SPI5 global interrupt + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // Quad-SPI global interrupt + 0, // ? + 0, // ? + 0, // I2CFMP1 event interrupt + 0 // I2CFMP1 error interrupt +}; diff --git a/ion/src/f730/boot/isr.h b/ion/src/f730/boot/isr.h new file mode 100644 index 000000000..ca5becb13 --- /dev/null +++ b/ion/src/f730/boot/isr.h @@ -0,0 +1,16 @@ +#ifndef ION_DEVICE_BOOT_ISR_H +#define ION_DEVICE_BOOT_ISR_H + +#ifdef __cplusplus +extern "C" { +#endif + +void start(); +void abort(); +void isr_systick(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ion/src/f730/boot/rt0.cpp b/ion/src/f730/boot/rt0.cpp new file mode 100644 index 000000000..e4ea05f0f --- /dev/null +++ b/ion/src/f730/boot/rt0.cpp @@ -0,0 +1,97 @@ +#include "isr.h" +#include +#include +#include +#include "../device.h" +#include "../timing.h" +#include "../console.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 abort() { +#if DEBUG + while (1) { + } +#else + Ion::Device::coreReset(); +#endif +} + +/* 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. */ +static void __attribute__((noinline)) non_inlined_ion_main() { + return ion_main(0, nullptr); +} + +void 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::initFPU(); + +#if 0 + Ion::Device::initMPU(); +#endif + + /* 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::init(); + + non_inlined_ion_main(); + + abort(); +} + +void __attribute__((interrupt)) isr_systick() { + Ion::Timing::Device::MillisElapsed++; +} diff --git a/ion/src/f730/console.cpp b/ion/src/f730/console.cpp new file mode 100644 index 000000000..15ba0b275 --- /dev/null +++ b/ion/src/f730/console.cpp @@ -0,0 +1,76 @@ +#include +#include "console.h" + +/* This file implements a serial console. + * We use a 115200 8N1 serial port */ + +namespace Ion { +namespace Console { + +char readChar() { + while (Device::UARTPort.SR()->getRXNE() == 0) { + } + return (char)Device::UARTPort.DR()->get(); +} + +void writeChar(char c) { + while (Device::UARTPort.SR()->getTXE() == 0) { + } + Device::UARTPort.DR()->set(c); +} + +} +} + +namespace Ion { +namespace Console { +namespace Device { + +void init() { + RCC.APB1ENR()->setUSART3EN(true); + + for(const GPIOPin & g : Pins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF7); + } + + UARTPort.CR1()->setUE(true); + UARTPort.CR1()->setTE(true); + UARTPort.CR1()->setRE(true); + + /* We need to set the baud rate of the UART port. + * This is set relative to the APB1 clock, which runs at 48 MHz. + * + * The baud rate is set by the following equation: + * BaudRate = fAPB1/(16*USARTDIV), where USARTDIV is a divider. + * In other words, USARDFIV = fAPB1/(16*BaudRate). All frequencies in Hz. + * + * In our case, fAPB1 = 48 MHz, so USARTDIV = 26.0416667 + * DIV_MANTISSA = 26 + * DIV_FRAC = 16*0.0416667 = 1 + */ + UARTPort.BRR()->setDIV_MANTISSA(26); + UARTPort.BRR()->setDIV_FRAC(1); +} + +void shutdown() { + for(const GPIOPin & g : Pins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } +} + +bool peerConnected() { + RxPin.group().PUPDR()->setPull(RxPin.pin(), GPIO::PUPDR::Pull::Down); + RxPin.group().MODER()->setMode(RxPin.pin(), GPIO::MODER::Mode::Input); + Timing::msleep(1); + bool result = RxPin.group().IDR()->get(RxPin.pin()); + RxPin.group().PUPDR()->setPull(RxPin.pin(), GPIO::PUPDR::Pull::None); + RxPin.group().MODER()->setMode(RxPin.pin(), GPIO::MODER::Mode::AlternateFunction); + return result; +} + + +} +} +} diff --git a/ion/src/f730/console.h b/ion/src/f730/console.h new file mode 100644 index 000000000..b1b14545f --- /dev/null +++ b/ion/src/f730/console.h @@ -0,0 +1,30 @@ +#ifndef ION_DEVICE_CONSOLE_H +#define ION_DEVICE_CONSOLE_H + +#include +#include "regs/regs.h" + +namespace Ion { +namespace Console { +namespace Device { + +/* Pin | Role | Mode + * -----+-------------------+-------------------- + * PC11 | UART3 RX | Alternate Function + * PD8 | UART3 TX | Alternate Function + */ + +void init(); +void shutdown(); +bool peerConnected(); + +constexpr USART UARTPort = USART(3); +constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 11); +constexpr static GPIOPin TxPin = GPIOPin(GPIOD, 8); +constexpr static GPIOPin Pins[] = { RxPin, TxPin }; + +} +} +} + +#endif diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp new file mode 100644 index 000000000..e25cc4106 --- /dev/null +++ b/ion/src/f730/device.cpp @@ -0,0 +1,325 @@ +#include "device.h" +#include "regs/regs.h" +extern "C" { +#include +} +#include +#include "led.h" +#include "display.h" +#include "keyboard.h" +#include "battery.h" +#include "sd_card.h" +#include "backlight.h" +#include "console.h" +#include "swd.h" +#include "usb.h" +#include "bench/bench.h" +#include "base64.h" + +#define USE_SD_CARD 0 + +extern "C" { + extern const void * _stack_end; +} + +// Public Ion methods + +uint32_t Ion::crc32(const uint32_t * data, size_t length) { + bool initialCRCEngineState = RCC.AHB1ENR()->getCRCEN(); + RCC.AHB1ENR()->setCRCEN(true); + CRC.CR()->setRESET(true); + + const uint32_t * end = data + length; + while (data < end) { + CRC.DR()->set(*data++); + } + + uint32_t result = CRC.DR()->get(); + RCC.AHB1ENR()->setCRCEN(initialCRCEngineState); + return result; +} + +uint32_t Ion::random() { + bool initialRNGEngineState = RCC.AHB2ENR()->getRNGEN(); + RCC.AHB2ENR()->setRNGEN(true); + + RNG.CR()->setRNGEN(true); + + while (RNG.SR()->getDRDY() == 0) { + } + uint32_t result = RNG.DR()->get(); + + RNG.CR()->setRNGEN(false); + RCC.AHB2ENR()->setRNGEN(initialRNGEngineState); + + return result; +} + +void Ion::Device::copySerialNumber(char * buffer) { + const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; + Base64::encode(rawUniqueID, 12, buffer); + buffer[SerialNumberLength] = 0; +} + +const char * Ion::serialNumber() { + static char serialNumber[Device::SerialNumberLength + 1] = {0}; + if (serialNumber[0] == 0) { + Device::copySerialNumber(serialNumber); + } + return serialNumber; +} + +const char * Ion::fccId() { + return "2ALWP-N0100"; +} + +// Private Ion::Device methods + +namespace Ion { +namespace Device { + +void initFPU() { + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BABDBFBJ.html + CM4.CPACR()->setAccess(10, CM4::CPACR::Access::Full); + CM4.CPACR()->setAccess(11, CM4::CPACR::Access::Full); + // FIXME: The pipeline should be flushed at this point +} + +#if 0 +void initMPU() { + /* Region 0 reprensents the last 128 bytes of the stack: accessing this + * memory means we are really likely to overflow the stack very soon. */ + MPU.RNR()->setREGION(0x00); + MPU.RBAR()->setADDR(&_stack_end); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::Bytes128); + MPU.RASR()->setENABLE(true); + MPU.RASR()->setAP(0x000); // Forbid access + MPU.CTRL()->setPRIVDEFENA(true); + MPU.CTRL()->setENABLE(true); +} +#endif + +void initSysTick() { + // CPU clock is 96 MHz, and systick clock source is divided by 8 + // To get 1 ms systick overflow we need to reset it to + // 96 000 000 (Hz) / 8 / 1 000 (ms/s) - 1 (because the counter resets *after* counting to 0) + CM4.SYST_RVR()->setRELOAD(11999); + CM4.SYST_CVR()->setCURRENT(0); + CM4.SYST_CSR()->setCLKSOURCE(CM4::SYST_CSR::CLKSOURCE::AHB_DIV8); + CM4.SYST_CSR()->setTICKINT(true); + CM4.SYST_CSR()->setENABLE(true); +} + +void shutdownSysTick() { + CM4.SYST_CSR()->setENABLE(false); + CM4.SYST_CSR()->setTICKINT(false); +} + +void coreReset() { + // Perform a full core reset + CM4.AIRCR()->requestReset(); +} + +void jumpReset() { + uint32_t * stackPointerAddress = reinterpret_cast(0x08000000); + uint32_t * resetHandlerAddress = reinterpret_cast(0x08000004); + + /* Jump to the reset service routine after having reset the stack pointer. + * Both addresses are fetched from the base of the Flash memory, just like a + * real reset would. These operations should be made at once, otherwise the C + * compiler might emit some instructions that modify the stack inbetween. */ + + asm volatile ( + "msr MSP, %[stackPointer] ; bx %[resetHandler]" + : : + [stackPointer] "r" (*stackPointerAddress), + [resetHandler] "r" (*resetHandlerAddress) + ); +} + +void init() { + initClocks(); + + // Ensure right location of interrupt vectors + // The bootloader leaves its own after flashing + SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory); + CM4.VTOR()->setVTOR((void*) 0); + + // Put all inputs as Analog Input, No pull-up nor pull-down + // Except for the SWD port (PB3, PA13, PA14) + GPIOA.MODER()->set(0xEBFFFFFF); + GPIOA.PUPDR()->set(0x24000000); + GPIOB.MODER()->set(0xFFFFFFBF); + GPIOB.PUPDR()->set(0x00000000); + for (int g=2; g<5; g++) { + GPIO(g).MODER()->set(0xFFFFFFFF); // All to "Analog" + GPIO(g).PUPDR()->set(0x00000000); // All to "None" + } + +#if EPSILON_DEVICE_BENCH + bool consolePeerConnectedOnBoot = Ion::Console::Device::peerConnected(); +#endif + + initPeripherals(); + +#if EPSILON_DEVICE_BENCH + if (consolePeerConnectedOnBoot) { + Ion::Device::Bench::run(); + } +#endif +} + +void shutdown() { + shutdownPeripherals(); + shutdownClocks(); +} + +void initPeripherals() { + Display::Device::init(); + Backlight::Device::init(); + Keyboard::Device::init(); + LED::Device::init(); + Battery::Device::init(); + USB::Device::init(); +#if USE_SD_CARD + SDCard::Device::init(); +#endif + Console::Device::init(); + SWD::Device::init(); + initSysTick(); +} + +void shutdownPeripherals(bool keepLEDAwake) { + shutdownSysTick(); + SWD::Device::shutdown(); + Console::Device::shutdown(); +#if USE_SD_CARD + SDCard::Device::shutdown(); +#endif + USB::Device::shutdown(); + Battery::Device::shutdown(); + if (!keepLEDAwake) { + LED::Device::shutdown(); + } + Keyboard::Device::shutdown(); + Backlight::Device::shutdown(); + Display::Device::shutdown(); +} + +void initClocks() { + /* System clock + * Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */ + + /* 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 90MHz the flash expects 3 WS. */ + FLASH.ACR()->setLATENCY(3); + + /* 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); + + /* Set flash instruction and data cache */ + FLASH.ACR()->setDCEN(true); + FLASH.ACR()->setICEN(true); + + /* 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 HSE and wait for it to be ready + RCC.CR()->setHSEON(true); + while(!RCC.CR()->getHSERDY()) { + } + + /* Given the crystal used on our device, the HSE will oscillate at 25 MHz. By + * piping it through a phase-locked loop (PLL) we can derive other frequencies + * for use in different parts of the system. Combining the default PLL values + * with a PLLM of 25 and a PLLQ of 4 yields both a 96 MHz frequency for SYSCLK + * and the required 48 MHz USB clock. */ + + // Configure the PLL ratios and use HSE as a PLL input + RCC.PLLCFGR()->setPLLM(25); + RCC.PLLCFGR()->setPLLQ(4); + RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); + // 96 MHz is too fast for APB1. Divide it by two to reach 48 MHz + RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy2); + + /* If you want to considerably slow down the whole machine uniformely, which + * can be very useful to diagnose performance issues, just uncomment the line + * below. Note that even booting takes a few seconds, so don't be surprised + * if the screen is black for a short while upon booting. */ + // RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy128); + + // Enable the PLL and wait for it to be ready + RCC.CR()->setPLLON(true); + 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(0); // 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); + + // APB2 bus + class RCC::APB2ENR apb2enr(0x00008000); // Reset value + apb2enr.setADC1EN(true); + apb2enr.setSYSCFGEN(true); +#if USE_SD_CARD + apb2enr.setSDIOEN(true); +#endif + RCC.APB2ENR()->set(apb2enr); +} + +void shutdownClocks(bool keepLEDAwake) { + // APB2 bus + RCC.APB2ENR()->set(0x00008000); // Reset value + + // APB1 + class RCC::APB1ENR apb1enr(0x00000400); // Reset value + // AHB1 bus + class RCC::AHB1ENR ahb1enr(0); // Reset value + if (keepLEDAwake) { + apb1enr.setTIM3EN(true); + ahb1enr.setGPIOBEN(true); + ahb1enr.setGPIOCEN(true); + } + RCC.APB1ENR()->set(apb1enr); + RCC.AHB1ENR()->set(ahb1enr); + + RCC.AHB3ENR()->setFSMCEN(false); +} + +} +} diff --git a/ion/src/f730/device.h b/ion/src/f730/device.h new file mode 100644 index 000000000..2bdccea28 --- /dev/null +++ b/ion/src/f730/device.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_H +#define ION_DEVICE_H + +namespace Ion { +namespace Device { + +void init(); +void shutdown(); + +void initFPU(); +#if 0 +void initMPU(); +#endif + +void initSysTick(); +void shutdownSysTick(); + +void coreReset(); +void jumpReset(); + +void initPeripherals(); +void shutdownPeripherals(bool keepLEDAwake = false); +void initClocks(); +void shutdownClocks(bool keepLEDAwake = false); + +/* The serial number is 96 bits long. That's equal to 16 digits in base 64. We + * expose a convenient "copySerialNumber" routine which can be called without + * using a static variable (and therefore without a .bss section). This is used + * in the RAM'ed DFU bootloader. */ +constexpr static int SerialNumberLength = 16; +void copySerialNumber(char * buffer); + +} +} + +#endif diff --git a/ion/src/f730/display.cpp b/ion/src/f730/display.cpp new file mode 100644 index 000000000..1804a7b19 --- /dev/null +++ b/ion/src/f730/display.cpp @@ -0,0 +1,376 @@ +#include +#include "display.h" +#include "regs/regs.h" +extern "C" { +#include +} + +/* This driver interfaces with the ST7789V LCD controller. + * This chip keeps a whole frame in SRAM memory and feeds it to the LCD panel as + * needed. We use the STM32's FSMC to drive the bus between the ST7789V. Once + * configured, we only need to write in the address space of the MCU to actually + * send some data to the LCD controller. */ + +#define USE_DMA_FOR_PUSH_PIXELS 0 +#define USE_DMA_FOR_PUSH_COLOR 0 + +#define USE_DMA (USE_DMA_FOR_PUSH_PIXELS|USE_DMA_FOR_PUSH_COLOR) + +// Public Ion::Display methods + +namespace Ion { +namespace Display { + +void pushRect(KDRect r, const KDColor * pixels) { +#if USE_DMA + Device::waitForPendingDMAUploadCompletion(); +#endif + Device::setDrawingArea(r, Device::Orientation::Landscape); + Device::pushPixels(pixels, r.width()*r.height()); +} + +void pushRectUniform(KDRect r, KDColor c) { +#if USE_DMA + Device::waitForPendingDMAUploadCompletion(); +#endif + Device::setDrawingArea(r, Device::Orientation::Portrait); + Device::pushColor(c, r.width()*r.height()); +} + +void pullRect(KDRect r, KDColor * pixels) { +#if USE_DMA + Device::waitForPendingDMAUploadCompletion(); +#endif + Device::setDrawingArea(r, Device::Orientation::Landscape); + Device::pullPixels(pixels, r.width()*r.height()); +} + +void waitForVBlank() { + // We want to return as soon as the TE line is transitionning from "DOWN" to "UP" + while (Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) { + // Loop while high, exit when low + // Wait for zero + } + while (!Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) { + // Loop while low, exit when high + } +} + +} +} + +// Private Ion::Display::Device methods + +namespace Ion { +namespace Display { +namespace Device { + +static inline void send_data(uint16_t d) { + *DataAddress = d; +} + +static inline uint16_t receive_data() { + return *DataAddress; +} + +template +static inline void send_data(uint16_t d, Args... other) { + send_data(d); + send_data(other...); +} + +static inline void send_command(Command c) { + *CommandAddress = c; +} + +template +static inline void send_command(Command c, Args... d) { + send_command(c); + send_data(d...); +} + +void init() { +#if USE_DMA + initDMA(); +#endif + initGPIO(); + initFSMC(); + initPanel(); +} + +void shutdown() { + shutdownPanel(); + shutdownFSMC(); + shutdownGPIO(); +} + +#if USE_DMA +void initDMA() { + // Only DMA2 can perform memory-to-memory transfers + //assert(DMAEngine == DMA2); + + /* In memory-to-memory transfers, the "peripheral" is the source and the + * "memory" is the destination. In other words, memory is copied from address + * DMA_SxPAR to address DMA_SxM0AR. */ + + DMAEngine.SCR(DMAStream)->setDIR(DMA::SCR::Direction::MemoryToMemory); + DMAEngine.SM0AR(DMAStream)->set((uint32_t)DataAddress); + DMAEngine.SCR(DMAStream)->setMSIZE(DMA::SCR::DataSize::HalfWord); + DMAEngine.SCR(DMAStream)->setPSIZE(DMA::SCR::DataSize::HalfWord); + DMAEngine.SCR(DMAStream)->setMBURST(DMA::SCR::Burst::Incremental4); + DMAEngine.SCR(DMAStream)->setPBURST(DMA::SCR::Burst::Incremental4); + DMAEngine.SCR(DMAStream)->setMINC(false); +} + +void waitForPendingDMAUploadCompletion() { + // Loop until DMA engine available + while (DMAEngine.SCR(DMAStream)->getEN()) { + } +} + +static inline void startDMAUpload(const KDColor * src, bool incrementSrc, uint16_t length) { + // Reset interruption markers + DMAEngine.LIFCR()->set(0xF7D0F7D); + + DMAEngine.SNDTR(DMAStream)->set(length); + DMAEngine.SPAR(DMAStream)->set((uint32_t)src); + DMAEngine.SCR(DMAStream)->setPINC(incrementSrc); + DMAEngine.SCR(DMAStream)->setEN(true); +} +#endif + +void initGPIO() { + // All the FSMC GPIO pins use the alternate function number 12 + for(const GPIOPin & g : FSMCPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF12); + } + + // Turn on the power + PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Output); + PowerPin.group().ODR()->set(PowerPin.pin(), true); + + // Turn on the reset pin + ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Output); + ResetPin.group().ODR()->set(ResetPin.pin(), true); + + // Turn on the extended command pin + ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Output); + ExtendedCommandPin.group().ODR()->set(ExtendedCommandPin.pin(), true); + + // Turn on the Tearing Effect pin + TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Input); + TearingEffectPin.group().PUPDR()->setPull(TearingEffectPin.pin(), GPIO::PUPDR::Pull::None); + + Timing::msleep(120); +} + + +void shutdownGPIO() { + // All the FSMC GPIO pins use the alternate function number 12 + for(const GPIOPin & g : FSMCPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } + + ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Analog); + ResetPin.group().PUPDR()->setPull(ResetPin.pin(), GPIO::PUPDR::Pull::None); + + PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Analog); + PowerPin.group().PUPDR()->setPull(PowerPin.pin(), GPIO::PUPDR::Pull::None); + + ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Analog); + ExtendedCommandPin.group().PUPDR()->setPull(ExtendedCommandPin.pin(), GPIO::PUPDR::Pull::None); + + TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Analog); +} + +void initFSMC() { + /* Set up the FSMC control registers. + * We address the LCD panel as if it were an SRAM module, using a 16bits wide + * bus, non-multiplexed. + * The STM32 FSMC supports two kinds of memory access modes : + * - Base modes (1 and 2), which use the same timings for reads and writes + * - Extended modes (named A to D), which can be customized further. + * The LCD panel can be written to faster than it can be read from, therefore + * we want to use one of the extended modes. */ + FSMC.BCR(FSMCMemoryBank)->setEXTMOD(true); + FSMC.BCR(FSMCMemoryBank)->setWREN(true); + FSMC.BCR(FSMCMemoryBank)->setMWID(FSMC::BCR::MWID::SIXTEEN_BITS); + FSMC.BCR(FSMCMemoryBank)->setMTYP(FSMC::BCR::MTYP::SRAM); + FSMC.BCR(FSMCMemoryBank)->setMUXEN(false); + FSMC.BCR(FSMCMemoryBank)->setMBKEN(true); + + /* We now need to set the actual timings. First, the FSMC and LCD specs don't + * use the same names. Here's the mapping: + * + * FSMC | LCD + * -----+----- + * NOE | RDX + * NWE | WRX + * NE1 | CSX + * A16 | D/CX + * Dn | Dn + * + * We need to set the values of the BTR and BWTR which gives the timings for + * reading and writing. Note that the STM32 datasheet doesn't take into + * account the time needed to actually switch from one logic state to another, + * whereas the ST7789V one does, so we'll add T(R) and T(F) as needed. + * Last but not least, timings on the STM32 have to be expressed in terms of + * HCLK = 1/96MHz = 10.42ns. + * - We'll pick Mode A which corresponds to SRAM with OE toggling + * - ADDSET = T(AST) + T(F) = 0ns + 15ns = 2 HCLK + * - ADDHLD is unused in this mode, set to 0 + * - DATAST(read) = T(RDLFM) + T(R) = 355ns + 15ns = 36 HCLK + * DATAST(write) = T(WRL) + T(R) = 15ns + 15ns = 3 HCLK + * - BUSTURN(read) = T(RDHFM) + T(F) = 90ns + 15ns = 10 HCLK + * BUSTURN(write) = T(RDHFM) + T(F) = 15ns + 15ns = 3 HCLK + */ + + // Read timing from the LCD + FSMC.BTR(FSMCMemoryBank)->setADDSET(2); + FSMC.BTR(FSMCMemoryBank)->setADDHLD(0); + FSMC.BTR(FSMCMemoryBank)->setDATAST(36); + FSMC.BTR(FSMCMemoryBank)->setBUSTURN(10); + FSMC.BTR(FSMCMemoryBank)->setACCMOD(FSMC::BTR::ACCMOD::A); + + // Write timings for the LCD + FSMC.BWTR(FSMCMemoryBank)->setADDSET(2); + FSMC.BWTR(FSMCMemoryBank)->setADDHLD(0); + FSMC.BWTR(FSMCMemoryBank)->setDATAST(3); + FSMC.BWTR(FSMCMemoryBank)->setBUSTURN(3); + FSMC.BWTR(FSMCMemoryBank)->setACCMOD(FSMC::BWTR::ACCMOD::A); +} + +void shutdownFSMC() { +} + +void initPanel() { + send_command(Command::Reset); + Timing::msleep(5); + + send_command(Command::SleepOut); + Timing::msleep(5); + + send_command(Command::PixelFormatSet, 0x05); + send_command(Command::TearingEffectLineOn, 0x00); + send_command(Command::FrameRateControl, 0x1E); // 40 Hz frame rate + + send_command(Command::DisplayOn); +} + +void shutdownPanel() { + send_command(Command::DisplayOff); + send_command(Command::SleepIn); + Timing::msleep(5); +} + +void setDrawingArea(KDRect r, Orientation o) { + uint16_t x_start, x_end, y_start, y_end; + + if (o == Orientation::Landscape) { + send_command(Command::MemoryAccessControl, 0xA0); + x_start = r.x(); + x_end = r.x() + r.width() - 1; + y_start = r.y(); + y_end = r.y() + r.height() - 1; + } else { + send_command(Command::MemoryAccessControl, 0x00); + x_start = r.y(); + x_end = r.y() + r.height() - 1; + y_start = Ion::Display::Width - (r.x() + r.width()); + y_end = Ion::Display::Width - r.x() - 1; + } + + send_command( + Command::ColumnAddressSet, + (x_start >> 8), + (x_start & 0xFF), + (x_end >> 8), + (x_end & 0xFF) + ); + + send_command( + Command::PageAddressSet, + (y_start >> 8), + (y_start & 0xFF), + (y_end >> 8), + (y_end & 0xFF) + ); +} + +void pushPixels(const KDColor * pixels, size_t numberOfPixels) { + send_command(Command::MemoryWrite); + /* Theoretically, we should not be able to use DMA here. Indeed, we have no + * guarantee that the content at "pixels" will remain valid once we exit this + * function call. In practice, we might be able to use DMA here because most + * of the time we push pixels from static locations. */ +#if USE_DMA_FOR_PUSH_PIXELS + startDMAUpload(pixels, true, numberOfPixels); +#else + while (numberOfPixels > 8) { + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + numberOfPixels -= 8; + } + while (numberOfPixels--) { + send_data(*pixels++); + } +#endif +} + +void pushColor(KDColor color, size_t numberOfPixels) { + send_command(Command::MemoryWrite); +#if USE_DMA_FOR_PUSH_COLOR + /* The "color" variable lives on the stack. We cannot take its address because + * it will stop being valid as soon as we return. An easy workaround is to + * duplicate the content in a static variable, whose value is guaranteed to be + * kept until the next pushColor call. */ + static KDColor staticColor; + staticColor = color; + startDMAUpload(&staticColor, false, (numberOfPixels > 64000 ? 64000 : numberOfPixels)); +#else + while (numberOfPixels--) { + send_data(color); + } +#endif +} + +void pullPixels(KDColor * pixels, size_t numberOfPixels) { + if (numberOfPixels == 0) { + return; + } + send_command(Command::PixelFormatSet, 0x06); + send_command(Command::MemoryRead); + + receive_data(); // First read is dummy data, per datasheet + while (true) { + if (numberOfPixels == 0) { + break; + } + uint16_t one = receive_data(); + uint16_t two = receive_data(); + uint16_t firstPixel = (one & 0xF800) | (one & 0xFC) << 3 | (two & 0xF800) >> 11; + *pixels++ = KDColor::RGB16(firstPixel); + numberOfPixels--; + + if (numberOfPixels == 0) { + break; + } + uint16_t three = receive_data(); + uint16_t secondPixel = (two & 0xF8) << 8 | (three & 0xFC00) >> 5 | (three & 0xF8) >> 3; + *pixels++ = KDColor::RGB16(secondPixel); + numberOfPixels--; + } + send_command(Command::PixelFormatSet, 0x05); +} + +} +} +} diff --git a/ion/src/f730/display.h b/ion/src/f730/display.h new file mode 100644 index 000000000..e9147b90a --- /dev/null +++ b/ion/src/f730/display.h @@ -0,0 +1,110 @@ +#ifndef ION_DEVICE_DISPLAY_H +#define ION_DEVICE_DISPLAY_H + +#include +#include +extern "C" { +#include +} +#include "regs/regs.h" + +namespace Ion { +namespace Display { +namespace Device { + +/* Pin | Role | Mode | Function | Note + * -----+-------------------+-----------------------+----------|------ + * PA2 | LCD D4 | Alternate Function 12 | FSMC_D4 | + * PA3 | LCD D5 | Alternate Function 12 | FSMC_D5 | + * PA4 | LCD D6 | Alternate Function 12 | FSMC_D6 | + * PB12 | LCD D13 | Alternate Function 12 | FSMC_D13 | + * PB14 | LCD power | Output | | LCD controller is powered directly from GPIO + * PD0 | LCD D2 | Alternate Function 12 | FSMC_D2 | + * PD1 | LCD D3 | Alternate Function 12 | FSMC_D3 | + * PD4 | LCD read signal | Alternate Function 12 | FSMC_NOE | + * PD5 | LCD write signal | Alternate Function 12 | FSMC_NWE + * PD7 | LCD chip select | Alternate Function 12 | FSMC_NE1 | Memory bank 1 + * PD9 | LCD D14 | Alternate Function 12 | FSMC_D14 | + * PD10 | LCD D15 | Alternate Function 12 | FSMC_D15 | + * PD11 | LCD data/command | Alternate Function 12 | FSMC_A16 | Data/Command is address bit 16 + * PD14 | LCD D0 | Alternate Function 12 | FSMC_D0 | + * PD15 | LCD D1 | Alternate Function 12 | FSMC_D1 | + * PE9 | LCD reset | Output | | + * PE10 | LCD D7 | Alternate Function 12 | FSMC_D7 | + * PE11 | LCD D8 | Alternate Function 12 | FSMC_D8 | + * PE12 | LCD D9 | Alternate Function 12 | FSMC_D9 | + * PE13 | LCD D10 | Alternate Function 12 | FSMC_D10 | + * PE14 | LCD D11 | Alternate Function 12 | FSMC_D11 | + * PE15 | LCD D12 | Alternate Function 12 | FSMC_D12 | + */ + +void init(); +void shutdown(); + +void initDMA(); +void initGPIO(); +void shutdownGPIO(); +void initFSMC(); +void shutdownFSMC(); +void initPanel(); +void shutdownPanel(); + +enum class Orientation { + Landscape = 0, + Portrait = 1 +}; + +void setDrawingArea(KDRect r, Orientation o); +void waitForPendingDMAUploadCompletion(); +void pushPixels(const KDColor * pixels, size_t numberOfPixels); +void pushColor(KDColor color, size_t numberOfPixels); +void pullPixels(KDColor * pixels, size_t numberOfPixels); + +enum class Command : uint16_t { + Nop = 0x00, + Reset = 0x01, + SleepIn = 0x10, + SleepOut = 0x11, + DisplayOff = 0x28, + DisplayOn = 0x29, + ColumnAddressSet = 0x2A, + PageAddressSet = 0x2B, + MemoryWrite = 0x2C, + MemoryRead = 0x2E, + TearingEffectLineOn = 0x35, + MemoryAccessControl = 0x36, + PixelFormatSet = 0x3A, + FrameRateControl = 0xC6 +}; + +constexpr static GPIOPin FSMCPins[] = { + GPIOPin(GPIOA, 2), GPIOPin(GPIOA, 3), GPIOPin(GPIOA, 4), GPIOPin(GPIOB, 12), + GPIOPin(GPIOB, 12), GPIOPin(GPIOD, 0), GPIOPin(GPIOD, 1), GPIOPin(GPIOD, 4), + GPIOPin(GPIOD, 5), GPIOPin(GPIOD, 7), GPIOPin(GPIOD, 9), GPIOPin(GPIOD, 10), + GPIOPin(GPIOD, 11), GPIOPin(GPIOD, 14), GPIOPin(GPIOD, 15), GPIOPin(GPIOE, 10), + GPIOPin(GPIOE, 11), GPIOPin(GPIOE, 12), GPIOPin(GPIOE, 13), GPIOPin(GPIOE, 14), + GPIOPin(GPIOE, 15) +}; + +constexpr static GPIOPin PowerPin = GPIOPin(GPIOB, 14); +constexpr static GPIOPin ResetPin = GPIOPin(GPIOE, 9); +constexpr static GPIOPin ExtendedCommandPin = GPIOPin(GPIOB, 13); +constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 10); + +constexpr static int FSMCMemoryBank = 1; +constexpr static int FSMCDataCommandAddressBit = 16; + +constexpr static uint32_t FSMCBaseAddress = 0x60000000; +constexpr static uint32_t FSMCBankAddress = FSMCBaseAddress + (FSMCMemoryBank-1)*0x04000000; + +constexpr static DMA DMAEngine = DMA2; +constexpr static int DMAStream = 0; + +static volatile Command * const CommandAddress = (Command *)(FSMCBankAddress); +static volatile uint16_t * const DataAddress = (uint16_t *)(FSMCBankAddress | (1<<(FSMCDataCommandAddressBit+1))); + +} +} +} + +#endif diff --git a/ion/src/f730/events.cpp b/ion/src/f730/events.cpp new file mode 100644 index 000000000..03de2de53 --- /dev/null +++ b/ion/src/f730/events.cpp @@ -0,0 +1,93 @@ +#include +#include + +namespace Ion { +namespace Events { + +static bool sleepWithTimeout(int duration, int * timeout) { + if (*timeout >= duration) { + Timing::msleep(duration); + *timeout -= duration; + return false; + } else { + Timing::msleep(*timeout); + *timeout = 0; + return true; + } +} + +Event sLastEvent = Events::None; +Keyboard::State sLastKeyboardState; +bool sLastUSBPlugged = false; +bool sLastUSBEnumerated = false; +bool sEventIsRepeating = 0; +constexpr int delayBeforeRepeat = 200; +constexpr int delayBetweenRepeat = 50; + +bool canRepeatEvent(Event e) { + return (e == Events::Left || e == Events::Up || e == Events::Down || e == Events::Right || e == Events::Backspace); +} + +Event getEvent(int * timeout) { + assert(*timeout > delayBeforeRepeat); + assert(*timeout > delayBetweenRepeat); + int time = 0; + uint64_t keysSeenUp = 0; + uint64_t keysSeenTransitionningFromUpToDown = 0; + while (true) { + // First, check if the USB plugged status has changed + bool usbPlugged = USB::isPlugged(); + if (usbPlugged != sLastUSBPlugged) { + sLastUSBPlugged = usbPlugged; + return Events::USBPlug; + } + + // Second, check if the USB device has been connected to an USB host + bool usbEnumerated = USB::isEnumerated(); + if (usbEnumerated != sLastUSBEnumerated) { + sLastUSBEnumerated = usbEnumerated; + if (usbEnumerated) { + return Events::USBEnumeration; + } + } + + Keyboard::State state = Keyboard::scan(); + keysSeenUp |= ~state; + keysSeenTransitionningFromUpToDown = keysSeenUp & state; + + if (keysSeenTransitionningFromUpToDown != 0) { + sEventIsRepeating = false; + /* The key that triggered the event corresponds to the first non-zero bit + * in "match". This is a rather simple logic operation for the which many + * processors have an instruction (ARM thumb uses CLZ). + * Unfortunately there's no way to express this in standard C, so we have + * to resort to using a builtin function. */ + Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitionningFromUpToDown)); + Event event(key, isShiftActive(), isAlphaActive()); + updateModifiersFromEvent(event); + sLastEvent = event; + sLastKeyboardState = state; + return event; + } + + if (sleepWithTimeout(10, timeout)) { + // Timeout occured + return Events::None; + } + time += 10; + + // At this point, we know that keysSeenTransitionningFromUpToDown has *always* been zero + // In other words, no new key has been pressed + if (canRepeatEvent(sLastEvent) && (state == sLastKeyboardState)) { + int delay = (sEventIsRepeating ? delayBetweenRepeat : delayBeforeRepeat); + if (time >= delay) { + sEventIsRepeating = true; + sLastKeyboardState = state; + return sLastEvent; + } + } + } +} + +} +} diff --git a/ion/src/f730/flash.cpp b/ion/src/f730/flash.cpp new file mode 100644 index 000000000..b5b0bae8e --- /dev/null +++ b/ion/src/f730/flash.cpp @@ -0,0 +1,233 @@ +#include "flash.h" +#include + +namespace Ion { +namespace Flash { +namespace Device { + +static inline void wait() { + // Wait for pending Flash operations to complete + while (FLASH.SR()->getBSY()) { + } +} + +static void open() { + // Unlock the Flash configuration register if needed + if (FLASH.CR()->getLOCK()) { + FLASH.KEYR()->set(0x45670123); + FLASH.KEYR()->set(0xCDEF89AB); + } + assert(FLASH.CR()->getLOCK() == false); + + // Set the programming parallelism + FLASH.CR()->setPSIZE(MemoryAccessWidth); +} + +static void close() { + // Lock the Flash configuration register + assert(!FLASH.CR()->getMER()); + assert(!FLASH.CR()->getSER()); + assert(!FLASH.CR()->getPG()); + FLASH.CR()->setLOCK(true); + + // Purge Data and instruction cache + if (FLASH.ACR()->getDCEN()) { + FLASH.ACR()->setDCEN(false); + FLASH.ACR()->setDCRST(true); + FLASH.ACR()->setDCRST(false); + FLASH.ACR()->setDCEN(true); + } + if (FLASH.ACR()->getICEN()) { + FLASH.ACR()->setICEN(false); + FLASH.ACR()->setICRST(true); + FLASH.ACR()->setICRST(false); + FLASH.ACR()->setICEN(true); + } +} + +// Compile-time log2 +static inline constexpr size_t clog2(size_t input) { + return (input == 1) ? 0 : clog2(input/2)+1; +} + +// Align a pointer to a given type's boundaries +// Returns a value that is lower or equal to input +template +static inline T * align(void * input) { + size_t k = clog2(sizeof(T)); + return reinterpret_cast(reinterpret_cast(input) & ~((1< +static inline T eat(void * ptr) { + T * pointer = *reinterpret_cast(ptr); + T result = *pointer; + *reinterpret_cast(ptr) = pointer+1; + return result; +} + +static inline ptrdiff_t byte_offset(void * p1, void * p2) { + return reinterpret_cast(p2) - reinterpret_cast(p1); +} + +template +static inline T min(T i, T j) { + return (i| + * |-- HeaderDelta ->| + */ + + MemoryAccessType * alignedDestination = align(destination); + ptrdiff_t headerDelta = byte_offset(alignedDestination, destination); + assert(headerDelta >= 0 && headerDelta < static_cast(sizeof(MemoryAccessType))); + + if (headerDelta > 0) { + // At this point, alignedDestination < destination + // We'll then retrieve the current value at alignedDestination, fill it with + // bytes from source, and write it back at alignedDestination. + + // First, retrieve the current value at alignedDestination + MemoryAccessType header = *alignedDestination; + + // Then copy headerLength bytes from source and put them in the header + uint8_t * headerStart = reinterpret_cast(&header); + // Here's where source data shall start being copied in the header + uint8_t * headerDataStart = headerStart + headerDelta; + // And here's where it should end + uint8_t * headerDataEnd = min( + headerStart + sizeof(MemoryAccessType), // Either at the end of the header + headerDataStart + length // or whenever src runs out of data + ); + for (uint8_t * h = headerDataStart; h(&source); + } + + // Then eventually write the header back into the aligned destination + *alignedDestination++ = header; + } + + /* Step 2 - Copy the bulk of the data + * At this point, we can use aligned MemoryAccessType pointers. */ + + MemoryAccessType * lastAlignedDestination = align(destination + length); + while (alignedDestination < lastAlignedDestination) { + *alignedDestination++ = eat(&source); + } + + /* Step 3 - Copy a footer if needed + * Some unaligned data can be pending at the end. Let's take care of it like + * we did for the header. + * + * _alignedDst _Destination+length + * | | + * --+--------+--------+--------+--------+--------+--------+-- + * | || | | | || | + *---+--------+--------+--------+--------+--------+--------+-- + * |<------------ Footer ------------->| + * |- footerLength ->| + */ + + ptrdiff_t footerLength = byte_offset(alignedDestination, destination + length); + assert(footerLength < static_cast(sizeof(MemoryAccessType))); + if (footerLength > 0) { + assert(alignedDestination == lastAlignedDestination); + + // First, retrieve the current value at alignedDestination + MemoryAccessType footer = *alignedDestination; + + /* Then copy footerLength bytes from source and put them at the beginning of + * the footer */ + uint8_t * footerPointer = reinterpret_cast(&footer); + for (ptrdiff_t i=0; i(&source); + } + + // Then eventually write the footer back into the aligned destination + *alignedDestination = footer; + } +} + +int SectorAtAddress(uint32_t address) { + uint32_t sectorAddresses[NumberOfSectors+1] = { + 0x08000000, 0x08004000, 0x08008000, 0x0800C000, + 0x08010000, 0x08020000, 0x08040000, 0x08060000, + 0x08080000, 0x080A0000, 0x080C0000, 0x080E0000, + 0x08100000 + }; + for (int i=0; i= sectorAddresses[i] && address < sectorAddresses[i+1]) { + return i; + } + } + return -1; +} + +void MassErase() { + open(); + FLASH.CR()->setMER(true); + FLASH.CR()->setSTRT(true); + wait(); + FLASH.CR()->setMER(false); + close(); +} + +void EraseSector(int i) { + assert(i >= 0 && i < NumberOfSectors); + open(); + FLASH.CR()->setSNB(i); + FLASH.CR()->setSER(true); + FLASH.CR()->setSTRT(true); + wait(); + FLASH.CR()->setSNB(0); + FLASH.CR()->setSER(false); + close(); +} + +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { + open(); + FLASH.CR()->setPG(true); + flash_memcpy(source, destination, length); + wait(); + FLASH.CR()->setPG(false); + close(); +} + +} +} +} diff --git a/ion/src/f730/flash.h b/ion/src/f730/flash.h new file mode 100644 index 000000000..6d8c9b657 --- /dev/null +++ b/ion/src/f730/flash.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_FLASH_H +#define ION_DEVICE_FLASH_H + +#include +#include "regs/flash.h" + +namespace Ion { +namespace Flash { +namespace Device { + +void MassErase(); + +constexpr int NumberOfSectors = 12; +int SectorAtAddress(uint32_t address); +void EraseSector(int i); + +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); + +/* The Device is powered by a 2.8V LDO. This allows us to perform writes to the + * Flash 32 bits at once. */ +constexpr FLASH::CR::PSIZE MemoryAccessWidth = FLASH::CR::PSIZE::X32; +typedef uint32_t MemoryAccessType; + +} +} +} + +#endif diff --git a/ion/src/f730/keyboard.cpp b/ion/src/f730/keyboard.cpp new file mode 100644 index 000000000..91c634dae --- /dev/null +++ b/ion/src/f730/keyboard.cpp @@ -0,0 +1,112 @@ +/* Keyboard initialization code + * + * The job of this code is to implement the "ion_key_state" function. + * + * The keyboard is a matrix that is laid out as follow: + * + * | PC0 | PC1 | PC2 | PC3 | PC4 | PC5 | + * -----+------+------+------+------+------+------+ + * PE0 | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 | + * -----+------+------+------+------+------+------+ + * PE1 | K_B1 | K_B2 | | | | | + * -----+------+------+------+------+------+------+ + * PE2 | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 | + * -----+------+------+------+------+------+------+ + * PE3 | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 | + * -----+------+------+------+------+------+------+ + * PE4 | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 | + * -----+------+------+------+------+------+------+ + * PE5 | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | | + * -----+------+------+------+------+------+------+ + * PE6 | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | | + * -----+------+------+------+------+------+------+ + * PE7 | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | | + * -----+------+------+------+------+------+------+ + * PE8 | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | | + * -----+------+------+------+------+------+------| + * + * We decide to drive the rows (PE0-8) and read the columns (PC0-5). + * + * To avoid short-circuits, the pins E0-E8 will not be standard outputs but + * only open-drain. Open drain means the pin is either driven low or left + * floating. + * When a user presses multiple keys, a connection between two rows can happen. + * If we don't use open drain outputs, this situation could trigger a short + * circuit between an output driving high and another driving low. + * + * If the outputs are open-drain, this means that the input must be pulled up. + * So if the input reads "1", this means the key is in fact *not* pressed, and + * if it reads "0" it means that there's a short to an open-drain output. Which + * means the corresponding key is pressed. + */ + +#include "keyboard.h" + +// Public Ion::Keyboard methods + +namespace Ion { +namespace Keyboard { + +State scan() { + uint64_t state = 0; + + for (uint8_t i=0; igetBitRange(5,0); + + /* The key is down if the input is brought low by the output. In other + * words, we want to return true if the input is low (false). So we need to + * append 6 bits of (not columns) to state. */ + state = (state << 6) | (~columns & 0x3F); + } + + /* Last but not least, keys number 8, 9, 10, 11, 35, 41, 47 and 53 are not + * defined. Therefore we want to make sure those bits are forced to zero in + * whatever value we return. */ + state = state & 0x1F7DF7FFFFF0FF; + + return State(state); +} + +} +} + +// Private Ion::Keyboard::Device methods + +namespace Ion { +namespace Keyboard { +namespace Device { + +void init() { + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Output); + RowGPIO.OTYPER()->setType(pin, GPIO::OTYPER::Type::OpenDrain); + } + + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Input); + ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::Up); + } +} + +void shutdown() { + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Analog); + RowGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None); + } + + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Analog); + ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None); + } +} + +} +} +} diff --git a/ion/src/f730/keyboard.h b/ion/src/f730/keyboard.h new file mode 100644 index 000000000..fa69ab39f --- /dev/null +++ b/ion/src/f730/keyboard.h @@ -0,0 +1,70 @@ +#ifndef ION_DEVICE_KEYBOARD_H +#define ION_DEVICE_KEYBOARD_H + +#include +#include +#include "regs/regs.h" + +namespace Ion { +namespace Keyboard { +namespace Device { + +/* 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 + * PE0 | Keyboard row A | Output, open drain + * PE1 | Keyboard row B | Output, open drain + * PE2 | Keyboard row C | Output, open drain + * PE3 | Keyboard row D | Output, open drain + * PE4 | Keyboard row E | Output, open drain + * PE5 | Keyboard row F | Output, open drain + * PE6 | Keyboard row G | Output, open drain + * PE7 | Keyboard row H | Output, open drain + * PE8 | Keyboard row I | Output, open drain + */ + +void init(); +void shutdown(); + +constexpr GPIO RowGPIO = GPIOE; +constexpr uint8_t numberOfRows = 9; +constexpr uint8_t RowPins[numberOfRows] = {0, 1, 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}; + +inline uint8_t rowForKey(Key key) { + return (int)key/numberOfColumns; +} +inline uint8_t columnForKey(Key key) { + return (int)key%numberOfColumns; +} + +inline void activateRow(uint8_t row) { + /* In open-drain mode, a 0 in the register drives the pin low, and a 1 lets + * the pin floating (Hi-Z). So we want to set the current row to zero and all + * the others to 1. */ + uint16_t rowState = ~(1<setBitRange(9, 0, rowState); + + // TODO: 100 us seems to work, but wasn't really calculated + Timing::usleep(100); +} + +inline bool columnIsActive(uint8_t column) { + return !(Device::ColumnGPIO.IDR()->getBitRange(column,column)); +} + +} +} +} + +#endif diff --git a/ion/src/f730/led.cpp b/ion/src/f730/led.cpp new file mode 100644 index 000000000..028e8e846 --- /dev/null +++ b/ion/src/f730/led.cpp @@ -0,0 +1,133 @@ +#include +#include +#include "device.h" +#include "led.h" +#include "regs/regs.h" + +// Public Ion::LED methods + +static KDColor sLedColor = KDColorBlack; + +KDColor Ion::LED::getColor() { + return sLedColor; +} + +void Ion::LED::setColor(KDColor c) { + sLedColor = c; + + /* Active all RGB colors */ + TIM3.CCMR()->setOC2M(TIM::CCMR::OCM::PWM1); + TIM3.CCMR()->setOC4M(TIM::CCMR::OCM::PWM1); + TIM3.CCMR()->setOC3M(TIM::CCMR::OCM::PWM1); + + /* Set the PWM duty cycles to display the right color */ + constexpr float maxColorValue = (float)((1 << 8) -1); + Device::setPeriodAndDutyCycles(Device::Mode::PWM, c.red()/maxColorValue, c.green()/maxColorValue, c.blue()/maxColorValue); +} + +void Ion::LED::setBlinking(uint16_t period, float dutyCycle) { + /* We want to use the PWM at a slow rate to display a seeable blink. + * Consequently, we do not use PWM to display the right color anymore but to + * blink. We cannot use the PWM to display the exact color so we 'project the + * color on 3 bits' : all colors have 2 states - active or not. */ + TIM3.CCMR()->setOC2M(sLedColor.red() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOC4M(sLedColor.green() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOC3M(sLedColor.blue() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + + Device::setPeriodAndDutyCycles(Device::Mode::Blink, dutyCycle, dutyCycle, dutyCycle, period); +} + +// Private Ion::Device::LED methods + +namespace Ion { +namespace LED { +namespace Device { + +void init() { + initGPIO(); + initTimer(); +} + +void shutdown() { + shutdownTimer(); + shutdownGPIO(); +} + +void initGPIO() { + /* RED_LED(PC7), GREEN_LED(PB1), and BLUE_LED(PB0) are driven using a timer, + * which is an alternate function. More precisely, we will use AF2, which maps + * PB0 to TIM3_CH2, PB1 to TIM3_CH4, and PC7 to TIM3_CH2. */ + for(const GPIOPin & g : RGBPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF2); + } +} + +void shutdownGPIO() { + for(const GPIOPin & g : RGBPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } +} + +void initTimer() { + // Output preload enable for channels 2-4 + TIM3.CCMR()->setOC2PE(true); + TIM3.CCMR()->setOC3PE(true); + TIM3.CCMR()->setOC4PE(true); + + // Auto-reload preload enable + TIM3.CR1()->setARPE(true); + + // Enable Capture/Compare for channel 2 to 4 + TIM3.CCER()->setCC2E(true); + TIM3.CCER()->setCC3E(true); + TIM3.CCER()->setCC4E(true); + + TIM3.BDTR()->setMOE(true); + + TIM3.CR1()->setCEN(true); +} + +void shutdownTimer() { + TIM3.CCMR()->setOC2M(TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOC4M(TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOC3M(TIM::CCMR::OCM::ForceInactive); +} + +/* Pulse width modulation mode allows you to generate a signal with a + * frequency determined by the value of the TIMx_ARR register and a duty cycle + * determined by the value of the TIMx_CCRx register. */ + +void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period) { + switch (mode) { + case Mode::PWM: + /* Let's set the prescaler to 1. Increasing the prescaler would slow down + * the modulation, which can be useful when debugging or when we want an + * actual blinking. */ + TIM3.PSC()->set(1); + TIM3.ARR()->set(PWMPeriod); + period = PWMPeriod; + break; + case Mode::Blink: + int systemClockFreq = 96; + /* We still want to do PWM, but at a rate slow enough to blink. Ideally, + * we want to pre-scale the period to be able to set it in milliseconds; + * however, as the prescaler is cap by 2^16-1, we divide it by a factor + * and correct the period consequently. */ + int factor = 2; + // TODO: explain the 2 ? + TIM3.PSC()->set(systemClockFreq*1000/factor); + period *= factor; + TIM3.ARR()->set(period); + break; + } + + TIM3.CCR2()->set(dutyCycleRed*period); + TIM3.CCR3()->set(dutyCycleBlue*period); + TIM3.CCR4()->set(dutyCycleGreen*period); +} + +} +} +} diff --git a/ion/src/f730/led.h b/ion/src/f730/led.h new file mode 100644 index 000000000..fe46994d9 --- /dev/null +++ b/ion/src/f730/led.h @@ -0,0 +1,48 @@ +#ifndef ION_DEVICE_LED_H +#define ION_DEVICE_LED_H + +#include "regs/regs.h" + +namespace Ion { +namespace LED { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PB0 | LED blue | Alternate Function 2 | TIM3_CH3 + * PB1 | LED green | Alternate Function 2 | TIM3_CH4 + * PC7 | LED red | Alternate Function 2 | TIM3_CH2 + */ + +enum class Mode { + PWM, + Blink +}; + +enum class Color { + Red, + Green, + Blue +}; + +void init(); +void shutdown(); +void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period = 0); + +void initGPIO(); +void shutdownGPIO(); +void initTimer(); +void shutdownTimer(); + +constexpr static GPIOPin RGBPins[] = { + GPIOPin(GPIOC, 7), GPIOPin(GPIOB, 1), GPIOPin(GPIOB, 0) +}; + + +constexpr uint16_t PWMPeriod = 40000; + +} +} +} + +#endif diff --git a/ion/src/f730/log.cpp b/ion/src/f730/log.cpp new file mode 100644 index 000000000..25ee1e7e2 --- /dev/null +++ b/ion/src/f730/log.cpp @@ -0,0 +1,15 @@ +#include +#include "regs/itm.h" + +// We're printing using SWO. +// This is achieved by writing to the ITM register, which is sent through the +// Cortex Debug bus + +void ion_log_string(const char * message) { + char character = 0; + while ((character = *message++) != 0) { + if (ITM.TER()->get(0)) { + ITM.STIM(0)->set(character); + } + } +} diff --git a/ion/src/f730/power.cpp b/ion/src/f730/power.cpp new file mode 100644 index 000000000..021d75cc6 --- /dev/null +++ b/ion/src/f730/power.cpp @@ -0,0 +1,69 @@ +#include +#include "battery.h" +#include "device.h" +#include "display.h" +#include "keyboard.h" +#include "led.h" +#include "usb.h" +#include "wakeup.h" +#include "regs/regs.h" + +void Ion::Power::suspend(bool checkIfPowerKeyReleased) { + bool isLEDActive = Ion::LED::getColor() != KDColorBlack; + if (checkIfPowerKeyReleased) { + /* Wait until power is released to avoid restarting just after suspending */ + bool isPowerDown = true; + while (isPowerDown) { + Keyboard::State scan = Keyboard::scan(); + isPowerDown = scan.keyDown(Keyboard::Key::B2); + } + } + Device::shutdownPeripherals(isLEDActive); + + PWR.CR()->setLPDS(true); // Turn the regulator off. Takes longer to wake up. + PWR.CR()->setFPDS(true); // Put the flash to sleep. Takes longer to wake up. + CM4.SCR()->setSLEEPDEEP(!isLEDActive); + + while (1) { +#if EPSILON_LED_WHILE_CHARGING + /* Update LEDS + * if the standby mode was stopped due to a "stop charging" event, we wait + * a while to be sure that the plug state of the USB is up-to-date. */ + msleep(200); + //Ion::LED::setCharging(Ion::USB::isPlugged(), Ion::Battery::isCharging()); +#endif + + WakeUp::Device::onPowerKeyDown(); + WakeUp::Device::onUSBPlugging(); +#if EPSILON_LED_WHILE_CHARGING + WakeUp::Device::onChargingEvent(); +#endif + + Device::shutdownClocks(isLEDActive); + + /* To enter sleep, we need to issue a WFE instruction, which waits for the + * event flag to be set and then clears it. However, the event flag might + * already be on. So the safest way to make sure we actually wait for a new + * event is to force the event flag to on (SEV instruction), use a first WFE + * to clear it, and then a second WFE to wait for a _new_ event. */ + asm("sev"); + asm("wfe"); + asm("nop"); + asm("wfe"); + + Device::initClocks(); + + Keyboard::Device::init(); + Keyboard::State scan = Keyboard::scan(); + Keyboard::Device::shutdown(); + + Ion::Keyboard::State OnlyPowerKeyDown = Keyboard::State(Keyboard::Key::B2); + if (scan == OnlyPowerKeyDown || USB::isPlugged()) { + // Wake up + break; + } + } + Device::initClocks(); + + Device::initPeripherals(); +} diff --git a/ion/src/f730/regs/adc.h b/ion/src/f730/regs/adc.h new file mode 100644 index 000000000..2881cf23b --- /dev/null +++ b/ion/src/f730/regs/adc.h @@ -0,0 +1,73 @@ +#ifndef REGS_ADC_H +#define REGS_ADC_H + +#include "register.h" + +class ADC { +public: + class SR : Register32 { + public: + REGS_BOOL_FIELD(EOC, 1); + }; + + class CR1 : Register32 { + public: + }; + + class CR2 : Register32 { + public: + REGS_BOOL_FIELD(ADON, 0); + REGS_BOOL_FIELD(SWSTART, 30); + }; + + class SMPR : Register64 { + /* The SMPR register doesn't exist per-se in the documentation. It is the + * consolidation of SMPR1 and SMPR2 which are two 32-bit registers. */ + public: + enum class SamplingTime { + Cycles3 = 0, + Cycles15 = 1, + Cycles28 = 2, + Cycles56 = 3, + Cycles84 = 4, + Cycles112 = 5, + Cycles144 = 6, + Cycles480 = 7 + }; + SamplingTime getSamplingTime(int channel) { + return (SamplingTime)getBitRange(3*channel+2, 3*channel); + } + void setSamplingTime(int channel, SamplingTime t) volatile { + setBitRange(3*channel+2, 3*channel, (uint64_t)t); + } + }; + + class SQR1 : Register32 { + public: + REGS_FIELD(L, uint8_t, 23, 20); + }; + + class SQR3 : Register32 { + public: + REGS_FIELD(SQ1, uint8_t, 4, 0); + }; + + class DR : public Register16 { + }; + + constexpr ADC() {}; + REGS_REGISTER_AT(SR, 0x0); + REGS_REGISTER_AT(CR2, 0x08); + REGS_REGISTER_AT(SMPR, 0x0C); + REGS_REGISTER_AT(SQR1, 0x2C); + REGS_REGISTER_AT(SQR3, 0x34); + REGS_REGISTER_AT(DR, 0x4C); +private: + constexpr uint32_t Base() const { + return 0x40012000; + }; +}; + +constexpr ADC ADC; + +#endif diff --git a/ion/src/f730/regs/cm4.h b/ion/src/f730/regs/cm4.h new file mode 100644 index 000000000..58c5af1cb --- /dev/null +++ b/ion/src/f730/regs/cm4.h @@ -0,0 +1,81 @@ +#ifndef REGS_CM4_H +#define REGS_CM4_H + +#include "register.h" + +class CM4 { +public: + // Vector table offset register + // http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf + class VTOR : Register32 { + public: + void setVTOR(void *address) volatile { setBitRange(29, 9, (uint32_t)address); } + }; + + // Coprocessor Access Control Register + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BEHBJHIG.html + class CPACR : public Register32 { + public: + enum class Access { + Denied = 0, + PrivilegedOnly = 1, + Full = 3 + }; + void setAccess(int index, Access a) volatile { setBitRange(2*index+1, 2*index, (uint32_t)a); } + }; + + + // Application Interrupt and Reset Control Register + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Cihehdge.html + class AIRCR : public Register32 { + public: + void requestReset() volatile { + set(0x5FA<<16 |(1<<2)); + } + }; + + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/Cihhjgdh.html + class SCR : public Register32 { + public: + REGS_BOOL_FIELD(SLEEPDEEP, 2); + }; + + class SYST_CSR : public Register32 { + public: + enum class CLKSOURCE : uint8_t { + AHB_DIV8 = 0, + AHB = 1 + }; + REGS_BOOL_FIELD(COUNTFLAG, 16); + REGS_TYPE_FIELD(CLKSOURCE, 2, 2); + REGS_BOOL_FIELD(TICKINT, 1); + REGS_BOOL_FIELD(ENABLE, 0); + }; + + class SYST_RVR : public Register32 { + public: + REGS_FIELD(RELOAD, uint32_t, 23, 0); + }; + + class SYST_CVR : public Register32 { + public: + REGS_FIELD(CURRENT, uint32_t, 23, 0); + }; + + constexpr CM4() {}; + REGS_REGISTER_AT(SYST_CSR, 0x10); + REGS_REGISTER_AT(SYST_RVR, 0x14); + REGS_REGISTER_AT(SYST_CVR, 0x18); + REGS_REGISTER_AT(VTOR, 0xD08); + REGS_REGISTER_AT(AIRCR, 0xD0C); + REGS_REGISTER_AT(SCR, 0xD10); + REGS_REGISTER_AT(CPACR, 0xD88); +private: + constexpr uint32_t Base() const { + return 0xE000E000; + } +}; + +constexpr CM4 CM4; + +#endif diff --git a/ion/src/f730/regs/crc.h b/ion/src/f730/regs/crc.h new file mode 100644 index 000000000..6ae4a2c10 --- /dev/null +++ b/ion/src/f730/regs/crc.h @@ -0,0 +1,27 @@ +#ifndef REGS_CRC_H +#define REGS_CRC_H + +#include "register.h" + +class CRC { +public: + class DR : public Register32 { + }; + + class CR : Register32 { + public: + REGS_BOOL_FIELD(RESET, 0); + }; + + constexpr CRC() {}; + REGS_REGISTER_AT(DR, 0x00); + REGS_REGISTER_AT(CR, 0x08); +private: + constexpr uint32_t Base() const { + return 0x40023000; + }; +}; + +constexpr CRC CRC; + +#endif diff --git a/ion/src/f730/regs/dma.h b/ion/src/f730/regs/dma.h new file mode 100644 index 000000000..de2acc702 --- /dev/null +++ b/ion/src/f730/regs/dma.h @@ -0,0 +1,63 @@ +#ifndef REGS_DMA_H +#define REGS_DMA_H + +#include "register.h" + +class DMA { +public: + class LIFCR : public Register32 { + }; + class SCR : Register32 { + public: + enum class Burst : uint8_t { + Single = 0, + Incremental4 = 1, + Incremental8 = 2, + Incremental16 = 3 + }; + enum class DataSize : uint8_t { + Byte = 0, + HalfWord = 1, + Word = 2 + }; + enum class Direction : uint8_t { + PeripheralToMemory = 0, + MemoryToPeripheral = 1, + MemoryToMemory = 2 + }; + REGS_FIELD(CHSEL, uint8_t, 27, 25); + REGS_FIELD(MBURST, Burst, 24, 23); + REGS_FIELD(PBURST, Burst, 22, 21); + REGS_FIELD(MSIZE, DataSize, 14, 13); + REGS_FIELD(PSIZE, DataSize, 12, 11); + REGS_BOOL_FIELD(MINC, 10); + REGS_BOOL_FIELD(PINC, 9); + REGS_BOOL_FIELD(CIRC, 8); + REGS_FIELD(DIR, Direction, 7, 6); + REGS_BOOL_FIELD(EN, 0); + }; + class SNDTR : public Register32 { + }; + class SPAR : public Register32 { + }; + class SM0AR : public Register32 { + }; + + constexpr DMA(int i) : m_index(i) {} + //constexpr operator int() const { return m_index; } + REGS_REGISTER_AT(LIFCR, 0x08); + volatile SCR * SCR(int i ) const { return (class SCR *)(Base() + 0x10 + 0x18*i); }; + volatile SNDTR * SNDTR(int i ) const { return (class SNDTR *)(Base() + 0x14 + 0x18*i); }; + volatile SPAR * SPAR(int i ) const { return (class SPAR *)(Base() + 0x18 + 0x18*i); }; + volatile SM0AR * SM0AR(int i ) const { return (class SM0AR *)(Base() + 0x1C + 0x18*i); }; +private: + constexpr uint32_t Base() const { + return 0x40026000 + 0x400*m_index; + }; + int m_index; +}; + +constexpr DMA DMA1(0); +constexpr DMA DMA2(1); + +#endif diff --git a/ion/src/f730/regs/exti.h b/ion/src/f730/regs/exti.h new file mode 100644 index 000000000..46ee8550b --- /dev/null +++ b/ion/src/f730/regs/exti.h @@ -0,0 +1,34 @@ +#ifndef REGS_EXTI_H +#define REGS_EXTI_H + +#include "register.h" + +class EXTI { +public: + class MaskRegister : Register32 { + public: + bool get(int index) { return (bool)getBitRange(index, index); } + void set(int index, bool state) volatile { setBitRange(index, index, state); } + }; + + class IMR : public MaskRegister { }; + class EMR : public MaskRegister { }; + class RTSR : public MaskRegister { }; + class FTSR : public MaskRegister { }; + class PR : public MaskRegister { }; + + constexpr EXTI() {}; + REGS_REGISTER_AT(IMR, 0x00); + REGS_REGISTER_AT(EMR, 0x04); + REGS_REGISTER_AT(RTSR, 0x08); + REGS_REGISTER_AT(FTSR, 0x0C); + REGS_REGISTER_AT(PR, 0x14); +private: + constexpr uint32_t Base() const { + return 0x40013C00; + } +}; + +constexpr EXTI EXTI; + +#endif diff --git a/ion/src/f730/regs/flash.h b/ion/src/f730/regs/flash.h new file mode 100644 index 000000000..218b17e4b --- /dev/null +++ b/ion/src/f730/regs/flash.h @@ -0,0 +1,56 @@ +#ifndef REGS_FLASH_H +#define REGS_FLASH_H + +#include "register.h" + +class FLASH { +public: + class ACR : public Register32 { + public: + REGS_FIELD(LATENCY, uint8_t, 3, 0); + REGS_BOOL_FIELD(PRFTEN, 8); + REGS_BOOL_FIELD(ICEN, 9); + REGS_BOOL_FIELD(DCEN, 10); + REGS_BOOL_FIELD(ICRST, 11); + REGS_BOOL_FIELD(DCRST, 12); + }; + + class KEYR : public Register32 { + }; + + class CR : public Register32 { + public: + enum class PSIZE : uint8_t { + X8 = 0, + X16 = 1, + X32 = 2, + X64 = 3 + }; + REGS_BOOL_FIELD(PG, 0); + REGS_BOOL_FIELD(SER, 1); + REGS_BOOL_FIELD(MER, 2); + REGS_FIELD(SNB, uint8_t, 6, 3); + REGS_TYPE_FIELD(PSIZE, 9, 8); + REGS_BOOL_FIELD(STRT, 16); + REGS_BOOL_FIELD(LOCK, 31); + }; + + class SR : public Register32 { + public: + REGS_BOOL_FIELD(BSY, 16); + }; + + constexpr FLASH() {}; + REGS_REGISTER_AT(ACR, 0x00); + REGS_REGISTER_AT(KEYR, 0x04); + REGS_REGISTER_AT(SR, 0x0C); + REGS_REGISTER_AT(CR, 0x10); +private: + constexpr uint32_t Base() const { + return 0x40023C00; + } +}; + +constexpr FLASH FLASH; + +#endif diff --git a/ion/src/f730/regs/fsmc.h b/ion/src/f730/regs/fsmc.h new file mode 100644 index 000000000..52bfcbdac --- /dev/null +++ b/ion/src/f730/regs/fsmc.h @@ -0,0 +1,79 @@ +#ifndef REGS_FSMC_H +#define REGS_FSMC_H + +#include "register.h" + +class FSMC { +public: + class BCR : Register32 { + public: + enum class MTYP : uint8_t { + SRAM = 0, + PSRAM = 1, + NOR = 2 + }; + enum class MWID : uint8_t { + EIGHT_BITS = 0, + SIXTEEN_BITS = 1 + }; + REGS_BOOL_FIELD(MBKEN, 0); + REGS_BOOL_FIELD(MUXEN, 1); + REGS_TYPE_FIELD(MTYP, 3, 2); + REGS_TYPE_FIELD(MWID, 5, 4); + REGS_BOOL_FIELD(WREN, 12); + REGS_BOOL_FIELD(EXTMOD, 14); + }; + + class BTR : Register32 { + public: + enum class ACCMOD : uint8_t { + A = 0, + B = 1, + C = 2, + D = 3 + }; + REGS_FIELD(ADDSET, uint8_t, 3, 0); + REGS_FIELD(ADDHLD, uint8_t, 7, 4); + REGS_FIELD(DATAST, uint8_t, 15, 8); + REGS_FIELD(BUSTURN, uint8_t, 19, 16); + REGS_FIELD(CLKDIV, uint8_t, 23, 20); + REGS_FIELD(DATLAT, uint8_t, 27, 24); + REGS_TYPE_FIELD(ACCMOD, 29, 28); + }; + + class BWTR : Register32 { + public: + enum class ACCMOD : uint8_t { + A = 0, + B = 1, + C = 2, + D = 3 + }; + REGS_FIELD(ADDSET, uint8_t, 3, 0); + REGS_FIELD(ADDHLD, uint8_t, 7, 4); + REGS_FIELD(DATAST, uint8_t, 15, 8); + REGS_FIELD(BUSTURN, uint8_t, 19, 16); + REGS_FIELD(CLKDIV, uint8_t, 23, 20); + REGS_FIELD(DATLAT, uint8_t, 27, 24); + REGS_TYPE_FIELD(ACCMOD, 29, 28); + }; + + constexpr FSMC() {} + volatile BCR * BCR(int index) const { + return (class BCR *)(Base() + 8*(index-1)); + } + volatile BTR * BTR(int index) const { + return (class BTR *)(Base() + 4 + 8*(index-1)); + } + volatile BWTR * BWTR(int index) const { + return (class BWTR *)(Base() + 0x104 + 8*(index-1)); + } +private: + constexpr uint32_t Base() const { + return 0xA0000000; + }; +}; + +constexpr FSMC FSMC; + +#endif diff --git a/ion/src/f730/regs/gpio.h b/ion/src/f730/regs/gpio.h new file mode 100644 index 000000000..2371ac0eb --- /dev/null +++ b/ion/src/f730/regs/gpio.h @@ -0,0 +1,104 @@ +#ifndef REGS_GPIO_H +#define REGS_GPIO_H + +#include "register.h" + +class GPIO { +public: + class MODER : public Register32 { + public: + enum class Mode { + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + }; + Mode getMode(int index) { return (Mode)getBitRange(2*index+1, 2*index); } + void setMode(int index, Mode mode) volatile { setBitRange(2*index+1, 2*index, (uint32_t)mode); } + }; + + class OTYPER : Register32 { + public: + enum class Type { + PushPull = 0, + OpenDrain = 1 + }; + Type getType(int index) { return (Type)getBitRange(index, index); } + void setType(int index, Type type) volatile { setBitRange(index, index, (uint32_t)type); } + }; + + class PUPDR : public Register32 { + public: + enum class Pull { + None = 0, + Up = 1, + Down = 2, + Reserved = 3 + }; + Pull getPull(int index) { return (Pull)getBitRange(2*index+1, 2*index); } + void setPull(int index, Pull pull) volatile { setBitRange(2*index+1, 2*index, (uint32_t)pull); } + }; + + class IDR : public Register32 { + public: + bool get(int index) volatile { return (bool)getBitRange(index, index); } + }; + + class ODR : public Register32 { + public: + bool get(int index) volatile { return (bool)getBitRange(index, index); } + void set(int index, bool state) volatile { setBitRange(index, index, state); } + }; + + class AFR : Register64 { + /* The AFR register doesn't exist per-se in the documentation. It is the + * consolidation of AFRL and AFRH which are two 32 bits registers. */ + public: + enum class AlternateFunction { + AF0 = 0, AF1 = 1, AF2 = 2, AF3 = 3, + AF4 = 4, AF5 = 5, AF6 = 6, AF7 = 7, + AF8 = 8, AF9 = 9, AF10 = 10, AF11 = 11, + AF12 = 12, AF13 = 13, AF14 = 14, AF15 = 15 + }; + AlternateFunction getAlternateFunction(int index) { + return (AlternateFunction)getBitRange(4*index+3, 4*index); + } + void setAlternateFunction(int index, AlternateFunction af) volatile { + setBitRange(4*index+3, 4*index, (uint64_t)af); + } + }; + + constexpr GPIO(int i) : m_index(i) {} + constexpr operator int() const { return m_index; } + REGS_REGISTER_AT(MODER, 0x00); + REGS_REGISTER_AT(OTYPER, 0x04); + REGS_REGISTER_AT(PUPDR, 0x0C); + REGS_REGISTER_AT(IDR, 0x10); + REGS_REGISTER_AT(ODR, 0x14); + REGS_REGISTER_AT(AFR, 0x20); +private: + constexpr uint32_t Base() const { + return 0x40020000 + 0x400*m_index; + }; + int m_index; +}; + +constexpr GPIO GPIOA(0); +constexpr GPIO GPIOB(1); +constexpr GPIO GPIOC(2); +constexpr GPIO GPIOD(3); +constexpr GPIO GPIOE(4); +constexpr GPIO GPIOF(5); +constexpr GPIO GPIOG(6); +constexpr GPIO GPIOH(7); + +class GPIOPin { +public: + constexpr GPIOPin(GPIO group, uint8_t pin) : m_data(((group&0xF) << 4) | (pin&0xF)) {} + GPIO group() const { return GPIO(m_data>>4); } + uint8_t pin() const { return m_data & 0xF; } +private: + uint8_t m_data; +}; + +#endif diff --git a/ion/src/f730/regs/itm.h b/ion/src/f730/regs/itm.h new file mode 100644 index 000000000..196737ca3 --- /dev/null +++ b/ion/src/f730/regs/itm.h @@ -0,0 +1,32 @@ +#ifndef REGS_ITM_H +#define REGS_ITM_H + +#include "register.h" + +// See ARM Cortex M4 TRM + +class ITM { +public: + class STIM : public Register8 { + }; + + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0314h/Chdbicac.html + class TER : Register32 { + public: + bool get(int index) volatile { return (bool)getBitRange(index, index); } + }; + + constexpr ITM() {}; + volatile STIM * STIM(int i) const { + return (class STIM *)(Base() + 4*i); + }; + REGS_REGISTER_AT(TER, 0xE00); +private: + constexpr uint32_t Base() const { + return 0xE0000000; + } +}; + +constexpr ITM ITM; + +#endif diff --git a/ion/src/f730/regs/mpu.h b/ion/src/f730/regs/mpu.h new file mode 100644 index 000000000..f9c2049f4 --- /dev/null +++ b/ion/src/f730/regs/mpu.h @@ -0,0 +1,71 @@ +#ifndef REGS_MPU_H +#define REGS_MPU_H + +#include "register.h" + +class MPU { +public: + class TYPER : Register32 { + public: + REGS_FIELD_R(IREGION, uint8_t, 23, 16); + REGS_FIELD_R(DREGION, uint8_t, 15, 8); + REGS_BOOL_FIELD_R(SEPARATE, 0); + }; + + class CTRL : Register32 { + public: + REGS_BOOL_FIELD(PRIVDEFENA, 2); + REGS_BOOL_FIELD(HFNMIENA, 1); + REGS_BOOL_FIELD(ENABLE, 0); + }; + + class RNR : Register32 { + public: + REGS_FIELD(REGION, uint8_t, 7, 0); + }; + + class RBAR : Register32 { + public: + void setADDR(void * address) volatile { assert(((uint32_t)address & 0b11111) == 0); setBitRange(31, 5, (uint32_t)address >> 5); } + REGS_BOOL_FIELD(VALID, 4); + REGS_FIELD(REGION, uint8_t, 3, 0); + }; + + class RASR : Register32 { + public: + REGS_BOOL_FIELD(XN, 28); + REGS_FIELD(AP, uint8_t, 26, 24); + REGS_FIELD(TEX, uint8_t, 21, 19); + REGS_BOOL_FIELD(S, 18); + REGS_BOOL_FIELD(C, 17); + REGS_BOOL_FIELD(B, 16); + REGS_FIELD(SRD, uint8_t, 15, 8); + enum class RegionSize : uint8_t { + Bytes32 = 0b00100, + Bytes64 = 0b00101, + Bytes128 = 0b00110, + KyloBytes1 = 0b01001, + MegaBytes1 = 0b10011, + GigaBytes1 = 0b11101, + GigaBytes4 = 0b11111 + }; + + REGS_FIELD(SIZE, RegionSize, 5, 1); + REGS_BOOL_FIELD(ENABLE, 0); + }; + + constexpr MPU() {}; + REGS_REGISTER_AT(TYPER, 0x0); + REGS_REGISTER_AT(CTRL, 0x04); + REGS_REGISTER_AT(RNR, 0x08); + REGS_REGISTER_AT(RBAR, 0x0C); + REGS_REGISTER_AT(RASR, 0x10); +private: + constexpr uint32_t Base() const { + return 0xE000ED90; + }; +}; + +constexpr MPU MPU; + +#endif diff --git a/ion/src/f730/regs/nvic.h b/ion/src/f730/regs/nvic.h new file mode 100644 index 000000000..dd72ea0d5 --- /dev/null +++ b/ion/src/f730/regs/nvic.h @@ -0,0 +1,37 @@ +#ifndef REGS_NVIC_H +#define REGS_NVIC_H + +#include "register.h" + +// http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf +class NVIC { +public: + class MaskRegister : Register32 { + public: + bool get(int index) { return (bool)getBitRange(index, index); } + void set(int index, bool state) volatile { setBitRange(index, index, state); } + }; + + class NVIC_ISER0 : public MaskRegister { }; + class NVIC_ISER1 : public MaskRegister { }; + class NVIC_ISER2 : public MaskRegister { }; + class NVIC_ICER0 : public MaskRegister { }; + class NVIC_ICER1 : public MaskRegister { }; + class NVIC_ICER2 : public MaskRegister { }; + + constexpr NVIC() {}; + REGS_REGISTER_AT(NVIC_ISER0, 0x00); + REGS_REGISTER_AT(NVIC_ISER1, 0x04); + REGS_REGISTER_AT(NVIC_ISER2, 0x08); + REGS_REGISTER_AT(NVIC_ICER0, 0x80); + REGS_REGISTER_AT(NVIC_ICER1, 0x84); + REGS_REGISTER_AT(NVIC_ICER2, 0x88); +private: + constexpr uint32_t Base() const { + return 0xE000E100; + } +}; + +constexpr NVIC NVIC; + +#endif diff --git a/ion/src/f730/regs/otg.h b/ion/src/f730/regs/otg.h new file mode 100644 index 000000000..5c4f0c89e --- /dev/null +++ b/ion/src/f730/regs/otg.h @@ -0,0 +1,195 @@ +#ifndef REGS_OTG_H +#define REGS_OTG_H + +#include "register.h" + +class OTG { +public: + class GAHBCFG : public Register32 { + public: + REGS_BOOL_FIELD(GINTMSK, 0); + }; + + class GUSBCFG : public Register32 { + public: + REGS_BOOL_FIELD(PHYSEL, 6); + REGS_FIELD(TRDT, uint8_t, 13, 10); + REGS_BOOL_FIELD(FDMOD, 30); + }; + + class GRSTCTL : public Register32 { + public: + REGS_BOOL_FIELD(CSRST, 0); + REGS_BOOL_FIELD(RXFFLSH, 4); + REGS_BOOL_FIELD(TXFFLSH, 5); + REGS_FIELD(TXFNUM, uint8_t, 10, 6); + REGS_BOOL_FIELD(AHBIDL, 31); + }; + + class GINTSTS : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(MMIS, 1); + REGS_BOOL_FIELD(SOF, 3); + REGS_BOOL_FIELD(RXFLVL, 4); + REGS_BOOL_FIELD(USBSUSP, 11); + REGS_BOOL_FIELD(USBRST, 12); + REGS_BOOL_FIELD(ENUMDNE, 13); + REGS_BOOL_FIELD(IEPINT, 18); + REGS_BOOL_FIELD(WKUPINT, 31); + }; + + class GINTMSK : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(RXFLVLM, 4); + REGS_BOOL_FIELD(USBSUSPM, 11); + REGS_BOOL_FIELD(USBRST, 12); + REGS_BOOL_FIELD(ENUMDNEM, 13); + REGS_BOOL_FIELD(IEPINT, 18); + REGS_BOOL_FIELD(WUIM, 31); + }; + + class GRXSTSP : public Register32 { + public: + using Register32::Register32; + enum class PKTSTS { + GlobalOutNAK = 1, + OutReceived = 2, + OutTransferCompleted = 3, // After each Out Transaction + SetupTransactionCompleted = 4, // Supposed to be after each SETUP transaction + SetupReceived = 6 + }; + REGS_FIELD(EPNUM, uint8_t, 3, 0); + REGS_FIELD(BCNT, uint16_t, 14, 4); + PKTSTS getPKTSTS() volatile { return (PKTSTS)getBitRange(20, 17); } + }; + + class GRXFSIZ : public Register32 { + public: + REGS_FIELD(RXFD, uint16_t, 15, 0); + }; + + class DIEPTXF0 : public Register32 { + public: + REGS_FIELD(TX0FSA, uint16_t, 15, 0); + REGS_FIELD(TX0FD, uint16_t, 31, 16); + }; + + class GCCFG : public Register32 { + public: + REGS_BOOL_FIELD(PWRDWN, 16); + REGS_BOOL_FIELD(VBDEN, 21); + }; + + class DCFG : public Register32 { + public: + enum class DSPD { + FullSpeed = 3, + }; + void setDSPD(DSPD s) volatile { setBitRange(1, 0, (uint8_t)s); } + REGS_FIELD(DAD, uint8_t, 10, 4); + }; + + class DCTL : public Register32 { + public: + REGS_BOOL_FIELD(SDIS, 1); + }; + + class DIEPMSK : public Register32 { + public: + REGS_BOOL_FIELD(XFRCM, 0); + }; + + class DAINTMSK : public Register32 { + public: + REGS_FIELD(IEPM, uint16_t, 15, 0); + REGS_FIELD(OEPM, uint16_t, 31, 16); + }; + + class DIEPCTL0 : public Register32 { + public: + enum class MPSIZ { + Size64 = 0, + Size32 = 1, + Size16 = 2, + Size8 = 3 + }; + using Register32::Register32; + void setMPSIZ(MPSIZ s) volatile { setBitRange(1, 0, (uint8_t)s); } + REGS_BOOL_FIELD(STALL, 21); + REGS_FIELD(TXFNUM, uint8_t, 25, 22); + REGS_BOOL_FIELD(CNAK, 26); + REGS_BOOL_FIELD(SNAK, 27); + REGS_BOOL_FIELD(EPENA, 31); + }; + + class DOEPCTL0 : public Register32 { + public: + REGS_BOOL_FIELD(CNAK, 26); + REGS_BOOL_FIELD(SNAK, 27); + REGS_BOOL_FIELD(EPENA, 31); + }; + + class DIEPINT : public Register32 { + public: + REGS_BOOL_FIELD(XFRC, 0); + REGS_BOOL_FIELD(INEPNE, 6); + }; + + class DIEPTSIZ0 : public Register32 { + public: + using Register32::Register32; + REGS_FIELD(XFRSIZ, uint8_t, 6, 0); + REGS_FIELD(PKTCNT, uint8_t, 20, 19); + }; + + class DOEPTSIZ0 : public Register32 { + public: + using Register32::Register32; + REGS_FIELD(XFRSIZ, uint8_t, 6, 0); + REGS_BOOL_FIELD(PKTCNT, 19); + REGS_FIELD(STUPCNT, uint8_t, 30, 29); + }; + + class PCGCCTL : public Register32 { + public: + REGS_BOOL_FIELD(STPPCLK, 0); + REGS_BOOL_FIELD(GATEHCLK, 1); + }; + + class DFIFO0 : public Register32 { + }; + + constexpr OTG() {}; + REGS_REGISTER_AT(GAHBCFG, 0x008); + REGS_REGISTER_AT(GUSBCFG, 0x00C); + REGS_REGISTER_AT(GRSTCTL, 0x010); + REGS_REGISTER_AT(GINTSTS, 0x014); + REGS_REGISTER_AT(GINTMSK, 0x018); + REGS_REGISTER_AT(GRXSTSP, 0x020); + REGS_REGISTER_AT(GRXFSIZ, 0x024); + REGS_REGISTER_AT(DIEPTXF0, 0x28); + REGS_REGISTER_AT(GCCFG, 0x038); + REGS_REGISTER_AT(DCFG, 0x800); + REGS_REGISTER_AT(DCTL, 0x804); + REGS_REGISTER_AT(DIEPMSK, 0x810); + REGS_REGISTER_AT(DAINTMSK, 0x81C); + REGS_REGISTER_AT(DIEPCTL0, 0x900); + REGS_REGISTER_AT(DIEPTSIZ0, 0x910); + REGS_REGISTER_AT(DOEPCTL0, 0xB00); + REGS_REGISTER_AT(DOEPTSIZ0, 0xB10); + REGS_REGISTER_AT(PCGCCTL, 0xE00); + REGS_REGISTER_AT(DFIFO0, 0x1000); + constexpr volatile DIEPINT * DIEPINT(int i) const { + return (class DIEPINT *)(Base() + 0x908 + i*0x20); + } +private: + constexpr uint32_t Base() const { + return 0x50000000; + } +}; + +constexpr OTG OTG; + +#endif diff --git a/ion/src/f730/regs/pwr.h b/ion/src/f730/regs/pwr.h new file mode 100644 index 000000000..089d72e18 --- /dev/null +++ b/ion/src/f730/regs/pwr.h @@ -0,0 +1,25 @@ +#ifndef REGS_PWR_H +#define REGS_PWR_H + +#include "register.h" + +class PWR { +public: + class CR : Register32 { + public: + REGS_BOOL_FIELD(LPDS, 0); + REGS_BOOL_FIELD(PPDS, 1); + REGS_BOOL_FIELD(FPDS, 9); + }; + + constexpr PWR() {}; + REGS_REGISTER_AT(CR, 0x00); +private: + constexpr uint32_t Base() const { + return 0x40007000; + }; +}; + +constexpr PWR PWR; + +#endif diff --git a/ion/src/f730/regs/rcc.h b/ion/src/f730/regs/rcc.h new file mode 100644 index 000000000..0536441d4 --- /dev/null +++ b/ion/src/f730/regs/rcc.h @@ -0,0 +1,149 @@ +#ifndef REGS_RCC_H +#define REGS_RCC_H + +#include "register.h" + +class RCC { +public: + class CR : public Register32 { + public: + REGS_BOOL_FIELD(HSION, 0); + REGS_BOOL_FIELD(HSEON, 16); + REGS_BOOL_FIELD_R(HSERDY, 17); + REGS_BOOL_FIELD(PLLON, 24); + REGS_BOOL_FIELD(PLLRDY, 25); + }; + + class PLLCFGR : public Register32 { + public: + REGS_FIELD(PLLM, uint8_t, 5, 0); + REGS_FIELD(PLLN, uint16_t, 14, 6); + REGS_FIELD(PLLP, uint8_t, 17, 16); + enum class PLLSRC { + HSI = 0, + HSE = 1 + }; + void setPLLSRC(PLLSRC s) volatile { setBitRange(22, 22, (uint8_t)s); } + REGS_FIELD(PLLQ, uint8_t, 27, 24); + REGS_FIELD(PLLR, uint8_t, 30, 28); + }; + + class CFGR : public Register32 { + public: + enum class SW { + HSI = 0, + HSE = 1, + PLL = 2 + }; + void setSW(SW s) volatile { setBitRange(1, 0, (uint8_t)s); } + SW getSWS() volatile { return (SW)getBitRange(3,2); } + enum class AHBPrescaler { + SysClk = 0, + SysClkDividedBy2 = 8, + SysClkDividedBy4 = 9, + SysClkDividedBy8 = 10, + SysClkDividedBy16 = 11, + SysClkDividedBy64 = 12, + SysClkDividedBy128 = 13, + SysClkDividedBy256 = 14, + SysClkDividedBy512 = 15 + }; + void setHPRE(AHBPrescaler p) volatile { setBitRange(7, 4, (uint32_t)p); } + enum class APBPrescaler{ + AHB = 0, + AHBDividedBy2 = 4, + AHBDividedBy4 = 5, + AHBDividedBy8 = 6, + AHBDividedBy16 = 7 + }; + void setPPRE1(APBPrescaler r) volatile { setBitRange(12, 10, (uint32_t)r); } + }; + + class AHB1ENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(GPIOAEN, 0); + REGS_BOOL_FIELD(GPIOBEN, 1); + REGS_BOOL_FIELD(GPIOCEN, 2); + REGS_BOOL_FIELD(GPIODEN, 3); + REGS_BOOL_FIELD(GPIOEEN, 4); + REGS_BOOL_FIELD(GPIOFEN, 5); + REGS_BOOL_FIELD(GPIOGEN, 6); + REGS_BOOL_FIELD(GPIOHEN, 7); + REGS_BOOL_FIELD(CRCEN, 12); + REGS_BOOL_FIELD(DMA1EN, 21); + REGS_BOOL_FIELD(DMA2EN, 22); + }; + + class AHB2ENR : Register32 { + public: + REGS_BOOL_FIELD(RNGEN, 6); + REGS_BOOL_FIELD(OTGFSEN, 7); + }; + + class AHB3ENR : Register32 { + public: + REGS_BOOL_FIELD(FSMCEN, 0); + REGS_BOOL_FIELD(QSPIEN, 1); + }; + + class APB1ENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(TIM3EN, 1); + REGS_BOOL_FIELD(SPI3EN, 15); + REGS_BOOL_FIELD(USART3EN, 18); + REGS_BOOL_FIELD(PWREN, 28); + }; + + class APB2ENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(TIM1EN, 0); + REGS_BOOL_FIELD(USART1EN, 4); + REGS_BOOL_FIELD(ADC1EN, 8); + REGS_BOOL_FIELD(SDIOEN, 11); + REGS_BOOL_FIELD(SPI1EN, 12); + REGS_BOOL_FIELD(SYSCFGEN, 14); + }; + + class AHB1LPENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(GPIOBLPEN, 1); + REGS_BOOL_FIELD(GPIOCLPEN, 2); + }; + + class APB1LPENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(TIM3LPEN, 1); + }; + + class DCKCFGR2 : Register32 { + public: + REGS_BOOL_FIELD(CK48MSEL, 27); + REGS_BOOL_FIELD(CKSDIOSEL, 28); + }; + + constexpr RCC() {}; + REGS_REGISTER_AT(CR, 0x00); + REGS_REGISTER_AT(PLLCFGR, 0x04); + REGS_REGISTER_AT(CFGR, 0x08); + REGS_REGISTER_AT(AHB1ENR, 0x30); + REGS_REGISTER_AT(AHB2ENR, 0x34); + REGS_REGISTER_AT(AHB3ENR, 0x38); + REGS_REGISTER_AT(APB1ENR, 0x40); + REGS_REGISTER_AT(APB2ENR, 0x44); + REGS_REGISTER_AT(AHB1LPENR, 0x50); + REGS_REGISTER_AT(APB1LPENR, 0x60); + REGS_REGISTER_AT(DCKCFGR2, 0x94); +private: + constexpr uint32_t Base() const { + return 0x40023800; + } +}; + +constexpr RCC RCC; + +#endif diff --git a/ion/src/f730/regs/register.h b/ion/src/f730/regs/register.h new file mode 100644 index 000000000..5ed38d779 --- /dev/null +++ b/ion/src/f730/regs/register.h @@ -0,0 +1,61 @@ +#ifndef REGS_REGISTER_H +#define REGS_REGISTER_H + +#include +#include + +template +class Register { +public: + Register() = delete; + Register(T v) : m_value(v) {} + void set(Register value) volatile { + m_value = value.m_value; + } + void set(T value) volatile { + m_value = value; + } + T get() volatile { + return m_value; + } + void setBitRange(uint8_t high, uint8_t low, T value) volatile { + m_value = bit_range_set_value(high, low, m_value, value); + } + T getBitRange(uint8_t high, uint8_t low) volatile { + /* "Shift behavior is undefined if the right operand is negative, or greater + * than or equal to the length in bits of the promoted left operand" according + * to C++ spec. */ + assert(low < 8*sizeof(T)); + return (m_value & bit_range_mask(high,low)) >> low; + } +protected: + static constexpr T bit_range_mask(uint8_t high, uint8_t low) { + // Same comment as for getBitRange: we should assert (high-low+1) < 8*sizeof(T) + return ((((T)1)<<(high-low+1))-1)< Register8; +typedef Register Register16; +typedef Register Register32; +typedef Register Register64; + +#define REGS_FIELD_R(name,type,high,low) type get##name() volatile { return (type)getBitRange(high,low); }; +#define REGS_FIELD_W(name,type,high,low) void set##name(type v) volatile { static_assert(sizeof(type) <= 4, "Invalid size"); setBitRange(high, low, static_cast(v)); }; +#define REGS_FIELD(name,type,high,low) REGS_FIELD_R(name,type,high,low); REGS_FIELD_W(name,type,high,low); +#define REGS_TYPE_FIELD(name,high,low) REGS_FIELD(name,name,high,low) +#define REGS_BOOL_FIELD(name,bit) REGS_FIELD(name,bool,bit,bit) +#define REGS_BOOL_FIELD_R(name,bit) REGS_FIELD_R(name,bool,bit,bit) +#define REGS_BOOL_FIELD_W(name,bit) REGS_FIELD_W(name,bool,bit,bit) +#define REGS_REGISTER_AT(name, offset) constexpr volatile name * name() const { return (class name *)(Base() + offset); }; + +#endif diff --git a/ion/src/f730/regs/regs.h b/ion/src/f730/regs/regs.h new file mode 100644 index 000000000..b5fad9e8a --- /dev/null +++ b/ion/src/f730/regs/regs.h @@ -0,0 +1,25 @@ +#ifndef REGS_REGS_H +#define REGS_REGS_H + +#include "adc.h" +#include "cm4.h" +#include "crc.h" +#include "dma.h" +#include "exti.h" +#include "flash.h" +#include "fsmc.h" +#include "gpio.h" +#include "itm.h" +#include "mpu.h" +#include "nvic.h" +#include "pwr.h" +#include "rcc.h" +#include "rng.h" +#include "otg.h" +#include "sdio.h" +#include "spi.h" +#include "syscfg.h" +#include "tim.h" +#include "usart.h" + +#endif diff --git a/ion/src/f730/regs/rng.h b/ion/src/f730/regs/rng.h new file mode 100644 index 000000000..6aa2d6505 --- /dev/null +++ b/ion/src/f730/regs/rng.h @@ -0,0 +1,33 @@ +#ifndef REGS_RNG_H +#define REGS_RNG_H + +#include "register.h" + +class RNG { +public: + class CR : Register32 { + public: + REGS_BOOL_FIELD(RNGEN, 2); + }; + + class SR : Register32 { + public: + REGS_BOOL_FIELD(DRDY, 0); + }; + + class DR : public Register32 { + }; + + constexpr RNG() {}; + REGS_REGISTER_AT(CR, 0x00); + REGS_REGISTER_AT(SR, 0x04); + REGS_REGISTER_AT(DR, 0x08); +private: + constexpr uint32_t Base() const { + return 0x50060800; + } +}; + +constexpr RNG RNG; + +#endif diff --git a/ion/src/f730/regs/sdio.h b/ion/src/f730/regs/sdio.h new file mode 100644 index 000000000..bb8149175 --- /dev/null +++ b/ion/src/f730/regs/sdio.h @@ -0,0 +1,112 @@ +#ifndef REGS_SDIO_H +#define REGS_SDIO_H + +#include "register.h" + +class SDIO { +public: + class POWER : Register32 { + public: + enum class PWRCTRL : uint8_t { + Off = 0, + On = 3 + }; + REGS_TYPE_FIELD(PWRCTRL, 1, 0); + }; + + class CLKCR : Register32 { + public: + enum class WIDBUS : uint8_t { + Default = 0, + FourBits = 1, + EightBits = 2 + }; + REGS_FIELD(CLKDIV, uint8_t, 7, 0); + REGS_BOOL_FIELD(CLKEN, 8); + REGS_BOOL_FIELD(PWRSAV, 9); + REGS_BOOL_FIELD(BYPASS, 10); + REGS_TYPE_FIELD(WIDBUS, 12, 11); + REGS_BOOL_FIELD(NEGEDGE, 13); + REGS_BOOL_FIELD(HWFC_EN, 14); + }; + + class ARG : public Register32 { + }; + + class CMD : public Register32 { + public: + using Register32::Register32; + enum class WAITRESP : uint8_t { + None = 0, + Short = 1, + Long = 3 + }; + REGS_FIELD(CMDINDEX, uint8_t, 5, 0); + REGS_TYPE_FIELD(WAITRESP, 7, 6); + REGS_BOOL_FIELD(WAITINT, 8); + REGS_BOOL_FIELD(WAITPEND, 9); + REGS_BOOL_FIELD(CPSMEN, 10); + }; + + class RESP : public Register32 { + }; + + class STA : Register32 { + public: + REGS_BOOL_FIELD_R(CCRCFAIL, 0); + REGS_BOOL_FIELD_R(DCRCFAIL, 1); + REGS_BOOL_FIELD_R(CTIMEOUT, 2); + REGS_BOOL_FIELD_R(DTIMEOUT, 3); + REGS_BOOL_FIELD_R(TXUNDERR, 4); + REGS_BOOL_FIELD_R(RXOVERR, 5); + REGS_BOOL_FIELD_R(CMDREND, 6); + REGS_BOOL_FIELD_R(CMDSENT, 7); + REGS_BOOL_FIELD_R(DATAEND, 8); + REGS_BOOL_FIELD_R(DBCKEND, 10); + REGS_BOOL_FIELD_R(CMDACT, 11); + REGS_BOOL_FIELD_R(TXACT, 12); + REGS_BOOL_FIELD_R(RXACT, 13); + REGS_BOOL_FIELD_R(TXFIFOHE, 14); + REGS_BOOL_FIELD_R(RXFIFOHF, 15); + REGS_BOOL_FIELD_R(TXFIFOF, 16); + REGS_BOOL_FIELD_R(RXFIFOF, 17); + REGS_BOOL_FIELD_R(TXFIFOE, 18); + REGS_BOOL_FIELD_R(RXFIFOE, 19); + REGS_BOOL_FIELD_R(TXDAVL, 20); + REGS_BOOL_FIELD_R(RXDAVL, 21); + REGS_BOOL_FIELD_R(SDIOIT, 22); + }; + + class ICR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD_W(CCRCFAILC, 0); + REGS_BOOL_FIELD_W(DCRCFAILC, 1); + REGS_BOOL_FIELD_W(CTIMEOUTC, 2); + REGS_BOOL_FIELD_W(DTIMEOUTC, 3); + REGS_BOOL_FIELD_W(TXUNDERRC, 4); + REGS_BOOL_FIELD_W(RXOVERRC, 5); + REGS_BOOL_FIELD_W(CMDRENDC, 6); + REGS_BOOL_FIELD_W(CMDSENTC, 7); + REGS_BOOL_FIELD_W(DATAENDC, 8); + REGS_BOOL_FIELD_W(DBCKENDC, 10); + REGS_BOOL_FIELD_W(SDIOITC, 22); + }; + + constexpr SDIO() {}; + REGS_REGISTER_AT(POWER, 0x00); + REGS_REGISTER_AT(CLKCR, 0x04); + REGS_REGISTER_AT(ARG, 0x08); + REGS_REGISTER_AT(CMD, 0x0C); + volatile RESP * RESP(int i ) const { return (class RESP *)(Base() + 0x10+4*i); }; + REGS_REGISTER_AT(STA, 0x34); + REGS_REGISTER_AT(ICR, 0x38); +private: + constexpr uint32_t Base() const { + return 0x40012C00; + } +}; + +constexpr SDIO SDIO; + +#endif diff --git a/ion/src/f730/regs/spi.h b/ion/src/f730/regs/spi.h new file mode 100644 index 000000000..abe050b97 --- /dev/null +++ b/ion/src/f730/regs/spi.h @@ -0,0 +1,45 @@ +#ifndef REGS_SPI_H +#define REGS_SPI_H + +#include "register.h" + +class SPI { +public: + class CR1 : Register16 { + public: + REGS_BOOL_FIELD(MSTR, 2); + REGS_BOOL_FIELD(SPE, 6); + REGS_BOOL_FIELD(LSBFIRST, 7); + REGS_BOOL_FIELD(SSI, 8); + REGS_BOOL_FIELD(SSM, 9); + REGS_BOOL_FIELD(RXONLY, 10); + REGS_BOOL_FIELD(DFF, 11); + }; + class CR2 : Register16 { + public: + REGS_BOOL_FIELD(RXDMAEN, 0); + }; + class SR : Register16 { + public: + REGS_BOOL_FIELD(RXNE, 0); + REGS_BOOL_FIELD(TXE, 1); + }; + class DR : public Register16 { + }; + + constexpr SPI(int i) : m_index(i) {} + constexpr operator int() const { return m_index; } + REGS_REGISTER_AT(CR1, 0x00); + REGS_REGISTER_AT(CR2, 0x04); + REGS_REGISTER_AT(SR, 0x08); + REGS_REGISTER_AT(DR, 0x0C); +private: + constexpr uint32_t Base() const { + return ((uint32_t []){0x40013000, 0x40003800, 0x40003C00})[m_index-1]; + }; + int m_index; +}; + +constexpr SPI SPI1(1); + +#endif diff --git a/ion/src/f730/regs/syscfg.h b/ion/src/f730/regs/syscfg.h new file mode 100644 index 000000000..0690b9430 --- /dev/null +++ b/ion/src/f730/regs/syscfg.h @@ -0,0 +1,44 @@ +#ifndef REGS_SYSCFG_H +#define REGS_SYSCFG_H + +#include "register.h" + +#include "gpio.h" + +class SYSCFG { +public: + class MEMRMP : Register32 { + public: + enum class MemMode : uint8_t { + MainFlashmemory = 0, + SystemFlashmemory = 1, + EmbeddedSRAM = 3 + }; + REGS_FIELD(MEM_MODE, MemMode, 1, 0); + }; + class EXTICR1 : Register32 { + public: + void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*index+3, 4*index, (uint32_t)gpio); } + }; + class EXTICR2 : Register32 { + public: + void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*(index-4)+3, 4*(index-4), (uint32_t)gpio); } + }; + class EXTICR3 : Register32 { + public: + void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*(index-8)+3, 4*(index-8), (uint32_t)gpio); } + }; + constexpr SYSCFG() {}; + REGS_REGISTER_AT(MEMRMP, 0x00); + REGS_REGISTER_AT(EXTICR1, 0x08); + REGS_REGISTER_AT(EXTICR2, 0x0C); + REGS_REGISTER_AT(EXTICR3, 0x10); +private: + constexpr uint32_t Base() const { + return 0x40013800; + } +}; + +constexpr SYSCFG SYSCFG; + +#endif diff --git a/ion/src/f730/regs/tim.h b/ion/src/f730/regs/tim.h new file mode 100644 index 000000000..d76f8e293 --- /dev/null +++ b/ion/src/f730/regs/tim.h @@ -0,0 +1,100 @@ +#ifndef REGS_TIM_H +#define REGS_TIM_H + +#include "register.h" + +template +class TIM { +public: + class CR1 : Register16 { + public: + REGS_BOOL_FIELD(CEN, 0); + REGS_BOOL_FIELD(ARPE, 7); + }; + + class CCMR : Register64 { + /* We're declaring CCMR as a 64 bits register. CCMR doesn't exsist per se, + * it is in fact the consolidation of CCMR1 and CCMR2. Both are 16 bits + * registers, so one could expect the consolidation to be 32 bits. However, + * both CCMR1 and CCMR2 live on 32-bits boundaries, so the consolidation has + * to be 64 bits wide, even though we'll only use 32 bits out of 64. */ + public: + enum class CC1S : uint8_t { + OUTPUT = 0, + INPUT_TI2 = 1, + INPUT_TI1 = 2, + INPUT_TRC = 3 + }; + enum class OCM : uint8_t { + Frozen = 0, + ActiveOnMatch = 1, + InactiveOnMatch = 2, + Toggle = 3, + ForceInactive = 4, + ForceActive = 5, + PWM1 = 6, + PWM2 = 7 + }; + typedef OCM OC1M; + typedef OCM OC2M; + typedef OCM OC3M; + typedef OCM OC4M; + REGS_BOOL_FIELD(OC1PE, 3); + REGS_TYPE_FIELD(OC1M, 6, 4); + REGS_BOOL_FIELD(OC2PE, 11); + REGS_TYPE_FIELD(OC2M, 14, 12); + REGS_BOOL_FIELD(OC3PE, 35); + REGS_TYPE_FIELD(OC3M, 38, 36); + REGS_BOOL_FIELD(OC4PE, 43); + REGS_TYPE_FIELD(OC4M, 46, 44); + }; + + class CCER : Register16 { + public: + REGS_BOOL_FIELD(CC1E, 0); + REGS_BOOL_FIELD(CC2E, 4); + REGS_BOOL_FIELD(CC3E, 8); + REGS_BOOL_FIELD(CC4E, 12); + }; + + class BDTR : Register16 { + public: + REGS_BOOL_FIELD(MOE, 15); + }; + + class PSC : public Register16 {}; + class ARR : public Register16 {}; + class CCR1 : public RegisterWidth {}; + class CCR2 : public RegisterWidth {}; + class CCR3 : public RegisterWidth {}; + class CCR4 : public RegisterWidth {}; + + constexpr TIM(int i) : m_index(i) {} + REGS_REGISTER_AT(CR1, 0x0); + REGS_REGISTER_AT(CCMR, 0x18); + REGS_REGISTER_AT(CCER, 0x20); + REGS_REGISTER_AT(PSC, 0x28); + REGS_REGISTER_AT(ARR, 0x2C); + REGS_REGISTER_AT(CCR1, 0x34); + REGS_REGISTER_AT(CCR2, 0x38); + REGS_REGISTER_AT(CCR3, 0x3C); + REGS_REGISTER_AT(CCR4, 0x40); + REGS_REGISTER_AT(BDTR, 0x44); +private: + constexpr uint32_t Base() const { + return (m_index == 1 ? 0x40010000 : + (m_index <= 7 ? 0x40000000 + 0x400*(m_index-2) : + (m_index == 8 ? 0x40010400 : + (m_index <= 11 ? 0x40014000 + 0x400*(m_index-9) : + 0x40001800 + 0x400*(m_index-12) + ) + ) + ) + ); + } + int m_index; +}; + +constexpr TIM TIM3(3); + +#endif diff --git a/ion/src/f730/regs/usart.h b/ion/src/f730/regs/usart.h new file mode 100644 index 000000000..cb454c2d6 --- /dev/null +++ b/ion/src/f730/regs/usart.h @@ -0,0 +1,50 @@ +#ifndef REGS_USART_H +#define REGS_USART_H + +#include "register.h" + +class USART { +public: + class SR : Register32 { + public: + REGS_BOOL_FIELD(RXNE, 5); + REGS_BOOL_FIELD(TXE, 7); + }; + + class DR : Register32 { + public: + uint16_t get() volatile { + return (uint16_t)getBitRange(8, 0); + } + void set(uint16_t v) volatile { + setBitRange(8, 0, v); + } + }; + + class BRR : Register32 { + public: + REGS_FIELD(DIV_FRAC, uint8_t, 3, 0); + REGS_FIELD(DIV_MANTISSA, uint16_t, 15, 4); + }; + + class CR1 : Register32 { + public: + REGS_BOOL_FIELD(UE, 13); + REGS_BOOL_FIELD(TE, 3); + REGS_BOOL_FIELD(RE, 2); + }; + + constexpr USART(int i) : m_index(i) {} + constexpr operator int() const { return m_index; } + REGS_REGISTER_AT(SR, 0x00); + REGS_REGISTER_AT(DR, 0x04); + REGS_REGISTER_AT(BRR, 0x08); + REGS_REGISTER_AT(CR1, 0x0C); +private: + constexpr uint32_t Base() const { + return ((uint32_t []){0x40011000, 0x40004400, 0x40004800})[m_index-1]; + }; + int m_index; +}; + +#endif diff --git a/ion/src/f730/sd_card.cpp b/ion/src/f730/sd_card.cpp new file mode 100644 index 000000000..e677c63be --- /dev/null +++ b/ion/src/f730/sd_card.cpp @@ -0,0 +1,95 @@ +#include "sd_card.h" +#include "regs/regs.h" +extern "C" { +#include +} + +namespace Ion { +namespace SDCard { +namespace Device { + +void init() { + initGPIO(); + initCard(); +} + +void initGPIO() { + // Configure GPIOs to use AF + GPIOA.MODER()->setMode(8, GPIO::MODER::Mode::AlternateFunction); + GPIOB.MODER()->setMode(4, GPIO::MODER::Mode::AlternateFunction); + GPIOB.MODER()->setMode(5, GPIO::MODER::Mode::AlternateFunction); + GPIOB.MODER()->setMode(15, GPIO::MODER::Mode::AlternateFunction); + GPIOC.MODER()->setMode(10, GPIO::MODER::Mode::AlternateFunction); + GPIOD.MODER()->setMode(2, GPIO::MODER::Mode::AlternateFunction); + + // More precisely, AF12 which correspond to SDIO alternate functions + GPIOA.AFR()->setAlternateFunction(8, GPIO::AFR::AlternateFunction::AF12); + GPIOB.AFR()->setAlternateFunction(4, GPIO::AFR::AlternateFunction::AF12); + GPIOB.AFR()->setAlternateFunction(5, GPIO::AFR::AlternateFunction::AF12); + GPIOB.AFR()->setAlternateFunction(15, GPIO::AFR::AlternateFunction::AF12); + GPIOC.AFR()->setAlternateFunction(10, GPIO::AFR::AlternateFunction::AF12); + GPIOD.AFR()->setAlternateFunction(2, GPIO::AFR::AlternateFunction::AF12); +} + +void initCard() { + + // Power on + SDIO.POWER()->setPWRCTRL(SDIO::POWER::PWRCTRL::On); + while (SDIO.POWER()->getPWRCTRL() != SDIO::POWER::PWRCTRL::On) { + } + + // Clock set + SDIO.CLKCR()->setCLKDIV(254); + SDIO.CLKCR()->setCLKEN(true); + + sendCommand(0, 0); + // CMD8 : 0b0001 = 2.7 - 3.6V + // 0xB7 = Pattern to see back in response + sendCommand(8, 0x1B7); + + assert(SDIO.RESP(1)->get() == 0x1B7); +} + +void sendCommand(uint32_t cmd, uint32_t arg) { + class SDIO::ICR icr(0); + icr.setCCRCFAILC(true); + icr.setCTIMEOUTC(true); + icr.setCMDRENDC(true); + icr.setCMDSENTC(true); + SDIO.ICR()->set(icr); + + SDIO.ARG()->set(arg); + + SDIO::CMD::WAITRESP responseType = SDIO::CMD::WAITRESP::Short; + switch (cmd) { + case 0: + responseType = SDIO::CMD::WAITRESP::None; + break; + case 2: + case 9: + case 10: + responseType = SDIO::CMD::WAITRESP::Long; + default: + break; + } + + class SDIO::CMD command(0); + command.setCMDINDEX(cmd); + command.setCPSMEN(true); + command.setWAITRESP(responseType); + SDIO.CMD()->set(command); + + if (responseType == SDIO::CMD::WAITRESP::None) { + // Wait for timeout or command sent + while (!SDIO.STA()->getCTIMEOUT() && !SDIO.STA()->getCMDSENT()) { + } + } else { + while (!SDIO.STA()->getCTIMEOUT() && !SDIO.STA()->getCMDREND() && !SDIO.STA()->getCCRCFAIL()) { + } + } + +} + +} +} +} diff --git a/ion/src/f730/sd_card.h b/ion/src/f730/sd_card.h new file mode 100644 index 000000000..1a2babfb7 --- /dev/null +++ b/ion/src/f730/sd_card.h @@ -0,0 +1,33 @@ +#ifndef ION_DEVICE_SD_CARD_H +#define ION_DEVICE_SD_CARD_H + +extern "C" { +#include +} + +namespace Ion { +namespace SDCard { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PA8 | SDIO D1 | Alternate Function 12 | SDIO_D1 + * PB4 | SDIO D0 | Alternate Function 12 | SDIO_D0 + * PB5 | SDIO D3 | Alternate Function 12 | SDIO_D3 + * PB15 | SDIO CLK | Alternate Function 12 | SDIO_CK + * PC10 | SDIO D2 | Alternate Function 12 | SDIO_D2 + * PD2 | SDIO CMD | Alternate Function 12 | SDIO_CMD + */ + +void init(); +void initGPIO(); + +void initCard(); + +void sendCommand(uint32_t cmd, uint32_t arg); + +} +} +} + +#endif diff --git a/ion/src/f730/stack.cpp b/ion/src/f730/stack.cpp new file mode 100644 index 000000000..141e222b5 --- /dev/null +++ b/ion/src/f730/stack.cpp @@ -0,0 +1,10 @@ +#include + +extern const void * _stack_start; +extern const void * _stack_end; + +bool Ion::stackSafe() { + volatile int stackDummy; + volatile void * stackPointer = &stackDummy; + return (stackPointer >= &_stack_end && stackPointer <= &_stack_start); +} diff --git a/ion/src/f730/swd.cpp b/ion/src/f730/swd.cpp new file mode 100644 index 000000000..008dc8110 --- /dev/null +++ b/ion/src/f730/swd.cpp @@ -0,0 +1,24 @@ +#include "swd.h" +#include "regs/regs.h" + +namespace Ion { +namespace SWD { +namespace Device { + +void init() { + for(const GPIOPin & g : Pins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF0); + } +} + +void shutdown() { + for(const GPIOPin & g : Pins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } +} + +} +} +} diff --git a/ion/src/f730/swd.h b/ion/src/f730/swd.h new file mode 100644 index 000000000..49ea414c8 --- /dev/null +++ b/ion/src/f730/swd.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_SWD_H +#define ION_DEVICE_SWD_H + +#include "regs/regs.h" + +namespace Ion { +namespace SWD { +namespace Device { + +/* Pin | Role | Mode + * -----+-------------------+--------------------- + * PA13 | SWDIO | Alternate Function 0 + * PA14 | SWCLK | Alternate Function 0 + * PB3 | SWO | Alternate Function 0 + */ + +void init(); +void shutdown(); + +constexpr static GPIOPin Pins[] = { + GPIOPin(GPIOA, 13), GPIOPin(GPIOA, 14), GPIOPin(GPIOB, 3) +}; + +} +} +} + +#endif diff --git a/ion/src/f730/timing.cpp b/ion/src/f730/timing.cpp new file mode 100644 index 000000000..c22d425f7 --- /dev/null +++ b/ion/src/f730/timing.cpp @@ -0,0 +1,36 @@ +#include "timing.h" + +namespace Ion { +namespace Timing { + +/* TODO: The delay methods 'msleep' and 'usleep' are currently dependent on the + * optimizations chosen by the compiler. To prevent that and to gain in + * precision, we could use the controller cycle counter (Systick). */ + +void msleep(uint32_t ms) { + for (volatile uint32_t i=0; i<8852*ms; i++) { + __asm volatile("nop"); + } +} +void usleep(uint32_t us) { + for (volatile uint32_t i=0; i<9*us; i++) { + __asm volatile("nop"); + } +} + +uint64_t millis() { + return Ion::Timing::Device::MillisElapsed; +} + +} +} + +namespace Ion { +namespace Timing { +namespace Device { + +volatile uint64_t MillisElapsed = 0; + +} +} +} diff --git a/ion/src/f730/timing.h b/ion/src/f730/timing.h new file mode 100644 index 000000000..017bbd31a --- /dev/null +++ b/ion/src/f730/timing.h @@ -0,0 +1,16 @@ +#ifndef ION_DEVICE_TIMING_H +#define ION_DEVICE_TIMING_H + +#include + +namespace Ion { +namespace Timing { +namespace Device { + +extern volatile uint64_t MillisElapsed; + +} +} +} + +#endif diff --git a/ion/src/f730/usb.cpp b/ion/src/f730/usb.cpp new file mode 100644 index 000000000..1a76cc4e1 --- /dev/null +++ b/ion/src/f730/usb.cpp @@ -0,0 +1,187 @@ +#include +#include "usb.h" +#include +#include "device.h" +#include "display.h" +#include "regs/regs.h" +#include + +namespace Ion { +namespace USB { + +bool isPlugged() { + return Device::VbusPin.group().IDR()->get(Device::VbusPin.pin()); +} + +bool isEnumerated() { + /* Note: This implementation is not perfect. One would assume isEnumerated to + * return true for as long as the device is enumerated. But the GINTSTS + * register will be cleared in the poll() routine. */ + return OTG.GINTSTS()->getENUMDNE(); +} + +void clearEnumerationInterrupt() { + OTG.GINTSTS()->setENUMDNE(true); +} + +void enable() { + // Get out of soft-disconnected state + OTG.DCTL()->setSDIS(false); +} + +void disable() { + // Get into soft-disconnected state + OTG.DCTL()->setSDIS(true); +} + +} +} + +namespace Ion { +namespace USB { +namespace Device { + +void init() { + initGPIO(); + initOTG(); +} + +void shutdown() { + shutdownOTG(); + shutdownGPIO(); +} + +static inline void DEBUGTOGGLE() { + bool state = GPIOC.ODR()->get(11); + GPIOC.ODR()->set(11, !state); +} + +#include + +void initGPIO() { + + // DEBUG GPIO pin + GPIOC.MODER()->setMode(11, GPIO::MODER::Mode::Output); + GPIOC.ODR()->set(11, false); + + /* Configure the GPIO + * The VBUS pin is connected to the USB VBUS port. To read if the USB is + * plugged, the pin must be pulled down. */ + // FIXME: Understand how the Vbus pin really works! +#if 0 + VbusPin.group().MODER()->setMode(VbusPin.pin(), GPIO::MODER::Mode::Input); + VbusPin.group().PUPDR()->setPull(VbusPin.pin(), GPIO::PUPDR::Pull::Down); +#else + VbusPin.group().MODER()->setMode(VbusPin.pin(), GPIO::MODER::Mode::AlternateFunction); + VbusPin.group().AFR()->setAlternateFunction(VbusPin.pin(), GPIO::AFR::AlternateFunction::AF10); +#endif + + DmPin.group().MODER()->setMode(DmPin.pin(), GPIO::MODER::Mode::AlternateFunction); + DmPin.group().AFR()->setAlternateFunction(DmPin.pin(), GPIO::AFR::AlternateFunction::AF10); + + DpPin.group().MODER()->setMode(DpPin.pin(), GPIO::MODER::Mode::AlternateFunction); + DpPin.group().AFR()->setAlternateFunction(DpPin.pin(), GPIO::AFR::AlternateFunction::AF10); +} + +void shutdownGPIO() { + constexpr static GPIOPin USBPins[] = {DpPin, DmPin, VbusPin}; + for (const GPIOPin & g : USBPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } +} + +void initOTG() { + // Wait for AHB idle + while (!OTG.GRSTCTL()->getAHBIDL()) { + } + + /* Core soft reset: Clears the interrupts and many of the CSR register bits, + * resets state machines, flushes the FIFOs and terminates USB transactions.*/ + OTG.GRSTCTL()->setCSRST(true); + while (OTG.GRSTCTL()->getCSRST()) { + } + + /* Enable the transceiver module of the PHY. It must be done to allow any USB + * operation */ + OTG.GCCFG()->setPWRDWN(true); + + /* Enable VBUS sensing comparators to detect valid levels for USB operation. + * This is used for instance to end the session if the host is switched off.*/ + OTG.GCCFG()->setVBDEN(true); + + // Force peripheral only mode + OTG.GUSBCFG()->setFDMOD(true); + + // Configure the USB turnaround time, depending on the AHB clock speed (96MHz) + OTG.GUSBCFG()->setTRDT(0x6); + + // Clear the interrupts + OTG.GINTSTS()->set(0); + + // Full speed device + OTG.DCFG()->setDSPD(OTG::DCFG::DSPD::FullSpeed); + + /* RxFIFO size. The value is in terms of 32-bit words. + * According to the reference manual, it should be, at minimum: + * (4 * number of control endpoints + 6) + * To receive SETUP packets on control endpoint + * + ((largest USB packet used / 4) + 1) + * To receive 1 USB packet + 1 packet status + * + (2 * number of OUT endpoints) + * Transfer complete status information + * + 1 for Global NAK + * So, for the calculator: (4*1+6) + (64/4 + 1) + (2*1) + 1 = 30 + * As the RAM size is 1.25kB, the size should be at most 320, minus the space + * for the Tx FIFOs. + * However, we tested and found that only values between 40 and 255 actually + * work. We arbitrarily chose 128. */ + OTG.GRXFSIZ()->setRXFD(128); + + // Unmask the interrupt line assertions + OTG.GAHBCFG()->setGINTMSK(true); + + // Restart the PHY clock + OTG.PCGCCTL()->setSTPPCLK(false); + + // Pick which interrupts we're interested in + class OTG::GINTMSK intMask(0); // Reset value + intMask.setENUMDNEM(true); // Speed enumeration done + intMask.setRXFLVLM(true); // Receive FIFO non empty + intMask.setIEPINT(true); // IN endpoint interrupt + OTG.GINTMSK()->set(intMask); + + // Unmask IN interrupts for endpoint 0 only + OTG.DAINTMSK()->setIEPM(1); + + /* Unmask the IN transfer completed interrupt for all endpoints. This + * interrupt warns that a IN transaction happened on the endpoint. */ + OTG.DIEPMSK()->setXFRCM(true); + + /* To communicate with a USB host, the device still needs to get out of soft- + * disconnected state (SDIS in the DCTL register). We do this when we detect + * that the USB cable is plugged. */ +} + +void shutdownOTG() { + // Core soft reset + OTG.GRSTCTL()->setCSRST(true); + while (OTG.GRSTCTL()->getCSRST()) { + } + + // Get into soft-disconnected state + OTG.DCTL()->setSDIS(true); + + // Stop the PHY clock + OTG.PCGCCTL()->setSTPPCLK(true); + + // Stop VBUS sensing + OTG.GCCFG()->setVBDEN(false); + + // Disable the transceiver module of the PHY + OTG.GCCFG()->setPWRDWN(false); +} + +} +} +} diff --git a/ion/src/f730/usb.h b/ion/src/f730/usb.h new file mode 100644 index 000000000..7c462cd47 --- /dev/null +++ b/ion/src/f730/usb.h @@ -0,0 +1,34 @@ +#ifndef ION_DEVICE_USB_H +#define ION_DEVICE_USB_H + +#include "regs/regs.h" +#include "ion.h" +#include "usb/calculator.h" + +namespace Ion { +namespace USB { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PA9 | VBUS | Input, pulled down//TODO | Low = unplugged, high = plugged + * PA11 | USB D- | Alternate Function 10 | + * PA12 | USB D+ | Alternate Function 10 | + */ + +constexpr static GPIOPin VbusPin = GPIOPin(GPIOA, 9); +constexpr static GPIOPin DmPin = GPIOPin(GPIOA, 11); +constexpr static GPIOPin DpPin = GPIOPin(GPIOA, 12); + +void init(); +void shutdown(); +void initGPIO(); +void shutdownGPIO(); +void initOTG(); +void shutdownOTG(); + +} +} +} + +#endif diff --git a/ion/src/f730/usb/Makefile b/ion/src/f730/usb/Makefile new file mode 100644 index 000000000..a2d840eef --- /dev/null +++ b/ion/src/f730/usb/Makefile @@ -0,0 +1,67 @@ +usb_objs += $(addprefix ion/src/device/usb/, \ + calculator.o \ + dfu_interface.o\ +) + +usb_objs += $(addprefix ion/src/device/usb/stack/, \ + device.o\ + endpoint0.o \ + interface.o\ + request_recipient.o\ + setup_packet.o\ + streamable.o\ +) + +usb_objs += $(addprefix ion/src/device/usb/stack/descriptor/, \ + bos_descriptor.o\ + configuration_descriptor.o \ + descriptor.o\ + device_descriptor.o\ + device_capability_descriptor.o\ + dfu_functional_descriptor.o\ + extended_compat_id_descriptor.o \ + interface_descriptor.o\ + language_id_string_descriptor.o \ + microsoft_os_string_descriptor.o\ + platform_device_capability_descriptor.o\ + string_descriptor.o\ + url_descriptor.o\ + webusb_platform_descriptor.o\ +) + +EPSILON_USB_DFU_XIP ?= 0 + +ifeq ($(EPSILON_USB_DFU_XIP),1) + +objs += ion/src/device/usb/dfu_xip.o +objs += $(usb_objs) + +else + +dfu_objs += liba/src/assert.o +dfu_objs += liba/src/strlen.o +dfu_objs += liba/src/strlcpy.o +dfu_objs += liba/src/memset.o +dfu_objs += liba/src/memcpy.o +dfu_objs += libaxx/src/cxxabi/pure_virtual.o +dfu_objs += ion/src/device/usb/boot.o +dfu_objs += ion/src/device/keyboard.o +dfu_objs += ion/src/device/device.o +dfu_objs += ion/src/device/usb.o +dfu_objs += ion/src/device/base64.o +dfu_objs += ion/src/device/flash.o +dfu_objs += ion/src/device/timing.o + +ion/src/device/usb/dfu.elf: LDSCRIPT = ion/src/device/usb/dfu.ld +ion/src/device/usb/dfu.elf: $(usb_objs) $(dfu_objs) + +ion/src/device/usb/dfu.o: ion/src/device/usb/dfu.bin + @echo "OBJCOPY $@" + $(Q) $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata --redefine-sym _binary_ion_src_device_usb_dfu_bin_start=_dfu_bootloader_flash_start --redefine-sym _binary_ion_src_device_usb_dfu_bin_end=_dfu_bootloader_flash_end $< $@ + +objs += ion/src/device/usb/dfu.o +objs += ion/src/device/usb/dfu_relocated.o + +products += $(usb_objs) $(addprefix ion/src/device/usb/dfu, .elf .bin) + +endif diff --git a/ion/src/f730/usb/boot.cpp b/ion/src/f730/usb/boot.cpp new file mode 100644 index 000000000..0eb8d71d6 --- /dev/null +++ b/ion/src/f730/usb/boot.cpp @@ -0,0 +1,2 @@ +extern "C" void abort() { +} diff --git a/ion/src/f730/usb/calculator.cpp b/ion/src/f730/usb/calculator.cpp new file mode 100644 index 000000000..fc23c1512 --- /dev/null +++ b/ion/src/f730/usb/calculator.cpp @@ -0,0 +1,94 @@ +#include "calculator.h" +#include +#include +#include +#include + +namespace Ion { +namespace USB { +namespace Device { + +void Calculator::PollAndReset(bool exitWithKeyboard) { + char serialNumber[Ion::Device::SerialNumberLength+1]; + Ion::Device::copySerialNumber(serialNumber); + Calculator c(serialNumber); + + /* Leave DFU mode if the Back key is pressed, the calculator unplugged or the + * USB core soft-disconnected. */ + Ion::Keyboard::Key exitKey = Ion::Keyboard::Key::A6; + uint8_t exitKeyRow = Ion::Keyboard::Device::rowForKey(exitKey); + uint8_t exitKeyColumn = Ion::Keyboard::Device::columnForKey(exitKey); + + Ion::Keyboard::Device::activateRow(exitKeyRow); + + while (!(exitWithKeyboard && Ion::Keyboard::Device::columnIsActive(exitKeyColumn)) && + Ion::USB::isPlugged() && + !c.isSoftDisconnected()) { + c.poll(); + } + if (!c.isSoftDisconnected()) { + c.detach(); + } + if (c.resetOnDisconnect()) { + /* We don't perform a core reset because at this point in time the USB cable + * is most likely plugged in. Doing a full core reset would be the clean + * thing to do but would therefore result in the device entering the ROMed + * DFU bootloader, which we want to avoid. By performing a jump-reset, we + * will enter the newly flashed firmware. */ + Ion::Device::jumpReset(); + } +} + +Descriptor * Calculator::descriptor(uint8_t type, uint8_t index) { + /* Special case: Microsoft OS String Descriptor should be returned when + * searching for string descriptor at index 0xEE. */ + if (type == m_microsoftOSStringDescriptor.type() && index == 0xEE) { + return &m_microsoftOSStringDescriptor; + } + int typeCount = 0; + for (size_t i=0; itype() != type) { + continue; + } + if (typeCount == index) { + return descriptor; + } else { + typeCount++; + } + } + return nullptr; +} + +bool Calculator::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (Device::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + return true; + } + if (request->requestType() == SetupPacket::RequestType::Vendor) { + if (request->bRequest() == k_webUSBVendorCode && request->wIndex() == 2) { + // This is a WebUSB, GET_URL request + assert(request->wValue() == k_webUSBLandingPageIndex); + return getURLCommand(transferBuffer, transferBufferLength, transferBufferMaxLength); + } + if (request->bRequest() == k_microsoftOSVendorCode && request->wIndex() == 0x0004) { + // This is a Microsoft OS descriptor, Extended Compat ID request + assert(request->wValue() == 0); + return getExtendedCompatIDCommand(transferBuffer, transferBufferLength, transferBufferMaxLength); + } + } + return false; +} + +bool Calculator::getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = m_workshopURLDescriptor.copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool Calculator::getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = m_extendedCompatIdDescriptor.copy(transferBuffer, transferBufferMaxLength); + return true; +} + +} +} +} diff --git a/ion/src/f730/usb/calculator.h b/ion/src/f730/usb/calculator.h new file mode 100644 index 000000000..d3383e419 --- /dev/null +++ b/ion/src/f730/usb/calculator.h @@ -0,0 +1,166 @@ +#ifndef ION_DEVICE_USB_CALCULATOR_H +#define ION_DEVICE_USB_CALCULATOR_H + +#include +#include +#include "dfu_interface.h" +#include "stack/device.h" +#include "stack/descriptor/bos_descriptor.h" +#include "stack/descriptor/configuration_descriptor.h" +#include "stack/descriptor/descriptor.h" +#include "stack/descriptor/device_descriptor.h" +#include "stack/descriptor/dfu_functional_descriptor.h" +#include "stack/descriptor/extended_compat_id_descriptor.h" +#include "stack/descriptor/interface_descriptor.h" +#include "stack/descriptor/language_id_string_descriptor.h" +#include "stack/descriptor/microsoft_os_string_descriptor.h" +#include "stack/descriptor/string_descriptor.h" +#include "stack/descriptor/url_descriptor.h" +#include "stack/descriptor/webusb_platform_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class Calculator : public Device { +public: + static void PollAndReset(bool exitWithKeyboard) + __attribute__((section(".dfu_entry_point"))) // Needed to pinpoint this symbol in the linker script + __attribute__((used)) // Make sure this symbol is not discarded at link time + ; // Return true if reset is needed + Calculator(const char * serialNumber) : + Device(&m_dfuInterface), + m_deviceDescriptor( + 0x0210, /* bcdUSB: USB Specification Number which the device complies + * to. Must be greater than 0x0200 to use the BOS. */ + 0, // bDeviceClass: The class is defined by the interface. + 0, // bDeviceSUBClass: The subclass is defined by the interface. + 0, // bDeviceProtocol: The protocol is defined by the interface. + 64, // bMaxPacketSize0: Maximum packet size for endpoint 0 + 0x0483, // idVendor + 0xA291, // idProduct + 0x0100, // bcdDevice: Device Release Number + 1, // iManufacturer: Index of the manufacturer name string, see m_descriptor + 2, // iProduct: Index of the product name string, see m_descriptor + 3, // iSerialNumber: Index of the SerialNumber string, see m_descriptor + 1), // bNumConfigurations + m_dfuFunctionalDescriptor( + 0b0011, /* bmAttributes: + * - bitWillDetach: If true, the device will perform a bus + * detach-attach sequence when it receives a DFU_DETACH + * request. The host must not issue a USB Reset. + * - bitManifestationTolerant: if true, the device is able to + * communicate via USB after Manifestation phase. The + * manifestation phase implies a reset in the calculator, so, + * even if the device is still plugged, it needs to be + * re-enumerated to communicate. + * - bitCanUpload + * - bitCanDnload */ + 0, /* wDetachTimeOut: Time, in milliseconds, that the device in APP + * mode will wait after receipt of the DFU_DETACH request before + * switching to DFU mode. It does not apply to the calculator.*/ + 2048, // wTransferSize: Maximum number of bytes that the device can accept per control-write transaction + 0x0100),// bcdDFUVersion + m_interfaceDescriptor( + 0, // bInterfaceNumber + k_dfuInterfaceAlternateSetting, // bAlternateSetting + 0, // bNumEndpoints: Other than endpoint 0 + 0xFE, // bInterfaceClass: DFU (http://www.usb.org/developers/defined_class) + 1, // bInterfaceSubClass: DFU + 2, // bInterfaceProtocol: DFU Mode (not DFU Runtime, which would be 1) + 4, // iInterface: Index of the Interface string, see m_descriptor + &m_dfuFunctionalDescriptor), + m_configurationDescriptor( + 9 + 9 + 9, // wTotalLength: configuration descriptor + interface descriptor + dfu functional descriptor lengths + 1, // bNumInterfaces + k_bConfigurationValue, // bConfigurationValue + 0, // iConfiguration: No string descriptor for the configuration + 0x80, /* bmAttributes: + * Bit 7: Reserved, set to 1 + * Bit 6: Self Powered + * Bit 5: Remote Wakeup (allows the device to wake up the host when the host is in suspend) + * Bit 4..0: Reserved, set to 0 */ + 0x32, // bMaxPower: half of the Maximum Power Consumption + &m_interfaceDescriptor), + m_webUSBPlatformDescriptor( + k_webUSBVendorCode, + k_webUSBLandingPageIndex), + m_bosDescriptor( + 5 + 24, // wTotalLength: BOS descriptor + webusb platform descriptor lengths + 1, // bNumDeviceCapabilities + &m_webUSBPlatformDescriptor), + m_languageStringDescriptor(), + m_manufacturerStringDescriptor("NumWorks"), + m_productStringDescriptor("NumWorks Calculator"), + m_serialNumberStringDescriptor(serialNumber), + m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg"), + //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), + /* Switch to this descriptor to use dfu-util to write in the SRAM. + * FIXME Should be an alternate Interface. */ + m_microsoftOSStringDescriptor(k_microsoftOSVendorCode), + m_workshopURLDescriptor(URLDescriptor::Scheme::HTTPS, "workshop.numworks.com"), + m_extendedCompatIdDescriptor("WINUSB"), + m_descriptors{ + &m_deviceDescriptor, // Type = Device, Index = 0 + &m_configurationDescriptor, // Type = Configuration, Index = 0 + &m_languageStringDescriptor, // Type = String, Index = 0 + &m_manufacturerStringDescriptor, // Type = String, Index = 1 + &m_productStringDescriptor, // Type = String, Index = 2 + &m_serialNumberStringDescriptor, // Type = String, Index = 3 + &m_interfaceStringDescriptor, // Type = String, Index = 4 + &m_bosDescriptor // Type = BOS, Index = 0 + }, + m_dfuInterface(this, &m_ep0, k_dfuInterfaceAlternateSetting) + { + } +protected: + virtual Descriptor * descriptor(uint8_t type, uint8_t index) override; + virtual void setActiveConfiguration(uint8_t configurationIndex) override { + assert(configurationIndex == k_bConfigurationValue); + } + virtual uint8_t getActiveConfiguration() override { + return k_bConfigurationValue; + } + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + +private: + static constexpr uint8_t k_bConfigurationValue = 1; + static constexpr uint8_t k_dfuInterfaceAlternateSetting = 0; + static constexpr uint8_t k_webUSBVendorCode = 1; + static constexpr uint8_t k_webUSBLandingPageIndex = 1; + static constexpr uint8_t k_microsoftOSVendorCode = 2; + + // WebUSB and MicrosoftOSDescriptor commands + bool getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + + // Descriptors + DeviceDescriptor m_deviceDescriptor; + DFUFunctionalDescriptor m_dfuFunctionalDescriptor; + InterfaceDescriptor m_interfaceDescriptor; + ConfigurationDescriptor m_configurationDescriptor; + WebUSBPlatformDescriptor m_webUSBPlatformDescriptor; + BOSDescriptor m_bosDescriptor; + LanguageIDStringDescriptor m_languageStringDescriptor; + StringDescriptor m_manufacturerStringDescriptor; + StringDescriptor m_productStringDescriptor; + StringDescriptor m_serialNumberStringDescriptor; + StringDescriptor m_interfaceStringDescriptor; + MicrosoftOSStringDescriptor m_microsoftOSStringDescriptor; + URLDescriptor m_workshopURLDescriptor; + ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor; + + Descriptor * m_descriptors[8]; + /* m_descriptors contains only descriptors that sould be returned via the + * method descriptor(uint8_t type, uint8_t index), so do not count descriptors + * included in other descriptors or returned by other functions. */ + + // Interface + DFUInterface m_dfuInterface; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/dfu.ld b/ion/src/f730/usb/dfu.ld new file mode 100644 index 000000000..934772384 --- /dev/null +++ b/ion/src/f730/usb/dfu.ld @@ -0,0 +1,50 @@ +/* DFU transfers can serve two purposes: + * - Transfering RAM data between the machine and the host, e.g. Python scripts + * - Upgrading the flash memory to perform a software update + * + * The second case raises a huge issue: code cannot be executed from memory that + * is being modified. We're solving this issue by copying the DFU code in RAM. + * + * This linker script will generate some code that expects to be executed from a + * fixed address in RAM. The corresponding instructions will be embedded in the + * main Epsilon ELF file, and copied to that address before execution. + * + * This address needs to live in RAM, and needs to be temporarily overwriteable + * when the program is being run. Epsilon has a large stack to allow deeply + * recursive code to run. But when doing DFU transfers it is safe to assume we + * will need very little stack space. We're therefore using the topmost 8K of + * the stack reserved by Epsilon. + * + * Last but not least, we'll want to jump to a known entry point when running + * the DFU code (namely, Ion::USB::Device::Calculator::Poll). We're simply + * making sure this is the first symbol output. */ + +EPSILON_STACK_END = 0x20000000 + 256K - 32K; + +MEMORY { + RAM_BUFFER (rw) : ORIGIN = EPSILON_STACK_END, LENGTH = 8K +} + +SECTIONS { + .text : { + . = ALIGN(4); + KEEP(*(.dfu_entry_point)) + *(.text) + *(.text.*) + } >RAM_BUFFER + + .rodata : { + *(.rodata) + *(.rodata.*) + } >RAM_BUFFER + + /DISCARD/ : { + /* For now, we do not need .bss and .data sections. This allows us to simply + * skip any rt0-style initialization and jump straight into the PollAndReset + * routine. */ + *(.bss) + *(.bss.*) + *(.data) + *(.data.*) + } +} diff --git a/ion/src/f730/usb/dfu_interface.cpp b/ion/src/f730/usb/dfu_interface.cpp new file mode 100644 index 000000000..029cce703 --- /dev/null +++ b/ion/src/f730/usb/dfu_interface.cpp @@ -0,0 +1,278 @@ +#include "dfu_interface.h" +#include +#include + +namespace Ion { +namespace USB { +namespace Device { + +static inline uint32_t min(uint32_t x, uint32_t y) { return (xpush(m_bStatus); + c->push(m_bwPollTimeout[2]); + c->push(m_bwPollTimeout[1]); + c->push(m_bwPollTimeout[0]); + c->push(m_bState); + c->push(m_iString); +} + +void DFUInterface::StateData::push(Channel * c) const { + c->push(m_bState); +} + +void DFUInterface::wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::Download) { + // Handle a download request + if (request->wValue() == 0) { + // The request is a special command + switch (transferBuffer[0]) { + case (uint8_t) DFUDownloadCommand::SetAddressPointer: + setAddressPointerCommand(request, transferBuffer, *transferBufferLength); + return; + case (uint8_t) DFUDownloadCommand::Erase: + eraseCommand(transferBuffer, *transferBufferLength); + return; + default: + m_state = State::dfuERROR; + m_status = Status::errSTALLEDPKT; + return; + } + } + if (request->wValue() == 1) { + m_ep0->stallTransaction(); + return; + } + if (request->wLength() > 0) { + // The request is a "real" download. Compute the writing address. + m_writeAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; + // Store the received data until we copy it on the flash. + memcpy(m_largeBuffer, transferBuffer, *transferBufferLength); + m_largeBufferLength = *transferBufferLength; + m_state = State::dfuDNLOADSYNC; + } + } +} + +void DFUInterface::wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::GetStatus) { + // Do any needed action after the GetStatus request. + if (m_state == State::dfuMANIFEST) { + // Leave DFU routine: Leave DFU, reset device, jump to application code + leaveDFUAndReset(); + } else if (m_state == State::dfuDNBUSY) { + if (m_largeBufferLength != 0) { + // Here, copy the data from the transfer buffer to the flash memory + writeOnMemory(); + } + changeAddressPointerIfNeeded(); + eraseMemoryIfNeeded(); + m_state = State::dfuDNLOADIDLE; + } + } +} + +bool DFUInterface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (Interface::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + return true; + } + switch (request->bRequest()) { + case (uint8_t) DFURequest::Detach: + m_device->detach(); + return true; + case (uint8_t) DFURequest::Download: + return processDownloadRequest(request->wLength(), transferBufferLength); + case (uint8_t) DFURequest::Upload: + return processUploadRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::GetStatus: + return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::ClearStatus: + return clearStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::GetState: + return getState(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::Abort: + return dfuAbort(transferBufferLength); + } + return false; +} + +bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuDNLOADIDLE) { + m_state = State::dfuERROR; + m_status = Status::errNOTDONE; + m_ep0->stallTransaction(); + return false; + } + if (wLength == 0) { + // Leave DFU routine: Reset the device and jump to application code + m_state = State::dfuMANIFESTSYNC; + } else { + // Prepare to receive the download data + m_ep0->clearForOutTransactions(wLength); + m_state = State::dfuDNLOADSYNC; + } + return true; +} + +bool DFUInterface::processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) { + m_ep0->stallTransaction(); + return false; + } + if (request->wValue() == 0) { + /* The host requests to read the commands supported by the bootloader. After + * receiving this command, the device should returns N bytes representing + * the command codes for : + * Get command / Set Address Pointer / Erase / Read Unprotect + * We no not need it for now. */ + return false; + } else if (request->wValue() == 1) { + m_ep0->stallTransaction(); + return false; + } else { + /* We decided to never protect Read operation. Else we would have to check + * here it is not protected before reading. */ + + // Compute the reading address + uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; + // Copy the requested memory zone into the transfer buffer. + uint16_t copySize = min(transferBufferMaxLength, request->wLength()); + memcpy(transferBuffer, (void *)readAddress, copySize); + *transferBufferLength = copySize; + } + m_state = State::dfuUPLOADIDLE; + return true; +} + +void DFUInterface::setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength) { + assert(transferBufferLength == 5); + // Compute the new address but change it after the next getStatus request. + m_potentialNewAddressPointer = transferBuffer[1] + + (transferBuffer[2] << 8) + + (transferBuffer[3] << 16) + + (transferBuffer[4] << 24); + m_state = State::dfuDNLOADSYNC; +} + +void DFUInterface::changeAddressPointerIfNeeded() { + if (m_potentialNewAddressPointer == 0) { + // There was no address change waiting. + return; + } + // If there is a new address pointer waiting, change the pointer address. + m_addressPointer = m_potentialNewAddressPointer; + m_potentialNewAddressPointer = 0; + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + +void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength) { + /* We determine whether the commands asks for a mass erase or which sector to + * erase. The erase must be done after the next getStatus request. */ + m_state = State::dfuDNLOADSYNC; + + if (transferBufferLength == 1) { + // Mass erase + m_erasePage = Flash::Device::NumberOfSectors; + return; + } + + // Sector erase + assert(transferBufferLength == 5); + + uint32_t eraseAddress = transferBuffer[1] + + (transferBuffer[2] << 8) + + (transferBuffer[3] << 16) + + (transferBuffer[4] << 24); + + m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); + + if (m_erasePage < 0) { + // Unrecognized sector + m_state = State::dfuERROR; + m_status = Status::errTARGET; + } +} + + +void DFUInterface::eraseMemoryIfNeeded() { + if (m_erasePage < 0) { + // There was no erase waiting. + return; + } + + if (m_erasePage == Ion::Flash::Device::NumberOfSectors) { + Flash::Device::MassErase(); + } else { + Flash::Device::EraseSector(m_erasePage); + } + + /* Put an out of range value in m_erasePage to indicate that no erase is + * waiting. */ + m_erasePage = -1; + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + +void DFUInterface::writeOnMemory() { + if (m_writeAddress >= k_flashStartAddress && m_writeAddress <= k_flashEndAddress) { + // Write to the Flash memory + Flash::Device::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress), m_largeBufferLength); + } else if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) { + // Write on SRAM + // FIXME We should check that we are not overriding the current instructions. + memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); + } else { + // Invalid write address + m_largeBufferLength = 0; + m_state = State::dfuERROR; + m_status = Status::errTARGET; + return; + } + + // Reset the buffer length + m_largeBufferLength = 0; + // Change the interface state and status + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + + +bool DFUInterface::getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + // Change the status if needed + if (m_state == State::dfuMANIFESTSYNC) { + m_state = State::dfuMANIFEST; + } else if (m_state == State::dfuDNLOADSYNC) { + m_state = State::dfuDNBUSY; + } + // Copy the status on the TxFifo + *transferBufferLength = StatusData(m_status, m_state).copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool DFUInterface::clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + m_status = Status::OK; + m_state = State::dfuIDLE; + return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); +} + +bool DFUInterface::getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize) { + *transferBufferLength = StateData(m_state).copy(transferBuffer, maxSize); + return true; +} + +bool DFUInterface::dfuAbort(uint16_t * transferBufferLength) { + m_status = Status::OK; + m_state = State::dfuIDLE; + *transferBufferLength = 0; + return true; +} + +void DFUInterface::leaveDFUAndReset() { + m_device->setResetOnDisconnect(true); + m_device->detach(); +} + +} +} +} diff --git a/ion/src/f730/usb/dfu_interface.h b/ion/src/f730/usb/dfu_interface.h new file mode 100644 index 000000000..6284ce951 --- /dev/null +++ b/ion/src/f730/usb/dfu_interface.h @@ -0,0 +1,172 @@ +#ifndef ION_DEVICE_USB_DFU_INTERFACE_H +#define ION_DEVICE_USB_DFU_INTERFACE_H + +#include +#include +#include "stack/device.h" +#include "stack/interface.h" +#include "stack/endpoint0.h" +#include "stack/setup_packet.h" +#include "stack/streamable.h" + +namespace Ion { +namespace USB { +namespace Device { + +class DFUInterface : public Interface { + +public: + DFUInterface(Device * device, Endpoint0 * ep0, uint8_t bInterfaceAlternateSetting) : + Interface(ep0), + m_device(device), + m_status(Status::OK), + m_state(State::dfuIDLE), + m_addressPointer(0), + m_potentialNewAddressPointer(0), + m_erasePage(-1), + m_largeBuffer{0}, + m_largeBufferLength(0), + m_writeAddress(0), + m_bInterfaceAlternateSetting(bInterfaceAlternateSetting) + { + } + void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; + void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; + +protected: + void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) override { + assert(interfaceAlternativeIndex == m_bInterfaceAlternateSetting); + } + uint8_t getActiveInterfaceAlternative() override { + return m_bInterfaceAlternateSetting; + } + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + +private: + // DFU Request Codes + enum class DFURequest { + Detach = 0, + Download = 1, + Upload = 2, + GetStatus = 3, + ClearStatus = 4, + GetState = 5, + Abort = 6 + }; + + // DFU Download Commmand Codes + enum class DFUDownloadCommand { + GetCommand = 0x00, + SetAddressPointer = 0x21, + Erase = 0x41, + ReadUnprotect = 0x92 + }; + + enum class Status : uint8_t { + OK = 0x00, + errTARGET = 0x01, + errFILE = 0x02, + errWRITE = 0x03, + errERASE = 0x04, + errCHECK_ERASED = 0x05, + errPROG = 0x06, + errVERIFY = 0x07, + errADDRESS = 0x08, + errNOTDONE = 0x09, + errFIRMWARE = 0x0A, + errVENDOR = 0x0B, + errUSBR = 0x0C, + errPOR = 0x0D, + errUNKNOWN = 0x0E, + errSTALLEDPKT = 0x0F + }; + + enum class State : uint8_t { + appIDLE = 0, + appDETACH = 1, + dfuIDLE = 2, + dfuDNLOADSYNC = 3, + dfuDNBUSY = 4, + dfuDNLOADIDLE = 5, + dfuMANIFESTSYNC = 6, + dfuMANIFEST = 7, + dfuMANIFESTWAITRESET = 8, + dfuUPLOADIDLE = 9, + dfuERROR = 10 + }; + + class StatusData : public Streamable { + public: + StatusData(Status status, State state, uint32_t pollTimeout = 1) : + /* We put a default pollTimeout value of 1ms: if the device is busy, the + * host has to wait 1ms before sending a getStatus Request. */ + m_bStatus((uint8_t)status), + m_bwPollTimeout{uint8_t((pollTimeout>>16) & 0xFF), uint8_t((pollTimeout>>8) & 0xFF), uint8_t(pollTimeout & 0xFF)}, + m_bState((uint8_t)state), + m_iString(0) + { + } + protected: + void push(Channel * c) const override; + private: + uint8_t m_bStatus; // Status resulting from the execution of the most recent request + uint8_t m_bwPollTimeout[3]; // m_bwPollTimeout is 24 bits + uint8_t m_bState; // State of the device immediately following transmission of this response + uint8_t m_iString; + }; + + class StateData : public Streamable { + public: + StateData(State state) : m_bState((uint8_t)state) {} + protected: + void push(Channel * c) const override; + private: + uint8_t m_bState; // Current state of the device + }; + + /* The Flash and SRAM addresses are in flash.ld. However, dfu_interface is + * linked with dfu.ld, so we cannot access the values. */ + constexpr static uint32_t k_flashStartAddress = 0x08000000; + constexpr static uint32_t k_flashEndAddress = 0x08100000; + constexpr static uint32_t k_sramStartAddress = 0x20000000; + constexpr static uint32_t k_sramEndAddress = 0x20040000; + + // Download and upload + bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); + bool processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + // Address pointer + void setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength); + void changeAddressPointerIfNeeded(); + // Access memory + void eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength); + void eraseMemoryIfNeeded(); + void writeOnMemory(); + void unlockFlashMemory(); + void lockFlashMemoryAndPurgeCaches(); + // Status + bool getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + // State + bool getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize); + // Abort + bool dfuAbort(uint16_t * transferBufferLength); + // Leave DFU + void leaveDFUAndReset(); + + Device * m_device; + Status m_status; + State m_state; + uint32_t m_addressPointer; + uint32_t m_potentialNewAddressPointer; + int32_t m_erasePage; + uint8_t m_largeBuffer[Endpoint0::MaxTransferSize]; + uint16_t m_largeBufferLength; + uint32_t m_writeAddress; + uint8_t m_bInterfaceAlternateSetting; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/dfu_relocated.cpp b/ion/src/f730/usb/dfu_relocated.cpp new file mode 100644 index 000000000..41566e2a0 --- /dev/null +++ b/ion/src/f730/usb/dfu_relocated.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +extern char _stack_end; +extern char _dfu_bootloader_flash_start; +extern char _dfu_bootloader_flash_end; + +namespace Ion { +namespace USB { + +typedef void (*PollFunctionPointer)(bool exitWithKeyboard); + +void DFU() { + + /* DFU transfers can serve two purposes: + * - Transfering RAM data between the machine and a host, e.g. Python scripts + * - Upgrading the flash memory to perform a software update + * + * The second case raises a huge issue: code cannot be executed from memory + * that is being modified. We're solving this issue by copying the DFU code in + * RAM. + * + * The new DFU address in RAM needs to be temporarily overwriteable when the + * program is being run. Epsilon has a large stack to allow deeply recursive + * code to run, but when doing DFU transfers it is safe to assume we will need + * very little stack space. We're therefore using the topmost 8K of the stack + * reserved by Epsilon. */ + + /* 1- The stack being in reverse order, the end of the stack will be the + * beginning of the DFU bootloader copied in RAM. */ + + size_t dfu_bootloader_size = &_dfu_bootloader_flash_end - &_dfu_bootloader_flash_start; + char * dfu_bootloader_ram_start = reinterpret_cast(&_stack_end); + assert(&_stack_end == (void *)(0x20000000 + 256*1024 - 32*1024)); + + /* 2- Verify there is enough free space on the stack to copy the DFU code. */ + + char foo; + char * stackPointer = &foo; + if (dfu_bootloader_ram_start + dfu_bootloader_size > stackPointer) { + // There is not enough room on the stack to copy the DFU bootloader. + return; + } + + /* 3- Copy the DFU bootloader from Flash to RAM. */ + + memcpy(dfu_bootloader_ram_start, &_dfu_bootloader_flash_start, dfu_bootloader_size); + + /* 4- Disable all interrupts + * The interrupt service routines live in the Flash and could be overwritten + * by garbage during a firmware upgrade opration, so we disable them. */ + Device::shutdownSysTick(); + + /* 5- Jump to DFU bootloader code. We made sure in the linker script that the + * first function we want to call is at the beginning of the DFU code. */ + + PollFunctionPointer dfu_bootloader_entry = reinterpret_cast(dfu_bootloader_ram_start); + + /* To have the right debug symbols for the reallocated code, break here and: + * - Get the address of the new .text section + * In a terminal: arm-none-eabi-readelf -a ion/src/device/usb/dfu.elf + * - Delete the current symbol table + * symbol-file + * - Add the new symbol table, with the address of the new .text section + * add-symbol-file ion/src/device/usb/dfu.elf 0x20038000 + */ + + dfu_bootloader_entry(true); + + /* 5- Restore interrupts */ + Device::initSysTick(); + + /* 6- That's all. The DFU bootloader on the stack is now dead code that will + * be overwritten when the stack grows. */ +} + +} +} diff --git a/ion/src/f730/usb/dfu_xip.cpp b/ion/src/f730/usb/dfu_xip.cpp new file mode 100644 index 000000000..ad2ef68bb --- /dev/null +++ b/ion/src/f730/usb/dfu_xip.cpp @@ -0,0 +1,12 @@ +#include "calculator.h" +#include "../device.h" + +namespace Ion { +namespace USB { + +void DFU() { + Ion::USB::Device::Calculator::PollAndReset(true); +} + +} +} diff --git a/ion/src/f730/usb/flasher.cpp b/ion/src/f730/usb/flasher.cpp new file mode 100644 index 000000000..71cebf261 --- /dev/null +++ b/ion/src/f730/usb/flasher.cpp @@ -0,0 +1,13 @@ +#include "../regs/regs.h" +#include "../usb/calculator.h" +#include + +void ion_main(int argc, char * argv[]) { + Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0xFFFF00)); + while (true) { + Ion::USB::enable(); + while (!Ion::USB::isEnumerated()) { + } + Ion::USB::Device::Calculator::PollAndReset(false); + } +} diff --git a/ion/src/f730/usb/flasher.ld b/ion/src/f730/usb/flasher.ld new file mode 100644 index 000000000..5621192b3 --- /dev/null +++ b/ion/src/f730/usb/flasher.ld @@ -0,0 +1,77 @@ +/* Create a USB DFU firmware that runs from RAM. + * Flashing using ST's ROMed DFU bootloader is reliable but very slow. To make + * flashing faster, we can leverage ST's bootloader to copy a small "flasher" in + * RAM, and run it from there. + * Caution: ST's bootloader uses some RAM, so we want to stay off of that memory + * region. Per AN2606, section 47, it's using 0x20003000 - 0x2003FFFF; We'll try + * to play safe and avoid the first 32KB of RAM. */ + +MEMORY { + RAM_BUFFER (rw) : ORIGIN = 0x20000000 + 32K, LENGTH = 256K - 32K +} + +/* The stack is quite large, and should really be equal to Epsilon's. Indeed, + * we're making the Calculator object live on the stack, and it's quite large + * (about 4K just for this single object). Using a stack too small will result + * in some memory being overwritten (for instance, vtables that live in the + * .rodata section). */ + +STACK_SIZE = 32K; + +SECTIONS { + .isr_vector_table ORIGIN(RAM_BUFFER) : { + KEEP(*(.isr_vector_table)) + } >RAM_BUFFER + + .text : { + . = ALIGN(4); + *(.text) + *(.text.*) + } >RAM_BUFFER + + .init_array : { + . = ALIGN(4); + _init_array_start = .; + KEEP (*(.init_array*)) + _init_array_end = .; + } >RAM_BUFFER + + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata.*) + } >RAM_BUFFER + + .data : { + . = ALIGN(4); + *(.data) + *(.data.*) + } >RAM_BUFFER + + .bss : { + . = ALIGN(4); + _bss_section_start_ram = .; + *(.bss) + *(.bss.*) + *(COMMON) + _bss_section_end_ram = .; + } >RAM_BUFFER + + .stack : { + . = ALIGN(8); + _stack_end = .; + . += (STACK_SIZE - 8); + . = ALIGN(8); + _stack_start = .; + } >RAM_BUFFER + + .phony : { + /* We won't do dynamic memory allocation */ + _heap_start = .; + _heap_end = .; + /* Effectively bypass copying .data to RAM */ + _data_section_start_flash = .; + _data_section_start_ram = .; + _data_section_end_ram = .; + } >RAM_BUFFER +} diff --git a/ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp new file mode 100644 index 000000000..069e6d56e --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp @@ -0,0 +1,22 @@ +#include "bos_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void BOSDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_wTotalLength); + c->push(m_bNumDeviceCaps); + for (uint8_t i = 0; i < m_bNumDeviceCaps; i++) { + m_deviceCapabilities[i].push(c); + } +} + +uint8_t BOSDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/bos_descriptor.h b/ion/src/f730/usb/stack/descriptor/bos_descriptor.h new file mode 100644 index 000000000..ce2626099 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/bos_descriptor.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_USB_STACK_BOS_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_BOS_DESCRIPTOR_H + +#include "descriptor.h" +#include "device_capability_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class BOSDescriptor : public Descriptor { +public: + constexpr BOSDescriptor( + uint16_t wTotalLength, + uint8_t bNumDeviceCapabilities, + const DeviceCapabilityDescriptor * deviceCapabilities) : + Descriptor(0x0F), + m_wTotalLength(wTotalLength), + m_bNumDeviceCaps(bNumDeviceCapabilities), + m_deviceCapabilities(deviceCapabilities) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint16_t m_wTotalLength; + uint8_t m_bNumDeviceCaps; + const DeviceCapabilityDescriptor * m_deviceCapabilities; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp new file mode 100644 index 000000000..3981837b5 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp @@ -0,0 +1,26 @@ +#include "configuration_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void ConfigurationDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_wTotalLength); + c->push(m_bNumInterfaces); + c->push(m_bConfigurationValue); + c->push(m_iConfiguration); + c->push(m_bmAttributes); + c->push(m_bMaxPower); + for (uint8_t i = 0; i < m_bNumInterfaces; i++) { + m_interfaces[i].push(c); + } +} + +uint8_t ConfigurationDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + 5*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/configuration_descriptor.h b/ion/src/f730/usb/stack/descriptor/configuration_descriptor.h new file mode 100644 index 000000000..fc43d59fb --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/configuration_descriptor.h @@ -0,0 +1,48 @@ +#ifndef ION_DEVICE_USB_STACK_CONFIGURATION_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_CONFIGURATION_DESCRIPTOR_H + +#include "descriptor.h" +#include "interface_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class ConfigurationDescriptor : public Descriptor { +public: + constexpr ConfigurationDescriptor( + uint16_t wTotalLength, + uint8_t bNumInterfaces, + uint8_t bConfigurationValue, + uint8_t iConfiguration, + uint8_t bmAttributes, + uint8_t bMaxPower, + const InterfaceDescriptor * interfaces) : + Descriptor(0x02), + m_wTotalLength(wTotalLength), + m_bNumInterfaces(bNumInterfaces), + m_bConfigurationValue(bConfigurationValue), + m_iConfiguration(iConfiguration), + m_bmAttributes(bmAttributes), + m_bMaxPower(bMaxPower), + m_interfaces(interfaces) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint16_t m_wTotalLength; + uint8_t m_bNumInterfaces; + uint8_t m_bConfigurationValue; + uint8_t m_iConfiguration; + uint8_t m_bmAttributes; + uint8_t m_bMaxPower; + const InterfaceDescriptor * m_interfaces; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/descriptor.cpp b/ion/src/f730/usb/stack/descriptor/descriptor.cpp new file mode 100644 index 000000000..cda9e7a77 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/descriptor.cpp @@ -0,0 +1,15 @@ +#include "descriptor.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +void Descriptor::push(Channel * c) const { + c->push(bLength()); + c->push(m_bDescriptorType); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/descriptor.h b/ion/src/f730/usb/stack/descriptor/descriptor.h new file mode 100644 index 000000000..9af940211 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/descriptor.h @@ -0,0 +1,32 @@ +#ifndef ION_DEVICE_USB_STACK_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_DESCRIPTOR_H + +#include "../streamable.h" + +namespace Ion { +namespace USB { +namespace Device { + +class InterfaceDescriptor; + +class Descriptor : public Streamable { + friend class InterfaceDescriptor; +public: + constexpr Descriptor(uint8_t bDescriptorType) : + m_bDescriptorType(bDescriptorType) + { + } + uint8_t type() const { return m_bDescriptorType; } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const { return 2*sizeof(uint8_t); } +private: + uint8_t m_bDescriptorType; +}; + + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp new file mode 100644 index 000000000..e3ab162d4 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp @@ -0,0 +1,18 @@ +#include "device_capability_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void DeviceCapabilityDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bDeviceCapabilityType); +} + +uint8_t DeviceCapabilityDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h new file mode 100644 index 000000000..cf600c9f1 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h @@ -0,0 +1,31 @@ +#ifndef ION_DEVICE_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class BOSDescriptor; + +class DeviceCapabilityDescriptor : public Descriptor { + friend class BOSDescriptor; +public: + constexpr DeviceCapabilityDescriptor(uint8_t bDeviceCapabilityType) : + Descriptor(0x10), + m_bDeviceCapabilityType(bDeviceCapabilityType) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint8_t m_bDeviceCapabilityType; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/device_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/device_descriptor.cpp new file mode 100644 index 000000000..1ce33847a --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/device_descriptor.cpp @@ -0,0 +1,29 @@ +#include "device_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void DeviceDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bcdUSB); + c->push(m_bDeviceClass); + c->push(m_bDeviceSubClass); + c->push(m_bDeviceProtocol); + c->push(m_bMaxPacketSize0); + c->push(m_idVendor); + c->push(m_idProduct); + c->push(m_bcdDevice); + c->push(m_iManufacturer); + c->push(m_iProduct); + c->push(m_iSerialNumber); + c->push(m_bNumConfigurations); +} + +uint8_t DeviceDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + 4*sizeof(uint8_t) + 3*sizeof(uint16_t) + 4*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/device_descriptor.h b/ion/src/f730/usb/stack/descriptor/device_descriptor.h new file mode 100644 index 000000000..d41241348 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/device_descriptor.h @@ -0,0 +1,62 @@ +#ifndef ION_DEVICE_USB_STACK_DEVICE_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_DEVICE_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class DeviceDescriptor : public Descriptor { +public: + constexpr DeviceDescriptor( + uint16_t bcdUSB, + uint8_t bDeviceClass, + uint8_t bDeviceSubClass, + uint8_t bDeviceProtocol, + uint8_t bMaxPacketSize0, + uint16_t idVendor, + uint16_t idProduct, + uint16_t bcdDevice, + uint8_t iManufacturer, + uint8_t iProduct, + uint8_t iSerialNumber, + uint8_t bNumConfigurations) : + Descriptor(0x01), + m_bcdUSB(bcdUSB), + m_bDeviceClass(bDeviceClass), + m_bDeviceSubClass(bDeviceSubClass), + m_bDeviceProtocol(bDeviceProtocol), + m_bMaxPacketSize0(bMaxPacketSize0), + m_idVendor(idVendor), + m_idProduct(idProduct), + m_bcdDevice(bcdDevice), + m_iManufacturer(iManufacturer), + m_iProduct(iProduct), + m_iSerialNumber(iSerialNumber), + m_bNumConfigurations(bNumConfigurations) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint16_t m_bcdUSB; + uint8_t m_bDeviceClass; + uint8_t m_bDeviceSubClass; + uint8_t m_bDeviceProtocol; + uint8_t m_bMaxPacketSize0; + uint16_t m_idVendor; + uint16_t m_idProduct; + uint16_t m_bcdDevice; + uint8_t m_iManufacturer; + uint8_t m_iProduct; + uint8_t m_iSerialNumber; + uint8_t m_bNumConfigurations; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp new file mode 100644 index 000000000..0d531965a --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp @@ -0,0 +1,21 @@ +#include "dfu_functional_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void DFUFunctionalDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bmAttributes); + c->push(m_wDetachTimeOut); + c->push(m_wTransferSize); + c->push(m_bcdDFUVersion); +} + +uint8_t DFUFunctionalDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint8_t) + 3*sizeof(uint16_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h b/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h new file mode 100644 index 000000000..5d51caa5a --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h @@ -0,0 +1,38 @@ +#ifndef ION_DEVICE_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class DFUFunctionalDescriptor : public Descriptor { +public: + constexpr DFUFunctionalDescriptor( + uint8_t bmAttributes, + uint16_t wDetachTimeOut, + uint16_t wTransferSize, + uint16_t bcdDFUVersion) : + Descriptor(0x21), + m_bmAttributes(bmAttributes), + m_wDetachTimeOut(wDetachTimeOut), + m_wTransferSize(wTransferSize), + m_bcdDFUVersion(bcdDFUVersion) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint8_t m_bmAttributes; + uint16_t m_wDetachTimeOut; + uint16_t m_wTransferSize; + uint16_t m_bcdDFUVersion; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp new file mode 100644 index 000000000..287ac8dd7 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp @@ -0,0 +1,61 @@ +#include "extended_compat_id_descriptor.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +ExtendedCompatIDDescriptor::ExtendedCompatIDDescriptor(const char * compatibleID) : + m_dwLength(sizeof(uint32_t) + + 2*sizeof(uint16_t) + + sizeof(uint8_t) + + k_reserved1Size * sizeof(uint8_t) + + 2*sizeof(uint8_t) + + k_compatibleIDSize * sizeof(uint8_t) + + k_compatibleIDSize * sizeof(uint8_t) + + k_reserved2Size * sizeof(uint8_t)), + m_bcdVersion(0x0100), // Microsoft OS Descriptors version 1 + m_wIndex(Index), + m_bCount(1), // We assume one function only. + m_reserved1{0, 0, 0, 0, 0, 0, 0}, + m_bFirstInterfaceNumber(0), + m_bReserved(1), + m_subCompatibleID{0, 0, 0, 0, 0, 0, 0, 0}, + m_reserved2{0, 0, 0, 0, 0, 0} +{ + /* Compatible ID has size k_compatibleIDSize, and any unused bytes should be + * filled with 0. */ + size_t compatibleIDSize = strlen(compatibleID); + size_t compatibleIDCopySize = k_compatibleIDSize < compatibleIDSize ? k_compatibleIDSize : compatibleIDSize; + for (size_t i = 0; i < compatibleIDCopySize; i++) { + m_compatibleID[i] = compatibleID[i]; + } + for (size_t i = compatibleIDCopySize; i < k_compatibleIDSize; i++) { + m_compatibleID[i] = 0; + } +} + +void ExtendedCompatIDDescriptor::push(Channel * c) const { + c->push(m_dwLength); + c->push(m_bcdVersion); + c->push(m_wIndex); + c->push(m_bCount); + for (uint8_t i = 0; i < k_reserved1Size; i++) { + c->push(m_reserved1[i]); + } + c->push(m_bFirstInterfaceNumber); + c->push(m_bReserved); + for (uint8_t i = 0; i < k_compatibleIDSize; i++) { + c->push(m_compatibleID[i]); + } + for (uint8_t i = 0; i < k_compatibleIDSize; i++) { + c->push(m_subCompatibleID[i]); + } + for (uint8_t i = 0; i < k_reserved2Size; i++) { + c->push(m_reserved2[i]); + } +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h b/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h new file mode 100644 index 000000000..199c39640 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h @@ -0,0 +1,43 @@ +#ifndef ION_DEVICE_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H + +#include "../streamable.h" + +namespace Ion { +namespace USB { +namespace Device { + +/* We use this descriptor to tell the Windows OS that the device should be + * treated as a WinUSB device. The Extended Compat ID Descriptor can set + * differents compat IDs according to the interface and function of the device, + * but we assume there is only one. */ + +class ExtendedCompatIDDescriptor : public Streamable { +public: + static constexpr uint8_t Index = 0x0004; + ExtendedCompatIDDescriptor(const char * compatibleID); +protected: + void push(Channel * c) const override; +private: + constexpr static uint8_t k_reserved1Size = 7; + constexpr static uint8_t k_compatibleIDSize = 8; + constexpr static uint8_t k_reserved2Size = 6; + // Header + uint32_t m_dwLength; // The length, in bytes, of the complete extended compat ID descriptor + uint16_t m_bcdVersion; // The descriptor’s version number, in binary coded decimal format + uint16_t m_wIndex; // An index that identifies the particular OS feature descriptor + uint8_t m_bCount; // The number of function sections + uint8_t m_reserved1[k_reserved1Size]; + // Function + uint8_t m_bFirstInterfaceNumber; // The interface or function number + uint8_t m_bReserved; + uint8_t m_compatibleID[k_compatibleIDSize]; + uint8_t m_subCompatibleID[k_compatibleIDSize]; + uint8_t m_reserved2[k_reserved2Size]; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp new file mode 100644 index 000000000..f4bb7d739 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp @@ -0,0 +1,27 @@ +#include "interface_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void InterfaceDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bInterfaceNumber); + c->push(m_bAlternateSetting); + c->push(m_bNumEndpoints); + c->push(m_bInterfaceClass); + c->push(m_bInterfaceSubClass); + c->push(m_bInterfaceProtocol); + c->push(m_iInterface); + if (m_additionalDescriptor != nullptr) { + m_additionalDescriptor->push(c); + } +} + +uint8_t InterfaceDescriptor::bLength() const { + return Descriptor::bLength() + 7*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/interface_descriptor.h b/ion/src/f730/usb/stack/descriptor/interface_descriptor.h new file mode 100644 index 000000000..2d1418c0d --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/interface_descriptor.h @@ -0,0 +1,55 @@ +#ifndef ION_DEVICE_USB_STACK_INTERFACE_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_INTERFACE_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class ConfigurationDescriptor; + +class InterfaceDescriptor : public Descriptor { + friend class ConfigurationDescriptor; +public: + constexpr InterfaceDescriptor( + uint8_t bInterfaceNumber, + uint8_t bAlternateSetting, + uint8_t bNumEndpoints, + uint8_t bInterfaceClass, + uint8_t bInterfaceSubClass, + uint8_t bInterfaceProtocol, + uint8_t iInterface, + Descriptor * additionalDescriptor) : + Descriptor(0x04), + m_bInterfaceNumber(bInterfaceNumber), + m_bAlternateSetting(bAlternateSetting), + m_bNumEndpoints(bNumEndpoints), + m_bInterfaceClass(bInterfaceClass), + m_bInterfaceSubClass(bInterfaceSubClass), + m_bInterfaceProtocol(bInterfaceProtocol), + m_iInterface(iInterface), + m_additionalDescriptor(additionalDescriptor) + /* There could be more than one additional descriptor, but we do not need + * this for now. */ + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint8_t m_bInterfaceNumber; + uint8_t m_bAlternateSetting; + uint8_t m_bNumEndpoints; + uint8_t m_bInterfaceClass; + uint8_t m_bInterfaceSubClass; + uint8_t m_bInterfaceProtocol; + uint8_t m_iInterface; + const Descriptor * m_additionalDescriptor; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp new file mode 100644 index 000000000..49dec8e10 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp @@ -0,0 +1,19 @@ +#include "language_id_string_descriptor.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +void LanguageIDStringDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push((uint16_t)(0x0409)); +} + +uint8_t LanguageIDStringDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h b/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h new file mode 100644 index 000000000..69533bdf5 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h @@ -0,0 +1,25 @@ +#ifndef ION_DEVICE_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +// For now this LanguageIDStringDescriptor only ever returns American English + +class LanguageIDStringDescriptor : public Descriptor { +public: + constexpr LanguageIDStringDescriptor() : + Descriptor(0x03) { } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp new file mode 100644 index 000000000..2d119dc09 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp @@ -0,0 +1,19 @@ +#include "microsoft_os_string_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void MicrosoftOSStringDescriptor::push(Channel * c) const { + StringDescriptor::push(c); + c->push(m_bMSVendorCode); + c->push(m_bPad); +} + +uint8_t MicrosoftOSStringDescriptor::bLength() const { + return StringDescriptor::bLength() + 2 * sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h b/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h new file mode 100644 index 000000000..9bf2295b6 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h @@ -0,0 +1,30 @@ +#ifndef ION_DEVICE_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H + +#include "string_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class MicrosoftOSStringDescriptor : public StringDescriptor { +public: + constexpr MicrosoftOSStringDescriptor(uint8_t bMSVendorCode) : + StringDescriptor("MSFT100"), + m_bMSVendorCode(bMSVendorCode), + m_bPad(0) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint8_t m_bMSVendorCode; + uint8_t m_bPad; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp new file mode 100644 index 000000000..b3739ec6e --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp @@ -0,0 +1,21 @@ +#include "platform_device_capability_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void PlatformDeviceCapabilityDescriptor::push(Channel * c) const { + DeviceCapabilityDescriptor::push(c); + c->push(m_bReserved); + for (int i = 0; i < k_platformCapabilityUUIDSize; i++) { + c->push(m_platformCapabilityUUID[i]); + } +} + +uint8_t PlatformDeviceCapabilityDescriptor::bLength() const { + return DeviceCapabilityDescriptor::bLength() + sizeof(uint8_t) + k_platformCapabilityUUIDSize*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h new file mode 100644 index 000000000..76087b565 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h @@ -0,0 +1,47 @@ +#ifndef ION_DEVICE_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H + +#include "device_capability_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class PlatformDeviceCapabilityDescriptor : public DeviceCapabilityDescriptor { +public: + constexpr PlatformDeviceCapabilityDescriptor(const uint8_t platformCapabilityUUID[]) : + DeviceCapabilityDescriptor(0x05), + m_bReserved(0), + m_platformCapabilityUUID{ + platformCapabilityUUID[0], + platformCapabilityUUID[1], + platformCapabilityUUID[2], + platformCapabilityUUID[3], + platformCapabilityUUID[4], + platformCapabilityUUID[5], + platformCapabilityUUID[6], + platformCapabilityUUID[7], + platformCapabilityUUID[8], + platformCapabilityUUID[9], + platformCapabilityUUID[10], + platformCapabilityUUID[11], + platformCapabilityUUID[12], + platformCapabilityUUID[13], + platformCapabilityUUID[14], + platformCapabilityUUID[15]} + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + constexpr static uint8_t k_platformCapabilityUUIDSize = 16; + uint8_t m_bReserved; + uint8_t m_platformCapabilityUUID[k_platformCapabilityUUIDSize]; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/string_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/string_descriptor.cpp new file mode 100644 index 000000000..c4ee5068e --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/string_descriptor.cpp @@ -0,0 +1,25 @@ +#include "string_descriptor.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +void StringDescriptor::push(Channel * c) const { + Descriptor::push(c); + const char * stringPointer = m_string; + while (*stringPointer != 0) { + uint16_t stringAsUTF16CodePoint = *stringPointer; + c->push(stringAsUTF16CodePoint); + stringPointer++; + } +} + +uint8_t StringDescriptor::bLength() const { + // The script is returned in UTF-16, hence the multiplication. + return Descriptor::bLength() + 2*strlen(m_string); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/string_descriptor.h b/ion/src/f730/usb/stack/descriptor/string_descriptor.h new file mode 100644 index 000000000..296ac79ca --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/string_descriptor.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_USB_STACK_STRING_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_STRING_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class StringDescriptor : public Descriptor { +public: + constexpr StringDescriptor(const char * string) : + Descriptor(0x03), + m_string(string) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + const char * m_string; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/url_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/url_descriptor.cpp new file mode 100644 index 000000000..3001d8143 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/url_descriptor.cpp @@ -0,0 +1,25 @@ +#include "url_descriptor.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +void URLDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bScheme); + const char * stringPointer = m_string; + while (*stringPointer != 0) { + c->push(*stringPointer); + stringPointer++; + } +} + +uint8_t URLDescriptor::bLength() const { + // The script is returned in UTF-8. + return Descriptor::bLength() + sizeof(uint8_t) + strlen(m_string); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/url_descriptor.h b/ion/src/f730/usb/stack/descriptor/url_descriptor.h new file mode 100644 index 000000000..d9e39af29 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/url_descriptor.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_USB_STACK_URL_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_URL_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class URLDescriptor : public Descriptor { +public: + enum class Scheme { + HTTP = 0, + HTTPS = 1, + IncludedInURL = 255 + }; + + constexpr URLDescriptor(Scheme scheme, const char * url) : + Descriptor(0x03), + m_bScheme((uint8_t)scheme), + m_string(url) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint8_t m_bScheme; + const char * m_string; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp new file mode 100644 index 000000000..e4a4ff548 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp @@ -0,0 +1,22 @@ +#include "webusb_platform_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +constexpr uint8_t WebUSBPlatformDescriptor::k_webUSBUUID[]; + +void WebUSBPlatformDescriptor::push(Channel * c) const { + PlatformDeviceCapabilityDescriptor::push(c); + c->push(m_bcdVersion); + c->push(m_bVendorCode); + c->push(m_iLandingPage); +} + +uint8_t WebUSBPlatformDescriptor::bLength() const { + return PlatformDeviceCapabilityDescriptor::bLength() + sizeof(uint16_t) + 2*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h b/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h new file mode 100644 index 000000000..89d39c712 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h @@ -0,0 +1,37 @@ +#ifndef ION_DEVICE_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H + +#include "platform_device_capability_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class WebUSBPlatformDescriptor : public PlatformDeviceCapabilityDescriptor { +public: + constexpr WebUSBPlatformDescriptor(uint8_t bVendorCode, uint8_t iLandingPage) : + PlatformDeviceCapabilityDescriptor(k_webUSBUUID), + m_bcdVersion(0x0100), + m_bVendorCode(bVendorCode), + m_iLandingPage(iLandingPage) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + /* Little-endian encoding of {3408B638-09A9-47A0-8BFD-A0768815B665}. + * See https://wicg.github.io/webusb/#webusb-platform-capability-descriptor */ + constexpr static uint8_t k_webUSBUUID[] = { + 0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, + 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65}; + uint16_t m_bcdVersion; + uint8_t m_bVendorCode; + uint8_t m_iLandingPage; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/device.cpp b/ion/src/f730/usb/stack/device.cpp new file mode 100644 index 000000000..27bf6d5de --- /dev/null +++ b/ion/src/f730/usb/stack/device.cpp @@ -0,0 +1,156 @@ +#include "device.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +static inline uint16_t min(uint16_t x, uint16_t y) { return (xget()); + + /* SETUP or OUT transaction + * If the Rx FIFO is not empty, there is a SETUP or OUT transaction. + * The interrupt is done AFTER THE HANSDHAKE of the transaction. */ + if (intsts.getRXFLVL()) { + class OTG::GRXSTSP grxstsp(OTG.GRXSTSP()->get()); + + // Store the packet status + OTG::GRXSTSP::PKTSTS pktsts = grxstsp.getPKTSTS(); + + // We only use endpoint 0 + assert(grxstsp.getEPNUM() == 0); + + if (pktsts == OTG::GRXSTSP::PKTSTS::OutTransferCompleted || pktsts == OTG::GRXSTSP::PKTSTS::SetupTransactionCompleted) { + // There is no data associated with this interrupt. + return; + } + + assert(pktsts != OTG::GRXSTSP::PKTSTS::GlobalOutNAK); + /* We did not enable the GONAKEFFM (Global OUT NAK effective mask) bit in + * GINTSTS, so we should never get this interrupt. */ + + assert(pktsts == OTG::GRXSTSP::PKTSTS::OutReceived || pktsts == OTG::GRXSTSP::PKTSTS::SetupReceived); + + TransactionType type = (pktsts == OTG::GRXSTSP::PKTSTS::OutReceived) ? TransactionType::Out : TransactionType::Setup; + + if (type == TransactionType::Setup && OTG.DIEPTSIZ0()->getPKTCNT()) { + // SETUP received but there is a packet in the Tx FIFO. Flush it. + m_ep0.flushTxFifo(); + } + + // Save the received packet byte count + m_ep0.setReceivedPacketSize(grxstsp.getBCNT()); + + if (type == TransactionType::Setup) { + m_ep0.readAndDispatchSetupPacket(); + } else { + assert(type == TransactionType::Out); + m_ep0.processOUTpacket(); + } + + m_ep0.discardUnreadData(); + } + + /* IN transactions. + * The interrupt is done AFTER THE HANSDHAKE of the transaction. */ + if (OTG.DIEPINT(0)->getXFRC()) { // We only check endpoint 0. + m_ep0.processINpacket(); + // Clear the Transfer Completed Interrupt + OTG.DIEPINT(0)->setXFRC(true); + } + + // Handle USB RESET. ENUMDNE = **SPEED** Enumeration Done + if (intsts.getENUMDNE()) { + // Clear the ENUMDNE bit + OTG.GINTSTS()->setENUMDNE(true); + /* After a USB reset, the host talks to the device by sending messages to + * address 0; */ + setAddress(0); + // Flush the FIFOs + m_ep0.reset(); + m_ep0.setup(); + /* In setup(), we should set the MPSIZ field in OTG_DIEPCTL0 to the maximum + * packet size depending on the enumeration speed (found in OTG_DSTS). We + * should always get FullSpeed, so we set the packet size accordingly. */ + } +} + +bool Device::isSoftDisconnected() const { + return OTG.DCTL()->getSDIS(); +} + +void Device::detach() { + // Get in soft-disconnected state + OTG.DCTL()->setSDIS(true); +} + +bool Device::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + // Device only handles standard requests. + if (request->requestType() != SetupPacket::RequestType::Standard) { + return false; + } + switch (request->bRequest()) { + case (int) Request::GetStatus: + return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetAddress: + // Make sure the request is adress is valid. + assert(request->wValue() < 128); + /* According to the reference manual, the address should be set after the + * Status stage of the current transaction, but this is not true. + * It should be set here, after the Data stage. */ + setAddress(request->wValue()); + *transferBufferLength = 0; + return true; + case (int) Request::GetDescriptor: + return getDescriptor(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetConfiguration: + *transferBufferLength = 0; + return setConfiguration(request); + case (int) Request::GetConfiguration: + return getConfiguration(transferBuffer, transferBufferLength); + } + return false; +} + +bool Device::getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = min(2, transferBufferMaxLength); + for (int i = 0; i<*transferBufferLength; i++) { + transferBuffer[i] = 0; // No remote wakeup, not self-powered. + } + return true; +} + +void Device::setAddress(uint8_t address) { + OTG.DCFG()->setDAD(address); +} + +bool Device::getDescriptor(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + Descriptor * wantedDescriptor = descriptor(request->descriptorType(), request->descriptorIndex()); + if (wantedDescriptor == nullptr) { + return false; + } + *transferBufferLength = wantedDescriptor->copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool Device::getConfiguration(uint8_t * transferBuffer, uint16_t * transferBufferLength) { + *transferBufferLength = 1; + transferBuffer[0] = getActiveConfiguration(); + return true; +} + +bool Device::setConfiguration(SetupPacket * request) { + // We support one configuration only + setActiveConfiguration(request->wValue()); + /* There is one configuration only, we no need to set it again, just reset the + * endpoint. */ + m_ep0.reset(); + return true; +} + +} +} +} diff --git a/ion/src/f730/usb/stack/device.h b/ion/src/f730/usb/stack/device.h new file mode 100644 index 000000000..6bc112940 --- /dev/null +++ b/ion/src/f730/usb/stack/device.h @@ -0,0 +1,66 @@ +#ifndef ION_DEVICE_USB_DEVICE_H +#define ION_DEVICE_USB_DEVICE_H + +#include "descriptor/descriptor.h" +#include "endpoint0.h" +#include "interface.h" +#include "request_recipient.h" +#include "setup_packet.h" + +namespace Ion { +namespace USB { +namespace Device { + +// We only handle control transfers, on EP0. +class Device : public RequestRecipient { +public: + Device(Interface * interface) : + RequestRecipient(&m_ep0), + m_ep0(this, interface), + m_resetOnDisconnect(false) + { + } + void poll(); + bool isSoftDisconnected() const; + void detach(); + bool resetOnDisconnect() { return m_resetOnDisconnect; } + void setResetOnDisconnect(bool reset) { m_resetOnDisconnect = reset; } +protected: + virtual Descriptor * descriptor(uint8_t type, uint8_t index) = 0; + virtual void setActiveConfiguration(uint8_t configurationIndex) = 0; + virtual uint8_t getActiveConfiguration() = 0; + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + Endpoint0 m_ep0; +private: + // USB Standard Device Request Codes + enum class Request { + GetStatus = 0, + ClearFeature = 1, + SetFeature = 3, + SetAddress = 5, + GetDescriptor = 6, + SetDescriptor = 7, + GetConfiguration = 8, + SetConfiguration = 9, + }; + + enum class TransactionType { + Setup, + In, + Out + }; + + void setAddress(uint8_t address); + bool getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getDescriptor(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getConfiguration(uint8_t * transferBuffer, uint16_t * transferBufferLength); + bool setConfiguration(SetupPacket * request); + + bool m_resetOnDisconnect; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/endpoint0.cpp b/ion/src/f730/usb/stack/endpoint0.cpp new file mode 100644 index 000000000..df41e4bd3 --- /dev/null +++ b/ion/src/f730/usb/stack/endpoint0.cpp @@ -0,0 +1,344 @@ +#include "endpoint0.h" +#include +#include +#include "device.h" +#include "interface.h" +#include "request_recipient.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +namespace Ion { +namespace USB { +namespace Device { + +void Endpoint0::setup() { + // Setup the IN direction + + // Reset the device IN endpoint 0 transfer size register + class OTG::DIEPTSIZ0 dieptsiz0(0); + /* Transfer size. The core interrupts the application only after it has + * exhausted the transfer size amount of data. The transfer size is set to the + * maximum packet size, to be interrupted at the end of each packet. */ + dieptsiz0.setXFRSIZ(k_maxPacketSize); + OTG.DIEPTSIZ0()->set(dieptsiz0); + + // Reset the device IN endpoint 0 control register + class OTG::DIEPCTL0 diepctl0(0); // Reset value + // Set the maximum packet size + diepctl0.setMPSIZ(OTG::DIEPCTL0::MPSIZ::Size64); + // Set the NAK bit: all IN transactions on endpoint 0 receive a NAK answer + diepctl0.setSNAK(true); + // Enable the endpoint + diepctl0.setEPENA(true); + OTG.DIEPCTL0()->set(diepctl0); + + // Setup the OUT direction + + setupOut(); + // Set the NAK bit + OTG.DOEPCTL0()->setSNAK(true); + // Enable the endpoint + enableOut(); + + // Setup the Tx FIFO + + /* Tx FIFO depth + * We process each packet as soon as it arrives, so we only need + * k_maxPacketSize bytes. TX0FD being in terms of 32-bit words, we divide + * k_maxPacketSize by 4. */ + OTG.DIEPTXF0()->setTX0FD(k_maxPacketSize/4); + /* Tx FIFO RAM start address. It starts just after the Rx FIFOso the value is + * Rx FIFO start address (0) + Rx FIFO depth. the Rx FIFO depth is set in + * usb.cpp, but because the code is linked separately, we cannot get it. */ + OTG.DIEPTXF0()->setTX0FSA(128); +} + +void Endpoint0::setupOut() { + class OTG::DOEPTSIZ0 doeptsiz0(0); + // Number of back-to-back SETUP data packets the endpoint can receive + doeptsiz0.setSTUPCNT(1); + // Packet count, false if a packet is written into the Rx FIFO + doeptsiz0.setPKTCNT(true); + /* Transfer size. The core interrupts the application only after it has + * exhausted the transfer size amount of data. The transfer size is set to the + * maximum packet size, to be interrupted at the end of each packet. */ + doeptsiz0.setXFRSIZ(64); + OTG.DOEPTSIZ0()->set(doeptsiz0); +} + +void Endpoint0::setOutNAK(bool nak) { + m_forceNAK = nak; + /* We need to keep track of the NAK state of the endpoint to use the value + * after a setupOut in poll() of device.cpp. */ + if (nak) { + OTG.DOEPCTL0()->setSNAK(true); + } else { + OTG.DOEPCTL0()->setCNAK(true); + } +} + +void Endpoint0::enableOut() { + OTG.DOEPCTL0()->setEPENA(true); +} + +void Endpoint0::reset() { + flushTxFifo(); + flushRxFifo(); +} + +void Endpoint0::readAndDispatchSetupPacket() { + setOutNAK(true); + + // Read the 8-bytes Setup packet + if (readPacket(m_largeBuffer, sizeof(SetupPacket)) != sizeof(SetupPacket)) { + stallTransaction(); + return; + }; + + m_request = SetupPacket(m_largeBuffer); + uint16_t maxBufferLength = MIN(m_request.wLength(), MaxTransferSize); + + // Forward the request to the request recipient + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->processSetupRequest(&m_request, m_largeBuffer, &m_transferBufferLength, maxBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->processSetupRequest(&m_request, m_largeBuffer, &m_transferBufferLength, maxBufferLength); + } +} + +void Endpoint0::processINpacket() { + switch (m_state) { + case State::DataIn: + sendSomeData(); + break; + case State::LastDataIn: + m_state = State::StatusOut; + // Prepare to receive the OUT Data[] transaction. + setOutNAK(false); + break; + case State::StatusIn: + { + m_state = State::Idle; + // All the data has been received. Callback the request recipient. + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->wholeDataReceivedCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->wholeDataReceivedCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } + } + break; + default: + stallTransaction(); + } +} + +void Endpoint0::processOUTpacket() { + switch (m_state) { + case State::DataOut: + if (receiveSomeData() < 0) { + break; + } + if ((m_request.wLength() - m_transferBufferLength) <= k_maxPacketSize) { + m_state = State::LastDataOut; + } + break; + case State::LastDataOut: + if (receiveSomeData() < 0) { + break; + } + // Send the DATA1[] to the host. + writePacket(NULL, 0); + m_state = State::StatusIn; + break; + case State::StatusOut: + { + // Read the DATA1[] sent by the host. + readPacket(NULL, 0); + m_state = State::Idle; + // All the data has been sent. Callback the request recipient. + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->wholeDataSentCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->wholeDataSentCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } + } + break; + default: + stallTransaction(); + } +} + +void Endpoint0::flushTxFifo() { + // Set IN endpoint NAK + OTG.DIEPCTL0()->setSNAK(true); + + // Wait for core to respond + while (!OTG.DIEPINT(0)->getINEPNE()) { + } + + // Get the Tx FIFO number + uint32_t fifo = OTG.DIEPCTL0()->getTXFNUM(); + + // Wait for AHB idle + while (!OTG.GRSTCTL()->getAHBIDL()) { + } + + // Flush Tx FIFO + OTG.GRSTCTL()->setTXFNUM(fifo); + OTG.GRSTCTL()->setTXFFLSH(true); + + // Reset packet counter + OTG.DIEPTSIZ0()->set(0); + + // Wait for the flush + while (OTG.GRSTCTL()->getTXFFLSH()) { + } +} + +void Endpoint0::flushRxFifo() { + // Set OUT endpoint NAK + OTG.DOEPCTL0()->setSNAK(true); + + // Wait for AHB idle + while (!OTG.GRSTCTL()->getAHBIDL()) { + } + + // Flush Rx FIFO + OTG.GRSTCTL()->setRXFFLSH(true); + + // Reset packet counter + OTG.DOEPTSIZ0()->set(0); + + // Wait for the flush + while (OTG.GRSTCTL()->getRXFFLSH()) { + } +} + +void Endpoint0::discardUnreadData() { + for (int i = 0; i < m_receivedPacketSize; i += 4) { + OTG.DFIFO0()->get(); + } + m_receivedPacketSize = 0; +} + +void Endpoint0::sendSomeData() { + if (k_maxPacketSize < m_transferBufferLength) { + // More than one packet needs to be sent + writePacket(m_largeBuffer + m_bufferOffset, k_maxPacketSize); + m_state = State::DataIn; + m_bufferOffset += k_maxPacketSize; + m_transferBufferLength -= k_maxPacketSize; + return; + } + // Last data packet sent + writePacket(m_largeBuffer + m_bufferOffset, m_transferBufferLength); + if (m_zeroLengthPacketNeeded) { + m_state = State::DataIn; + } else { + m_state = State::LastDataIn; + } + m_bufferOffset = 0; + m_zeroLengthPacketNeeded = false; + m_transferBufferLength = 0; +} + +void Endpoint0::clearForOutTransactions(uint16_t wLength) { + m_transferBufferLength = 0; + m_state = (wLength > k_maxPacketSize) ? State::DataOut : State::LastDataOut; + setOutNAK(false); +} + +uint16_t Endpoint0::receiveSomeData() { + // If it is the first chunk of data to be received, m_transferBufferLength is 0. + uint16_t packetSize = MIN(k_maxPacketSize, m_request.wLength() - m_transferBufferLength); + uint16_t sizeOfPacketRead = readPacket(m_largeBuffer + m_transferBufferLength, packetSize); + if (sizeOfPacketRead != packetSize) { + stallTransaction(); + return -1; + } + m_transferBufferLength += packetSize; + return packetSize; +} + +uint16_t Endpoint0::readPacket(void * buffer, uint16_t length) { + uint32_t * buffer32 = (uint32_t *) buffer; + uint16_t buffer32Length = MIN(length, m_receivedPacketSize); + + int i; + // The RX FIFO is read 4 bytes by 4 bytes + for (i = buffer32Length; i >= 4; i -= 4) { + *buffer32++ = OTG.DFIFO0()->get(); + m_receivedPacketSize -= 4; + } + + if (i) { + /* If there are remaining bytes that should be read, read the next 4 bytes + * and copy only the wanted bytes. */ + uint32_t extraData = OTG.DFIFO0()->get(); + memcpy(buffer32, &extraData, i); + if (m_receivedPacketSize < 4) { + m_receivedPacketSize = 0; + } else { + m_receivedPacketSize -= 4; + } + } + return buffer32Length; +} + +uint16_t Endpoint0::writePacket(const void * buffer, uint16_t length) { + const uint32_t * buffer32 = (uint32_t *) buffer; + + // Return if there is already a packet waiting to be read in the TX FIFO + if (OTG.DIEPTSIZ0()->getPKTCNT()) { + return 0; + } + + // Enable transmission + + class OTG::DIEPTSIZ0 dieptsiz0(0); + // Indicate that the Transfer Size is one packet + dieptsiz0.setPKTCNT(1); + // Indicate the length of the Transfer Size + dieptsiz0.setXFRSIZ(length); + OTG.DIEPTSIZ0()->set(dieptsiz0); + // Enable the endpoint + OTG.DIEPCTL0()->setEPENA(true); + // Clear the NAK bit + OTG.DIEPCTL0()->setCNAK(true); + + // Copy the buffer to the TX FIFO by writing data 32bits by 32 bits. + for (int i = length; i > 0; i -= 4) { + OTG.DFIFO0()->set(*buffer32++); + } + + return length; +} + +void Endpoint0::stallTransaction() { + OTG.DIEPCTL0()->setSTALL(true); + m_state = State::Idle; +} + +void Endpoint0::computeZeroLengthPacketNeeded() { + if (m_transferBufferLength + && m_transferBufferLength < m_request.wLength() + && m_transferBufferLength % k_maxPacketSize == 0) + { + m_zeroLengthPacketNeeded = true; + return; + } + m_zeroLengthPacketNeeded = false; +} + +} +} +} diff --git a/ion/src/f730/usb/stack/endpoint0.h b/ion/src/f730/usb/stack/endpoint0.h new file mode 100644 index 000000000..2088aaadd --- /dev/null +++ b/ion/src/f730/usb/stack/endpoint0.h @@ -0,0 +1,79 @@ +#ifndef ION_DEVICE_USB_ENDPOINT0_H +#define ION_DEVICE_USB_ENDPOINT0_H + +#include "setup_packet.h" + +namespace Ion { +namespace USB { +namespace Device { + +class RequestRecipient; + +class Endpoint0 { +public: + enum class State { + Idle, + Stalled, + DataIn, + LastDataIn, + StatusIn, + DataOut, + LastDataOut, + StatusOut, + }; + + constexpr static int k_maxPacketSize = 64; + constexpr static int MaxTransferSize = 2048; + + constexpr Endpoint0(RequestRecipient * device, RequestRecipient * interface) : + m_forceNAK(false), + m_bufferOffset(0), + m_transferBufferLength(0), + m_receivedPacketSize(0), + m_zeroLengthPacketNeeded(false), + m_request(), + m_requestRecipients{device, interface}, + m_state(State::Idle), + m_largeBuffer{0} + { + } + void setup(); + void setupOut(); + void setOutNAK(bool nak); + void enableOut(); + void reset(); + bool NAKForced() const { return m_forceNAK; } + void readAndDispatchSetupPacket(); + void processINpacket(); + void processOUTpacket(); + void flushTxFifo(); + void flushRxFifo(); + void setReceivedPacketSize(uint16_t size) { m_receivedPacketSize = size; } + void discardUnreadData(); + void stallTransaction(); + void computeZeroLengthPacketNeeded(); + void setState(State state) { m_state = state; } + void sendSomeData(); // Writes the next data packet and updates the state. + void clearForOutTransactions(uint16_t wLength); + +private: + uint16_t receiveSomeData(); + uint16_t readPacket(void * buffer, uint16_t length); + uint16_t writePacket(const void * buffer, uint16_t length); + + bool m_forceNAK; + int m_bufferOffset; // When sending large data stored in the buffer, the offset keeps tracks of which data packet should be sent next. + uint16_t m_transferBufferLength; + uint16_t m_receivedPacketSize; + bool m_zeroLengthPacketNeeded; + SetupPacket m_request; + RequestRecipient * m_requestRecipients[2]; + State m_state; + uint8_t m_largeBuffer[MaxTransferSize]; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/interface.cpp b/ion/src/f730/usb/stack/interface.cpp new file mode 100644 index 000000000..3fa95947e --- /dev/null +++ b/ion/src/f730/usb/stack/interface.cpp @@ -0,0 +1,66 @@ +#include "interface.h" + +namespace Ion { +namespace USB { +namespace Device { + +static inline uint16_t min(uint16_t x, uint16_t y) { return (xrequestType() != SetupPacket::RequestType::Standard) { + return false; + } + switch (request->bRequest()) { + case (int) Request::GetStatus: + return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetInterface: + return setInterface(request, transferBufferLength); + case (int) Request::GetInterface: + return getInterface(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::ClearFeature: + return clearFeature(transferBufferLength); + case (int) Request::SetFeature: + return setFeature(transferBufferLength); + } + return false; +} + +bool Interface::getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = min(2, transferBufferMaxLength); + for (int i = 0; i<*transferBufferLength; i++) { + transferBuffer[i] = 0; // Reserved, must be set to 0 + } + return true; +} + +bool Interface::getInterface(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = min(1, transferBufferMaxLength);; + if (*transferBufferLength > 0) { + transferBuffer[0] = getActiveInterfaceAlternative(); + } + return true; +} + +bool Interface::setInterface(SetupPacket * request, uint16_t * transferBufferLength) { + // We support one interface only + setActiveInterfaceAlternative(request->wValue()); + // There is one interface alternative only, we no need to set it again. + *transferBufferLength = 0; + return true; +} + +bool Interface::clearFeature(uint16_t * transferBufferLength) { + // Not needed for now + *transferBufferLength = 0; + return true; +} + +bool Interface::setFeature(uint16_t * transferBufferLength) { + // Not needed for now + *transferBufferLength = 0; + return true; +} + +} +} +} diff --git a/ion/src/f730/usb/stack/interface.h b/ion/src/f730/usb/stack/interface.h new file mode 100644 index 000000000..40376e197 --- /dev/null +++ b/ion/src/f730/usb/stack/interface.h @@ -0,0 +1,42 @@ +#ifndef ION_DEVICE_USB_INTERFACE_H +#define ION_DEVICE_USB_INTERFACE_H + +#include "endpoint0.h" +#include "request_recipient.h" +#include "setup_packet.h" + +namespace Ion { +namespace USB { +namespace Device { + +class Interface : public RequestRecipient { +public: + Interface(Endpoint0 * ep0) : + RequestRecipient(ep0) + { + } +protected: + virtual void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) = 0; + virtual uint8_t getActiveInterfaceAlternative() = 0; + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; +private: + // USB Standard Interface Request Codes + enum class Request { + GetStatus = 0, + ClearFeature = 1, + SetFeature = 3, + GetInterface = 10, + SetInterface = 11, + }; + bool getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getInterface(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool setInterface(SetupPacket * request, uint16_t * transferBufferLength); + bool clearFeature(uint16_t * transferBufferLength); + bool setFeature(uint16_t * transferBufferLength); +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/request_recipient.cpp b/ion/src/f730/usb/stack/request_recipient.cpp new file mode 100644 index 000000000..c00745763 --- /dev/null +++ b/ion/src/f730/usb/stack/request_recipient.cpp @@ -0,0 +1,32 @@ +#include "request_recipient.h" + +namespace Ion { +namespace USB { +namespace Device { + +bool RequestRecipient::processSetupRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (request->followingTransaction() == SetupPacket::TransactionType::InTransaction) { + // There is no data stage in this transaction, or the data stage will be in IN direction. + if (!processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + m_ep0->stallTransaction(); + return false; + } + if (*transferBufferLength > 0) { + m_ep0->computeZeroLengthPacketNeeded(); + m_ep0->sendSomeData(); + } else { + m_ep0->sendSomeData(); + // On seeing a zero length packet, sendSomeData changed endpoint0 state to + // LastDataIn, but it should be StatusIn as there was no data stage. + m_ep0->setState(Endpoint0::State::StatusIn); + } + } else { + // The following transaction will be an OUT transaction. + m_ep0->clearForOutTransactions(request->wLength()); + } + return true; +} + +} +} +} diff --git a/ion/src/f730/usb/stack/request_recipient.h b/ion/src/f730/usb/stack/request_recipient.h new file mode 100644 index 000000000..7f0fc8819 --- /dev/null +++ b/ion/src/f730/usb/stack/request_recipient.h @@ -0,0 +1,29 @@ +#ifndef ION_DEVICE_USB_REQUEST_RECIPIENT_H +#define ION_DEVICE_USB_REQUEST_RECIPIENT_H + +#include "endpoint0.h" +#include "setup_packet.h" + +namespace Ion { +namespace USB { +namespace Device { + +class RequestRecipient { +public: + RequestRecipient(Endpoint0 * ep0): + m_ep0(ep0) + { + } + bool processSetupRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + virtual void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {} + virtual void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {} +protected: + virtual bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) = 0; + Endpoint0 * m_ep0; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/setup_packet.cpp b/ion/src/f730/usb/stack/setup_packet.cpp new file mode 100644 index 000000000..d879b59e9 --- /dev/null +++ b/ion/src/f730/usb/stack/setup_packet.cpp @@ -0,0 +1,38 @@ +#include "setup_packet.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +SetupPacket::SetupPacket(void * buffer) { + memcpy(this, buffer, sizeof(SetupPacket)); +} + +SetupPacket::TransactionType SetupPacket::followingTransaction() const { + if (m_wLength == 0 || (m_bmRequestType & 0b10000000) != 0) { + return TransactionType::InTransaction; + } else { + return TransactionType::OutTransaction; + } +} + +SetupPacket::RequestType SetupPacket::requestType() const { + return (RequestType) ((m_bmRequestType & 0b01100000) >> 5); +} + +SetupPacket::RecipientType SetupPacket::recipientType() const { + return (RecipientType) (m_bmRequestType & 0b00001111); +} + +int SetupPacket::descriptorIndex() { + return m_wValue & 0xFF; +} + +int SetupPacket::descriptorType() { + return m_wValue >> 8; +} + +} +} +} diff --git a/ion/src/f730/usb/stack/setup_packet.h b/ion/src/f730/usb/stack/setup_packet.h new file mode 100644 index 000000000..054ff81e9 --- /dev/null +++ b/ion/src/f730/usb/stack/setup_packet.h @@ -0,0 +1,65 @@ +#ifndef ION_DEVICE_USB_SETUP_PACKET_H +#define ION_DEVICE_USB_SETUP_PACKET_H + +#include + +namespace Ion { +namespace USB { +namespace Device { + +class SetupPacket { +public: + enum class TransactionType { + SetupTransaction, + InTransaction, + OutTransaction + }; + + enum class RequestType { + Standard = 0, + Class = 1, + Vendor = 2 + }; + + enum class RecipientType { + Device = 0, + Interface = 1, + Endpoint = 2, + Other = 3 + }; + + constexpr SetupPacket() : + m_bmRequestType(0), + m_bRequest(0), + m_wValue(0), + m_wIndex(0), + m_wLength(0) + { + } + + SetupPacket(void * buffer); + TransactionType followingTransaction() const; + RequestType requestType() const; + RecipientType recipientType() const; + int descriptorIndex(); + int descriptorType(); + uint8_t bmRequestType() { return m_bmRequestType; } + uint8_t bRequest() { return m_bRequest; } + uint16_t wValue() { return m_wValue; } + uint16_t wIndex() { return m_wIndex; } + uint16_t wLength() { return m_wLength; } +private: + uint8_t m_bmRequestType; + uint8_t m_bRequest; + uint16_t m_wValue; + uint16_t m_wIndex; + uint16_t m_wLength; +}; + +static_assert(sizeof(SetupPacket) == 8, "SetupData must be packed"); + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/streamable.cpp b/ion/src/f730/usb/stack/streamable.cpp new file mode 100644 index 000000000..3f4677df8 --- /dev/null +++ b/ion/src/f730/usb/stack/streamable.cpp @@ -0,0 +1,15 @@ +#include "streamable.h" + +namespace Ion { +namespace USB { +namespace Device { + +uint16_t Streamable::copy(void * target, size_t maxSize) const { + Channel c(target, maxSize); + push(&c); + return maxSize - c.sizeLeft(); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/streamable.h b/ion/src/f730/usb/stack/streamable.h new file mode 100644 index 000000000..3a653ef1e --- /dev/null +++ b/ion/src/f730/usb/stack/streamable.h @@ -0,0 +1,44 @@ +#ifndef ION_DEVICE_USB_STREAMABLE_H +#define ION_DEVICE_USB_STREAMABLE_H + +#include +#include + +namespace Ion { +namespace USB { +namespace Device { + +class Streamable { +public: + uint16_t copy(void * target, size_t maxSize) const; +protected: + class Channel { + public: + Channel(void * pointer, size_t maxSize) : + m_pointer(pointer), + m_sizeLeft(maxSize) + { + } + template + void push(T data) { + if (m_sizeLeft >= sizeof(T)) { + T * typedPointer = static_cast(m_pointer); + *typedPointer++ = data; // Actually push the data + m_pointer = static_cast(typedPointer); + m_sizeLeft -= sizeof(T); + } + } + size_t sizeLeft() { return m_sizeLeft; } + private: + void * m_pointer; + size_t m_sizeLeft; + }; + virtual void push(Channel * c) const = 0; +}; + + +} +} +} + +#endif diff --git a/ion/src/f730/wakeup.cpp b/ion/src/f730/wakeup.cpp new file mode 100644 index 000000000..3582d6ad3 --- /dev/null +++ b/ion/src/f730/wakeup.cpp @@ -0,0 +1,69 @@ +#include "wakeup.h" +#include "regs/regs.h" +#include "battery.h" +#include "usb.h" +#include "keyboard.h" + +namespace Ion { +namespace WakeUp { +namespace Device { + +void onChargingEvent() { + Battery::Device::initGPIO(); + + /* Warning: pins with the same number in different groups cannot be set as + * source input for EXTI at the same time. Here, EXTICR1 register is filled + * between position 0-3 (charging pin = 0) with + * 0000 (ChargingGPIO = group A). */ + SYSCFG.EXTICR1()->setEXTI(Battery::Device::ChargingPin, Battery::Device::ChargingGPIO); + + EXTI.EMR()->set(Battery::Device::ChargingPin, true); + + /* We need to detect when the battery stops charging. We set the + * wake up event on the rising edge. */ + EXTI.RTSR()->set(Battery::Device::ChargingPin, true); +} + +void onUSBPlugging() { + USB::Device::initGPIO(); + /* Here, EXTICR3 register is filled between position 4-7 (Vbus pin = 9) with + * 0000 (Vbus GPIO = group A). */ + SYSCFG.EXTICR3()->setEXTI(USB::Device::VbusPin.pin(), USB::Device::VbusPin.group()); + + EXTI.EMR()->set(USB::Device::VbusPin.pin(), true); +#if EPSILON_LED_WHILE_CHARGING + EXTI.FTSR()->set(USB::Device::VbusPin.pin(), true); +#endif + EXTI.RTSR()->set(USB::Device::VbusPin.pin(), true); +} + + +void onPowerKeyDown() { + Keyboard::Key key = Keyboard::Key::B2; + uint8_t rowPin = Keyboard::Device::RowPins[Keyboard::Device::rowForKey(key)]; + Keyboard::Device::RowGPIO.MODER()->setMode(rowPin, GPIO::MODER::Mode::Output); + Keyboard::Device::RowGPIO.OTYPER()->setType(rowPin, GPIO::OTYPER::Type::OpenDrain); + Keyboard::Device::RowGPIO.ODR()->set(rowPin, 0); + + uint8_t column = Keyboard::Device::columnForKey(key); + uint8_t columnPin = Keyboard::Device::ColumnPins[column]; + + Keyboard::Device::ColumnGPIO.MODER()->setMode(columnPin, GPIO::MODER::Mode::Input); + Keyboard::Device::ColumnGPIO.PUPDR()->setPull(columnPin, GPIO::PUPDR::Pull::Up); + + /* Here, EXTICR1 register is filled between position 4-7 (column pin = 1) with + * 0010 (ColumnGPIO = group C). */ + + SYSCFG.EXTICR1()->setEXTI(columnPin, Keyboard::Device::ColumnGPIO); + + EXTI.EMR()->set(columnPin, true); + + /* When the key is pressed, it will go from 1 (because it's pulled up) to + * zero (because it's connected to the open-drain output. In other words, + * we're waiting for a falling edge. */ + EXTI.FTSR()->set(columnPin, true); +} + +} +} +} diff --git a/ion/src/f730/wakeup.h b/ion/src/f730/wakeup.h new file mode 100644 index 000000000..0c7d641b9 --- /dev/null +++ b/ion/src/f730/wakeup.h @@ -0,0 +1,41 @@ +#ifndef ION_DEVICE_WAKE_UP_H +#define ION_DEVICE_WAKE_UP_H + +#include "regs/regs.h" + +namespace Ion { +namespace WakeUp { +namespace Device { + +/* All wakeup functions can be called together without overwriting the same + * register. All togethed, they will set SYSCFG and EXTi registers as follow: + * + * GPIO Pin Number|EXTI_EMR|EXTI_FTSR|EXTI_RTSR|EXTICR1|EXTICR2|EXTICR3| Wake up + * ---------------+--------+---------+---------+-------+-------+-------+------------------------- + * 0 | 1 | 0 | 1 | A | ***** | ***** | Rising edge GPIO A pin 0 + * 1 | 1 | 1 | 0 | C | ***** | ***** | Falling edge GPIO C pin 1 + * 2 | 0 | 0 | 0 | A | ***** | ***** | + * 3 | 0 | 0 | 0 | A | ***** | ***** | + * 4 | 0 | 0 | 0 | ***** | A | ***** | + * 5 | 0 | 0 | 0 | ***** | A | ***** | + * 6 | 0 | 0 | 0 | ***** | A | ***** | + * 7 | 0 | 0 | 0 | ***** | A | ***** | + * 8 | 0 | 0 | 0 | ***** | ***** | A | + * 9 | 1 | 1 | 1 | ***** | ***** | A | Falling/Rising edge GPIO A pin 9 + * 10 | 0 | 0 | 0 | ***** | ***** | A | + * 11 | 0 | 0 | 0 | ***** | ***** | A | + * 12 | 0 | 0 | 0 | ***** | ***** | ***** | + * 13 | 0 | 0 | 0 | ***** | ***** | ***** | + * 14 | 0 | 0 | 0 | ***** | ***** | ***** | + * 15 | 0 | 0 | 0 | ***** | ***** | ***** | + */ + +void onChargingEvent(); +void onUSBPlugging(); +void onPowerKeyDown(); + +} +} +} + +#endif From a9661a27741df0aeb882048086b82ed97520902a Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:08:31 +0100 Subject: [PATCH 0002/1750] [ion/f730] Switch to FMC --- ion/src/f730/regs/{fsmc.h => fmc.h} | 10 +++++----- ion/src/f730/regs/regs.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) rename ion/src/f730/regs/{fsmc.h => fmc.h} (94%) diff --git a/ion/src/f730/regs/fsmc.h b/ion/src/f730/regs/fmc.h similarity index 94% rename from ion/src/f730/regs/fsmc.h rename to ion/src/f730/regs/fmc.h index 52bfcbdac..302c5be74 100644 --- a/ion/src/f730/regs/fsmc.h +++ b/ion/src/f730/regs/fmc.h @@ -1,9 +1,9 @@ -#ifndef REGS_FSMC_H -#define REGS_FSMC_H +#ifndef REGS_FMC_H +#define REGS_FMC_H #include "register.h" -class FSMC { +class FMC { public: class BCR : Register32 { public: @@ -58,7 +58,7 @@ public: REGS_TYPE_FIELD(ACCMOD, 29, 28); }; - constexpr FSMC() {} + constexpr FMC() {} volatile BCR * BCR(int index) const { return (class BCR *)(Base() + 8*(index-1)); } @@ -74,6 +74,6 @@ private: }; }; -constexpr FSMC FSMC; +constexpr FMC FMC; #endif diff --git a/ion/src/f730/regs/regs.h b/ion/src/f730/regs/regs.h index b5fad9e8a..ed5b48bdb 100644 --- a/ion/src/f730/regs/regs.h +++ b/ion/src/f730/regs/regs.h @@ -7,7 +7,7 @@ #include "dma.h" #include "exti.h" #include "flash.h" -#include "fsmc.h" +#include "fmc.h" #include "gpio.h" #include "itm.h" #include "mpu.h" From 144ec0659c16fcdef5eaee570f84ee2d26f11580 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:36:38 +0100 Subject: [PATCH 0003/1750] [build] Add an f730 folder --- build/f730/gdb_script.gdb | 36 ++++++++++++++++++++++++++++++++++++ build/f730/openocd.cfg | 7 +++++++ build/platform.f730.mak | 1 + build/targets.device.mak | 4 ++-- build/targets.f730.mak | 1 + 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 build/f730/gdb_script.gdb create mode 100644 build/f730/openocd.cfg create mode 100644 build/platform.f730.mak create mode 100644 build/targets.f730.mak diff --git a/build/f730/gdb_script.gdb b/build/f730/gdb_script.gdb new file mode 100644 index 000000000..515e20021 --- /dev/null +++ b/build/f730/gdb_script.gdb @@ -0,0 +1,36 @@ +# Add a routine to debug ARM exceptions + +define armex + printf "SCB_CFSR 0x%x\n", *0xE000ED28 + printf "SCB_HFSR 0x%x\n", *0xE000ED2C + printf "SCB_HFSR 0x%x\n", *0xE000ED2C + printf "SCB_MMAR 0x%x\n", *0xE000ED34 + printf "SCB_BFAR 0x%x\n", *0xE000ED38 + printf "xPSR 0x%x\n", *(int *)($msp+28) + printf "ReturnAddress 0x%x\n", *(int *)($msp+24) + printf "LR (R14) 0x%x\n", *(int *)($msp+20) + printf "R12 0x%x\n", *(int *)($msp+16) + printf "R3 0x%x\n", *(int *)($msp+12) + printf "R2 0x%x\n", *(int *)($msp+8) + printf "R1 0x%x\n", *(int *)($msp+4) + printf "R0 0x%x\n", *(int *)($msp) + printf "Return instruction:\n" + x/i *(int *)($msp+24) +end + +document armex +ARMv7 Exception entry behavior. +xPSR, ReturnAddress, LR (R14), R12, R3, R2, R1, and R0 +end + +# Let's connect to OpenOCD +target remote localhost:3333 + +# GDB pagniation is annoying +set pagination off + +# Load our executable +load + +# Tell OpenOCD to reset and halt +monitor reset halt diff --git a/build/f730/openocd.cfg b/build/f730/openocd.cfg new file mode 100644 index 000000000..0645bf05a --- /dev/null +++ b/build/f730/openocd.cfg @@ -0,0 +1,7 @@ +source [find interface/stlink.cfg] + +transport select hla_swd + +source [find target/stm32f7x.cfg] + +reset_config none separate diff --git a/build/platform.f730.mak b/build/platform.f730.mak new file mode 100644 index 000000000..1c9bcd804 --- /dev/null +++ b/build/platform.f730.mak @@ -0,0 +1 @@ +include build/platform.device.mak diff --git a/build/targets.device.mak b/build/targets.device.mak index 5a33eea6f..4df3b5f38 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -25,7 +25,7 @@ products += $(patsubst %.$(EXE),%.map,$(filter %.$(EXE),$(products))) .PHONY: %_run %_run: %.$(EXE) - $(GDB) -x build/device/gdb_script.gdb $< + $(GDB) -x build/$(PLATFORM)/gdb_script.gdb $< %.map: %.elf @echo "LDMAP $@" @@ -48,7 +48,7 @@ products += $(patsubst %.$(EXE),%.map,$(filter %.$(EXE),$(products))) .PHONY: openocd openocd: - openocd -f build/device/openocd.cfg + openocd -f build/$(PLATFORM)/openocd.cfg ifeq ($(EPSILON_USB_DFU_XIP)$(EPSILON_DEVICE_BENCH),10) flasher.$(EXE): LDFLAGS = --gc-sections -T ion/src/device/usb/flasher.ld diff --git a/build/targets.f730.mak b/build/targets.f730.mak new file mode 100644 index 000000000..11effd8c7 --- /dev/null +++ b/build/targets.f730.mak @@ -0,0 +1 @@ +include build/targets.device.mak From c3b87deb4bc2e6aff81517cfd18ad757e0bf8038 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:37:45 +0100 Subject: [PATCH 0004/1750] [ion/f730] Fix makefiles --- ion/src/f730/Makefile | 18 +++++++++--------- ion/src/f730/usb/Makefile | 34 +++++++++++++++++----------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/ion/src/f730/Makefile b/ion/src/f730/Makefile index 3d243bd47..4c5ffee55 100644 --- a/ion/src/f730/Makefile +++ b/ion/src/f730/Makefile @@ -1,6 +1,6 @@ -include ion/src/device/boot/Makefile -include ion/src/device/bench/Makefile -include ion/src/device/usb/Makefile +include ion/src/f730/boot/Makefile +include ion/src/f730/bench/Makefile +include ion/src/f730/usb/Makefile ion/src/shared/platform_info.o: SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" @@ -13,7 +13,7 @@ objs += $(addprefix ion/src/shared/, \ # If you need to profile execution, you can replace events_keyboard with # events_replay.o and dummy/events_modifier.o -objs += $(addprefix ion/src/device/, \ +objs += $(addprefix ion/src/f730/, \ backlight.o \ battery.o\ base64.o\ @@ -41,11 +41,11 @@ objs += $(addprefix ion/src/device/, \ ifneq ($(DEBUG),1) ifneq ($(COMPILER),llvm) -ion/src/device/led.o: SFLAGS+=-O3 -ion/src/device/console.o: SFLAGS+=-O3 -ion/src/device/display.o: SFLAGS+=-O3 -ion/src/device/swd.o: SFLAGS+=-O3 +ion/src/f730/led.o: SFLAGS+=-O3 +ion/src/f730/console.o: SFLAGS+=-O3 +ion/src/f730/display.o: SFLAGS+=-O3 +ion/src/f730/swd.o: SFLAGS+=-O3 endif endif -#objs += $(addprefix ion/src/device/keyboard/, keyboard.o) +#objs += $(addprefix ion/src/f730/keyboard/, keyboard.o) diff --git a/ion/src/f730/usb/Makefile b/ion/src/f730/usb/Makefile index a2d840eef..7d38b05d6 100644 --- a/ion/src/f730/usb/Makefile +++ b/ion/src/f730/usb/Makefile @@ -1,9 +1,9 @@ -usb_objs += $(addprefix ion/src/device/usb/, \ +usb_objs += $(addprefix ion/src/f730/usb/, \ calculator.o \ dfu_interface.o\ ) -usb_objs += $(addprefix ion/src/device/usb/stack/, \ +usb_objs += $(addprefix ion/src/f730/usb/stack/, \ device.o\ endpoint0.o \ interface.o\ @@ -12,7 +12,7 @@ usb_objs += $(addprefix ion/src/device/usb/stack/, \ streamable.o\ ) -usb_objs += $(addprefix ion/src/device/usb/stack/descriptor/, \ +usb_objs += $(addprefix ion/src/f730/usb/stack/descriptor/, \ bos_descriptor.o\ configuration_descriptor.o \ descriptor.o\ @@ -33,7 +33,7 @@ EPSILON_USB_DFU_XIP ?= 0 ifeq ($(EPSILON_USB_DFU_XIP),1) -objs += ion/src/device/usb/dfu_xip.o +objs += ion/src/f730/usb/dfu_xip.o objs += $(usb_objs) else @@ -44,24 +44,24 @@ dfu_objs += liba/src/strlcpy.o dfu_objs += liba/src/memset.o dfu_objs += liba/src/memcpy.o dfu_objs += libaxx/src/cxxabi/pure_virtual.o -dfu_objs += ion/src/device/usb/boot.o -dfu_objs += ion/src/device/keyboard.o -dfu_objs += ion/src/device/device.o -dfu_objs += ion/src/device/usb.o -dfu_objs += ion/src/device/base64.o -dfu_objs += ion/src/device/flash.o -dfu_objs += ion/src/device/timing.o +dfu_objs += ion/src/f730/usb/boot.o +dfu_objs += ion/src/f730/keyboard.o +dfu_objs += ion/src/f730/device.o +dfu_objs += ion/src/f730/usb.o +dfu_objs += ion/src/f730/base64.o +dfu_objs += ion/src/f730/flash.o +dfu_objs += ion/src/f730/timing.o -ion/src/device/usb/dfu.elf: LDSCRIPT = ion/src/device/usb/dfu.ld -ion/src/device/usb/dfu.elf: $(usb_objs) $(dfu_objs) +ion/src/f730/usb/dfu.elf: LDSCRIPT = ion/src/f730/usb/dfu.ld +ion/src/f730/usb/dfu.elf: $(usb_objs) $(dfu_objs) -ion/src/device/usb/dfu.o: ion/src/device/usb/dfu.bin +ion/src/f730/usb/dfu.o: ion/src/f730/usb/dfu.bin @echo "OBJCOPY $@" $(Q) $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata --redefine-sym _binary_ion_src_device_usb_dfu_bin_start=_dfu_bootloader_flash_start --redefine-sym _binary_ion_src_device_usb_dfu_bin_end=_dfu_bootloader_flash_end $< $@ -objs += ion/src/device/usb/dfu.o -objs += ion/src/device/usb/dfu_relocated.o +objs += ion/src/f730/usb/dfu.o +objs += ion/src/f730/usb/dfu_relocated.o -products += $(usb_objs) $(addprefix ion/src/device/usb/dfu, .elf .bin) +products += $(usb_objs) $(addprefix ion/src/f730/usb/dfu, .elf .bin) endif From 9b8b4281f90d6f581bdb1c460ad955b3f6b00f20 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:38:30 +0100 Subject: [PATCH 0005/1750] [ion/f730] Rename FSMC to FMC --- ion/src/f730/device.cpp | 4 +- ion/src/f730/display.cpp | 58 +++++++++++++-------------- ion/src/f730/display.h | 87 ++++++++++++++++++++-------------------- ion/src/f730/regs/rcc.h | 2 +- 4 files changed, 76 insertions(+), 75 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index e25cc4106..ab6f47baf 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -285,7 +285,7 @@ void initClocks() { RCC.AHB2ENR()->setOTGFSEN(true); // AHB3 bus - RCC.AHB3ENR()->setFSMCEN(true); + RCC.AHB3ENR()->setFMCEN(true); // APB1 bus // We're using TIM3 for the LEDs @@ -318,7 +318,7 @@ void shutdownClocks(bool keepLEDAwake) { RCC.APB1ENR()->set(apb1enr); RCC.AHB1ENR()->set(ahb1enr); - RCC.AHB3ENR()->setFSMCEN(false); + RCC.AHB3ENR()->setFMCEN(false); } } diff --git a/ion/src/f730/display.cpp b/ion/src/f730/display.cpp index 1804a7b19..1fa38012d 100644 --- a/ion/src/f730/display.cpp +++ b/ion/src/f730/display.cpp @@ -7,7 +7,7 @@ extern "C" { /* This driver interfaces with the ST7789V LCD controller. * This chip keeps a whole frame in SRAM memory and feeds it to the LCD panel as - * needed. We use the STM32's FSMC to drive the bus between the ST7789V. Once + * needed. We use the STM32's FMC to drive the bus between the ST7789V. Once * configured, we only need to write in the address space of the MCU to actually * send some data to the LCD controller. */ @@ -94,13 +94,13 @@ void init() { initDMA(); #endif initGPIO(); - initFSMC(); + initFMC(); initPanel(); } void shutdown() { shutdownPanel(); - shutdownFSMC(); + shutdownFMC(); shutdownGPIO(); } @@ -140,8 +140,8 @@ static inline void startDMAUpload(const KDColor * src, bool incrementSrc, uint16 #endif void initGPIO() { - // All the FSMC GPIO pins use the alternate function number 12 - for(const GPIOPin & g : FSMCPins) { + // All the FMC GPIO pins use the alternate function number 12 + for(const GPIOPin & g : FMCPins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF12); } @@ -167,8 +167,8 @@ void initGPIO() { void shutdownGPIO() { - // All the FSMC GPIO pins use the alternate function number 12 - for(const GPIOPin & g : FSMCPins) { + // All the FMC GPIO pins use the alternate function number 12 + for(const GPIOPin & g : FMCPins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); } @@ -185,26 +185,26 @@ void shutdownGPIO() { TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Analog); } -void initFSMC() { - /* Set up the FSMC control registers. +void initFMC() { + /* Set up the FMC control registers. * We address the LCD panel as if it were an SRAM module, using a 16bits wide * bus, non-multiplexed. - * The STM32 FSMC supports two kinds of memory access modes : + * The STM32 FMC supports two kinds of memory access modes : * - Base modes (1 and 2), which use the same timings for reads and writes * - Extended modes (named A to D), which can be customized further. * The LCD panel can be written to faster than it can be read from, therefore * we want to use one of the extended modes. */ - FSMC.BCR(FSMCMemoryBank)->setEXTMOD(true); - FSMC.BCR(FSMCMemoryBank)->setWREN(true); - FSMC.BCR(FSMCMemoryBank)->setMWID(FSMC::BCR::MWID::SIXTEEN_BITS); - FSMC.BCR(FSMCMemoryBank)->setMTYP(FSMC::BCR::MTYP::SRAM); - FSMC.BCR(FSMCMemoryBank)->setMUXEN(false); - FSMC.BCR(FSMCMemoryBank)->setMBKEN(true); + FMC.BCR(FMCMemoryBank)->setEXTMOD(true); + FMC.BCR(FMCMemoryBank)->setWREN(true); + FMC.BCR(FMCMemoryBank)->setMWID(FMC::BCR::MWID::SIXTEEN_BITS); + FMC.BCR(FMCMemoryBank)->setMTYP(FMC::BCR::MTYP::SRAM); + FMC.BCR(FMCMemoryBank)->setMUXEN(false); + FMC.BCR(FMCMemoryBank)->setMBKEN(true); - /* We now need to set the actual timings. First, the FSMC and LCD specs don't + /* We now need to set the actual timings. First, the FMC and LCD specs don't * use the same names. Here's the mapping: * - * FSMC | LCD + * FMC | LCD * -----+----- * NOE | RDX * NWE | WRX @@ -228,21 +228,21 @@ void initFSMC() { */ // Read timing from the LCD - FSMC.BTR(FSMCMemoryBank)->setADDSET(2); - FSMC.BTR(FSMCMemoryBank)->setADDHLD(0); - FSMC.BTR(FSMCMemoryBank)->setDATAST(36); - FSMC.BTR(FSMCMemoryBank)->setBUSTURN(10); - FSMC.BTR(FSMCMemoryBank)->setACCMOD(FSMC::BTR::ACCMOD::A); + FMC.BTR(FMCMemoryBank)->setADDSET(2); + FMC.BTR(FMCMemoryBank)->setADDHLD(0); + FMC.BTR(FMCMemoryBank)->setDATAST(36); + FMC.BTR(FMCMemoryBank)->setBUSTURN(10); + FMC.BTR(FMCMemoryBank)->setACCMOD(FMC::BTR::ACCMOD::A); // Write timings for the LCD - FSMC.BWTR(FSMCMemoryBank)->setADDSET(2); - FSMC.BWTR(FSMCMemoryBank)->setADDHLD(0); - FSMC.BWTR(FSMCMemoryBank)->setDATAST(3); - FSMC.BWTR(FSMCMemoryBank)->setBUSTURN(3); - FSMC.BWTR(FSMCMemoryBank)->setACCMOD(FSMC::BWTR::ACCMOD::A); + FMC.BWTR(FMCMemoryBank)->setADDSET(2); + FMC.BWTR(FMCMemoryBank)->setADDHLD(0); + FMC.BWTR(FMCMemoryBank)->setDATAST(3); + FMC.BWTR(FMCMemoryBank)->setBUSTURN(3); + FMC.BWTR(FMCMemoryBank)->setACCMOD(FMC::BWTR::ACCMOD::A); } -void shutdownFSMC() { +void shutdownFMC() { } void initPanel() { diff --git a/ion/src/f730/display.h b/ion/src/f730/display.h index e9147b90a..03d08b492 100644 --- a/ion/src/f730/display.h +++ b/ion/src/f730/display.h @@ -12,30 +12,32 @@ namespace Ion { namespace Display { namespace Device { -/* Pin | Role | Mode | Function | Note - * -----+-------------------+-----------------------+----------|------ - * PA2 | LCD D4 | Alternate Function 12 | FSMC_D4 | - * PA3 | LCD D5 | Alternate Function 12 | FSMC_D5 | - * PA4 | LCD D6 | Alternate Function 12 | FSMC_D6 | - * PB12 | LCD D13 | Alternate Function 12 | FSMC_D13 | - * PB14 | LCD power | Output | | LCD controller is powered directly from GPIO - * PD0 | LCD D2 | Alternate Function 12 | FSMC_D2 | - * PD1 | LCD D3 | Alternate Function 12 | FSMC_D3 | - * PD4 | LCD read signal | Alternate Function 12 | FSMC_NOE | - * PD5 | LCD write signal | Alternate Function 12 | FSMC_NWE - * PD7 | LCD chip select | Alternate Function 12 | FSMC_NE1 | Memory bank 1 - * PD9 | LCD D14 | Alternate Function 12 | FSMC_D14 | - * PD10 | LCD D15 | Alternate Function 12 | FSMC_D15 | - * PD11 | LCD data/command | Alternate Function 12 | FSMC_A16 | Data/Command is address bit 16 - * PD14 | LCD D0 | Alternate Function 12 | FSMC_D0 | - * PD15 | LCD D1 | Alternate Function 12 | FSMC_D1 | - * PE9 | LCD reset | Output | | - * PE10 | LCD D7 | Alternate Function 12 | FSMC_D7 | - * PE11 | LCD D8 | Alternate Function 12 | FSMC_D8 | - * PE12 | LCD D9 | Alternate Function 12 | FSMC_D9 | - * PE13 | LCD D10 | Alternate Function 12 | FSMC_D10 | - * PE14 | LCD D11 | Alternate Function 12 | FSMC_D11 | - * PE15 | LCD D12 | Alternate Function 12 | FSMC_D12 | +/* Pin | Role | Mode | Function | Note + * -----+--------------------+-----------------------+----------|------ + * PB11 | LCD Tearing effect | Input | | + * PC8 | LCD power | Output | | LCD controller is powered directly from GPIO + * PD0 | LCD D2 | Alternate Function 12 | FMC_D2 | + * PD1 | LCD D3 | Alternate Function 12 | FMC_D3 | + * PD4 | LCD read signal | Alternate Function 12 | FMC_NOE | + * PD5 | LCD write signal | Alternate Function 12 | FMC_NWE | + * PD6 | LCD extended cmd | Output | | + * PD7 | LCD chip select | Alternate Function 12 | FMC_NE1 | Memory bank 1 + * PD8 | LCD D13 | Alternate Function 12 | FMC_D13 | + * PD9 | LCD D14 | Alternate Function 12 | FMC_D14 | + * PD10 | LCD D15 | Alternate Function 12 | FMC_D15 | + * PD11 | LCD data/command | Alternate Function 12 | FMC_A16 | Data/Command is address bit 16 + * PD14 | LCD D0 | Alternate Function 12 | FMC_D0 | + * PD15 | LCD D1 | Alternate Function 12 | FMC_D1 | + * PE1 | LCD reset | Output | | + * PE7 | LCD D4 | Alternate Function 12 | FMC_D4 | + * PE8 | LCD D5 | Alternate Function 12 | FMC_D5 | + * PE9 | LCD D6 | Alternate Function 12 | FMC_D6 | + * PE10 | LCD D7 | Alternate Function 12 | FMC_D7 | + * PE11 | LCD D8 | Alternate Function 12 | FMC_D8 | + * PE12 | LCD D9 | Alternate Function 12 | FMC_D9 | + * PE13 | LCD D10 | Alternate Function 12 | FMC_D10 | + * PE14 | LCD D11 | Alternate Function 12 | FMC_D11 | + * PE15 | LCD D12 | Alternate Function 12 | FMC_D12 | */ void init(); @@ -44,8 +46,8 @@ void shutdown(); void initDMA(); void initGPIO(); void shutdownGPIO(); -void initFSMC(); -void shutdownFSMC(); +void initFMC(); +void shutdownFMC(); void initPanel(); void shutdownPanel(); @@ -77,31 +79,30 @@ enum class Command : uint16_t { FrameRateControl = 0xC6 }; -constexpr static GPIOPin FSMCPins[] = { - GPIOPin(GPIOA, 2), GPIOPin(GPIOA, 3), GPIOPin(GPIOA, 4), GPIOPin(GPIOB, 12), - GPIOPin(GPIOB, 12), GPIOPin(GPIOD, 0), GPIOPin(GPIOD, 1), GPIOPin(GPIOD, 4), - GPIOPin(GPIOD, 5), GPIOPin(GPIOD, 7), GPIOPin(GPIOD, 9), GPIOPin(GPIOD, 10), - GPIOPin(GPIOD, 11), GPIOPin(GPIOD, 14), GPIOPin(GPIOD, 15), GPIOPin(GPIOE, 10), - GPIOPin(GPIOE, 11), GPIOPin(GPIOE, 12), GPIOPin(GPIOE, 13), GPIOPin(GPIOE, 14), - GPIOPin(GPIOE, 15) +constexpr static GPIOPin FMCPins[] = { + 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(GPIOB, 14); -constexpr static GPIOPin ResetPin = GPIOPin(GPIOE, 9); -constexpr static GPIOPin ExtendedCommandPin = GPIOPin(GPIOB, 13); -constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 10); +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 int FSMCMemoryBank = 1; -constexpr static int FSMCDataCommandAddressBit = 16; +constexpr static int FMCMemoryBank = 1; +constexpr static int FMCDataCommandAddressBit = 16; -constexpr static uint32_t FSMCBaseAddress = 0x60000000; -constexpr static uint32_t FSMCBankAddress = FSMCBaseAddress + (FSMCMemoryBank-1)*0x04000000; +constexpr static uint32_t FMCBaseAddress = 0x60000000; +constexpr static uint32_t FMCBankAddress = FMCBaseAddress + (FMCMemoryBank-1)*0x04000000; constexpr static DMA DMAEngine = DMA2; constexpr static int DMAStream = 0; -static volatile Command * const CommandAddress = (Command *)(FSMCBankAddress); -static volatile uint16_t * const DataAddress = (uint16_t *)(FSMCBankAddress | (1<<(FSMCDataCommandAddressBit+1))); +static volatile Command * const CommandAddress = (Command *)(FMCBankAddress); +static volatile uint16_t * const DataAddress = (uint16_t *)(FMCBankAddress | (1<<(FMCDataCommandAddressBit+1))); } } diff --git a/ion/src/f730/regs/rcc.h b/ion/src/f730/regs/rcc.h index 0536441d4..83841b007 100644 --- a/ion/src/f730/regs/rcc.h +++ b/ion/src/f730/regs/rcc.h @@ -83,7 +83,7 @@ public: class AHB3ENR : Register32 { public: - REGS_BOOL_FIELD(FSMCEN, 0); + REGS_BOOL_FIELD(FMCEN, 0); REGS_BOOL_FIELD(QSPIEN, 1); }; From 0c1af95f3d8ea85a662b59a93a6a1f356457fb30 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:38:55 +0100 Subject: [PATCH 0006/1750] [ion/f730] Fix the backlight --- ion/src/f730/backlight.cpp | 14 +++++++------- ion/src/f730/backlight.h | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ion/src/f730/backlight.cpp b/ion/src/f730/backlight.cpp index efcd49b9d..2b52448b0 100644 --- a/ion/src/f730/backlight.cpp +++ b/ion/src/f730/backlight.cpp @@ -33,23 +33,23 @@ namespace Device { static uint8_t sLevel; void init() { - GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Output); + BacklightPin.group().MODER()->setMode(BacklightPin.pin(), GPIO::MODER::Mode::Output); sLevel = 0xF; resume(); } void shutdown() { - GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Analog); - GPIOC.PUPDR()->setPull(6, GPIO::PUPDR::Pull::None); + BacklightPin.group().MODER()->setMode(BacklightPin.pin(), GPIO::MODER::Mode::Analog); + BacklightPin.group().PUPDR()->setPull(BacklightPin.pin(), GPIO::PUPDR::Pull::None); } void suspend() { - GPIOC.ODR()->set(6, false); + BacklightPin.group().ODR()->set(BacklightPin.pin(), false); Timing::msleep(3); // Might not need to be blocking } void resume() { - GPIOC.ODR()->set(6, true); + BacklightPin.group().ODR()->set(BacklightPin.pin(), true); Timing::usleep(50); uint8_t level = sLevel; sLevel = 0xF; @@ -73,9 +73,9 @@ uint8_t level() { void sendPulses(int n) { for (int i=0; iset(6, false); + BacklightPin.group().ODR()->set(BacklightPin.pin(), false); Timing::usleep(20); - GPIOC.ODR()->set(6, true); + BacklightPin.group().ODR()->set(BacklightPin.pin(), true); Timing::usleep(20); } } diff --git a/ion/src/f730/backlight.h b/ion/src/f730/backlight.h index 15eab4fb0..026fcd537 100644 --- a/ion/src/f730/backlight.h +++ b/ion/src/f730/backlight.h @@ -9,7 +9,7 @@ namespace Device { /* Pin | Role | Mode | Function * -----+-------------------+-----------------------+---------- - * PC6 | Backlight Enable | Output | + * PE0 | Backlight Enable | Output | */ void init(); @@ -21,6 +21,8 @@ uint8_t level(); void sendPulses(int n); +constexpr static GPIOPin BacklightPin = GPIOPin(GPIOE, 0); + } } } From 815c22f80150fab92e4194242bad80c3eafddab3 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:40:13 +0100 Subject: [PATCH 0007/1750] [ion/f730] Use the proper GPIOs for the keyboard --- ion/src/f730/keyboard.cpp | 22 +++++++++++----------- ion/src/f730/keyboard.h | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ion/src/f730/keyboard.cpp b/ion/src/f730/keyboard.cpp index 91c634dae..0ec131551 100644 --- a/ion/src/f730/keyboard.cpp +++ b/ion/src/f730/keyboard.cpp @@ -6,28 +6,28 @@ * * | PC0 | PC1 | PC2 | PC3 | PC4 | PC5 | * -----+------+------+------+------+------+------+ - * PE0 | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 | + * PA0 | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 | * -----+------+------+------+------+------+------+ - * PE1 | K_B1 | K_B2 | | | | | + * PA1 | K_B1 | K_B2 | | | | | * -----+------+------+------+------+------+------+ - * PE2 | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 | + * PA2 | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 | * -----+------+------+------+------+------+------+ - * PE3 | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 | + * PA3 | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 | * -----+------+------+------+------+------+------+ - * PE4 | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 | + * PA4 | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 | * -----+------+------+------+------+------+------+ - * PE5 | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | | + * PA5 | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | | * -----+------+------+------+------+------+------+ - * PE6 | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | | + * PA6 | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | | * -----+------+------+------+------+------+------+ - * PE7 | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | | + * PA7 | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | | * -----+------+------+------+------+------+------+ - * PE8 | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | | + * PA8 | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | | * -----+------+------+------+------+------+------| * - * We decide to drive the rows (PE0-8) and read the columns (PC0-5). + * We decide to drive the rows (PA0-8) and read the columns (PC0-5). * - * To avoid short-circuits, the pins E0-E8 will not be standard outputs but + * To avoid short-circuits, the pins PA0-PA8 will not be standard outputs but * only open-drain. Open drain means the pin is either driven low or left * floating. * When a user presses multiple keys, a connection between two rows can happen. diff --git a/ion/src/f730/keyboard.h b/ion/src/f730/keyboard.h index fa69ab39f..d00ef5632 100644 --- a/ion/src/f730/keyboard.h +++ b/ion/src/f730/keyboard.h @@ -11,27 +11,27 @@ namespace Device { /* Pin | Role | Mode * -----+-------------------+-------------------- + * PA0 | Keyboard row A | Output, open drain + * PA1 | 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 * 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 - * PE0 | Keyboard row A | Output, open drain - * PE1 | Keyboard row B | Output, open drain - * PE2 | Keyboard row C | Output, open drain - * PE3 | Keyboard row D | Output, open drain - * PE4 | Keyboard row E | Output, open drain - * PE5 | Keyboard row F | Output, open drain - * PE6 | Keyboard row G | Output, open drain - * PE7 | Keyboard row H | Output, open drain - * PE8 | Keyboard row I | Output, open drain */ void init(); void shutdown(); -constexpr GPIO RowGPIO = GPIOE; +constexpr GPIO RowGPIO = GPIOA; constexpr uint8_t numberOfRows = 9; constexpr uint8_t RowPins[numberOfRows] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; From e934ce07fd0a761feb559ed7855cb3428328607d Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:54:36 +0100 Subject: [PATCH 0008/1750] [ion/f730] Fix some makefiles --- ion/src/f730/bench/Makefile | 4 ++-- ion/src/f730/boot/Makefile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ion/src/f730/bench/Makefile b/ion/src/f730/bench/Makefile index 90b0c82b6..28e33f7fa 100644 --- a/ion/src/f730/bench/Makefile +++ b/ion/src/f730/bench/Makefile @@ -1,10 +1,10 @@ -objs += $(addprefix ion/src/device/bench/, \ +objs += $(addprefix ion/src/f730/bench/, \ bench.o \ command_handler.o \ command_list.o \ ) -objs += $(addprefix ion/src/device/bench/command/, \ +objs += $(addprefix ion/src/f730/bench/command/, \ command.o \ adc.o \ backlight.o \ diff --git a/ion/src/f730/boot/Makefile b/ion/src/f730/boot/Makefile index ded572acd..153e0b034 100644 --- a/ion/src/f730/boot/Makefile +++ b/ion/src/f730/boot/Makefile @@ -1,2 +1,2 @@ -objs += $(addprefix ion/src/device/boot/, isr.o rt0.o) -LDSCRIPT = ion/src/device/boot/flash.ld +objs += $(addprefix ion/src/f730/boot/, isr.o rt0.o) +LDSCRIPT = ion/src/f730/boot/flash.ld From 54758d408cff4ed68b58a6e6244132d07ac3675a Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:55:15 +0100 Subject: [PATCH 0009/1750] [ion/f730] Use the MPU to force FMC access width --- ion/src/f730/boot/rt0.cpp | 2 +- ion/src/f730/device.cpp | 23 ++++++++++++++++++++++- ion/src/f730/device.h | 2 +- ion/src/f730/regs/mpu.h | 20 +++++++++++++++----- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/ion/src/f730/boot/rt0.cpp b/ion/src/f730/boot/rt0.cpp index e4ea05f0f..852cd7edb 100644 --- a/ion/src/f730/boot/rt0.cpp +++ b/ion/src/f730/boot/rt0.cpp @@ -61,7 +61,7 @@ void start() { * For example, static C++ objects are very likely to manipulate float values */ Ion::Device::initFPU(); -#if 0 +#if 1 Ion::Device::initMPU(); #endif diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index ab6f47baf..50a9daa1d 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -85,8 +85,11 @@ void initFPU() { // FIXME: The pipeline should be flushed at this point } -#if 0 +#if 1 void initMPU() { + +# if 0 + // This prevent overflowing the stack /* Region 0 reprensents the last 128 bytes of the stack: accessing this * memory means we are really likely to overflow the stack very soon. */ MPU.RNR()->setREGION(0x00); @@ -96,6 +99,24 @@ void initMPU() { MPU.RASR()->setAP(0x000); // Forbid access MPU.CTRL()->setPRIVDEFENA(true); MPU.CTRL()->setENABLE(true); +#endif + + // Configure MPU settings for the FMC memory area + // This is needed for interfacing with the LCD + MPU.RNR()->setREGION(0x00); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32MB); + MPU.RASR()->setXN(true); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setTEX(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(1); + MPU.RASR()->setENABLE(true); + + MPU.CTRL()->setPRIVDEFENA(true); + MPU.CTRL()->setENABLE(true); + + } #endif diff --git a/ion/src/f730/device.h b/ion/src/f730/device.h index 2bdccea28..e7e136f61 100644 --- a/ion/src/f730/device.h +++ b/ion/src/f730/device.h @@ -8,7 +8,7 @@ void init(); void shutdown(); void initFPU(); -#if 0 +#if 1 void initMPU(); #endif diff --git a/ion/src/f730/regs/mpu.h b/ion/src/f730/regs/mpu.h index f9c2049f4..2d82a5e10 100644 --- a/ion/src/f730/regs/mpu.h +++ b/ion/src/f730/regs/mpu.h @@ -26,7 +26,7 @@ public: class RBAR : Register32 { public: - void setADDR(void * address) volatile { assert(((uint32_t)address & 0b11111) == 0); setBitRange(31, 5, (uint32_t)address >> 5); } + void setADDR(uint32_t address) volatile { /* assert((address & 0b11111) == 0);*/ setBitRange(31, 5, address >> 5); } REGS_BOOL_FIELD(VALID, 4); REGS_FIELD(REGION, uint8_t, 3, 0); }; @@ -34,11 +34,19 @@ public: class RASR : Register32 { public: REGS_BOOL_FIELD(XN, 28); - REGS_FIELD(AP, uint8_t, 26, 24); + enum class AccessPermission : uint8_t { + NoAccess = 0, + PrivilegedRO = 5, + PrivilegedRW = 1, + PrivilegedRWUnprivilegedRO = 2, + RO = 6, + RW = 3 + }; + REGS_FIELD(AP, AccessPermission, 26, 24); REGS_FIELD(TEX, uint8_t, 21, 19); - REGS_BOOL_FIELD(S, 18); - REGS_BOOL_FIELD(C, 17); - REGS_BOOL_FIELD(B, 16); + REGS_BOOL_FIELD(S, 18); // Shareable + REGS_BOOL_FIELD(C, 17); // Cacheable + REGS_BOOL_FIELD(B, 16); // Buffereable REGS_FIELD(SRD, uint8_t, 15, 8); enum class RegionSize : uint8_t { Bytes32 = 0b00100, @@ -46,6 +54,8 @@ public: Bytes128 = 0b00110, KyloBytes1 = 0b01001, MegaBytes1 = 0b10011, + _1MB = 19, + _32MB = 24, GigaBytes1 = 0b11101, GigaBytes4 = 0b11111 }; From 1c2bc9c0b4869b11e4cc0b29f4f8760ce350ccc1 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:57:46 +0100 Subject: [PATCH 0010/1750] [ion/f730] Fix a reset value --- ion/src/f730/device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 50a9daa1d..764f80258 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -314,7 +314,7 @@ void initClocks() { RCC.APB1ENR()->setPWREN(true); // APB2 bus - class RCC::APB2ENR apb2enr(0x00008000); // Reset value + class RCC::APB2ENR apb2enr(0); // Reset value apb2enr.setADC1EN(true); apb2enr.setSYSCFGEN(true); #if USE_SD_CARD From 1ff2fa1a3bd9be309faccbf018579030461f94f2 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 11:00:16 +0100 Subject: [PATCH 0011/1750] [ion/f730] Temporarily disable extra peripherals --- ion/src/f730/device.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 764f80258..3f111c497 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -201,6 +201,7 @@ void initPeripherals() { Keyboard::Device::init(); LED::Device::init(); Battery::Device::init(); + return; // FIXME, obviously! USB::Device::init(); #if USE_SD_CARD SDCard::Device::init(); From ee472ca2b55b864005ef038e1d4bc7f864761c20 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 11:00:36 +0100 Subject: [PATCH 0012/1750] [ion/f730] Don't init clocks for now --- ion/src/f730/device.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 3f111c497..7cc714002 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -229,6 +229,7 @@ void shutdownPeripherals(bool keepLEDAwake) { } void initClocks() { +#if 0 /* System clock * Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */ @@ -288,6 +289,7 @@ void initClocks() { // Now that we don't need use it anymore, turn the HSI off RCC.CR()->setHSION(false); +#endif // Peripheral clocks From 8f111b89f9296f379defefed65336240681bad51 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 11:00:49 +0100 Subject: [PATCH 0013/1750] [apps] Enable a DUMMY_MAIN option --- apps/main.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/main.cpp b/apps/main.cpp index fb2c90dc1..1be40ff7d 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -2,6 +2,20 @@ #include "global_preferences.h" #include +#define DUMMY_MAIN 1 +#if DUMMY_MAIN + +void ion_main(int argc, char * argv[]) { + while (1) { + Ion::Display::pushRectUniform(KDRect(0,0,10,10), KDColorRed); + Ion::Timing::msleep(100); + Ion::Display::pushRectUniform(KDRect(0,0,10,10), KDColorBlue); + Ion::Timing::msleep(100); + } +} + +#else + void ion_main(int argc, char * argv[]) { // Initialize Poincare::TreePool::sharedPool Poincare::Init(); @@ -44,3 +58,5 @@ void ion_main(int argc, char * argv[]) { #endif AppsContainerStorage::sharedContainer()->run(); } + +#endif From 962e340689a32f48d49c7f0decdc747955a0f23b Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 19 Nov 2018 15:23:17 +0100 Subject: [PATCH 0014/1750] [ion/device] Add QuadSPI regs --- ion/src/device/regs/quadspi.h | 126 ++++++++++++++++++++++++++++++++++ ion/src/device/regs/regs.h | 1 + 2 files changed, 127 insertions(+) create mode 100644 ion/src/device/regs/quadspi.h diff --git a/ion/src/device/regs/quadspi.h b/ion/src/device/regs/quadspi.h new file mode 100644 index 000000000..ed46c3b6b --- /dev/null +++ b/ion/src/device/regs/quadspi.h @@ -0,0 +1,126 @@ +#ifndef REGS_QUADSPI_H +#define REGS_QUADSPI_H + +#include "register.h" + +// Quad-SPI register map on STM32 microcontroller +// See section 12 of the STM32F412 reference manual + +class QUADSPI { +public: + class CR : public Register32 { // Control register + public: + using Register32::Register32; + REGS_BOOL_FIELD(EN, 0); + REGS_BOOL_FIELD(TCEN, 3); // Lower-power timeout counter enable in memory-mapped mode + REGS_BOOL_FIELD(SSHIFT, 4); + REGS_FIELD(PRESCALER, uint8_t, 31, 24); + }; + + class DCR : public Register32 { // Device configuration register + public: + using Register32::Register32; + REGS_BOOL_FIELD(CKMODE, 0); // MODE 0 or 3 + REGS_FIELD(CSHT, uint8_t, 10, 8); // Chip select minimum high time + REGS_FIELD(FSIZE, uint8_t, 20, 16); + }; + + class SR : Register32 { // Status register + public: + REGS_BOOL_FIELD(TEF, 0); // Transfer error flag + REGS_BOOL_FIELD(TCF, 1); // Transfer complete flag + REGS_BOOL_FIELD(BUSY, 5); + }; + + class FCR : Register32 { // Flag clear register + }; + + class DLR : public Register32 { // Data length register + // In indirect and status-polling modes + // Number of bytes to be transferred = DLR + 1 + }; + + class CCR : public Register32 { // Communication configuration register + public: + using Register32::Register32; + enum class Size : uint8_t { + OneByte = 0, + TwoBytes = 1, + ThreeBytes = 2, + FourBytes = 3 + }; + enum class OperatingMode : uint8_t { + NoData = 0, + Single = 1, + Dual = 2, + Quad = 3 + }; + enum class FunctionalMode : uint8_t { + IndirectWrite = 0, + IndirectRead = 1, + StatusPolling = 2, + MemoryMapped = 3 + }; + REGS_FIELD(INSTRUCTION, uint8_t, 7, 0); + REGS_FIELD(IMODE, OperatingMode, 9, 8); + REGS_FIELD(ADMODE, OperatingMode, 11, 10); + REGS_FIELD(ADSIZE, Size, 13, 12); + REGS_FIELD(ABMODE, OperatingMode, 15, 14); + REGS_FIELD(ABSIZE, Size, 17, 16); + REGS_FIELD(DCYC, uint8_t, 22, 18); + REGS_FIELD(DMODE, OperatingMode, 25, 24); + REGS_FIELD(FMODE, FunctionalMode, 27, 26); + REGS_BOOL_FIELD(SIOO, 28); + }; + + class AR : public Register32 { // Address register + }; + + class ABR : Register32 { // Alternate bytes register + }; + + class DR : public Register8 { // Data register + /* In indirect (read or write) mode, any data must be written to or read from + * the data register, which supports byte, halfword and word access + * (aligned to the bottom of the register). + * N bytes read/written remove/add N bytes from/to the FIFO. + */ + }; + + class PSMKR : Register32 { // Polling status mask register + }; + + class PSMAR : Register32 { // Polling status match register + }; + + class PIR : Register32 { // Polling interval register + }; + + class LPTR : public Register16 { // Low-power timeout register + // Period before putting the Flash memory in lower-power state (in memory-mapped mode) + }; + + constexpr QUADSPI() {}; + + REGS_REGISTER_AT(CR, 0x00); + REGS_REGISTER_AT(DCR, 0x04); + REGS_REGISTER_AT(SR, 0x08); + REGS_REGISTER_AT(FCR, 0x0C); + REGS_REGISTER_AT(DLR, 0x10); + REGS_REGISTER_AT(CCR, 0x14); + REGS_REGISTER_AT(AR, 0x18); + REGS_REGISTER_AT(ABR, 0x1C); + REGS_REGISTER_AT(DR, 0x20); + REGS_REGISTER_AT(PSMKR, 0x24); + REGS_REGISTER_AT(PSMAR, 0x28); + REGS_REGISTER_AT(PIR, 0x2C); + REGS_REGISTER_AT(LPTR, 0x30); +private: + constexpr uint32_t Base() const { + return 0xA0001000; + } +}; + +constexpr QUADSPI QUADSPI; + +#endif diff --git a/ion/src/device/regs/regs.h b/ion/src/device/regs/regs.h index b5fad9e8a..c77040ee7 100644 --- a/ion/src/device/regs/regs.h +++ b/ion/src/device/regs/regs.h @@ -16,6 +16,7 @@ #include "rcc.h" #include "rng.h" #include "otg.h" +#include "quadspi.h" #include "sdio.h" #include "spi.h" #include "syscfg.h" From 0418cbeaeb62bbaec6a43ddd6ba808dc604bff46 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 20 Nov 2018 10:59:48 +0100 Subject: [PATCH 0015/1750] [ion/device] Start implement external flash API --- ion/src/device/external_flash.cpp | 209 ++++++++++++++++++++++++++++++ ion/src/device/external_flash.h | 36 +++++ 2 files changed, 245 insertions(+) create mode 100644 ion/src/device/external_flash.cpp create mode 100644 ion/src/device/external_flash.h diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp new file mode 100644 index 000000000..27a9902c7 --- /dev/null +++ b/ion/src/device/external_flash.cpp @@ -0,0 +1,209 @@ +#include "external_flash.h" +#include +#include +#include + +namespace Ion { +namespace ExternalFlash { +namespace Device { + +void waitController() { + while (QUADSPI.SR()->getBUSY()) { + } +} + +static enum DeviceStatusRegister : uint16_t { + // Status Register 1 + BUSY = 1 << 0, // Erase, Program, Write Status Register in Progress + WEL = 1 << 1, // Write Enable Latch + BP0 = 1 << 2, // Block Protect + BP1 = 1 << 3, // Block Protect + BP2 = 1 << 4, // Block Protect + TB = 1 << 5, // Top/Bottom Write Protect + SEC = 1 << 6, // Sector Protect + SRP = 1 << 7, // Status Register Protect 0 + // Status Register 2 + SRP1 = 1 << 8, // Status Register Protect 1 + QE = 1 << 9, // Quad-SPI Enable + // Bits 10, 11, 12, 13 are reserved + CMP = 1 << 14, // + SUS = 1 << 15 +}; + +/* +------------------------------------------------------------------------------------------------------------------------------------------------ + Bit Volatile? Reset value Short name Long name +------------------------------------------------------------------------------------------------------------------------------------------------ +Status Register-1 0 volatile BUSY Erase, Program, Write Status Register in Progress + + Any instruction, except for Read Register, will be ignored while BUSY=1. + It is recommended to check the BUSY bit before sending a new instruction, after a Program, Erase, Write Status Register operation. + TODO Status polling instead of while... + + 1 volatile WEL Write Enable Latch + + must be set before any Program, Erase or Write Status Register instruction, with the Write Enable instruction. + is automatically reset write-disable status of “0†after Powerâ€up and upon completion of any Program, Erase or Write Status Register instruction. + + 2 non-volatile BP0 Block Protect + 3 non-volatile BP1 Block Protect + 4 non-volatile BP2 Block Protect + 5 non-volatile TB Top/Bottom Write Protect + 6 non-volatile 0 SEC Sector Protect + + controls if the Block Protect Bits (BP2, BP1, BP0) protect 4KB Sectors (SEC=1) or 64KB Blocks (SEC=0) in the Top (TB=0) or the Bottom (TB=1) of the array. + + 7 non-volatile SRP Status Register Protect 0 +Status Register-2 8 non-volatile SRP1 Status Register Protect 1 + 9 non-volatile QE Quad SPI Enable + 14 non-volatile 0 CMP Complement Protect + + CMP=0: a top 4KB sector can be protected while the rest of the array is not + CMP=1: the top 4KB sector will become unprotected while the rest of the array become read-only. + + 15 volatile 0 SUS Suspend Status +------------------------------------------------------------------------------------------------------------------------------------------------ +*/ + +void waitDevice() { + do { + ReadStatusRegister1.write(); + } while(QUADSPI.DR()->get() & 1); +} + +void initGPIO() { + /* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI BK1_NCS + * PC9 | QUADSPI BK1_IO0/SO | Alternate Function 9 | QUADSPI BK1_IO0 + * PD12 | QUADSPI BK1_IO1/SI | Alternate Function 9 | QUADSPI BK1_IO1 + * PC8 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI BK1_IO2 + * PD13 | QUADSPI BK1_IO3/HOLD | Alternate Function 9 | QUADSPI BK1_IO3 */ + + // Enable GPIOB, C, D AHB1 peripheral clocks + RCC.AHB1ENR()->setGPIOBEN(true); + RCC.AHB1ENR()->setGPIOCEN(true); + RCC.AHB1ENR()->setGPIODEN(true); + + constexpr static GPIOPin QUADSPIPins[] = { + GPIOPin(GPIOB, 2), + GPIOPin(GPIOB, 6), + GPIOPin(GPIOC, 9), + GPIOPin(GPIOD,12), + GPIOPin(GPIOC, 8), + GPIOPin(GPIOD,13) + } + for(const GPIOPin & g : QUADSPIPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), (g == QUADSPIPins[1]) ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9); + } +} + +void init() { + initGPIO(); + + // Enable QUADSPI AHB3 peripheral clocks + RCC.AHB3ENR()->setQSPIEN(true); + // Configure controller for target device + QUADSPI.DCR()->setFSIZE(22); // 2^(22+1) bytes in device + /* TODO CKMODE, CSHT + * Instructions are initiated on the falling edge of CS and are completed on its rising edge. + * Mode 0: SCK is low when CS is high + * Mode 3: SCK is high when CS is high + * Input bits are sampled/latched on the rising edge of SCK. + * Output bits are shifted out on the falling edge of SCK. + * + * The IO pins should be at high impedance prior to the falling edge of the first data out clock. + * Data bytes are shifted with Most Significant Bit first. + * + * Configure and enable QUADSPI peripheral + * All instructions, except Read Data, supported SPI clock frequencies of up to 104MHz. + * Read Data supports up to 50Mhz. + * TODO PRESCALER + * TODO trade-off ? consumption? */ + QUADSPI.CR()->setPRESCALER(255); + QUADSPI.CR()->setEN(true); +} + +class Instruction { +public: + using QUADSPI::CCR::OperatingMode; + using QUADSPI::CCR::Size; + using QUADSPI::CCR::FunctionalMode; + constexpr Instruction( + uint8_t instructionCode, OperatingMode IMode, bool SIOO, + OperatingMode ADMode, + uint8_t DCyc, + OperatingMode DMode, uint32_t DLength, + FunctionalMode FMode + ) { + m_ccr = QUADSPI::CCR((uint32_t)0); + m_ccr.setINSTRUCTION(instructionCode); + m_ccr.setIMODE (IMode); + m_ccr.setSIOO (SIOO); + m_ccr.setADMODE(ADMode); + m_ccr.setADSIZE(Size::ThreeBytes); + m_ccr.setDCYC (DCyc); + m_ccr.setDMODE (DMode); + m_ccr.setFMODE (FMode); + // DDR not supported by Adesto + } + void setAlternateBytes(OperatingMode ABMode, Size ABSize) { + m_ccr.setABMODE(ABMode); + m_ccr.setABSIZE(ABSize); + } + void write() { + QUADSPI.CCR()->set(m_ccr); + QUADSPI.DLR()->set(0); + } +private: + class QUADSPI::CCR m_ccr; + uint32_t m_dlr; +}; + +//TODO default ccr +/* Modes IMODE ADMODE ABMODE DMODE + * SPIStandard 1-1-1-1 + * SPIDualOutput 1-1-1-2 + * SPIDualIO 1-2-2-2 + * SPIQuadOutput 1-1-1-4 + * SPIQuadIO 1-4-4-4 + * Quad SPI instructions require the QE bit in Status Register-2 to be set. + * QPI 4-4-4-4 + * Enable/Disable QPI instruction + * ADSIZE is always ThreeBytes + * MODE may be NoData*/ + +/* All Read instructions can be completed after any clocked bit. + * However, all Write, Program or Erase instructions must complete on a byte (CS driven high + * after a full byte has been clocked) otherwise the instruction will be terminated. */ + +using QUADSPI::CCR::OperatingMode; +using QUADSPI::CCR::Size; +using QUADSPI::CCR::FunctionalMode; + +static enum InstructionSet : Instruction { + ReadStatusRegister1 = Instruction(0x05, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectRead), + WriteEnable = Instruction(0x06, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectWrite), + // Before any Program, Erase or Write Status Register instruction + EraseChip = Instruction(0x60, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectWrite) + /* + ReadStatusRegister2 = Instruction(0x35), + ReadData = Instruction(0x03), + Erase4kBlock = Instruction(0x20), + PageProgram = Instruction(0x02) + */ +}; + +void eraseChip() { + WriteEnable.write(); + EraseChip.write(); + waitDevice(); +} + +} +} +} + +#endif diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h new file mode 100644 index 000000000..f651f4635 --- /dev/null +++ b/ion/src/device/external_flash.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_EXTERNAL_FLASH_H +#define ION_DEVICE_EXTERNAL_FLASH_H + +// Quad-SPI on STM32 microcontroller +// https://www.st.com/resource/en/application_note/dm00227538.pdf + +// Adesto Technologies AT25SF641 +// https://www.adestotech.com/wp-content/uploads/AT25SF641_111.pdf + +/* External Flash Memory map Address space + * 8MiB chip 0x000000 - 0x7FFFFF + * 2^7 64KiB blocks 0x..0000 - 0x..FFFF + * 2^7 * 2 32KiB blocks 0x..0000 - 0x..7FFF or 0x..8000 - 0x..FFFF + * 2^7 * 2 * 2^3 4KiB blocks 0x...000 - 0x...FFF + * 2^7 * 2 * 2^3 * 2^4 256B pages 0x....00 - 0x....FF */ + +namespace Ion { +namespace ExternalFlash { +namespace Device { + +// Read, Erase, Write +// at the fastest possible speed + +void eraseChip(); //block? + +void program(uint32_t * source, uint32_t * destination, size_t length); + +void read(); // in indirect read mode + +// memory-mapped mode + +} +} +} + +#endif From 8a0572a3d3766a58feada66568d9ee81e68bb52f Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 20 Nov 2018 15:25:23 +0100 Subject: [PATCH 0016/1750] [ion/device] Clean the external flash init code --- ion/src/device/Makefile | 1 + ion/src/device/device.cpp | 2 + ion/src/device/external_flash.cpp | 298 +++++++++++++----------------- ion/src/device/external_flash.h | 42 ++++- 4 files changed, 166 insertions(+), 177 deletions(-) diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 3d243bd47..31a060382 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -21,6 +21,7 @@ objs += $(addprefix ion/src/device/, \ device.o\ display.o\ events.o\ + external_flash.o\ flash.o\ keyboard.o\ led.o\ diff --git a/ion/src/device/device.cpp b/ion/src/device/device.cpp index e25cc4106..d01941f32 100644 --- a/ion/src/device/device.cpp +++ b/ion/src/device/device.cpp @@ -15,6 +15,7 @@ extern "C" { #include "usb.h" #include "bench/bench.h" #include "base64.h" +#include "external_flash.h" #define USE_SD_CARD 0 @@ -187,6 +188,7 @@ void initPeripherals() { Console::Device::init(); SWD::Device::init(); initSysTick(); + ExternalFlash::Device::init(); } void shutdownPeripherals(bool keepLEDAwake) { diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 27a9902c7..90c17f9e8 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -1,209 +1,165 @@ #include "external_flash.h" -#include -#include -#include namespace Ion { namespace ExternalFlash { namespace Device { -void waitController() { - while (QUADSPI.SR()->getBUSY()) { - } +/* Explain here how QuadSPI and QPI are different. */ + +static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Single; + +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t * data, size_t dataLength); + +static inline void send_command(Command c) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + DefaultOperatingMode, + c, + 0, nullptr, 0 + ); } -static enum DeviceStatusRegister : uint16_t { - // Status Register 1 - BUSY = 1 << 0, // Erase, Program, Write Status Register in Progress - WEL = 1 << 1, // Write Enable Latch - BP0 = 1 << 2, // Block Protect - BP1 = 1 << 3, // Block Protect - BP2 = 1 << 4, // Block Protect - TB = 1 << 5, // Top/Bottom Write Protect - SEC = 1 << 6, // Sector Protect - SRP = 1 << 7, // Status Register Protect 0 - // Status Register 2 - SRP1 = 1 << 8, // Status Register Protect 1 - QE = 1 << 9, // Quad-SPI Enable - // Bits 10, 11, 12, 13 are reserved - CMP = 1 << 14, // - SUS = 1 << 15 -}; +static inline void send_command_single(Command c) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + QUADSPI::CCR::OperatingMode::Single, + c, + 0, nullptr, 0 + ); +} -/* ------------------------------------------------------------------------------------------------------------------------------------------------- - Bit Volatile? Reset value Short name Long name ------------------------------------------------------------------------------------------------------------------------------------------------- -Status Register-1 0 volatile BUSY Erase, Program, Write Status Register in Progress +static inline void send_write_command(Command c, uint32_t address, uint8_t * data, size_t dataLength) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + DefaultOperatingMode, + c, + address, data, dataLength + ); +} - Any instruction, except for Read Register, will be ignored while BUSY=1. - It is recommended to check the BUSY bit before sending a new instruction, after a Program, Erase, Write Status Register operation. - TODO Status polling instead of while... +static inline void send_read_command(Command c, uint32_t address, uint8_t * data, size_t dataLength) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + DefaultOperatingMode, + c, + address, data, dataLength + ); +} - 1 volatile WEL Write Enable Latch - - must be set before any Program, Erase or Write Status Register instruction, with the Write Enable instruction. - is automatically reset write-disable status of “0†after Powerâ€up and upon completion of any Program, Erase or Write Status Register instruction. - - 2 non-volatile BP0 Block Protect - 3 non-volatile BP1 Block Protect - 4 non-volatile BP2 Block Protect - 5 non-volatile TB Top/Bottom Write Protect - 6 non-volatile 0 SEC Sector Protect - - controls if the Block Protect Bits (BP2, BP1, BP0) protect 4KB Sectors (SEC=1) or 64KB Blocks (SEC=0) in the Top (TB=0) or the Bottom (TB=1) of the array. - - 7 non-volatile SRP Status Register Protect 0 -Status Register-2 8 non-volatile SRP1 Status Register Protect 1 - 9 non-volatile QE Quad SPI Enable - 14 non-volatile 0 CMP Complement Protect - - CMP=0: a top 4KB sector can be protected while the rest of the array is not - CMP=1: the top 4KB sector will become unprotected while the rest of the array become read-only. - - 15 volatile 0 SUS Suspend Status ------------------------------------------------------------------------------------------------------------------------------------------------- -*/ - -void waitDevice() { +static inline void wait() { + class ExternalFlashStatusRegister : Register16 { + public: + using Register16::Register16; + REGS_BOOL_FIELD(BUSY, 0); + }; + ExternalFlashStatusRegister statusRegister(0); do { - ReadStatusRegister1.write(); - } while(QUADSPI.DR()->get() & 1); + send_read_command(Command::ReadStatusRegister, 0, reinterpret_cast(&statusRegister), sizeof(statusRegister)); + } while (statusRegister.getBUSY()); } -void initGPIO() { - /* Pin | Role | Mode | Function - * -----+----------------------+-----------------------+----------------- - * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI CLK - * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI BK1_NCS - * PC9 | QUADSPI BK1_IO0/SO | Alternate Function 9 | QUADSPI BK1_IO0 - * PD12 | QUADSPI BK1_IO1/SI | Alternate Function 9 | QUADSPI BK1_IO1 - * PC8 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI BK1_IO2 - * PD13 | QUADSPI BK1_IO3/HOLD | Alternate Function 9 | QUADSPI BK1_IO3 */ +static void set_as_memory_mapped() { + send_command_full( + QUADSPI::CCR::FunctionalMode::MemoryMapped, + DefaultOperatingMode, + Command::FastRead, + 0, nullptr, 0 + ); +} - // Enable GPIOB, C, D AHB1 peripheral clocks - RCC.AHB1ENR()->setGPIOBEN(true); - RCC.AHB1ENR()->setGPIOCEN(true); - RCC.AHB1ENR()->setGPIODEN(true); - - constexpr static GPIOPin QUADSPIPins[] = { - GPIOPin(GPIOB, 2), - GPIOPin(GPIOB, 6), - GPIOPin(GPIOC, 9), - GPIOPin(GPIOD,12), - GPIOPin(GPIOC, 8), - GPIOPin(GPIOD,13) +void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t * data, size_t dataLength) { + class QUADSPI::CCR ccr(0); + ccr.setFMODE(functionalMode); + if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setDMODE(operatingMode); } - for(const GPIOPin & g : QUADSPIPins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), (g == QUADSPIPins[1]) ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9); + if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setADMODE(operatingMode); + ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); + QUADSPI.AR()->set(address); + } + ccr.setIMODE(operatingMode); + ccr.setINSTRUCTION(static_cast(c)); + QUADSPI.CCR()->set(ccr); + + // FIXME: Handle access sizes + if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { + for (size_t i=0; iset(data[i]); + } + } else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + for (size_t i=0; iget(); + } + } + + /* 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." */ + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + while (QUADSPI.SR()->getBUSY()) { + } } } void init() { initGPIO(); + initQSPI(); + initChip(); +} +void initGPIO() { + for(const GPIOPin & g : QSPIPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), (g.pin() == 6 ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9)); + } +} + +void initQSPI() { // Enable QUADSPI AHB3 peripheral clocks RCC.AHB3ENR()->setQSPIEN(true); // Configure controller for target device QUADSPI.DCR()->setFSIZE(22); // 2^(22+1) bytes in device - /* TODO CKMODE, CSHT - * Instructions are initiated on the falling edge of CS and are completed on its rising edge. - * Mode 0: SCK is low when CS is high - * Mode 3: SCK is high when CS is high - * Input bits are sampled/latched on the rising edge of SCK. - * Output bits are shifted out on the falling edge of SCK. - * - * The IO pins should be at high impedance prior to the falling edge of the first data out clock. - * Data bytes are shifted with Most Significant Bit first. - * - * Configure and enable QUADSPI peripheral - * All instructions, except Read Data, supported SPI clock frequencies of up to 104MHz. - * Read Data supports up to 50Mhz. - * TODO PRESCALER - * TODO trade-off ? consumption? */ - QUADSPI.CR()->setPRESCALER(255); + + QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal + QUADSPI.CR()->setPRESCALER(255); // Highest value. TODO: make it optimal + QUADSPI.CR()->setEN(true); } -class Instruction { -public: - using QUADSPI::CCR::OperatingMode; - using QUADSPI::CCR::Size; - using QUADSPI::CCR::FunctionalMode; - constexpr Instruction( - uint8_t instructionCode, OperatingMode IMode, bool SIOO, - OperatingMode ADMode, - uint8_t DCyc, - OperatingMode DMode, uint32_t DLength, - FunctionalMode FMode - ) { - m_ccr = QUADSPI::CCR((uint32_t)0); - m_ccr.setINSTRUCTION(instructionCode); - m_ccr.setIMODE (IMode); - m_ccr.setSIOO (SIOO); - m_ccr.setADMODE(ADMode); - m_ccr.setADSIZE(Size::ThreeBytes); - m_ccr.setDCYC (DCyc); - m_ccr.setDMODE (DMode); - m_ccr.setFMODE (FMode); - // DDR not supported by Adesto +void initChip() { + /* The chip initially expects commands in SPI mode. We need to use SPI to tell + * it to switch to QPI, hence the "_single". */ + if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { + send_command_single(Command::EnableQPI); } - void setAlternateBytes(OperatingMode ABMode, Size ABSize) { - m_ccr.setABMODE(ABMode); - m_ccr.setABSIZE(ABSize); - } - void write() { - QUADSPI.CCR()->set(m_ccr); - QUADSPI.DLR()->set(0); - } -private: - class QUADSPI::CCR m_ccr; - uint32_t m_dlr; -}; + set_as_memory_mapped(); +} -//TODO default ccr -/* Modes IMODE ADMODE ABMODE DMODE - * SPIStandard 1-1-1-1 - * SPIDualOutput 1-1-1-2 - * SPIDualIO 1-2-2-2 - * SPIQuadOutput 1-1-1-4 - * SPIQuadIO 1-4-4-4 - * Quad SPI instructions require the QE bit in Status Register-2 to be set. - * QPI 4-4-4-4 - * Enable/Disable QPI instruction - * ADSIZE is always ThreeBytes - * MODE may be NoData*/ +void MassErase() { + send_command(Command::WriteEnable); + send_command(Command::ChipErase); + //send_command(ReadStatusRegister); + wait(); + set_as_memory_mapped(); +} -/* All Read instructions can be completed after any clocked bit. - * However, all Write, Program or Erase instructions must complete on a byte (CS driven high - * after a full byte has been clocked) otherwise the instruction will be terminated. */ +void EraseSector() { + send_command(Command::WriteEnable); + //send_command(Command::BlockErase /* PICK SIZE */, addressOfSector); + wait(); + set_as_memory_mapped(); +} -using QUADSPI::CCR::OperatingMode; -using QUADSPI::CCR::Size; -using QUADSPI::CCR::FunctionalMode; - -static enum InstructionSet : Instruction { - ReadStatusRegister1 = Instruction(0x05, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectRead), - WriteEnable = Instruction(0x06, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectWrite), - // Before any Program, Erase or Write Status Register instruction - EraseChip = Instruction(0x60, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectWrite) - /* - ReadStatusRegister2 = Instruction(0x35), - ReadData = Instruction(0x03), - Erase4kBlock = Instruction(0x20), - PageProgram = Instruction(0x02) - */ -}; - -void eraseChip() { - WriteEnable.write(); - EraseChip.write(); - waitDevice(); +void WriteMemory(uint32_t * source, uint32_t * destination, size_t length) { + send_command(Command::WriteEnable); + //send_write_command(Command::QuadPageProgram, (destination-reinterpret_cast(QSPIBaseAddress)), source, length); + // TODO: Apprently, here we must issue a new send_command every 256 byte + wait(); + set_as_memory_mapped(); } } } } - -#endif diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index f651f4635..659a4f8dc 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -1,6 +1,10 @@ #ifndef ION_DEVICE_EXTERNAL_FLASH_H #define ION_DEVICE_EXTERNAL_FLASH_H +#include +#include +#include "regs/regs.h" + // Quad-SPI on STM32 microcontroller // https://www.st.com/resource/en/application_note/dm00227538.pdf @@ -18,16 +22,42 @@ namespace Ion { namespace ExternalFlash { namespace Device { -// Read, Erase, Write -// at the fastest possible speed +/* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS + * PC8 | 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 + */ -void eraseChip(); //block? +void init(); +void shutdown(); -void program(uint32_t * source, uint32_t * destination, size_t length); +void initGPIO(); +void initQSPI(); +void initChip(); -void read(); // in indirect read mode +void MassErase(); +void EraseSector(int sector); +void WriteMemory(uint32_t * source, uint32_t * destination, size_t length); -// memory-mapped mode +enum class Command : uint8_t { + ReadStatusRegister = 0x05, + WriteEnable = 0x06, + FastRead = 0x0B, + QuadPageProgram = 0x33, + EnableQPI = 0x38, + ChipErase = 0xC7 +}; + +constexpr static uint32_t QSPIBaseAddress = 0x90000000; + +constexpr static GPIOPin QSPIPins[] = { + GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), + GPIOPin(GPIOC, 8), GPIOPin(GPIOD,13) +}; } } From 1feb82e345d5790a5d3c36760f25af65a8d884df Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Wed, 21 Nov 2018 17:06:12 +0100 Subject: [PATCH 0017/1750] [ion/device] Read Data from external flash in memory-mapped mode --- ion/src/device/external_flash.cpp | 31 +++++++++++++++++++++++++++---- ion/src/device/external_flash.h | 3 +++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 90c17f9e8..179ec4c3b 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -4,7 +4,27 @@ namespace Ion { namespace ExternalFlash { namespace Device { -/* Explain here how QuadSPI and QPI are different. */ +/* 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. + * + * After the external flash receives a Read instructions and shifts a byte out, + * it automatically increments the provided address and shifts out the corresponding byte, + * and so on as long as the clock continues, allowing for a continuous stream of data. + */ static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Single; @@ -62,7 +82,7 @@ static void set_as_memory_mapped() { send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, - Command::FastRead, + Command::ReadData, 0, nullptr, 0 ); } @@ -73,14 +93,17 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setDMODE(operatingMode); } + QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); - QUADSPI.AR()->set(address); } ccr.setIMODE(operatingMode); ccr.setINSTRUCTION(static_cast(c)); QUADSPI.CCR()->set(ccr); + if (address != 0) { + QUADSPI.AR()->set(address); + } // FIXME: Handle access sizes if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { @@ -120,7 +143,7 @@ void initQSPI() { // Enable QUADSPI AHB3 peripheral clocks RCC.AHB3ENR()->setQSPIEN(true); // Configure controller for target device - QUADSPI.DCR()->setFSIZE(22); // 2^(22+1) bytes in device + QUADSPI.DCR()->setFSIZE(FlashNumberOfAddressBits-1); QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal QUADSPI.CR()->setPRESCALER(255); // Highest value. TODO: make it optimal diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index 659a4f8dc..ae78f42f4 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -46,6 +46,7 @@ void WriteMemory(uint32_t * source, uint32_t * destination, size_t length); enum class Command : uint8_t { ReadStatusRegister = 0x05, WriteEnable = 0x06, + ReadData = 0x03, FastRead = 0x0B, QuadPageProgram = 0x33, EnableQPI = 0x38, @@ -53,6 +54,8 @@ enum class Command : uint8_t { }; constexpr static uint32_t QSPIBaseAddress = 0x90000000; +constexpr static uint32_t FlashNumberOfAddressBits = 23; +constexpr static uint32_t FlashAddressSpaceSize = 1 << FlashNumberOfAddressBits; constexpr static GPIOPin QSPIPins[] = { GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), From ee302e84134d932853d26a0c59fbf6112e5c245b Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Wed, 21 Nov 2018 18:18:00 +0100 Subject: [PATCH 0018/1750] [ion/device] Add dummyCycles parameter for ExternalFlash commands --- ion/src/device/external_flash.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 179ec4c3b..0c201a3ec 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -28,14 +28,14 @@ namespace Device { static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Single; -static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t * data, size_t dataLength); +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); static inline void send_command(Command c) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, DefaultOperatingMode, c, - 0, nullptr, 0 + 0, 0, nullptr, 0 ); } @@ -44,7 +44,7 @@ static inline void send_command_single(Command c) { QUADSPI::CCR::FunctionalMode::IndirectWrite, QUADSPI::CCR::OperatingMode::Single, c, - 0, nullptr, 0 + 0, 0, nullptr, 0 ); } @@ -53,7 +53,7 @@ static inline void send_write_command(Command c, uint32_t address, uint8_t * dat QUADSPI::CCR::FunctionalMode::IndirectWrite, DefaultOperatingMode, c, - address, data, dataLength + address, 0, data, dataLength ); } @@ -62,7 +62,7 @@ static inline void send_read_command(Command c, uint32_t address, uint8_t * data QUADSPI::CCR::FunctionalMode::IndirectRead, DefaultOperatingMode, c, - address, data, dataLength + address, 0, data, dataLength ); } @@ -83,17 +83,18 @@ static void set_as_memory_mapped() { QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::ReadData, - 0, nullptr, 0 + 0, 0, nullptr, 0 ); } -void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t * data, size_t dataLength) { +void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { class QUADSPI::CCR ccr(0); ccr.setFMODE(functionalMode); if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setDMODE(operatingMode); } QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + ccr.setDCYC(dummyCycles); if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); From db2193b8ef417edac13eef12c9bbd0f081424455 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 22 Nov 2018 09:44:32 +0100 Subject: [PATCH 0019/1750] [ion/device] Fast Read external flash in SPI mode --- ion/src/device/external_flash.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 0c201a3ec..8e94ef89d 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -82,8 +82,8 @@ static void set_as_memory_mapped() { send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, - Command::ReadData, - 0, 0, nullptr, 0 + Command::FastRead, + 0, 8, nullptr, 0 ); } From ffc4f28fb984091f0e09cb7a3b08224fa861c67e Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 22 Nov 2018 10:15:43 +0100 Subject: [PATCH 0020/1750] [ion/device] Fast Read external flash in QPI mode --- ion/src/device/external_flash.cpp | 68 ++++++++++++++++++------------- ion/src/device/external_flash.h | 1 + 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 8e94ef89d..81c4eda7d 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -26,64 +26,65 @@ namespace Device { * and so on as long as the clock continues, allowing for a continuous stream of data. */ -static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Single; +class ExternalFlashStatusRegister { +public: + class StatusRegister1 : Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_R(BUSY, 0); + }; + class StatusRegister2 : Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_W(QE, 1); + }; +}; + +static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); -static inline void send_command(Command c) { +static inline void send_command(Command c, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, - DefaultOperatingMode, + operatingMode, c, 0, 0, nullptr, 0 ); } -static inline void send_command_single(Command c) { +static inline void send_write_command(Command c, uint32_t address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, - QUADSPI::CCR::OperatingMode::Single, - c, - 0, 0, nullptr, 0 - ); -} - -static inline void send_write_command(Command c, uint32_t address, uint8_t * data, size_t dataLength) { - send_command_full( - QUADSPI::CCR::FunctionalMode::IndirectWrite, - DefaultOperatingMode, + operatingMode, c, address, 0, data, dataLength ); } -static inline void send_read_command(Command c, uint32_t address, uint8_t * data, size_t dataLength) { +static inline void send_read_command(Command c, uint32_t address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectRead, - DefaultOperatingMode, + operatingMode, c, address, 0, data, dataLength ); } -static inline void wait() { - class ExternalFlashStatusRegister : Register16 { - public: - using Register16::Register16; - REGS_BOOL_FIELD(BUSY, 0); - }; - ExternalFlashStatusRegister statusRegister(0); +static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); do { - send_read_command(Command::ReadStatusRegister, 0, reinterpret_cast(&statusRegister), sizeof(statusRegister)); - } while (statusRegister.getBUSY()); + send_read_command(Command::ReadStatusRegister, 0, reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); + } while (statusRegister1.getBUSY()); } static void set_as_memory_mapped() { + constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : 4; send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::FastRead, - 0, 8, nullptr, 0 + 0, FastReadDummyCycles, nullptr, 0 ); } @@ -93,7 +94,9 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setDMODE(operatingMode); } - QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + } ccr.setDCYC(dummyCycles); if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); @@ -154,9 +157,16 @@ void initQSPI() { void initChip() { /* The chip initially expects commands in SPI mode. We need to use SPI to tell - * it to switch to QPI, hence the "_single". */ + * it to switch to QPI. */ if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { - send_command_single(Command::EnableQPI); + send_command(Command::WriteEnable, QUADSPI::CCR::OperatingMode::Single); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + statusRegister2.setQE(true); + wait(QUADSPI::CCR::OperatingMode::Single); + send_write_command(Command::WriteStatusRegister2, 0, reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); + wait(QUADSPI::CCR::OperatingMode::Single); + send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); + wait(); } set_as_memory_mapped(); } diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index ae78f42f4..17f170b8f 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -45,6 +45,7 @@ void WriteMemory(uint32_t * source, uint32_t * destination, size_t length); enum class Command : uint8_t { ReadStatusRegister = 0x05, + WriteStatusRegister2 = 0x31, WriteEnable = 0x06, ReadData = 0x03, FastRead = 0x0B, From db91e8b4047092ac605fc5249a335715f4b3e12f Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 22 Nov 2018 15:51:54 +0100 Subject: [PATCH 0021/1750] [ion/device] Convert external flash addresses from uint32_t to uint8_t * --- ion/src/device/external_flash.cpp | 24 ++++++++++++------------ ion/src/device/external_flash.h | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 81c4eda7d..04233a00f 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -42,18 +42,18 @@ public: static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; -static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); static inline void send_command(Command c, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, c, - 0, 0, nullptr, 0 + nullptr, 0, nullptr, 0 ); } -static inline void send_write_command(Command c, uint32_t address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { +static inline void send_write_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, @@ -62,7 +62,7 @@ static inline void send_write_command(Command c, uint32_t address, uint8_t * dat ); } -static inline void send_read_command(Command c, uint32_t address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { +static inline void send_read_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectRead, operatingMode, @@ -74,7 +74,7 @@ static inline void send_read_command(Command c, uint32_t address, uint8_t * data static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); do { - send_read_command(Command::ReadStatusRegister, 0, reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); + send_read_command(Command::ReadStatusRegister, nullptr, reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); } while (statusRegister1.getBUSY()); } @@ -84,11 +84,11 @@ static void set_as_memory_mapped() { QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::FastRead, - 0, FastReadDummyCycles, nullptr, 0 + nullptr, FastReadDummyCycles, nullptr, 0 ); } -void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { +void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { class QUADSPI::CCR ccr(0); ccr.setFMODE(functionalMode); if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { @@ -98,15 +98,15 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); } ccr.setDCYC(dummyCycles); - if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + if (address != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); } ccr.setIMODE(operatingMode); ccr.setINSTRUCTION(static_cast(c)); QUADSPI.CCR()->set(ccr); - if (address != 0) { - QUADSPI.AR()->set(address); + if (address != nullptr) { + QUADSPI.AR()->set(reinterpret_cast(address)); } // FIXME: Handle access sizes @@ -163,7 +163,7 @@ void initChip() { ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); statusRegister2.setQE(true); wait(QUADSPI::CCR::OperatingMode::Single); - send_write_command(Command::WriteStatusRegister2, 0, reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); + send_write_command(Command::WriteStatusRegister2, nullptr, reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); wait(QUADSPI::CCR::OperatingMode::Single); send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); wait(); @@ -186,7 +186,7 @@ void EraseSector() { set_as_memory_mapped(); } -void WriteMemory(uint32_t * source, uint32_t * destination, size_t length) { +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { send_command(Command::WriteEnable); //send_write_command(Command::QuadPageProgram, (destination-reinterpret_cast(QSPIBaseAddress)), source, length); // TODO: Apprently, here we must issue a new send_command every 256 byte diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index 17f170b8f..123ab07e6 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -41,7 +41,7 @@ void initChip(); void MassErase(); void EraseSector(int sector); -void WriteMemory(uint32_t * source, uint32_t * destination, size_t length); +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); enum class Command : uint8_t { ReadStatusRegister = 0x05, @@ -55,7 +55,7 @@ enum class Command : uint8_t { }; constexpr static uint32_t QSPIBaseAddress = 0x90000000; -constexpr static uint32_t FlashNumberOfAddressBits = 23; +constexpr static uint8_t FlashNumberOfAddressBits = 23; constexpr static uint32_t FlashAddressSpaceSize = 1 << FlashNumberOfAddressBits; constexpr static GPIOPin QSPIPins[] = { From d55a86f9090cfa5090a549fd7689359e066b82cc Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 22 Nov 2018 17:03:11 +0100 Subject: [PATCH 0022/1750] [ion/device] External flash WriteMemory --- ion/src/device/external_flash.cpp | 26 +++++++++++++++++++++----- ion/src/device/external_flash.h | 2 ++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 04233a00f..76eb2ebee 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -174,7 +174,6 @@ void initChip() { void MassErase() { send_command(Command::WriteEnable); send_command(Command::ChipErase); - //send_command(ReadStatusRegister); wait(); set_as_memory_mapped(); } @@ -187,10 +186,27 @@ void EraseSector() { } void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { - send_command(Command::WriteEnable); - //send_write_command(Command::QuadPageProgram, (destination-reinterpret_cast(QSPIBaseAddress)), source, length); - // TODO: Apprently, here we must issue a new send_command every 256 byte - wait(); + /* 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; + constexpr Command pageProgram = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? Command::PageProgram : Command::QuadPageProgram; + uint8_t offset = reinterpret_cast(destination) & (PageSize - 1); + size_t lengthThatFitsInPage = PageSize - offset; + while (length > 0) { + if (lengthThatFitsInPage > length) { + lengthThatFitsInPage = length; + } + send_command(Command::WriteEnable); + wait(); + send_write_command(pageProgram, destination, source, lengthThatFitsInPage); + length -= lengthThatFitsInPage; + destination += lengthThatFitsInPage; + source += lengthThatFitsInPage; + lengthThatFitsInPage = PageSize; + wait(); + } set_as_memory_mapped(); } diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index 123ab07e6..1c12a7f19 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -49,6 +49,8 @@ enum class Command : uint8_t { WriteEnable = 0x06, ReadData = 0x03, FastRead = 0x0B, + // Program previously erased memory areas as being "0" + PageProgram = 0x02, QuadPageProgram = 0x33, EnableQPI = 0x38, ChipErase = 0xC7 From 46707b294a83075c06c326a643e004d53257c784 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 26 Nov 2018 14:18:18 +0100 Subject: [PATCH 0023/1750] [ion/device] Add explanatory comments to external flash --- ion/src/device/external_flash.cpp | 41 +++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 76eb2ebee..cc4dbc3c7 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -21,10 +21,33 @@ namespace Device { * The external flash supports clock frequencies up to 104MHz for all instructions, * except for Read Data (0x03) which is supported up to 50Mhz. * - * After the external flash receives a Read instructions and shifts a byte out, - * it automatically increments the provided address and shifts out the corresponding byte, - * and so on as long as the clock continues, allowing for a continuous stream of data. - */ + * + * Quad-SPI block diagram + * + * +----------------------+ +------------+ + * | Quad-SPI | | | + * | peripheral | | External | + * | | read | flash | + * AHB <-- | data <-- 32-byte | <-- | memory | + * matrix --> | regsiter --> 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. */ class ExternalFlashStatusRegister { public: @@ -79,6 +102,15 @@ static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOpera } 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.) */ constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : 4; send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, @@ -109,7 +141,6 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR QUADSPI.AR()->set(reinterpret_cast(address)); } - // FIXME: Handle access sizes if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { for (size_t i=0; iset(data[i]); From cce2eaa2288f29f450f6e5db6a7db49f42be5a62 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Wed, 28 Nov 2018 18:09:30 +0100 Subject: [PATCH 0024/1750] [ion/device] External flash erase routines --- ion/src/device/external_flash.cpp | 17 ++++++++++++++--- ion/src/device/external_flash.h | 15 +++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index cc4dbc3c7..1f6a5c4b2 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -178,7 +178,7 @@ void initQSPI() { // Enable QUADSPI AHB3 peripheral clocks RCC.AHB3ENR()->setQSPIEN(true); // Configure controller for target device - QUADSPI.DCR()->setFSIZE(FlashNumberOfAddressBits-1); + QUADSPI.DCR()->setFSIZE(NumberOfAddressBitsInChip - 1); QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal QUADSPI.CR()->setPRESCALER(255); // Highest value. TODO: make it optimal @@ -202,16 +202,27 @@ void initChip() { set_as_memory_mapped(); } +int SectorAtAddress(uint32_t address) { + int i = address >> NumberOfAddressBitsIn64KbyteBlock; + if (i >= NumberOfSectors) { + return -1; + } + return i; +} + void MassErase() { send_command(Command::WriteEnable); + wait(); send_command(Command::ChipErase); wait(); set_as_memory_mapped(); } -void EraseSector() { +void EraseSector(int i) { + assert(i >= 0 && i < NumberOfSectors); send_command(Command::WriteEnable); - //send_command(Command::BlockErase /* PICK SIZE */, addressOfSector); + wait(); + send_write_command(Command::Erase64KbyteBlock, reinterpret_cast(i << NumberOfAddressBitsIn64KbyteBlock), nullptr, 0); wait(); set_as_memory_mapped(); } diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index 1c12a7f19..ff4d9e7b8 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -40,7 +40,11 @@ void initQSPI(); void initChip(); void MassErase(); -void EraseSector(int sector); + +constexpr int NumberOfSectors = 128; +int SectorAtAddress(uint32_t address); +void EraseSector(int i); + void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); enum class Command : uint8_t { @@ -53,12 +57,15 @@ enum class Command : uint8_t { PageProgram = 0x02, QuadPageProgram = 0x33, EnableQPI = 0x38, - ChipErase = 0xC7 + // Erase the whole chip or a 64-Kbyte block as being "1" + ChipErase = 0xC7, + Erase64KbyteBlock = 0xD8, }; constexpr static uint32_t QSPIBaseAddress = 0x90000000; -constexpr static uint8_t FlashNumberOfAddressBits = 23; -constexpr static uint32_t FlashAddressSpaceSize = 1 << FlashNumberOfAddressBits; +constexpr static uint8_t NumberOfAddressBitsInChip = 23; +constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; constexpr static GPIOPin QSPIPins[] = { GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), From 300ba9cf6705343493c34fbb340530a7238ae642 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 29 Nov 2018 15:56:26 +0100 Subject: [PATCH 0025/1750] [ion/device] External flash: replace nullptr address by an out-of-range address --- ion/src/device/external_flash.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 1f6a5c4b2..5aa124d3b 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -72,7 +72,7 @@ static inline void send_command(Command c, QUADSPI::CCR::OperatingMode operating QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, c, - nullptr, 0, nullptr, 0 + reinterpret_cast(FlashAddressSpaceSize), 0, nullptr, 0 ); } @@ -97,7 +97,7 @@ static inline void send_read_command(Command c, uint8_t * address, uint8_t * dat static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); do { - send_read_command(Command::ReadStatusRegister, nullptr, reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); + send_read_command(Command::ReadStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); } while (statusRegister1.getBUSY()); } @@ -116,7 +116,7 @@ static void set_as_memory_mapped() { QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::FastRead, - nullptr, FastReadDummyCycles, nullptr, 0 + reinterpret_cast(FlashAddressSpaceSize), FastReadDummyCycles, nullptr, 0 ); } @@ -130,14 +130,14 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); } ccr.setDCYC(dummyCycles); - if (address != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); } ccr.setIMODE(operatingMode); ccr.setINSTRUCTION(static_cast(c)); QUADSPI.CCR()->set(ccr); - if (address != nullptr) { + if (address != reinterpret_cast(FlashAddressSpaceSize)) { QUADSPI.AR()->set(reinterpret_cast(address)); } @@ -194,7 +194,7 @@ void initChip() { ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); statusRegister2.setQE(true); wait(QUADSPI::CCR::OperatingMode::Single); - send_write_command(Command::WriteStatusRegister2, nullptr, reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); + send_write_command(Command::WriteStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); wait(QUADSPI::CCR::OperatingMode::Single); send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); wait(); From cfe2ce731f3a33e734679b3560cfa2d12d0e129e Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 3 Dec 2018 09:58:35 +0100 Subject: [PATCH 0026/1750] [ion/device] Access external flash through DFU --- ion/src/device/usb/Makefile | 1 + ion/src/device/usb/calculator.h | 2 +- ion/src/device/usb/dfu_interface.cpp | 20 ++++++++++++++------ ion/src/device/usb/dfu_interface.h | 2 ++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ion/src/device/usb/Makefile b/ion/src/device/usb/Makefile index a2d840eef..6a859fcc9 100644 --- a/ion/src/device/usb/Makefile +++ b/ion/src/device/usb/Makefile @@ -50,6 +50,7 @@ dfu_objs += ion/src/device/device.o dfu_objs += ion/src/device/usb.o dfu_objs += ion/src/device/base64.o dfu_objs += ion/src/device/flash.o +dfu_objs += ion/src/device/external_flash.o dfu_objs += ion/src/device/timing.o ion/src/device/usb/dfu.elf: LDSCRIPT = ion/src/device/usb/dfu.ld diff --git a/ion/src/device/usb/calculator.h b/ion/src/device/usb/calculator.h index d3383e419..81b8c9b2a 100644 --- a/ion/src/device/usb/calculator.h +++ b/ion/src/device/usb/calculator.h @@ -93,7 +93,7 @@ public: m_manufacturerStringDescriptor("NumWorks"), m_productStringDescriptor("NumWorks Calculator"), m_serialNumberStringDescriptor(serialNumber), - m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg"), + m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg/0x90000000/64*064Kg,64*064Kg"), //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), /* Switch to this descriptor to use dfu-util to write in the SRAM. * FIXME Should be an alternate Interface. */ diff --git a/ion/src/device/usb/dfu_interface.cpp b/ion/src/device/usb/dfu_interface.cpp index 029cce703..8b30e7ea2 100644 --- a/ion/src/device/usb/dfu_interface.cpp +++ b/ion/src/device/usb/dfu_interface.cpp @@ -1,6 +1,7 @@ #include "dfu_interface.h" #include #include +#include namespace Ion { namespace USB { @@ -173,7 +174,7 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe if (transferBufferLength == 1) { // Mass erase - m_erasePage = Flash::Device::NumberOfSectors; + m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors; return; } @@ -185,9 +186,11 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe + (transferBuffer[3] << 16) + (transferBuffer[4] << 24); - m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); - - if (m_erasePage < 0) { + if (eraseAddress >= k_flashStartAddress && eraseAddress <= k_flashEndAddress) { + m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); + } else if (eraseAddress >= k_externalFlashStartAddress && eraseAddress <= k_externalFlashEndAddress) { + m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::SectorAtAddress(eraseAddress - k_externalFlashStartAddress); + } else { // Unrecognized sector m_state = State::dfuERROR; m_status = Status::errTARGET; @@ -201,10 +204,13 @@ void DFUInterface::eraseMemoryIfNeeded() { return; } - if (m_erasePage == Ion::Flash::Device::NumberOfSectors) { + if (m_erasePage == Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors) { Flash::Device::MassErase(); - } else { + ExternalFlash::Device::MassErase(); + } else if (m_erasePage < Flash::Device::NumberOfSectors) { Flash::Device::EraseSector(m_erasePage); + } else { + ExternalFlash::Device::EraseSector(m_erasePage - Flash::Device::NumberOfSectors); } /* Put an out of range value in m_erasePage to indicate that no erase is @@ -222,6 +228,8 @@ void DFUInterface::writeOnMemory() { // Write on SRAM // FIXME We should check that we are not overriding the current instructions. memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); + } else if (m_writeAddress >= k_externalFlashStartAddress && m_writeAddress <= k_externalFlashEndAddress) { + ExternalFlash::Device::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress) - k_externalFlashStartAddress, m_largeBufferLength); } else { // Invalid write address m_largeBufferLength = 0; diff --git a/ion/src/device/usb/dfu_interface.h b/ion/src/device/usb/dfu_interface.h index 6284ce951..7fce4bf40 100644 --- a/ion/src/device/usb/dfu_interface.h +++ b/ion/src/device/usb/dfu_interface.h @@ -130,6 +130,8 @@ private: constexpr static uint32_t k_flashEndAddress = 0x08100000; constexpr static uint32_t k_sramStartAddress = 0x20000000; constexpr static uint32_t k_sramEndAddress = 0x20040000; + constexpr static uint32_t k_externalFlashStartAddress = 0x90000000; + constexpr static uint32_t k_externalFlashEndAddress = 0x90800000; // Download and upload bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); From 9c9ece40b4e9cd9c72d0ff8e3d48aca76d0ea23d Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 6 Dec 2018 13:56:04 +0100 Subject: [PATCH 0027/1750] [ion/device] Add external flash tests --- ion/Makefile | 8 ++ ion/test/external_flash.cpp | 165 ++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 ion/test/external_flash.cpp diff --git a/ion/Makefile b/ion/Makefile index e123b4595..611e42916 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -32,3 +32,11 @@ tests += $(addprefix ion/test/,\ keyboard.cpp\ storage.cpp\ ) + +TEST_EXT_FLASH ?= 0 +ifeq ($(TEST_EXT_FLASH),1) +SFLAGS += -DTEST_EXT_FLASH=1 +tests += ion/test/external_flash.cpp +TEST_EXT_FLASH_REPROGRAM ?= 0 +SFLAGS += -DTEST_EXT_FLASH_REPROGRAM=$(TEST_EXT_FLASH_REPROGRAM) +endif diff --git a/ion/test/external_flash.cpp b/ion/test/external_flash.cpp new file mode 100644 index 000000000..392a058da --- /dev/null +++ b/ion/test/external_flash.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include "ion/src/device/external_flash.h" +#include "ion/include/ion/timing.h" + +// Choose some not too uniform data to program and test the external flash memory with. + +static inline uint8_t expected_value_at(uint8_t * ptr) { + uint32_t address = reinterpret_cast(ptr) - Ion::ExternalFlash::Device::QSPIBaseAddress; + return (address / 0x10000) + (address / 0x100) + address; + // Example: the value expected at the address 0x123456 is 0x12 + 0x34 + 0x56. +} + +static inline uint16_t expected_value_at(uint16_t * ptr) { + uint8_t * ptr8 = reinterpret_cast(ptr); + return (static_cast(expected_value_at(ptr8+1)) << 8) | static_cast(expected_value_at(ptr8)); +} + +static inline uint32_t expected_value_at(uint32_t * ptr) { + uint16_t * ptr16 = reinterpret_cast(ptr); + return (static_cast(expected_value_at(ptr16+1)) << 16) + static_cast(expected_value_at(ptr16)); +} + +template +static inline void check(volatile T * p, int repeat) { + for (int i = 0; i < repeat; i++) { + quiz_assert(*p == expected_value_at(const_cast(p))); + } +} + +template +void test(int accessType, int repeat) { + uint8_t * start = reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress); + uint8_t * end = reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress + Ion::ExternalFlash::Device::FlashAddressSpaceSize); + + // Forward sequential access + if (accessType == 0) { + for (uint8_t * p = start; p <= end-sizeof(T); p++) { + volatile T * q = reinterpret_cast(p); + check(q, repeat); + } + } + + // Backward sequential access + if (accessType == 1) { + for (uint8_t * p = end - sizeof(T); p >= start; p--) { + volatile T * q = reinterpret_cast(p); + check(q, repeat); + } + } + + // Random access + if (accessType == 2) { + T * endT = reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress + Ion::ExternalFlash::Device::FlashAddressSpaceSize); + for (size_t i=0; i> (32 - Ion::ExternalFlash::Device::NumberOfAddressBitsInChip); + volatile T * q = reinterpret_cast(randomAddr + Ion::ExternalFlash::Device::QSPIBaseAddress); + if (q <= endT - 1) { + check(q, repeat); + } + } + } +} + +static size_t uint64ToString(uint64_t n, char buffer[]) { + size_t len = 0; + do { + buffer[len++] = (n % 10) + '0'; + } while ((n /= 10) > 0); + int i = 0; + int j = len - 1; + while (i < j) { + char c = buffer[i]; + buffer[i++] = buffer[j]; + buffer[j--] = c; + } + return len; +} + +static void printElapsedTime(uint64_t startTime) { + char buffer[7+26+2] = " time: "; + size_t len = uint64ToString((Ion::Timing::millis() - startTime)/1000, buffer+7); + buffer[7+len] = 's'; + buffer[7+len+1] = 0; + quiz_print(buffer); +} + +QUIZ_CASE(ion_ext_flash_erase) { +#if TEST_EXT_FLASH_REPROGRAM + uint64_t startTime = Ion::Timing::millis(); + Ion::ExternalFlash::Device::MassErase(); + printElapsedTime(startTime); +#endif +} + +QUIZ_CASE(ion_ext_flash_program) { +#if TEST_EXT_FLASH_REPROGRAM + // Program separately each page of the flash memory + uint64_t startTime = Ion::Timing::millis(); + for (int page = 0; page < (1<<15); page++) { + uint8_t buffer[256]; + for (int byte = 0; byte < 256; byte++) { + buffer[byte] = expected_value_at(reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress + page * 256 + byte)); + } + Ion::ExternalFlash::Device::WriteMemory(buffer, reinterpret_cast(page * 256), 256); + } + printElapsedTime(startTime); +#endif +} + +QUIZ_CASE(ion_extflash_read_byte_fwd) { + uint64_t startTime = Ion::Timing::millis(); + test(0, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_byte_bck) { + uint64_t startTime = Ion::Timing::millis(); + test(1, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_byte_rand) { + uint64_t startTime = Ion::Timing::millis(); + test(2, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_half_fwd) { + uint64_t startTime = Ion::Timing::millis(); + test(0, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_half_bck) { + uint64_t startTime = Ion::Timing::millis(); + test(1, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_half_rand) { + uint64_t startTime = Ion::Timing::millis(); + test(2, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_word_fwd) { + uint64_t startTime = Ion::Timing::millis(); + test(0, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_word_bck) { + uint64_t startTime = Ion::Timing::millis(); + test(1, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_word_rand) { + uint64_t startTime = Ion::Timing::millis(); + test(2, 1); + printElapsedTime(startTime); + Ion::Timing::msleep(3000); +} From 6555277995749dffb59999369f684c2bae13461d Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 10 Dec 2018 09:57:22 +0100 Subject: [PATCH 0028/1750] [ion/device] Define GPIO OSPEEDR register --- ion/src/device/regs/gpio.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ion/src/device/regs/gpio.h b/ion/src/device/regs/gpio.h index 2371ac0eb..bf7300295 100644 --- a/ion/src/device/regs/gpio.h +++ b/ion/src/device/regs/gpio.h @@ -27,6 +27,19 @@ public: void setType(int index, Type type) volatile { setBitRange(index, index, (uint32_t)type); } }; + class OSPEEDR : Register32 { + // Datasheet, page 120, table 58 + public: + enum class OutputSpeed { + Low = 0, + Medium = 1, + Fast = 2, + High = 3 + }; + OutputSpeed getOutputSpeed(int index) { return (OutputSpeed)getBitRange(2*index+1, 2*index); } + void setOutputSpeed(int index, OutputSpeed speed) volatile { setBitRange(2*index+1, 2*index, (uint32_t)speed); } + }; + class PUPDR : public Register32 { public: enum class Pull { @@ -72,6 +85,7 @@ public: constexpr operator int() const { return m_index; } REGS_REGISTER_AT(MODER, 0x00); REGS_REGISTER_AT(OTYPER, 0x04); + REGS_REGISTER_AT(OSPEEDR, 0x08); REGS_REGISTER_AT(PUPDR, 0x0C); REGS_REGISTER_AT(IDR, 0x10); REGS_REGISTER_AT(ODR, 0x14); From 534b6b781670f10c1b916b3d0a1fdd49027c8a71 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 17 Dec 2018 14:56:41 +0100 Subject: [PATCH 0029/1750] [ion/device] External flash: add constexpr ClockFrequencyDivisor --- ion/src/device/external_flash.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 5aa124d3b..7b754a2ec 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -64,6 +64,7 @@ public: }; static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; +static constexpr int ClockFrequencyDivisor = 256; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); @@ -111,7 +112,7 @@ static void set_as_memory_mapped() { * * It goes low, only if the low-power timeout counter is enabled. * (Flash memories tend to consume more when nCS is held low.) */ - constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : 4; + constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : (ClockFrequencyDivisor > 1) ? 4 : 6; send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, @@ -181,7 +182,7 @@ void initQSPI() { QUADSPI.DCR()->setFSIZE(NumberOfAddressBitsInChip - 1); QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal - QUADSPI.CR()->setPRESCALER(255); // Highest value. TODO: make it optimal + QUADSPI.CR()->setPRESCALER(ClockFrequencyDivisor - 1); // Highest value. TODO: make it optimal QUADSPI.CR()->setEN(true); } From 36c996641909e03f333cfe122d652fef74e2d032 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 17 Dec 2018 16:32:04 +0100 Subject: [PATCH 0030/1750] [ion/device] External flash: SetReadParameters (dummy cycles) according to clock frequency --- ion/src/device/external_flash.cpp | 12 ++++++++++++ ion/src/device/external_flash.h | 1 + 2 files changed, 13 insertions(+) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 7b754a2ec..d39e084f8 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -199,6 +199,18 @@ void initChip() { wait(QUADSPI::CCR::OperatingMode::Single); send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); wait(); + if (ClockFrequencyDivisor == 1) { + class ReadParameters : Register8 { + public: + /* Parameters sent along with SetReadParameters instruction in order + * to configure the number of dummy cycles for the QPI Read instructions. */ + using Register8::Register8; + REGS_BOOL_FIELD_W(P5, 1); + }; + ReadParameters readParameters(0); + readParameters.setP5(true); + send_write_command(Command::SetReadParameters, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&readParameters), sizeof(readParameters)); + } } set_as_memory_mapped(); } diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index ff4d9e7b8..f05bcf476 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -60,6 +60,7 @@ enum class Command : uint8_t { // Erase the whole chip or a 64-Kbyte block as being "1" ChipErase = 0xC7, Erase64KbyteBlock = 0xD8, + SetReadParameters = 0xC0 }; constexpr static uint32_t QSPIBaseAddress = 0x90000000; From de424ace3cb41c5704dcb696257fa1a14122086f Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 10 Dec 2018 10:00:26 +0100 Subject: [PATCH 0031/1750] [ion/device] Optimize external flash initQSPI --- ion/src/device/external_flash.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index d39e084f8..96f2538cf 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -64,7 +64,8 @@ public: }; static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; -static constexpr int ClockFrequencyDivisor = 256; +static constexpr int ClockFrequencyDivisor = 2; +static constexpr int ChipSelectHighTime = (ClockFrequencyDivisor == 1) ? 3 : (ClockFrequencyDivisor == 2) ? 2 : 1; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); @@ -170,21 +171,25 @@ void init() { void initGPIO() { for(const GPIOPin & g : QSPIPins) { + g.group().OSPEEDR()->setOutputSpeed(g.pin(), GPIO::OSPEEDR::OutputSpeed::High); g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), (g.pin() == 6 ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9)); } } void initQSPI() { - // Enable QUADSPI AHB3 peripheral clocks + // Enable QUADSPI AHB3 peripheral clock RCC.AHB3ENR()->setQSPIEN(true); // Configure controller for target device - QUADSPI.DCR()->setFSIZE(NumberOfAddressBitsInChip - 1); - - QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal - QUADSPI.CR()->setPRESCALER(ClockFrequencyDivisor - 1); // Highest value. TODO: make it optimal - - QUADSPI.CR()->setEN(true); + class QUADSPI::DCR dcr(0); + dcr.setFSIZE(NumberOfAddressBitsInChip - 1); + dcr.setCSHT(ChipSelectHighTime - 1); + dcr.setCKMODE(true); + QUADSPI.DCR()->set(dcr); + class QUADSPI::CR cr(0); + cr.setPRESCALER(ClockFrequencyDivisor - 1); + cr.setEN(true); + QUADSPI.CR()->set(cr); } void initChip() { From 4f5f3952d585ed3e06e3edc542de95dbd948c654 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 11 Dec 2018 14:38:18 +0100 Subject: [PATCH 0032/1750] [quiz] Fix quiz_print to fit screen height --- quiz/src/runner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quiz/src/runner.cpp b/quiz/src/runner.cpp index b8f0e4ba7..454fd51f0 100644 --- a/quiz/src/runner.cpp +++ b/quiz/src/runner.cpp @@ -17,7 +17,7 @@ void quiz_print(const char * message) { int line_height = font->glyphSize().height(); ctx->drawString(message, KDPoint(0, line_y), font); line_y += line_height; - if (line_y > Ion::Display::Height) { + if (line_y + line_height > Ion::Display::Height) { line_y = 0; // Clear screen maybe? } From 9489911eb33f1314e453bfaba08a963dcfb7e0fb Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 18 Dec 2018 10:22:23 +0100 Subject: [PATCH 0033/1750] [ion/device] External flash: include alternate bytes in Quad-SPI instructions --- ion/src/device/external_flash.cpp | 29 +++++++++++++++++++++++------ ion/src/device/regs/quadspi.h | 2 +- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 96f2538cf..ece42b00c 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -67,14 +67,17 @@ static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR static constexpr int ClockFrequencyDivisor = 2; static constexpr int ChipSelectHighTime = (ClockFrequencyDivisor == 1) ? 3 : (ClockFrequencyDivisor == 2) ? 2 : 1; -static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, 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, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, c, - reinterpret_cast(FlashAddressSpaceSize), 0, nullptr, 0 + reinterpret_cast(FlashAddressSpaceSize), + 0, 0, + 0, + nullptr, 0 ); } @@ -83,7 +86,10 @@ static inline void send_write_command(Command c, uint8_t * address, uint8_t * da QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, c, - address, 0, data, dataLength + address, + 0, 0, + 0, + data, dataLength ); } @@ -92,7 +98,10 @@ static inline void send_read_command(Command c, uint8_t * address, uint8_t * dat QUADSPI::CCR::FunctionalMode::IndirectRead, operatingMode, c, - address, 0, data, dataLength + address, + 0, 0, + 0, + data, dataLength ); } @@ -118,11 +127,14 @@ static void set_as_memory_mapped() { QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::FastRead, - reinterpret_cast(FlashAddressSpaceSize), FastReadDummyCycles, nullptr, 0 + reinterpret_cast(FlashAddressSpaceSize), + 0, 0, + FastReadDummyCycles, + nullptr, 0 ); } -void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { +void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { class QUADSPI::CCR ccr(0); ccr.setFMODE(functionalMode); if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { @@ -132,6 +144,11 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); } ccr.setDCYC(dummyCycles); + if (numberOfAltBytes > 0) { + ccr.setABMODE(operatingMode); + ccr.setABSIZE(static_cast(numberOfAltBytes - 1)); + QUADSPI.ABR()->set(altBytes); + } if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); diff --git a/ion/src/device/regs/quadspi.h b/ion/src/device/regs/quadspi.h index ed46c3b6b..18d6c81f0 100644 --- a/ion/src/device/regs/quadspi.h +++ b/ion/src/device/regs/quadspi.h @@ -76,7 +76,7 @@ public: class AR : public Register32 { // Address register }; - class ABR : Register32 { // Alternate bytes register + class ABR : public Register32 { // Alternate bytes register }; class DR : public Register8 { // Data register From 85e08f500d258ac1ffde8e3dccc312adf4c3005f Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 18 Dec 2018 11:45:41 +0100 Subject: [PATCH 0034/1750] [ion/device] External flash: Send instruction only once in memory-mapped mode --- ion/src/device/external_flash.cpp | 28 +++++++++++++++++++++++++--- ion/src/device/external_flash.h | 1 + 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index ece42b00c..5de68b64c 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -126,14 +126,28 @@ static void set_as_memory_mapped() { send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, - Command::FastRead, + Command::FastReadQuadIO, reinterpret_cast(FlashAddressSpaceSize), - 0, 0, - FastReadDummyCycles, + 0xA0, 1, + 2, //FIXME 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, + DefaultOperatingMode, + Command::FastReadQuadIO, + 0, + ~(0xA0), 1, + 2, //FIXME + &dummyData, 1 + ); +} + void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { class QUADSPI::CCR ccr(0); ccr.setFMODE(functionalMode); @@ -155,6 +169,11 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR } ccr.setIMODE(operatingMode); ccr.setINSTRUCTION(static_cast(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(FlashAddressSpaceSize)) { QUADSPI.AR()->set(reinterpret_cast(address)); @@ -246,6 +265,7 @@ int SectorAtAddress(uint32_t address) { } void MassErase() { + unset_memory_mapped_mode(); send_command(Command::WriteEnable); wait(); send_command(Command::ChipErase); @@ -255,6 +275,7 @@ void MassErase() { void EraseSector(int i) { assert(i >= 0 && i < NumberOfSectors); + unset_memory_mapped_mode(); send_command(Command::WriteEnable); wait(); send_write_command(Command::Erase64KbyteBlock, reinterpret_cast(i << NumberOfAddressBitsIn64KbyteBlock), nullptr, 0); @@ -263,6 +284,7 @@ void EraseSector(int i) { } void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { + 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. diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index f05bcf476..bde2678f8 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -53,6 +53,7 @@ enum class Command : uint8_t { WriteEnable = 0x06, ReadData = 0x03, FastRead = 0x0B, + FastReadQuadIO = 0xEB, // Program previously erased memory areas as being "0" PageProgram = 0x02, QuadPageProgram = 0x33, From fadee535d5ba50a5e59f05b594bf77c993fe3394 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:36:33 +0100 Subject: [PATCH 0035/1750] [build] Allow proper mcpu/mfpu flags for different targets --- build/platform.device.mak | 2 +- build/platform.f730.mak | 1 + build/toolchain.arm-gcc-m4f.mak | 3 +++ build/toolchain.arm-gcc-m7f.mak | 3 +++ build/toolchain.arm-gcc.mak | 1 - 5 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 build/toolchain.arm-gcc-m4f.mak create mode 100644 build/toolchain.arm-gcc-m7f.mak diff --git a/build/platform.device.mak b/build/platform.device.mak index 4c6be1d4c..d435f6a06 100644 --- a/build/platform.device.mak +++ b/build/platform.device.mak @@ -1,4 +1,4 @@ -TOOLCHAIN ?= arm-gcc +TOOLCHAIN ?= arm-gcc-m4f USE_LIBA = 1 EXE = elf EPSILON_BOOT_PROMPT = update diff --git a/build/platform.f730.mak b/build/platform.f730.mak index 1c9bcd804..76511cbcd 100644 --- a/build/platform.f730.mak +++ b/build/platform.f730.mak @@ -1 +1,2 @@ +TOOLCHAIN ?= arm-gcc-m7f include build/platform.device.mak diff --git a/build/toolchain.arm-gcc-m4f.mak b/build/toolchain.arm-gcc-m4f.mak new file mode 100644 index 000000000..5f5ae4056 --- /dev/null +++ b/build/toolchain.arm-gcc-m4f.mak @@ -0,0 +1,3 @@ +include build/toolchain.arm-gcc.mak +SFLAGS += -mthumb -march=armv7e-m -mfloat-abi=hard +SFLAGS += -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 diff --git a/build/toolchain.arm-gcc-m7f.mak b/build/toolchain.arm-gcc-m7f.mak new file mode 100644 index 000000000..b23b86b9d --- /dev/null +++ b/build/toolchain.arm-gcc-m7f.mak @@ -0,0 +1,3 @@ +include build/toolchain.arm-gcc.mak +SFLAGS += -mthumb -march=armv7e-m -mfloat-abi=hard +SFLAGS += -mcpu=cortex-m7 -mfpu=fpv5-sp-d16 diff --git a/build/toolchain.arm-gcc.mak b/build/toolchain.arm-gcc.mak index 14995b2d1..09e057b50 100644 --- a/build/toolchain.arm-gcc.mak +++ b/build/toolchain.arm-gcc.mak @@ -25,5 +25,4 @@ SFLAGS += -fdata-sections -ffunction-sections LDFLAGS += -Wl,--gc-sections endif -SFLAGS += -mthumb -march=armv7e-m -mfloat-abi=hard -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 LDFLAGS += $(SFLAGS) -lgcc -Wl,-T,$(LDSCRIPT) From 4807308980ad6b8e6f2987fb7da994f87a8b4043 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:38:13 +0100 Subject: [PATCH 0036/1750] [ion/f730] Don't use DTCM for now --- ion/src/f730/boot/flash.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/boot/flash.ld b/ion/src/f730/boot/flash.ld index e5ddcc9e4..fbf7d5138 100644 --- a/ion/src/f730/boot/flash.ld +++ b/ion/src/f730/boot/flash.ld @@ -10,7 +10,7 @@ * be stored in Flash. */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K - SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K + SRAM (rw) : ORIGIN = 0x20010000, LENGTH = 176K } STACK_SIZE = 32K; From 0ceabe0be7006833992c636bb8275aed4b801847 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:39:23 +0100 Subject: [PATCH 0037/1750] [ion/f730] Enable HSE and PLL for a HCLK of 192 MHz --- ion/src/f730/device.cpp | 21 +++++++++++---------- ion/src/f730/regs/rcc.h | 1 + 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 7cc714002..e438f7ab2 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -229,16 +229,16 @@ void shutdownPeripherals(bool keepLEDAwake) { } void initClocks() { -#if 0 /* System clock * Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */ /* 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 90MHz the flash expects 3 WS. */ - FLASH.ACR()->setLATENCY(3); + * The spec tells us that at 2.8V and over 210MHz the flash expects 7 WS. */ + FLASH.ACR()->setLATENCY(7); +#if 0 /* Enable prefetching flash instructions */ /* Fetching instructions increases slightly the power consumption but the * increase is negligible compared to the screen consumption. */ @@ -247,6 +247,7 @@ void initClocks() { /* Set flash instruction and data cache */ FLASH.ACR()->setDCEN(true); FLASH.ACR()->setICEN(true); +#endif /* 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 @@ -260,16 +261,17 @@ void initClocks() { /* Given the crystal used on our device, the HSE will oscillate at 25 MHz. By * piping it through a phase-locked loop (PLL) we can derive other frequencies - * for use in different parts of the system. Combining the default PLL values - * with a PLLM of 25 and a PLLQ of 4 yields both a 96 MHz frequency for SYSCLK - * and the required 48 MHz USB clock. */ + * for use in different parts of the system. */ // Configure the PLL ratios and use HSE as a PLL input RCC.PLLCFGR()->setPLLM(25); - RCC.PLLCFGR()->setPLLQ(4); + RCC.PLLCFGR()->setPLLN(384); + RCC.PLLCFGR()->setPLLQ(8); RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); - // 96 MHz is too fast for APB1. Divide it by two to reach 48 MHz - RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy2); + // 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz + RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy4); + // 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz + RCC.CFGR()->setPPRE2(RCC::CFGR::APBPrescaler::AHBDividedBy2); /* If you want to considerably slow down the whole machine uniformely, which * can be very useful to diagnose performance issues, just uncomment the line @@ -289,7 +291,6 @@ void initClocks() { // Now that we don't need use it anymore, turn the HSI off RCC.CR()->setHSION(false); -#endif // Peripheral clocks diff --git a/ion/src/f730/regs/rcc.h b/ion/src/f730/regs/rcc.h index 83841b007..3306c0b07 100644 --- a/ion/src/f730/regs/rcc.h +++ b/ion/src/f730/regs/rcc.h @@ -57,6 +57,7 @@ public: AHBDividedBy16 = 7 }; void setPPRE1(APBPrescaler r) volatile { setBitRange(12, 10, (uint32_t)r); } + void setPPRE2(APBPrescaler r) volatile { setBitRange(15, 13, (uint32_t)r); } }; class AHB1ENR : public Register32 { From 30849b0611365d24b0e24ac67b229b9674d15c62 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:39:59 +0100 Subject: [PATCH 0038/1750] [ion/f730] Enable USB controller --- ion/src/f730/device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index e438f7ab2..009883032 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -201,8 +201,8 @@ void initPeripherals() { Keyboard::Device::init(); LED::Device::init(); Battery::Device::init(); - return; // FIXME, obviously! USB::Device::init(); + return; // FIXME, obviously! #if USE_SD_CARD SDCard::Device::init(); #endif From b5ef99384a65d9aed329d59715be9f77429a0f19 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:40:19 +0100 Subject: [PATCH 0039/1750] [ion/f730] Double all FMC access times to account for HCLK from 96 to 192 MHz --- ion/src/f730/display.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ion/src/f730/display.cpp b/ion/src/f730/display.cpp index 1fa38012d..ff349cee5 100644 --- a/ion/src/f730/display.cpp +++ b/ion/src/f730/display.cpp @@ -228,17 +228,17 @@ void initFMC() { */ // Read timing from the LCD - FMC.BTR(FMCMemoryBank)->setADDSET(2); + FMC.BTR(FMCMemoryBank)->setADDSET(4); FMC.BTR(FMCMemoryBank)->setADDHLD(0); - FMC.BTR(FMCMemoryBank)->setDATAST(36); - FMC.BTR(FMCMemoryBank)->setBUSTURN(10); + FMC.BTR(FMCMemoryBank)->setDATAST(72); + FMC.BTR(FMCMemoryBank)->setBUSTURN(20); FMC.BTR(FMCMemoryBank)->setACCMOD(FMC::BTR::ACCMOD::A); // Write timings for the LCD - FMC.BWTR(FMCMemoryBank)->setADDSET(2); + FMC.BWTR(FMCMemoryBank)->setADDSET(4); FMC.BWTR(FMCMemoryBank)->setADDHLD(0); - FMC.BWTR(FMCMemoryBank)->setDATAST(3); - FMC.BWTR(FMCMemoryBank)->setBUSTURN(3); + FMC.BWTR(FMCMemoryBank)->setDATAST(6); + FMC.BWTR(FMCMemoryBank)->setBUSTURN(6); FMC.BWTR(FMCMemoryBank)->setACCMOD(FMC::BWTR::ACCMOD::A); } From 73722b71ca5cffdac89fdf7695b1b588d3065653 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:41:45 +0100 Subject: [PATCH 0040/1750] [ion/f730] WIP: Temporarily increase delays for msleep/usleep --- ion/src/f730/timing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/f730/timing.cpp b/ion/src/f730/timing.cpp index c22d425f7..dc316fcb0 100644 --- a/ion/src/f730/timing.cpp +++ b/ion/src/f730/timing.cpp @@ -8,12 +8,12 @@ namespace Timing { * precision, we could use the controller cycle counter (Systick). */ void msleep(uint32_t ms) { - for (volatile uint32_t i=0; i<8852*ms; i++) { + for (volatile uint32_t i=0; i<28852*ms; i++) { __asm volatile("nop"); } } void usleep(uint32_t us) { - for (volatile uint32_t i=0; i<9*us; i++) { + for (volatile uint32_t i=0; i<28*us; i++) { __asm volatile("nop"); } } From d413fd0816c654218ccf94dbda95decfc965c3c6 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 10:14:55 +0100 Subject: [PATCH 0041/1750] [ion/f730] Fix RCC resst values --- ion/src/f730/device.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 009883032..d53097d9f 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -297,7 +297,7 @@ void initClocks() { // 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(0); // Reset value + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value ahb1enr.setGPIOAEN(true); ahb1enr.setGPIOBEN(true); ahb1enr.setGPIOCEN(true); @@ -329,12 +329,12 @@ void initClocks() { void shutdownClocks(bool keepLEDAwake) { // APB2 bus - RCC.APB2ENR()->set(0x00008000); // Reset value + RCC.APB2ENR()->set(0); // Reset value // APB1 - class RCC::APB1ENR apb1enr(0x00000400); // Reset value + class RCC::APB1ENR apb1enr(0); // Reset value // AHB1 bus - class RCC::AHB1ENR ahb1enr(0); // Reset value + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value if (keepLEDAwake) { apb1enr.setTIM3EN(true); ahb1enr.setGPIOBEN(true); From 3d018045ddbedc50aa6617b0abb57cde5763737c Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 10:15:37 +0100 Subject: [PATCH 0042/1750] [ion/f730] Restore use DTCM --- ion/src/f730/boot/flash.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/boot/flash.ld b/ion/src/f730/boot/flash.ld index fbf7d5138..e5ddcc9e4 100644 --- a/ion/src/f730/boot/flash.ld +++ b/ion/src/f730/boot/flash.ld @@ -10,7 +10,7 @@ * be stored in Flash. */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K - SRAM (rw) : ORIGIN = 0x20010000, LENGTH = 176K + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K } STACK_SIZE = 32K; From 08d9416ad8ffe779ac54f9d689530c04274f5838 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 10:16:43 +0100 Subject: [PATCH 0043/1750] [ion/f730] WIP: Rename SDIOEN to SDMMCEN, don't init it anymore --- ion/src/f730/device.cpp | 3 --- ion/src/f730/regs/rcc.h | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index d53097d9f..6ab3e8707 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -321,9 +321,6 @@ void initClocks() { class RCC::APB2ENR apb2enr(0); // Reset value apb2enr.setADC1EN(true); apb2enr.setSYSCFGEN(true); -#if USE_SD_CARD - apb2enr.setSDIOEN(true); -#endif RCC.APB2ENR()->set(apb2enr); } diff --git a/ion/src/f730/regs/rcc.h b/ion/src/f730/regs/rcc.h index 3306c0b07..576dea281 100644 --- a/ion/src/f730/regs/rcc.h +++ b/ion/src/f730/regs/rcc.h @@ -103,7 +103,7 @@ public: REGS_BOOL_FIELD(TIM1EN, 0); REGS_BOOL_FIELD(USART1EN, 4); REGS_BOOL_FIELD(ADC1EN, 8); - REGS_BOOL_FIELD(SDIOEN, 11); + REGS_BOOL_FIELD(SDMMC1EN, 11); REGS_BOOL_FIELD(SPI1EN, 12); REGS_BOOL_FIELD(SYSCFGEN, 14); }; From 03524471856cd055f6480e6b6a7c75cdf4b45d0f Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 15:19:36 +0100 Subject: [PATCH 0044/1750] [ion/f730] Fix battery GPIO --- ion/src/f730/battery.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ion/src/f730/battery.h b/ion/src/f730/battery.h index ef558dc86..7299aa8d4 100644 --- a/ion/src/f730/battery.h +++ b/ion/src/f730/battery.h @@ -9,8 +9,8 @@ namespace Device { /* Pin | Role | Mode | Function * -----+-------------------+-----------------------+---------- - * PA0 | BAT_CHRG | Input, pulled up | Low = charging, high = full - * PA1 | VBAT_SNS | Analog | ADC1_1 + * PE3 | BAT_CHRG | Input, pulled up | Low = charging, high = full + * PB1 | VBAT_SNS | Analog | ADC1_1 */ void init(); @@ -18,10 +18,10 @@ void shutdown(); void initGPIO(); void initADC(); -constexpr GPIO ChargingGPIO = GPIOA; -constexpr uint8_t ChargingPin = 0; +constexpr GPIO ChargingGPIO = GPIOE; +constexpr uint8_t ChargingPin = 3; -constexpr GPIO ADCGPIO = GPIOA; +constexpr GPIO ADCGPIO = GPIOB; constexpr uint8_t ADCPin = 1; constexpr uint8_t ADCChannel = 1; From db053db6091b56c226498d36d926cbe5e53286bc Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 15:22:15 +0100 Subject: [PATCH 0045/1750] [ion/keyboard] Do not use magic numbers --- ion/src/f730/keyboard.cpp | 2 +- ion/src/f730/keyboard.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ion/src/f730/keyboard.cpp b/ion/src/f730/keyboard.cpp index 0ec131551..3fc9f9948 100644 --- a/ion/src/f730/keyboard.cpp +++ b/ion/src/f730/keyboard.cpp @@ -59,7 +59,7 @@ State scan() { /* The key is down if the input is brought low by the output. In other * words, we want to return true if the input is low (false). So we need to * append 6 bits of (not columns) to state. */ - state = (state << 6) | (~columns & 0x3F); + state = (state << Device::numberOfColumns) | (~columns & 0x3F); } /* Last but not least, keys number 8, 9, 10, 11, 35, 41, 47 and 53 are not diff --git a/ion/src/f730/keyboard.h b/ion/src/f730/keyboard.h index d00ef5632..d1537bc5a 100644 --- a/ion/src/f730/keyboard.h +++ b/ion/src/f730/keyboard.h @@ -52,8 +52,8 @@ inline void activateRow(uint8_t row) { * the others to 1. */ uint16_t rowState = ~(1<setBitRange(9, 0, rowState); + // TODO: Assert pin numbers are sequentials and dynamically find 8 and 0 + Device::RowGPIO.ODR()->setBitRange(numberOfRows-1, 0, rowState); // TODO: 100 us seems to work, but wasn't really calculated Timing::usleep(100); From c72b604aa8c1083413e88d6c5c8f58faad6394f9 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 15:32:10 +0100 Subject: [PATCH 0046/1750] [ion/f730] Fix includes for the right PLATFORM --- ion/src/f730/backlight.h | 1 + ion/src/f730/bench/command/adc.cpp | 2 +- ion/src/f730/bench/command/backlight.cpp | 2 +- ion/src/f730/bench/command/display.cpp | 2 +- ion/src/f730/bench/command/led.cpp | 2 +- ion/src/f730/bench/command/print.cpp | 2 +- ion/src/f730/bench/command/vblank.cpp | 2 +- ion/src/f730/usb/calculator.cpp | 6 +++--- ion/src/f730/usb/dfu_interface.cpp | 2 +- ion/src/f730/usb/dfu_relocated.cpp | 6 +++--- ion/src/f730/usb/stack/device.cpp | 2 +- ion/src/f730/usb/stack/endpoint0.cpp | 2 +- 12 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ion/src/f730/backlight.h b/ion/src/f730/backlight.h index 026fcd537..527bc1344 100644 --- a/ion/src/f730/backlight.h +++ b/ion/src/f730/backlight.h @@ -2,6 +2,7 @@ #define ION_DEVICE_BACKLIGHT_H #include +#include "regs/regs.h" namespace Ion { namespace Backlight { diff --git a/ion/src/f730/bench/command/adc.cpp b/ion/src/f730/bench/command/adc.cpp index 8c3e677a4..26f86c0a9 100644 --- a/ion/src/f730/bench/command/adc.cpp +++ b/ion/src/f730/bench/command/adc.cpp @@ -1,7 +1,7 @@ #include "command.h" #include #include -#include +#include namespace Ion { namespace Device { diff --git a/ion/src/f730/bench/command/backlight.cpp b/ion/src/f730/bench/command/backlight.cpp index b4a7f2d9f..86e630c55 100644 --- a/ion/src/f730/bench/command/backlight.cpp +++ b/ion/src/f730/bench/command/backlight.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include namespace Ion { namespace Device { diff --git a/ion/src/f730/bench/command/display.cpp b/ion/src/f730/bench/command/display.cpp index 553e6e54a..61b74c322 100644 --- a/ion/src/f730/bench/command/display.cpp +++ b/ion/src/f730/bench/command/display.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include #include namespace Ion { diff --git a/ion/src/f730/bench/command/led.cpp b/ion/src/f730/bench/command/led.cpp index 43f7fcac1..4d6f80e81 100644 --- a/ion/src/f730/bench/command/led.cpp +++ b/ion/src/f730/bench/command/led.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include namespace Ion { namespace Device { diff --git a/ion/src/f730/bench/command/print.cpp b/ion/src/f730/bench/command/print.cpp index 8b5eb3bfc..fa73e6234 100644 --- a/ion/src/f730/bench/command/print.cpp +++ b/ion/src/f730/bench/command/print.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include #include namespace Ion { diff --git a/ion/src/f730/bench/command/vblank.cpp b/ion/src/f730/bench/command/vblank.cpp index 3c6971061..d292cab26 100644 --- a/ion/src/f730/bench/command/vblank.cpp +++ b/ion/src/f730/bench/command/vblank.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include namespace Ion { namespace Device { diff --git a/ion/src/f730/usb/calculator.cpp b/ion/src/f730/usb/calculator.cpp index fc23c1512..47150f053 100644 --- a/ion/src/f730/usb/calculator.cpp +++ b/ion/src/f730/usb/calculator.cpp @@ -1,8 +1,8 @@ #include "calculator.h" #include -#include -#include -#include +#include +#include +#include namespace Ion { namespace USB { diff --git a/ion/src/f730/usb/dfu_interface.cpp b/ion/src/f730/usb/dfu_interface.cpp index 029cce703..395576cc6 100644 --- a/ion/src/f730/usb/dfu_interface.cpp +++ b/ion/src/f730/usb/dfu_interface.cpp @@ -1,6 +1,6 @@ #include "dfu_interface.h" #include -#include +#include namespace Ion { namespace USB { diff --git a/ion/src/f730/usb/dfu_relocated.cpp b/ion/src/f730/usb/dfu_relocated.cpp index 41566e2a0..6f4191ea8 100644 --- a/ion/src/f730/usb/dfu_relocated.cpp +++ b/ion/src/f730/usb/dfu_relocated.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include extern char _stack_end; extern char _dfu_bootloader_flash_start; @@ -60,11 +60,11 @@ void DFU() { /* To have the right debug symbols for the reallocated code, break here and: * - Get the address of the new .text section - * In a terminal: arm-none-eabi-readelf -a ion/src/device/usb/dfu.elf + * In a terminal: arm-none-eabi-readelf -a ion/src/f730/usb/dfu.elf * - Delete the current symbol table * symbol-file * - Add the new symbol table, with the address of the new .text section - * add-symbol-file ion/src/device/usb/dfu.elf 0x20038000 + * add-symbol-file ion/src/f730/usb/dfu.elf 0x20038000 */ dfu_bootloader_entry(true); diff --git a/ion/src/f730/usb/stack/device.cpp b/ion/src/f730/usb/stack/device.cpp index 27bf6d5de..cd2d7fb5b 100644 --- a/ion/src/f730/usb/stack/device.cpp +++ b/ion/src/f730/usb/stack/device.cpp @@ -1,5 +1,5 @@ #include "device.h" -#include +#include namespace Ion { namespace USB { diff --git a/ion/src/f730/usb/stack/endpoint0.cpp b/ion/src/f730/usb/stack/endpoint0.cpp index df41e4bd3..957a577f2 100644 --- a/ion/src/f730/usb/stack/endpoint0.cpp +++ b/ion/src/f730/usb/stack/endpoint0.cpp @@ -1,6 +1,6 @@ #include "endpoint0.h" #include -#include +#include #include "device.h" #include "interface.h" #include "request_recipient.h" From 440d0b47f6f4da4a15de1d3daf10602c41eb8fd8 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 16:53:47 +0100 Subject: [PATCH 0047/1750] [ion/f730] WIP: Adapt linker script and DEBUG=1 to fit in flash --- build/defaults.mak | 2 +- ion/src/f730/boot/flash.ld | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/defaults.mak b/build/defaults.mak index e7b364888..e90a2b158 100644 --- a/build/defaults.mak +++ b/build/defaults.mak @@ -8,7 +8,7 @@ CXXFLAGS = -std=c++11 -fno-exceptions -fno-rtti -fno-threadsafe-statics # Flags - Optimizations ifeq ($(DEBUG),1) -SFLAGS = -O0 -g +SFLAGS = -Og -g else SFLAGS = -Os endif diff --git a/ion/src/f730/boot/flash.ld b/ion/src/f730/boot/flash.ld index e5ddcc9e4..a6179f9f4 100644 --- a/ion/src/f730/boot/flash.ld +++ b/ion/src/f730/boot/flash.ld @@ -9,7 +9,7 @@ * This will let us use shortcuts such as ">FLASH" to ask for a given section to * be stored in Flash. */ MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K } From e4c64e31ee8da3955c780323c0fd7c0cc942677d Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 16:55:55 +0100 Subject: [PATCH 0048/1750] [apps] WIP: Disable a DUMMY_MAIN option --- apps/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/main.cpp b/apps/main.cpp index 1be40ff7d..806c63d13 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -2,7 +2,7 @@ #include "global_preferences.h" #include -#define DUMMY_MAIN 1 +#define DUMMY_MAIN 0 #if DUMMY_MAIN void ion_main(int argc, char * argv[]) { From d5ff7b118a662703dce790b03d14c756a17d5a09 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 16:56:52 +0100 Subject: [PATCH 0049/1750] [escher] WIP: Temporarily comment Ion::Display::waitForVBlank() --- escher/src/window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escher/src/window.cpp b/escher/src/window.cpp index 618b6fba4..1b94c65c9 100644 --- a/escher/src/window.cpp +++ b/escher/src/window.cpp @@ -13,7 +13,7 @@ void Window::redraw(bool force) { if (force) { markRectAsDirty(bounds()); } - Ion::Display::waitForVBlank(); + //Ion::Display::waitForVBlank(); View::redraw(bounds()); } From 1351964f894f7bd881217696713b7fab52dac1e7 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 16:58:23 +0100 Subject: [PATCH 0050/1750] [ion/f730/battery] WIP: Temporary dummy Charge::FULL level --- ion/src/f730/battery.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/src/f730/battery.cpp b/ion/src/f730/battery.cpp index f3ed16364..212ab7214 100644 --- a/ion/src/f730/battery.cpp +++ b/ion/src/f730/battery.cpp @@ -18,6 +18,7 @@ bool isCharging() { } Charge level() { + return Charge::FULL; if (voltage() < 3.2f) { return Charge::EMPTY; } From f3073b3a6b6fcc224d7138558c139c417e8b20f3 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 17:00:50 +0100 Subject: [PATCH 0051/1750] [ion/f730] WIP: Temporary dummy serial number --- ion/src/f730/device.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 6ab3e8707..145b3e68f 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -56,9 +56,10 @@ uint32_t Ion::random() { } void Ion::Device::copySerialNumber(char * buffer) { - const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; - Base64::encode(rawUniqueID, 12, buffer); - buffer[SerialNumberLength] = 0; + //const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; + //Base64::encode(rawUniqueID, 12, buffer); + buffer[0]='A'; + buffer[1] = 0; } const char * Ion::serialNumber() { From b547f8bfd9aa8f7a56eca3508a8f74a38b2dfe4d Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:01:28 +0100 Subject: [PATCH 0052/1750] [ion] Initial add of the f730 folder --- ion/src/f730/Makefile | 51 +++ ion/src/f730/backlight.cpp | 85 ++++ ion/src/f730/backlight.h | 28 ++ ion/src/f730/base64.cpp | 48 +++ ion/src/f730/base64.h | 5 + ion/src/f730/battery.cpp | 88 ++++ ion/src/f730/battery.h | 35 ++ ion/src/f730/bench/Makefile | 21 + ion/src/f730/bench/bench.cpp | 46 +++ ion/src/f730/bench/bench.h | 14 + ion/src/f730/bench/command/adc.cpp | 27 ++ ion/src/f730/bench/command/backlight.cpp | 34 ++ ion/src/f730/bench/command/charge.cpp | 24 ++ ion/src/f730/bench/command/command.cpp | 47 +++ ion/src/f730/bench/command/command.h | 42 ++ ion/src/f730/bench/command/display.cpp | 74 ++++ ion/src/f730/bench/command/exit.cpp | 19 + ion/src/f730/bench/command/keyboard.cpp | 26 ++ ion/src/f730/bench/command/led.cpp | 35 ++ ion/src/f730/bench/command/mcu_serial.cpp | 23 ++ ion/src/f730/bench/command/ping.cpp | 19 + ion/src/f730/bench/command/print.cpp | 30 ++ ion/src/f730/bench/command/suspend.cpp | 22 + ion/src/f730/bench/command/vblank.cpp | 26 ++ ion/src/f730/bench/command_handler.cpp | 44 ++ ion/src/f730/bench/command_handler.h | 27 ++ ion/src/f730/bench/command_list.cpp | 22 + ion/src/f730/bench/command_list.h | 22 + ion/src/f730/boot/Makefile | 2 + ion/src/f730/boot/flash.ld | 112 ++++++ ion/src/f730/boot/isr.c | 129 ++++++ ion/src/f730/boot/isr.h | 16 + ion/src/f730/boot/rt0.cpp | 97 +++++ ion/src/f730/console.cpp | 76 ++++ ion/src/f730/console.h | 30 ++ ion/src/f730/device.cpp | 325 +++++++++++++++ ion/src/f730/device.h | 36 ++ ion/src/f730/display.cpp | 376 ++++++++++++++++++ ion/src/f730/display.h | 110 +++++ ion/src/f730/events.cpp | 93 +++++ ion/src/f730/flash.cpp | 233 +++++++++++ ion/src/f730/flash.h | 28 ++ ion/src/f730/keyboard.cpp | 112 ++++++ ion/src/f730/keyboard.h | 70 ++++ ion/src/f730/led.cpp | 133 +++++++ ion/src/f730/led.h | 48 +++ ion/src/f730/log.cpp | 15 + ion/src/f730/power.cpp | 69 ++++ ion/src/f730/regs/adc.h | 73 ++++ ion/src/f730/regs/cm4.h | 81 ++++ ion/src/f730/regs/crc.h | 27 ++ ion/src/f730/regs/dma.h | 63 +++ ion/src/f730/regs/exti.h | 34 ++ ion/src/f730/regs/flash.h | 56 +++ ion/src/f730/regs/fsmc.h | 79 ++++ ion/src/f730/regs/gpio.h | 104 +++++ ion/src/f730/regs/itm.h | 32 ++ ion/src/f730/regs/mpu.h | 71 ++++ ion/src/f730/regs/nvic.h | 37 ++ ion/src/f730/regs/otg.h | 195 +++++++++ ion/src/f730/regs/pwr.h | 25 ++ ion/src/f730/regs/rcc.h | 149 +++++++ ion/src/f730/regs/register.h | 61 +++ ion/src/f730/regs/regs.h | 25 ++ ion/src/f730/regs/rng.h | 33 ++ ion/src/f730/regs/sdio.h | 112 ++++++ ion/src/f730/regs/spi.h | 45 +++ ion/src/f730/regs/syscfg.h | 44 ++ ion/src/f730/regs/tim.h | 100 +++++ ion/src/f730/regs/usart.h | 50 +++ ion/src/f730/sd_card.cpp | 95 +++++ ion/src/f730/sd_card.h | 33 ++ ion/src/f730/stack.cpp | 10 + ion/src/f730/swd.cpp | 24 ++ ion/src/f730/swd.h | 28 ++ ion/src/f730/timing.cpp | 36 ++ ion/src/f730/timing.h | 16 + ion/src/f730/usb.cpp | 187 +++++++++ ion/src/f730/usb.h | 34 ++ ion/src/f730/usb/Makefile | 67 ++++ ion/src/f730/usb/boot.cpp | 2 + ion/src/f730/usb/calculator.cpp | 94 +++++ ion/src/f730/usb/calculator.h | 166 ++++++++ ion/src/f730/usb/dfu.ld | 50 +++ ion/src/f730/usb/dfu_interface.cpp | 278 +++++++++++++ ion/src/f730/usb/dfu_interface.h | 172 ++++++++ ion/src/f730/usb/dfu_relocated.cpp | 80 ++++ ion/src/f730/usb/dfu_xip.cpp | 12 + ion/src/f730/usb/flasher.cpp | 13 + ion/src/f730/usb/flasher.ld | 77 ++++ .../usb/stack/descriptor/bos_descriptor.cpp | 22 + .../usb/stack/descriptor/bos_descriptor.h | 36 ++ .../descriptor/configuration_descriptor.cpp | 26 ++ .../descriptor/configuration_descriptor.h | 48 +++ .../f730/usb/stack/descriptor/descriptor.cpp | 15 + .../f730/usb/stack/descriptor/descriptor.h | 32 ++ .../device_capability_descriptor.cpp | 18 + .../descriptor/device_capability_descriptor.h | 31 ++ .../stack/descriptor/device_descriptor.cpp | 29 ++ .../usb/stack/descriptor/device_descriptor.h | 62 +++ .../descriptor/dfu_functional_descriptor.cpp | 21 + .../descriptor/dfu_functional_descriptor.h | 38 ++ .../extended_compat_id_descriptor.cpp | 61 +++ .../extended_compat_id_descriptor.h | 43 ++ .../stack/descriptor/interface_descriptor.cpp | 27 ++ .../stack/descriptor/interface_descriptor.h | 55 +++ .../language_id_string_descriptor.cpp | 19 + .../language_id_string_descriptor.h | 25 ++ .../microsoft_os_string_descriptor.cpp | 19 + .../microsoft_os_string_descriptor.h | 30 ++ .../platform_device_capability_descriptor.cpp | 21 + .../platform_device_capability_descriptor.h | 47 +++ .../stack/descriptor/string_descriptor.cpp | 25 ++ .../usb/stack/descriptor/string_descriptor.h | 28 ++ .../usb/stack/descriptor/url_descriptor.cpp | 25 ++ .../usb/stack/descriptor/url_descriptor.h | 36 ++ .../descriptor/webusb_platform_descriptor.cpp | 22 + .../descriptor/webusb_platform_descriptor.h | 37 ++ ion/src/f730/usb/stack/device.cpp | 156 ++++++++ ion/src/f730/usb/stack/device.h | 66 +++ ion/src/f730/usb/stack/endpoint0.cpp | 344 ++++++++++++++++ ion/src/f730/usb/stack/endpoint0.h | 79 ++++ ion/src/f730/usb/stack/interface.cpp | 66 +++ ion/src/f730/usb/stack/interface.h | 42 ++ ion/src/f730/usb/stack/request_recipient.cpp | 32 ++ ion/src/f730/usb/stack/request_recipient.h | 29 ++ ion/src/f730/usb/stack/setup_packet.cpp | 38 ++ ion/src/f730/usb/stack/setup_packet.h | 65 +++ ion/src/f730/usb/stack/streamable.cpp | 15 + ion/src/f730/usb/stack/streamable.h | 44 ++ ion/src/f730/wakeup.cpp | 69 ++++ ion/src/f730/wakeup.h | 41 ++ 132 files changed, 8048 insertions(+) create mode 100644 ion/src/f730/Makefile create mode 100644 ion/src/f730/backlight.cpp create mode 100644 ion/src/f730/backlight.h create mode 100644 ion/src/f730/base64.cpp create mode 100644 ion/src/f730/base64.h create mode 100644 ion/src/f730/battery.cpp create mode 100644 ion/src/f730/battery.h create mode 100644 ion/src/f730/bench/Makefile create mode 100644 ion/src/f730/bench/bench.cpp create mode 100644 ion/src/f730/bench/bench.h create mode 100644 ion/src/f730/bench/command/adc.cpp create mode 100644 ion/src/f730/bench/command/backlight.cpp create mode 100644 ion/src/f730/bench/command/charge.cpp create mode 100644 ion/src/f730/bench/command/command.cpp create mode 100644 ion/src/f730/bench/command/command.h create mode 100644 ion/src/f730/bench/command/display.cpp create mode 100644 ion/src/f730/bench/command/exit.cpp create mode 100644 ion/src/f730/bench/command/keyboard.cpp create mode 100644 ion/src/f730/bench/command/led.cpp create mode 100644 ion/src/f730/bench/command/mcu_serial.cpp create mode 100644 ion/src/f730/bench/command/ping.cpp create mode 100644 ion/src/f730/bench/command/print.cpp create mode 100644 ion/src/f730/bench/command/suspend.cpp create mode 100644 ion/src/f730/bench/command/vblank.cpp create mode 100644 ion/src/f730/bench/command_handler.cpp create mode 100644 ion/src/f730/bench/command_handler.h create mode 100644 ion/src/f730/bench/command_list.cpp create mode 100644 ion/src/f730/bench/command_list.h create mode 100644 ion/src/f730/boot/Makefile create mode 100644 ion/src/f730/boot/flash.ld create mode 100644 ion/src/f730/boot/isr.c create mode 100644 ion/src/f730/boot/isr.h create mode 100644 ion/src/f730/boot/rt0.cpp create mode 100644 ion/src/f730/console.cpp create mode 100644 ion/src/f730/console.h create mode 100644 ion/src/f730/device.cpp create mode 100644 ion/src/f730/device.h create mode 100644 ion/src/f730/display.cpp create mode 100644 ion/src/f730/display.h create mode 100644 ion/src/f730/events.cpp create mode 100644 ion/src/f730/flash.cpp create mode 100644 ion/src/f730/flash.h create mode 100644 ion/src/f730/keyboard.cpp create mode 100644 ion/src/f730/keyboard.h create mode 100644 ion/src/f730/led.cpp create mode 100644 ion/src/f730/led.h create mode 100644 ion/src/f730/log.cpp create mode 100644 ion/src/f730/power.cpp create mode 100644 ion/src/f730/regs/adc.h create mode 100644 ion/src/f730/regs/cm4.h create mode 100644 ion/src/f730/regs/crc.h create mode 100644 ion/src/f730/regs/dma.h create mode 100644 ion/src/f730/regs/exti.h create mode 100644 ion/src/f730/regs/flash.h create mode 100644 ion/src/f730/regs/fsmc.h create mode 100644 ion/src/f730/regs/gpio.h create mode 100644 ion/src/f730/regs/itm.h create mode 100644 ion/src/f730/regs/mpu.h create mode 100644 ion/src/f730/regs/nvic.h create mode 100644 ion/src/f730/regs/otg.h create mode 100644 ion/src/f730/regs/pwr.h create mode 100644 ion/src/f730/regs/rcc.h create mode 100644 ion/src/f730/regs/register.h create mode 100644 ion/src/f730/regs/regs.h create mode 100644 ion/src/f730/regs/rng.h create mode 100644 ion/src/f730/regs/sdio.h create mode 100644 ion/src/f730/regs/spi.h create mode 100644 ion/src/f730/regs/syscfg.h create mode 100644 ion/src/f730/regs/tim.h create mode 100644 ion/src/f730/regs/usart.h create mode 100644 ion/src/f730/sd_card.cpp create mode 100644 ion/src/f730/sd_card.h create mode 100644 ion/src/f730/stack.cpp create mode 100644 ion/src/f730/swd.cpp create mode 100644 ion/src/f730/swd.h create mode 100644 ion/src/f730/timing.cpp create mode 100644 ion/src/f730/timing.h create mode 100644 ion/src/f730/usb.cpp create mode 100644 ion/src/f730/usb.h create mode 100644 ion/src/f730/usb/Makefile create mode 100644 ion/src/f730/usb/boot.cpp create mode 100644 ion/src/f730/usb/calculator.cpp create mode 100644 ion/src/f730/usb/calculator.h create mode 100644 ion/src/f730/usb/dfu.ld create mode 100644 ion/src/f730/usb/dfu_interface.cpp create mode 100644 ion/src/f730/usb/dfu_interface.h create mode 100644 ion/src/f730/usb/dfu_relocated.cpp create mode 100644 ion/src/f730/usb/dfu_xip.cpp create mode 100644 ion/src/f730/usb/flasher.cpp create mode 100644 ion/src/f730/usb/flasher.ld create mode 100644 ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/bos_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/configuration_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/device_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/device_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/interface_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/string_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/string_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/url_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/url_descriptor.h create mode 100644 ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp create mode 100644 ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h create mode 100644 ion/src/f730/usb/stack/device.cpp create mode 100644 ion/src/f730/usb/stack/device.h create mode 100644 ion/src/f730/usb/stack/endpoint0.cpp create mode 100644 ion/src/f730/usb/stack/endpoint0.h create mode 100644 ion/src/f730/usb/stack/interface.cpp create mode 100644 ion/src/f730/usb/stack/interface.h create mode 100644 ion/src/f730/usb/stack/request_recipient.cpp create mode 100644 ion/src/f730/usb/stack/request_recipient.h create mode 100644 ion/src/f730/usb/stack/setup_packet.cpp create mode 100644 ion/src/f730/usb/stack/setup_packet.h create mode 100644 ion/src/f730/usb/stack/streamable.cpp create mode 100644 ion/src/f730/usb/stack/streamable.h create mode 100644 ion/src/f730/wakeup.cpp create mode 100644 ion/src/f730/wakeup.h diff --git a/ion/src/f730/Makefile b/ion/src/f730/Makefile new file mode 100644 index 000000000..3d243bd47 --- /dev/null +++ b/ion/src/f730/Makefile @@ -0,0 +1,51 @@ +include ion/src/device/boot/Makefile +include ion/src/device/bench/Makefile +include ion/src/device/usb/Makefile + +ion/src/shared/platform_info.o: SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" + +objs += $(addprefix ion/src/shared/, \ + console_line.o \ + crc32_padded.o\ + events_modifier.o \ +) + +# If you need to profile execution, you can replace events_keyboard with +# events_replay.o and dummy/events_modifier.o + +objs += $(addprefix ion/src/device/, \ + backlight.o \ + battery.o\ + base64.o\ + console.o \ + device.o\ + display.o\ + events.o\ + flash.o\ + keyboard.o\ + led.o\ + power.o\ + sd_card.o\ + stack.o\ + swd.o \ + timing.o \ + usb.o \ + wakeup.o \ +) + +# When using the register.h C++ file in production mode, we expect the compiler +# to completely inline all bit manipulations. For some reason, if we build using +# the -Os optimization flag, GCC doesn't inline everything and and ends up +# emitting calls to aeabi_llsl for 64-bits registers. This is very sub-optimal +# so we're enforcing -O3 for this specific file. + +ifneq ($(DEBUG),1) +ifneq ($(COMPILER),llvm) +ion/src/device/led.o: SFLAGS+=-O3 +ion/src/device/console.o: SFLAGS+=-O3 +ion/src/device/display.o: SFLAGS+=-O3 +ion/src/device/swd.o: SFLAGS+=-O3 +endif +endif + +#objs += $(addprefix ion/src/device/keyboard/, keyboard.o) diff --git a/ion/src/f730/backlight.cpp b/ion/src/f730/backlight.cpp new file mode 100644 index 000000000..efcd49b9d --- /dev/null +++ b/ion/src/f730/backlight.cpp @@ -0,0 +1,85 @@ +#include +#include "regs/regs.h" +#include "backlight.h" + +/* This driver controls the RT9365 LED driver. + * This chip allows the brightness to be set to 16 different values. It starts + * at full brightness on power on. Applying a pulse on the EN pin will select + * the next value in decreasing order. Once it reaches the minimal value, the + * next pulse will loop back to full brightness. */ + +// Public Ion::Backlight methods + +namespace Ion { +namespace Backlight { + +void setBrightness(uint8_t b) { + Device::setLevel(b >> 4); +} + +uint8_t brightness() { + return Device::level() << 4; +} + +} +} + +// Private Ion::Backlight::Device methods + +namespace Ion { +namespace Backlight { +namespace Device { + +static uint8_t sLevel; + +void init() { + GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Output); + sLevel = 0xF; + resume(); +} + +void shutdown() { + GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Analog); + GPIOC.PUPDR()->setPull(6, GPIO::PUPDR::Pull::None); +} + +void suspend() { + GPIOC.ODR()->set(6, false); + Timing::msleep(3); // Might not need to be blocking +} + +void resume() { + GPIOC.ODR()->set(6, true); + Timing::usleep(50); + uint8_t level = sLevel; + sLevel = 0xF; + setLevel(level); +} + +void setLevel(uint8_t level) { + // From sLevel = 12 to level 7 : 5 pulses + // From sLevel = 5 to level 9 : 12 pulses (5 to go to level 16, and 7 to 9) + if (sLevel < level) { + sendPulses(16 + sLevel - level); + } else { + sendPulses(sLevel - level); + } + sLevel = level; +} + +uint8_t level() { + return sLevel; +} + +void sendPulses(int n) { + for (int i=0; iset(6, false); + Timing::usleep(20); + GPIOC.ODR()->set(6, true); + Timing::usleep(20); + } +} + +} +} +} diff --git a/ion/src/f730/backlight.h b/ion/src/f730/backlight.h new file mode 100644 index 000000000..15eab4fb0 --- /dev/null +++ b/ion/src/f730/backlight.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_BACKLIGHT_H +#define ION_DEVICE_BACKLIGHT_H + +#include + +namespace Ion { +namespace Backlight { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PC6 | Backlight Enable | Output | + */ + +void init(); +void shutdown(); +void suspend(); +void resume(); +void setLevel(uint8_t level); +uint8_t level(); + +void sendPulses(int n); + +} +} +} + +#endif diff --git a/ion/src/f730/base64.cpp b/ion/src/f730/base64.cpp new file mode 100644 index 000000000..6b4bb950c --- /dev/null +++ b/ion/src/f730/base64.cpp @@ -0,0 +1,48 @@ +namespace Base64 { + +static constexpr char encodeTable[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', +}; + +constexpr char Padding = '='; + +void encode(const unsigned char * input, unsigned int inputLength, char * output) { + unsigned int i, j; + for (i = j = 0; i < inputLength; i++) { + int s = i % 3; /* from 6/gcd(6, 8) */ + + switch (s) { + case 0: + output[j++] = encodeTable[(input[i] >> 2) & 0x3F]; + continue; + case 1: + output[j++] = encodeTable[((input[i-1] & 0x3) << 4) + ((input[i] >> 4) & 0xF)]; + continue; + case 2: + output[j++] = encodeTable[((input[i-1] & 0xF) << 2) + ((input[i] >> 6) & 0x3)]; + output[j++] = encodeTable[input[i] & 0x3F]; + } + } + + /* move back */ + i -= 1; + + /* check the last and add padding */ + if ((i % 3) == 0) { + output[j++] = encodeTable[(input[i] & 0x3) << 4]; + output[j++] = Padding; + output[j++] = Padding; + } else if ((i % 3) == 1) { + output[j++] = encodeTable[(input[i] & 0xF) << 2]; + output[j++] = Padding; + } +} + +} diff --git a/ion/src/f730/base64.h b/ion/src/f730/base64.h new file mode 100644 index 000000000..b9a4f2852 --- /dev/null +++ b/ion/src/f730/base64.h @@ -0,0 +1,5 @@ +namespace Base64 { + +void encode(const unsigned char * input, unsigned int inputLength, char * output); + +} diff --git a/ion/src/f730/battery.cpp b/ion/src/f730/battery.cpp new file mode 100644 index 000000000..f3ed16364 --- /dev/null +++ b/ion/src/f730/battery.cpp @@ -0,0 +1,88 @@ +#include +#include "battery.h" +#include "regs/regs.h" + +/* To measure the battery voltage, we're using the internal ADC. The ADC works + * by comparing the input voltage to a reference voltage. The only fixed voltage + * we have around is 2.8V, so that's the one we're using as a refrence. However, + * and ADC can only measure voltage that is lower than the reference voltage. So + * we need to use a voltage divider before sampling Vbat. + * To avoid draining the battery, we're using an high-impedence voltage divider, + * so we need to be careful when sampling the ADC. See AN2834 for more info. */ + +namespace Ion { +namespace Battery { + +bool isCharging() { + return !Device::ChargingGPIO.IDR()->get(Device::ChargingPin); +} + +Charge level() { + if (voltage() < 3.2f) { + return Charge::EMPTY; + } + if (voltage() < 3.5f) { + return Charge::LOW; + } + if (voltage() < 3.8f) { + return Charge::SOMEWHERE_INBETWEEN; + } + return Charge::FULL; +} + +float voltage() { + ADC.CR2()->setSWSTART(true); + while (ADC.SR()->getEOC() != true) { + } + uint16_t value = ADC.DR()->get(); + + // The ADC is 12 bits by default + return Device::ADCDividerBridgeRatio*(Device::ADCReferenceVoltage * value)/0xFFF; +} + +} +} + +namespace Ion { +namespace Battery { +namespace Device { + +void init() { + initGPIO(); + + /* The BAT_SNS pin is connected to Vbat through a divider bridge. It therefore + * has a voltage of Vbat/2. We'll measure this using ADC channel 0. */ + ADCGPIO.MODER()->setMode(ADCPin, GPIO::MODER::Mode::Analog); + + // Step 2 - Enable the ADC + RCC.APB2ENR()->setADC1EN(true); + ADC.CR2()->setADON(true); + + // Configure the ADC channel + ADC.SQR1()->setL(0); // Always sample the same channel + ADC.SQR3()->setSQ1(ADCChannel); + ADC.SMPR()->setSamplingTime(ADCChannel, ADC::SMPR::SamplingTime::Cycles480); // Use the max sampling time +} + +void initGPIO() { + /* Step 1 - Configure the GPIOs + * The BAT_CHRG pin is connected to the Li-Po charging IC. That pin uses an + * open-drain output. Open-drain output are either connected to ground or left + * floating. To interact with such an output, our input must therefore be + * pulled up. */ + ChargingGPIO.MODER()->setMode(ChargingPin, GPIO::MODER::Mode::Input); + ChargingGPIO.PUPDR()->setPull(ChargingPin, GPIO::PUPDR::Pull::Up); +} + +void shutdown() { + ChargingGPIO.MODER()->setMode(ChargingPin, GPIO::MODER::Mode::Analog); + ChargingGPIO.PUPDR()->setPull(ChargingPin, GPIO::PUPDR::Pull::None); + + // Disable the ADC + ADC.CR2()->setADON(false); + RCC.APB2ENR()->setADC1EN(false); +} + +} +} +} diff --git a/ion/src/f730/battery.h b/ion/src/f730/battery.h new file mode 100644 index 000000000..ef558dc86 --- /dev/null +++ b/ion/src/f730/battery.h @@ -0,0 +1,35 @@ +#ifndef ION_DEVICE_BATTERY_H +#define ION_DEVICE_BATTERY_H + +#include "regs/regs.h" + +namespace Ion { +namespace Battery { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PA0 | BAT_CHRG | Input, pulled up | Low = charging, high = full + * PA1 | VBAT_SNS | Analog | ADC1_1 + */ + +void init(); +void shutdown(); +void initGPIO(); +void initADC(); + +constexpr GPIO ChargingGPIO = GPIOA; +constexpr uint8_t ChargingPin = 0; + +constexpr GPIO ADCGPIO = GPIOA; +constexpr uint8_t ADCPin = 1; +constexpr uint8_t ADCChannel = 1; + +constexpr float ADCReferenceVoltage = 2.8f; +constexpr float ADCDividerBridgeRatio = 2.0f; + +} +} +} + +#endif diff --git a/ion/src/f730/bench/Makefile b/ion/src/f730/bench/Makefile new file mode 100644 index 000000000..90b0c82b6 --- /dev/null +++ b/ion/src/f730/bench/Makefile @@ -0,0 +1,21 @@ +objs += $(addprefix ion/src/device/bench/, \ + bench.o \ + command_handler.o \ + command_list.o \ +) + +objs += $(addprefix ion/src/device/bench/command/, \ + command.o \ + adc.o \ + backlight.o \ + charge.o \ + display.o \ + exit.o \ + keyboard.o \ + led.o \ + mcu_serial.o \ + ping.o \ + print.o \ + suspend.o \ + vblank.o \ +) diff --git a/ion/src/f730/bench/bench.cpp b/ion/src/f730/bench/bench.cpp new file mode 100644 index 000000000..5458111bc --- /dev/null +++ b/ion/src/f730/bench/bench.cpp @@ -0,0 +1,46 @@ +#include "bench.h" +#include +#include +#include "command_list.h" + +namespace Ion { +namespace Device { +namespace Bench { + +constexpr CommandHandler handles[] = { + CommandHandler("ADC", Command::ADC), + CommandHandler("BACKLIGHT", Command::Backlight), + CommandHandler("CHARGE", Command::Charge), + CommandHandler("DISPLAY", Command::Display), + CommandHandler("EXIT", Command::Exit), + CommandHandler("KEYBOARD", Command::Keyboard), + CommandHandler("LED", Command::LED), + CommandHandler("MCU_SERIAL", Command::MCUSerial), + CommandHandler("PING", Command::Ping), + CommandHandler("PRINT", Command::Print), + CommandHandler("SUSPEND", Command::Suspend), + CommandHandler("VBLANK", Command::VBlank), + CommandHandler(nullptr, nullptr) +}; + +constexpr const CommandList sCommandList = CommandList(handles); + +constexpr int kMaxCommandLength = 255; + +void run() { + KDContext * ctx = KDIonContext::sharedContext(); + ctx->fillRect(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColorWhite); + ctx->drawString("BENCH", KDPoint((320-50)/2, (240-18)/2)); + char command[kMaxCommandLength]; + while (true) { + Ion::Console::readLine(command, kMaxCommandLength); + const CommandHandler * ch = sCommandList.dispatch(command); + if (ch != nullptr && ch->function() == Command::Exit) { + break; + } + } +} + +} +} +} diff --git a/ion/src/f730/bench/bench.h b/ion/src/f730/bench/bench.h new file mode 100644 index 000000000..de999cd37 --- /dev/null +++ b/ion/src/f730/bench/bench.h @@ -0,0 +1,14 @@ +#ifndef ION_DEVICE_BENCH_BENCH_H +#define ION_DEVICE_BENCH_BENCH_H + +namespace Ion { +namespace Device { +namespace Bench { + +void run(); + +} +} +} + +#endif diff --git a/ion/src/f730/bench/command/adc.cpp b/ion/src/f730/bench/command/adc.cpp new file mode 100644 index 000000000..8c3e677a4 --- /dev/null +++ b/ion/src/f730/bench/command/adc.cpp @@ -0,0 +1,27 @@ +#include "command.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void ADC(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + float result = Ion::Battery::voltage(); + constexpr int precision = 8; + constexpr int bufferSize = Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(precision); + char responseBuffer[bufferSize+4] = {'A', 'D', 'C', '='}; // ADC= + Poincare::PrintFloat::convertFloatToText(result, responseBuffer+4, bufferSize, precision, Poincare::Preferences::PrintFloatMode::Decimal); + reply(responseBuffer); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/backlight.cpp b/ion/src/f730/bench/command/backlight.cpp new file mode 100644 index 000000000..b4a7f2d9f --- /dev/null +++ b/ion/src/f730/bench/command/backlight.cpp @@ -0,0 +1,34 @@ +#include "command.h" +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Backlight(const char * input) { + // Input must be of the form "0xAA" or "ON" or "OFF" + if (strcmp(input, sON) == 0) { + Ion::Backlight::Device::init(); + reply(sOK); + return; + } + if (strcmp(input, sOFF) == 0) { + Ion::Backlight::Device::shutdown(); + reply(sOK); + return; + } + if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || input[4] != NULL) { + reply(sSyntaxError); + return; + } + uint32_t brightness = hexNumber(input+2); + Ion::Backlight::setBrightness(brightness); + reply(sOK); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/charge.cpp b/ion/src/f730/bench/command/charge.cpp new file mode 100644 index 000000000..8aa14f536 --- /dev/null +++ b/ion/src/f730/bench/command/charge.cpp @@ -0,0 +1,24 @@ +#include "command.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Charge(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + if (Ion::Battery::isCharging()) { + reply("CHARGE=ON"); + } else { + reply("CHARGE=OFF"); + } +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/command.cpp b/ion/src/f730/bench/command/command.cpp new file mode 100644 index 000000000..645b224d9 --- /dev/null +++ b/ion/src/f730/bench/command/command.cpp @@ -0,0 +1,47 @@ +#include "command.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +const char * const sOK = "OK"; +const char * const sKO = "KO"; +const char * const sSyntaxError = "SYNTAX_ERROR"; +const char * const sON = "ON"; +const char * const sOFF = "OFF"; + +void reply(const char * s) { + Console::writeLine(s); +} + +int8_t hexChar(char c) { + if (c >= '0' && c <= '9') { + return (c - '0'); + } + if (c >= 'A' && c <= 'F') { + return (c - 'A') + 0xA; + } + return -1; +} + +bool isHex(char c) { + return hexChar(c) >= 0; +} + +uint32_t hexNumber(const char * s, int maxLength) { + uint32_t result = 0; + int index = 0; + while ((maxLength < 0 || index < maxLength) && s[index] != NULL) { + result = (result << 4) | hexChar(s[index]); + index++; + } + return result; +} + + +} +} +} +} diff --git a/ion/src/f730/bench/command/command.h b/ion/src/f730/bench/command/command.h new file mode 100644 index 000000000..c749d3728 --- /dev/null +++ b/ion/src/f730/bench/command/command.h @@ -0,0 +1,42 @@ +#ifndef ION_DEVICE_BENCH_COMMAND_COMMAND_H +#define ION_DEVICE_BENCH_COMMAND_COMMAND_H + +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +typedef void (*Function)(const char * input); + +void ADC(const char * input); +void Backlight(const char * input); +void Charge(const char * input); +void Display(const char * input); +void Exit(const char * input); +void Keyboard(const char * input); +void LED(const char * input); +void MCUSerial(const char * input); +void Ping(const char * input); +void Print(const char * input); +void Suspend(const char * input); +void VBlank(const char * input); + +extern const char * const sOK; +extern const char * const sKO; +extern const char * const sSyntaxError; +extern const char * const sON; +extern const char * const sOFF; + +void reply(const char * s); +int8_t hexChar(char c); +bool isHex(char c); +uint32_t hexNumber(const char * s, int maxLength = -1); + +} +} +} +} + +#endif diff --git a/ion/src/f730/bench/command/display.cpp b/ion/src/f730/bench/command/display.cpp new file mode 100644 index 000000000..553e6e54a --- /dev/null +++ b/ion/src/f730/bench/command/display.cpp @@ -0,0 +1,74 @@ +#include "command.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +// Input must be of the form "0xAABBCC" or "ON" or "OFF" +void Display(const char * input) { + + if (strcmp(input, sON) == 0) { + Ion::Display::Device::init(); + reply(sOK); + return; + } + if (strcmp(input, sOFF) == 0) { + Ion::Display::Device::shutdown(); + reply(sOK); + return; + } + if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || !isHex(input[4]) || !isHex(input[5]) || !isHex(input[6]) || !isHex(input[7]) || input[8] != NULL) { + reply(sSyntaxError); + return; + } + + /* We fill the screen with a color and return OK if we read that color back everywhere. */ + + KDColor c = KDColor::RGB24(hexNumber(input)); + + constexpr int stampHeight = 10; + constexpr int stampWidth = 10; + static_assert(Ion::Display::Width % stampWidth == 0, "Stamps must tesselate the display"); + static_assert(Ion::Display::Height % stampHeight == 0, "Stamps must tesselate the display"); + static_assert(stampHeight % 2 == 0 || stampWidth % 2 == 0, "Even number of XOR needed."); + + KDColor stamp[stampWidth*stampHeight]; + for (int i=0;i +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +// Input must be of the form "0xAABBCC" or "ON" or "OFF" +void LED(const char * input) { + if (strcmp(input, sON) == 0) { + Ion::LED::Device::init(); + Ion::Console::writeLine(sOK); + return; + } + if (strcmp(input, sOFF) == 0) { + Ion::LED::Device::shutdown(); + Ion::Console::writeLine(sOK); + return; + } + if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || !isHex(input[4]) || !isHex(input[5]) || !isHex(input[6]) || !isHex(input[7]) || input[8] != NULL) { + Ion::Console::writeLine(sSyntaxError); + return; + } + uint32_t hexColor = hexNumber(input+2); + KDColor ledColor = KDColor::RGB24(hexColor); + Ion::LED::setColor(ledColor); + Ion::Console::writeLine(sOK); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/mcu_serial.cpp b/ion/src/f730/bench/command/mcu_serial.cpp new file mode 100644 index 000000000..b6703085b --- /dev/null +++ b/ion/src/f730/bench/command/mcu_serial.cpp @@ -0,0 +1,23 @@ +#include "command.h" +#include +#include "../../device.h" + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void MCUSerial(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + char response[11 + Ion::Device::SerialNumberLength + 1] = {'M', 'C', 'U', '_', 'S', 'E', 'R', 'I', 'A', 'L', '=', 0}; + Ion::Device::copySerialNumber(response + 11); + reply(response); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/ping.cpp b/ion/src/f730/bench/command/ping.cpp new file mode 100644 index 000000000..23649a7dc --- /dev/null +++ b/ion/src/f730/bench/command/ping.cpp @@ -0,0 +1,19 @@ +#include "command.h" + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Ping(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + reply("PONG"); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/print.cpp b/ion/src/f730/bench/command/print.cpp new file mode 100644 index 000000000..8b5eb3bfc --- /dev/null +++ b/ion/src/f730/bench/command/print.cpp @@ -0,0 +1,30 @@ +#include "command.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +// Input must be of the form "XX,YY,STRING" +void Print(const char * input) { + if (input == nullptr || !isHex(input[0]) || !isHex(input[1]) || input[2] != ',' || !isHex(input[3]) || !isHex(input[4]) || input[5] != ',') { + reply(sKO); + return; + } + + char x = hexNumber(input, 2); + char y = hexNumber(input+3, 2); + + KDContext * ctx = KDIonContext::sharedContext(); + ctx->drawString(input+6, KDPoint(x, y)); + + reply(sOK); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/suspend.cpp b/ion/src/f730/bench/command/suspend.cpp new file mode 100644 index 000000000..a494501c0 --- /dev/null +++ b/ion/src/f730/bench/command/suspend.cpp @@ -0,0 +1,22 @@ +#include "command.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Suspend(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + reply(sOK); + Ion::Timing::msleep(100); + Ion::Power::suspend(); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command/vblank.cpp b/ion/src/f730/bench/command/vblank.cpp new file mode 100644 index 000000000..3c6971061 --- /dev/null +++ b/ion/src/f730/bench/command/vblank.cpp @@ -0,0 +1,26 @@ +#include "command.h" +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void VBlank(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + + for (int i=0; i<6; i++) { + Ion::Display::waitForVBlank(); + } + + reply(sOK); +} + +} +} +} +} diff --git a/ion/src/f730/bench/command_handler.cpp b/ion/src/f730/bench/command_handler.cpp new file mode 100644 index 000000000..082d229a0 --- /dev/null +++ b/ion/src/f730/bench/command_handler.cpp @@ -0,0 +1,44 @@ +#include "command_handler.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { + +bool CommandHandler::valid() const { + return (m_name != nullptr && m_function != nullptr); +} + +bool CommandHandler::handle(const char * command) const { + if (matches(command)) { + size_t nameLength = strlen(m_name); + if (command[nameLength] == '=') { + m_function(command+nameLength+1); // Skip the "Equal character" + } else { + m_function(nullptr); + } + return true; + } + return false; +} + +bool CommandHandler::matches(const char * command) const { + const char * c = command; + const char * n = m_name; + while (true) { + if (*n == NULL) { + if (*c == NULL || *c == '=') { + return true; + } + } + if (*c != *n) { + return false; + } + c++; + n++; + } +} + +} +} +} diff --git a/ion/src/f730/bench/command_handler.h b/ion/src/f730/bench/command_handler.h new file mode 100644 index 000000000..be453e80c --- /dev/null +++ b/ion/src/f730/bench/command_handler.h @@ -0,0 +1,27 @@ +#ifndef ION_DEVICE_BENCH_COMMAND_HANDLER_H +#define ION_DEVICE_BENCH_COMMAND_HANDLER_H + +#include "command/command.h" + +namespace Ion { +namespace Device { +namespace Bench { + +class CommandHandler { +public: + constexpr CommandHandler(const char * name, Command::Function function) : + m_name(name), m_function(function) {} + bool valid() const; + bool handle(const char * command) const; + Command::Function function() const { return m_function; } +private: + bool matches(const char * command) const; + const char * m_name; + Command::Function m_function; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/bench/command_list.cpp b/ion/src/f730/bench/command_list.cpp new file mode 100644 index 000000000..917f54dfb --- /dev/null +++ b/ion/src/f730/bench/command_list.cpp @@ -0,0 +1,22 @@ +#include "command_list.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { + +const CommandHandler * CommandList::dispatch(const char * command) const { + const CommandHandler * handler = m_handlers; + while (handler->valid()) { + if (handler->handle(command)) { + return handler; + } + handler++; + } + Console::writeLine("NOT_FOUND"); + return nullptr; +} + +} +} +} diff --git a/ion/src/f730/bench/command_list.h b/ion/src/f730/bench/command_list.h new file mode 100644 index 000000000..2b47b88b3 --- /dev/null +++ b/ion/src/f730/bench/command_list.h @@ -0,0 +1,22 @@ +#ifndef ION_DEVICE_BENCH_COMMAND_LIST_H +#define ION_DEVICE_BENCH_COMMAND_LIST_H + +#include "command_handler.h" + +namespace Ion { +namespace Device { +namespace Bench { + +class CommandList { +public: + constexpr CommandList(const CommandHandler * handlers) : m_handlers(handlers) {} + const CommandHandler * dispatch(const char * command) const; +private: + const CommandHandler * m_handlers; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/boot/Makefile b/ion/src/f730/boot/Makefile new file mode 100644 index 000000000..ded572acd --- /dev/null +++ b/ion/src/f730/boot/Makefile @@ -0,0 +1,2 @@ +objs += $(addprefix ion/src/device/boot/, isr.o rt0.o) +LDSCRIPT = ion/src/device/boot/flash.ld diff --git a/ion/src/f730/boot/flash.ld b/ion/src/f730/boot/flash.ld new file mode 100644 index 000000000..e5ddcc9e4 --- /dev/null +++ b/ion/src/f730/boot/flash.ld @@ -0,0 +1,112 @@ +/* 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 { + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K +} + +STACK_SIZE = 32K; + +SECTIONS { + .isr_vector_table ORIGIN(FLASH) : { + /* 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. */ + + KEEP(*(.isr_vector_table)) + } >FLASH + + .header : { + KEEP(*(.header)) + } >FLASH + + .text : { + . = ALIGN(4); + *(.text) + *(.text.*) + } >FLASH + + .init_array : { + . = ALIGN(4); + _init_array_start = .; + KEEP (*(.init_array*)) + _init_array_end = .; + } >FLASH + + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata.*) + } >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 +} diff --git a/ion/src/f730/boot/isr.c b/ion/src/f730/boot/isr.c new file mode 100644 index 000000000..5c201aa67 --- /dev/null +++ b/ion/src/f730/boot/isr.c @@ -0,0 +1,129 @@ +#include "isr.h" +extern const void * _stack_start; + +/* Interrupt Service Routines are void->void functions */ +typedef void(*ISR)(void); + +/* Notice: The Cortex-M4 expects all jumps to be made at an odd address when + * jumping to Thumb code. For example, if you want to execute Thumb code at + * address 0x100, you'll have to jump to 0x101. Luckily, this idiosyncrasy is + * properly handled by the C compiler that will generate proper addresses when + * using function pointers. */ + +#define INITIALISATION_VECTOR_SIZE 0x71 + +ISR InitialisationVector[INITIALISATION_VECTOR_SIZE] + __attribute__((section(".isr_vector_table"))) + __attribute__((used)) + = { + (ISR)&_stack_start, // Stack start + start, // Reset service routine, + 0, // NMI service routine, + abort, // HardFault service routine, + 0, // MemManage service routine, + 0, // BusFault service routine, + 0, // UsageFault service routine, + 0, 0, 0, 0, // Reserved + 0, // SVCall service routine, + 0, // DebugMonitor service routine, + 0, // Reserved + 0, // PendSV service routine, + isr_systick, // SysTick service routine + 0, // WWDG service routine + 0, // PVD service routine + 0, // TampStamp service routine + 0, // RtcWakeup service routine + 0, // Flash service routine + 0, // RCC service routine + 0, // EXTI0 service routine + 0, // EXTI1 service routine + 0, // EXTI2 service routine + 0, // EXTI3 service routine + 0, // EXTI4 service routine + 0, // DMA1Stream0 service routine + 0, // DMA1Stream1 service routine + 0, // DMA1Stream2 service routine + 0, // DMA1Stream3 service routine + 0, // DMA1Stream4 service routine + 0, // DMA1Stream5 service routine + 0, // DMA1Stream6 service routine + 0, // ADC1 global interrupt + 0, // CAN1 TX interrupt + 0, // CAN1 RX0 interrupt + 0, // CAN1 RX1 interrupt + 0, // CAN1 SCE interrupt + 0, // EXTI Line[9:5] interrupts + 0, // TIM1 Break interrupt and TIM9 global interrupt + 0, // TIM1 update interrupt and TIM10 global interrupt + 0, // TIM1 Trigger & Commutation interrupts and TIM11 global interrupt + 0, // TIM1 Capture Compare interrupt + 0, // TIM2 global interrupt + 0, // TIM3 global interrupt + 0, // TIM4 global interrupt + 0, // I2C1 global event interrupt + 0, // I2C1 global error interrupt + 0, // I2C2 global event interrupt + 0, // I2C2 global error interrupt + 0, // SPI1 global interrupt + 0, // SPI2 global interrupt + 0, // USART1 global interrupt + 0, // USART2 global interrupt + 0, // USART3 global interrupt + 0, // EXTI Line[15:10] interrupts + 0, // EXTI Line 17 interrupt RTC Alarms (A and B) through EXTI line interrupt + 0, // EXTI Line 18 interrupt / USB On-The-Go FS Wakeup through EXTI line interrupt + 0, // TIM8 Break interrupt TIM12 global interrupt + 0, // TIM8 Update interrupt TIM13 global interrupt + 0, // TIM8 Trigger & Commutation interrupt TIM14 global interrupt + 0, // TIM8 Cap/Com interrupt + 0, // DMA1 global interrupt Channel 7 + 0, // FSMC global interrupt + 0, // SDIO global interrupt + 0, // TIM5 global interrupt + 0, // SPI3 global interrupt + 0, // ? + 0, // ? + 0, // TIM6 global interrupt + 0, // TIM7 global interrupt + 0, // DMA2 Stream0 global interrupt + 0, // DMA2 Stream1 global interrupt + 0, // DMA2 Stream2 global interrupt + 0, // DMA2 Stream3 global interrupt + 0, // DMA2 Stream4 global interrupt + 0, // SD filter0 global interrupt + 0, // SD filter1 global interrupt + 0, // CAN2 TX interrupt + 0, // BXCAN2 RX0 interrupt + 0, // BXCAN2 RX1 interrupt + 0, // CAN2 SCE interrupt + 0, // USB On The Go FS global interrupt + 0, // DMA2 Stream5 global interrupts + 0, // DMA2 Stream6 global interrupt + 0, // DMA2 Stream7 global interrupt + 0, // USART6 global interrupt + 0, // I2C3 event interrupt + 0, // I2C3 error interrupt + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // RNG global interrupt + 0, // FPU global interrupt + 0, // ? + 0, // ? + 0, // SPI4 global interrupt + 0, // SPI5 global interrupt + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // ? + 0, // Quad-SPI global interrupt + 0, // ? + 0, // ? + 0, // I2CFMP1 event interrupt + 0 // I2CFMP1 error interrupt +}; diff --git a/ion/src/f730/boot/isr.h b/ion/src/f730/boot/isr.h new file mode 100644 index 000000000..ca5becb13 --- /dev/null +++ b/ion/src/f730/boot/isr.h @@ -0,0 +1,16 @@ +#ifndef ION_DEVICE_BOOT_ISR_H +#define ION_DEVICE_BOOT_ISR_H + +#ifdef __cplusplus +extern "C" { +#endif + +void start(); +void abort(); +void isr_systick(); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ion/src/f730/boot/rt0.cpp b/ion/src/f730/boot/rt0.cpp new file mode 100644 index 000000000..e4ea05f0f --- /dev/null +++ b/ion/src/f730/boot/rt0.cpp @@ -0,0 +1,97 @@ +#include "isr.h" +#include +#include +#include +#include "../device.h" +#include "../timing.h" +#include "../console.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 abort() { +#if DEBUG + while (1) { + } +#else + Ion::Device::coreReset(); +#endif +} + +/* 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. */ +static void __attribute__((noinline)) non_inlined_ion_main() { + return ion_main(0, nullptr); +} + +void 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::initFPU(); + +#if 0 + Ion::Device::initMPU(); +#endif + + /* 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::init(); + + non_inlined_ion_main(); + + abort(); +} + +void __attribute__((interrupt)) isr_systick() { + Ion::Timing::Device::MillisElapsed++; +} diff --git a/ion/src/f730/console.cpp b/ion/src/f730/console.cpp new file mode 100644 index 000000000..15ba0b275 --- /dev/null +++ b/ion/src/f730/console.cpp @@ -0,0 +1,76 @@ +#include +#include "console.h" + +/* This file implements a serial console. + * We use a 115200 8N1 serial port */ + +namespace Ion { +namespace Console { + +char readChar() { + while (Device::UARTPort.SR()->getRXNE() == 0) { + } + return (char)Device::UARTPort.DR()->get(); +} + +void writeChar(char c) { + while (Device::UARTPort.SR()->getTXE() == 0) { + } + Device::UARTPort.DR()->set(c); +} + +} +} + +namespace Ion { +namespace Console { +namespace Device { + +void init() { + RCC.APB1ENR()->setUSART3EN(true); + + for(const GPIOPin & g : Pins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF7); + } + + UARTPort.CR1()->setUE(true); + UARTPort.CR1()->setTE(true); + UARTPort.CR1()->setRE(true); + + /* We need to set the baud rate of the UART port. + * This is set relative to the APB1 clock, which runs at 48 MHz. + * + * The baud rate is set by the following equation: + * BaudRate = fAPB1/(16*USARTDIV), where USARTDIV is a divider. + * In other words, USARDFIV = fAPB1/(16*BaudRate). All frequencies in Hz. + * + * In our case, fAPB1 = 48 MHz, so USARTDIV = 26.0416667 + * DIV_MANTISSA = 26 + * DIV_FRAC = 16*0.0416667 = 1 + */ + UARTPort.BRR()->setDIV_MANTISSA(26); + UARTPort.BRR()->setDIV_FRAC(1); +} + +void shutdown() { + for(const GPIOPin & g : Pins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } +} + +bool peerConnected() { + RxPin.group().PUPDR()->setPull(RxPin.pin(), GPIO::PUPDR::Pull::Down); + RxPin.group().MODER()->setMode(RxPin.pin(), GPIO::MODER::Mode::Input); + Timing::msleep(1); + bool result = RxPin.group().IDR()->get(RxPin.pin()); + RxPin.group().PUPDR()->setPull(RxPin.pin(), GPIO::PUPDR::Pull::None); + RxPin.group().MODER()->setMode(RxPin.pin(), GPIO::MODER::Mode::AlternateFunction); + return result; +} + + +} +} +} diff --git a/ion/src/f730/console.h b/ion/src/f730/console.h new file mode 100644 index 000000000..b1b14545f --- /dev/null +++ b/ion/src/f730/console.h @@ -0,0 +1,30 @@ +#ifndef ION_DEVICE_CONSOLE_H +#define ION_DEVICE_CONSOLE_H + +#include +#include "regs/regs.h" + +namespace Ion { +namespace Console { +namespace Device { + +/* Pin | Role | Mode + * -----+-------------------+-------------------- + * PC11 | UART3 RX | Alternate Function + * PD8 | UART3 TX | Alternate Function + */ + +void init(); +void shutdown(); +bool peerConnected(); + +constexpr USART UARTPort = USART(3); +constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 11); +constexpr static GPIOPin TxPin = GPIOPin(GPIOD, 8); +constexpr static GPIOPin Pins[] = { RxPin, TxPin }; + +} +} +} + +#endif diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp new file mode 100644 index 000000000..e25cc4106 --- /dev/null +++ b/ion/src/f730/device.cpp @@ -0,0 +1,325 @@ +#include "device.h" +#include "regs/regs.h" +extern "C" { +#include +} +#include +#include "led.h" +#include "display.h" +#include "keyboard.h" +#include "battery.h" +#include "sd_card.h" +#include "backlight.h" +#include "console.h" +#include "swd.h" +#include "usb.h" +#include "bench/bench.h" +#include "base64.h" + +#define USE_SD_CARD 0 + +extern "C" { + extern const void * _stack_end; +} + +// Public Ion methods + +uint32_t Ion::crc32(const uint32_t * data, size_t length) { + bool initialCRCEngineState = RCC.AHB1ENR()->getCRCEN(); + RCC.AHB1ENR()->setCRCEN(true); + CRC.CR()->setRESET(true); + + const uint32_t * end = data + length; + while (data < end) { + CRC.DR()->set(*data++); + } + + uint32_t result = CRC.DR()->get(); + RCC.AHB1ENR()->setCRCEN(initialCRCEngineState); + return result; +} + +uint32_t Ion::random() { + bool initialRNGEngineState = RCC.AHB2ENR()->getRNGEN(); + RCC.AHB2ENR()->setRNGEN(true); + + RNG.CR()->setRNGEN(true); + + while (RNG.SR()->getDRDY() == 0) { + } + uint32_t result = RNG.DR()->get(); + + RNG.CR()->setRNGEN(false); + RCC.AHB2ENR()->setRNGEN(initialRNGEngineState); + + return result; +} + +void Ion::Device::copySerialNumber(char * buffer) { + const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; + Base64::encode(rawUniqueID, 12, buffer); + buffer[SerialNumberLength] = 0; +} + +const char * Ion::serialNumber() { + static char serialNumber[Device::SerialNumberLength + 1] = {0}; + if (serialNumber[0] == 0) { + Device::copySerialNumber(serialNumber); + } + return serialNumber; +} + +const char * Ion::fccId() { + return "2ALWP-N0100"; +} + +// Private Ion::Device methods + +namespace Ion { +namespace Device { + +void initFPU() { + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BABDBFBJ.html + CM4.CPACR()->setAccess(10, CM4::CPACR::Access::Full); + CM4.CPACR()->setAccess(11, CM4::CPACR::Access::Full); + // FIXME: The pipeline should be flushed at this point +} + +#if 0 +void initMPU() { + /* Region 0 reprensents the last 128 bytes of the stack: accessing this + * memory means we are really likely to overflow the stack very soon. */ + MPU.RNR()->setREGION(0x00); + MPU.RBAR()->setADDR(&_stack_end); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::Bytes128); + MPU.RASR()->setENABLE(true); + MPU.RASR()->setAP(0x000); // Forbid access + MPU.CTRL()->setPRIVDEFENA(true); + MPU.CTRL()->setENABLE(true); +} +#endif + +void initSysTick() { + // CPU clock is 96 MHz, and systick clock source is divided by 8 + // To get 1 ms systick overflow we need to reset it to + // 96 000 000 (Hz) / 8 / 1 000 (ms/s) - 1 (because the counter resets *after* counting to 0) + CM4.SYST_RVR()->setRELOAD(11999); + CM4.SYST_CVR()->setCURRENT(0); + CM4.SYST_CSR()->setCLKSOURCE(CM4::SYST_CSR::CLKSOURCE::AHB_DIV8); + CM4.SYST_CSR()->setTICKINT(true); + CM4.SYST_CSR()->setENABLE(true); +} + +void shutdownSysTick() { + CM4.SYST_CSR()->setENABLE(false); + CM4.SYST_CSR()->setTICKINT(false); +} + +void coreReset() { + // Perform a full core reset + CM4.AIRCR()->requestReset(); +} + +void jumpReset() { + uint32_t * stackPointerAddress = reinterpret_cast(0x08000000); + uint32_t * resetHandlerAddress = reinterpret_cast(0x08000004); + + /* Jump to the reset service routine after having reset the stack pointer. + * Both addresses are fetched from the base of the Flash memory, just like a + * real reset would. These operations should be made at once, otherwise the C + * compiler might emit some instructions that modify the stack inbetween. */ + + asm volatile ( + "msr MSP, %[stackPointer] ; bx %[resetHandler]" + : : + [stackPointer] "r" (*stackPointerAddress), + [resetHandler] "r" (*resetHandlerAddress) + ); +} + +void init() { + initClocks(); + + // Ensure right location of interrupt vectors + // The bootloader leaves its own after flashing + SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory); + CM4.VTOR()->setVTOR((void*) 0); + + // Put all inputs as Analog Input, No pull-up nor pull-down + // Except for the SWD port (PB3, PA13, PA14) + GPIOA.MODER()->set(0xEBFFFFFF); + GPIOA.PUPDR()->set(0x24000000); + GPIOB.MODER()->set(0xFFFFFFBF); + GPIOB.PUPDR()->set(0x00000000); + for (int g=2; g<5; g++) { + GPIO(g).MODER()->set(0xFFFFFFFF); // All to "Analog" + GPIO(g).PUPDR()->set(0x00000000); // All to "None" + } + +#if EPSILON_DEVICE_BENCH + bool consolePeerConnectedOnBoot = Ion::Console::Device::peerConnected(); +#endif + + initPeripherals(); + +#if EPSILON_DEVICE_BENCH + if (consolePeerConnectedOnBoot) { + Ion::Device::Bench::run(); + } +#endif +} + +void shutdown() { + shutdownPeripherals(); + shutdownClocks(); +} + +void initPeripherals() { + Display::Device::init(); + Backlight::Device::init(); + Keyboard::Device::init(); + LED::Device::init(); + Battery::Device::init(); + USB::Device::init(); +#if USE_SD_CARD + SDCard::Device::init(); +#endif + Console::Device::init(); + SWD::Device::init(); + initSysTick(); +} + +void shutdownPeripherals(bool keepLEDAwake) { + shutdownSysTick(); + SWD::Device::shutdown(); + Console::Device::shutdown(); +#if USE_SD_CARD + SDCard::Device::shutdown(); +#endif + USB::Device::shutdown(); + Battery::Device::shutdown(); + if (!keepLEDAwake) { + LED::Device::shutdown(); + } + Keyboard::Device::shutdown(); + Backlight::Device::shutdown(); + Display::Device::shutdown(); +} + +void initClocks() { + /* System clock + * Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */ + + /* 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 90MHz the flash expects 3 WS. */ + FLASH.ACR()->setLATENCY(3); + + /* 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); + + /* Set flash instruction and data cache */ + FLASH.ACR()->setDCEN(true); + FLASH.ACR()->setICEN(true); + + /* 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 HSE and wait for it to be ready + RCC.CR()->setHSEON(true); + while(!RCC.CR()->getHSERDY()) { + } + + /* Given the crystal used on our device, the HSE will oscillate at 25 MHz. By + * piping it through a phase-locked loop (PLL) we can derive other frequencies + * for use in different parts of the system. Combining the default PLL values + * with a PLLM of 25 and a PLLQ of 4 yields both a 96 MHz frequency for SYSCLK + * and the required 48 MHz USB clock. */ + + // Configure the PLL ratios and use HSE as a PLL input + RCC.PLLCFGR()->setPLLM(25); + RCC.PLLCFGR()->setPLLQ(4); + RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); + // 96 MHz is too fast for APB1. Divide it by two to reach 48 MHz + RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy2); + + /* If you want to considerably slow down the whole machine uniformely, which + * can be very useful to diagnose performance issues, just uncomment the line + * below. Note that even booting takes a few seconds, so don't be surprised + * if the screen is black for a short while upon booting. */ + // RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy128); + + // Enable the PLL and wait for it to be ready + RCC.CR()->setPLLON(true); + 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(0); // 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); + + // APB2 bus + class RCC::APB2ENR apb2enr(0x00008000); // Reset value + apb2enr.setADC1EN(true); + apb2enr.setSYSCFGEN(true); +#if USE_SD_CARD + apb2enr.setSDIOEN(true); +#endif + RCC.APB2ENR()->set(apb2enr); +} + +void shutdownClocks(bool keepLEDAwake) { + // APB2 bus + RCC.APB2ENR()->set(0x00008000); // Reset value + + // APB1 + class RCC::APB1ENR apb1enr(0x00000400); // Reset value + // AHB1 bus + class RCC::AHB1ENR ahb1enr(0); // Reset value + if (keepLEDAwake) { + apb1enr.setTIM3EN(true); + ahb1enr.setGPIOBEN(true); + ahb1enr.setGPIOCEN(true); + } + RCC.APB1ENR()->set(apb1enr); + RCC.AHB1ENR()->set(ahb1enr); + + RCC.AHB3ENR()->setFSMCEN(false); +} + +} +} diff --git a/ion/src/f730/device.h b/ion/src/f730/device.h new file mode 100644 index 000000000..2bdccea28 --- /dev/null +++ b/ion/src/f730/device.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_H +#define ION_DEVICE_H + +namespace Ion { +namespace Device { + +void init(); +void shutdown(); + +void initFPU(); +#if 0 +void initMPU(); +#endif + +void initSysTick(); +void shutdownSysTick(); + +void coreReset(); +void jumpReset(); + +void initPeripherals(); +void shutdownPeripherals(bool keepLEDAwake = false); +void initClocks(); +void shutdownClocks(bool keepLEDAwake = false); + +/* The serial number is 96 bits long. That's equal to 16 digits in base 64. We + * expose a convenient "copySerialNumber" routine which can be called without + * using a static variable (and therefore without a .bss section). This is used + * in the RAM'ed DFU bootloader. */ +constexpr static int SerialNumberLength = 16; +void copySerialNumber(char * buffer); + +} +} + +#endif diff --git a/ion/src/f730/display.cpp b/ion/src/f730/display.cpp new file mode 100644 index 000000000..1804a7b19 --- /dev/null +++ b/ion/src/f730/display.cpp @@ -0,0 +1,376 @@ +#include +#include "display.h" +#include "regs/regs.h" +extern "C" { +#include +} + +/* This driver interfaces with the ST7789V LCD controller. + * This chip keeps a whole frame in SRAM memory and feeds it to the LCD panel as + * needed. We use the STM32's FSMC to drive the bus between the ST7789V. Once + * configured, we only need to write in the address space of the MCU to actually + * send some data to the LCD controller. */ + +#define USE_DMA_FOR_PUSH_PIXELS 0 +#define USE_DMA_FOR_PUSH_COLOR 0 + +#define USE_DMA (USE_DMA_FOR_PUSH_PIXELS|USE_DMA_FOR_PUSH_COLOR) + +// Public Ion::Display methods + +namespace Ion { +namespace Display { + +void pushRect(KDRect r, const KDColor * pixels) { +#if USE_DMA + Device::waitForPendingDMAUploadCompletion(); +#endif + Device::setDrawingArea(r, Device::Orientation::Landscape); + Device::pushPixels(pixels, r.width()*r.height()); +} + +void pushRectUniform(KDRect r, KDColor c) { +#if USE_DMA + Device::waitForPendingDMAUploadCompletion(); +#endif + Device::setDrawingArea(r, Device::Orientation::Portrait); + Device::pushColor(c, r.width()*r.height()); +} + +void pullRect(KDRect r, KDColor * pixels) { +#if USE_DMA + Device::waitForPendingDMAUploadCompletion(); +#endif + Device::setDrawingArea(r, Device::Orientation::Landscape); + Device::pullPixels(pixels, r.width()*r.height()); +} + +void waitForVBlank() { + // We want to return as soon as the TE line is transitionning from "DOWN" to "UP" + while (Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) { + // Loop while high, exit when low + // Wait for zero + } + while (!Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) { + // Loop while low, exit when high + } +} + +} +} + +// Private Ion::Display::Device methods + +namespace Ion { +namespace Display { +namespace Device { + +static inline void send_data(uint16_t d) { + *DataAddress = d; +} + +static inline uint16_t receive_data() { + return *DataAddress; +} + +template +static inline void send_data(uint16_t d, Args... other) { + send_data(d); + send_data(other...); +} + +static inline void send_command(Command c) { + *CommandAddress = c; +} + +template +static inline void send_command(Command c, Args... d) { + send_command(c); + send_data(d...); +} + +void init() { +#if USE_DMA + initDMA(); +#endif + initGPIO(); + initFSMC(); + initPanel(); +} + +void shutdown() { + shutdownPanel(); + shutdownFSMC(); + shutdownGPIO(); +} + +#if USE_DMA +void initDMA() { + // Only DMA2 can perform memory-to-memory transfers + //assert(DMAEngine == DMA2); + + /* In memory-to-memory transfers, the "peripheral" is the source and the + * "memory" is the destination. In other words, memory is copied from address + * DMA_SxPAR to address DMA_SxM0AR. */ + + DMAEngine.SCR(DMAStream)->setDIR(DMA::SCR::Direction::MemoryToMemory); + DMAEngine.SM0AR(DMAStream)->set((uint32_t)DataAddress); + DMAEngine.SCR(DMAStream)->setMSIZE(DMA::SCR::DataSize::HalfWord); + DMAEngine.SCR(DMAStream)->setPSIZE(DMA::SCR::DataSize::HalfWord); + DMAEngine.SCR(DMAStream)->setMBURST(DMA::SCR::Burst::Incremental4); + DMAEngine.SCR(DMAStream)->setPBURST(DMA::SCR::Burst::Incremental4); + DMAEngine.SCR(DMAStream)->setMINC(false); +} + +void waitForPendingDMAUploadCompletion() { + // Loop until DMA engine available + while (DMAEngine.SCR(DMAStream)->getEN()) { + } +} + +static inline void startDMAUpload(const KDColor * src, bool incrementSrc, uint16_t length) { + // Reset interruption markers + DMAEngine.LIFCR()->set(0xF7D0F7D); + + DMAEngine.SNDTR(DMAStream)->set(length); + DMAEngine.SPAR(DMAStream)->set((uint32_t)src); + DMAEngine.SCR(DMAStream)->setPINC(incrementSrc); + DMAEngine.SCR(DMAStream)->setEN(true); +} +#endif + +void initGPIO() { + // All the FSMC GPIO pins use the alternate function number 12 + for(const GPIOPin & g : FSMCPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF12); + } + + // Turn on the power + PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Output); + PowerPin.group().ODR()->set(PowerPin.pin(), true); + + // Turn on the reset pin + ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Output); + ResetPin.group().ODR()->set(ResetPin.pin(), true); + + // Turn on the extended command pin + ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Output); + ExtendedCommandPin.group().ODR()->set(ExtendedCommandPin.pin(), true); + + // Turn on the Tearing Effect pin + TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Input); + TearingEffectPin.group().PUPDR()->setPull(TearingEffectPin.pin(), GPIO::PUPDR::Pull::None); + + Timing::msleep(120); +} + + +void shutdownGPIO() { + // All the FSMC GPIO pins use the alternate function number 12 + for(const GPIOPin & g : FSMCPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } + + ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Analog); + ResetPin.group().PUPDR()->setPull(ResetPin.pin(), GPIO::PUPDR::Pull::None); + + PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Analog); + PowerPin.group().PUPDR()->setPull(PowerPin.pin(), GPIO::PUPDR::Pull::None); + + ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Analog); + ExtendedCommandPin.group().PUPDR()->setPull(ExtendedCommandPin.pin(), GPIO::PUPDR::Pull::None); + + TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Analog); +} + +void initFSMC() { + /* Set up the FSMC control registers. + * We address the LCD panel as if it were an SRAM module, using a 16bits wide + * bus, non-multiplexed. + * The STM32 FSMC supports two kinds of memory access modes : + * - Base modes (1 and 2), which use the same timings for reads and writes + * - Extended modes (named A to D), which can be customized further. + * The LCD panel can be written to faster than it can be read from, therefore + * we want to use one of the extended modes. */ + FSMC.BCR(FSMCMemoryBank)->setEXTMOD(true); + FSMC.BCR(FSMCMemoryBank)->setWREN(true); + FSMC.BCR(FSMCMemoryBank)->setMWID(FSMC::BCR::MWID::SIXTEEN_BITS); + FSMC.BCR(FSMCMemoryBank)->setMTYP(FSMC::BCR::MTYP::SRAM); + FSMC.BCR(FSMCMemoryBank)->setMUXEN(false); + FSMC.BCR(FSMCMemoryBank)->setMBKEN(true); + + /* We now need to set the actual timings. First, the FSMC and LCD specs don't + * use the same names. Here's the mapping: + * + * FSMC | LCD + * -----+----- + * NOE | RDX + * NWE | WRX + * NE1 | CSX + * A16 | D/CX + * Dn | Dn + * + * We need to set the values of the BTR and BWTR which gives the timings for + * reading and writing. Note that the STM32 datasheet doesn't take into + * account the time needed to actually switch from one logic state to another, + * whereas the ST7789V one does, so we'll add T(R) and T(F) as needed. + * Last but not least, timings on the STM32 have to be expressed in terms of + * HCLK = 1/96MHz = 10.42ns. + * - We'll pick Mode A which corresponds to SRAM with OE toggling + * - ADDSET = T(AST) + T(F) = 0ns + 15ns = 2 HCLK + * - ADDHLD is unused in this mode, set to 0 + * - DATAST(read) = T(RDLFM) + T(R) = 355ns + 15ns = 36 HCLK + * DATAST(write) = T(WRL) + T(R) = 15ns + 15ns = 3 HCLK + * - BUSTURN(read) = T(RDHFM) + T(F) = 90ns + 15ns = 10 HCLK + * BUSTURN(write) = T(RDHFM) + T(F) = 15ns + 15ns = 3 HCLK + */ + + // Read timing from the LCD + FSMC.BTR(FSMCMemoryBank)->setADDSET(2); + FSMC.BTR(FSMCMemoryBank)->setADDHLD(0); + FSMC.BTR(FSMCMemoryBank)->setDATAST(36); + FSMC.BTR(FSMCMemoryBank)->setBUSTURN(10); + FSMC.BTR(FSMCMemoryBank)->setACCMOD(FSMC::BTR::ACCMOD::A); + + // Write timings for the LCD + FSMC.BWTR(FSMCMemoryBank)->setADDSET(2); + FSMC.BWTR(FSMCMemoryBank)->setADDHLD(0); + FSMC.BWTR(FSMCMemoryBank)->setDATAST(3); + FSMC.BWTR(FSMCMemoryBank)->setBUSTURN(3); + FSMC.BWTR(FSMCMemoryBank)->setACCMOD(FSMC::BWTR::ACCMOD::A); +} + +void shutdownFSMC() { +} + +void initPanel() { + send_command(Command::Reset); + Timing::msleep(5); + + send_command(Command::SleepOut); + Timing::msleep(5); + + send_command(Command::PixelFormatSet, 0x05); + send_command(Command::TearingEffectLineOn, 0x00); + send_command(Command::FrameRateControl, 0x1E); // 40 Hz frame rate + + send_command(Command::DisplayOn); +} + +void shutdownPanel() { + send_command(Command::DisplayOff); + send_command(Command::SleepIn); + Timing::msleep(5); +} + +void setDrawingArea(KDRect r, Orientation o) { + uint16_t x_start, x_end, y_start, y_end; + + if (o == Orientation::Landscape) { + send_command(Command::MemoryAccessControl, 0xA0); + x_start = r.x(); + x_end = r.x() + r.width() - 1; + y_start = r.y(); + y_end = r.y() + r.height() - 1; + } else { + send_command(Command::MemoryAccessControl, 0x00); + x_start = r.y(); + x_end = r.y() + r.height() - 1; + y_start = Ion::Display::Width - (r.x() + r.width()); + y_end = Ion::Display::Width - r.x() - 1; + } + + send_command( + Command::ColumnAddressSet, + (x_start >> 8), + (x_start & 0xFF), + (x_end >> 8), + (x_end & 0xFF) + ); + + send_command( + Command::PageAddressSet, + (y_start >> 8), + (y_start & 0xFF), + (y_end >> 8), + (y_end & 0xFF) + ); +} + +void pushPixels(const KDColor * pixels, size_t numberOfPixels) { + send_command(Command::MemoryWrite); + /* Theoretically, we should not be able to use DMA here. Indeed, we have no + * guarantee that the content at "pixels" will remain valid once we exit this + * function call. In practice, we might be able to use DMA here because most + * of the time we push pixels from static locations. */ +#if USE_DMA_FOR_PUSH_PIXELS + startDMAUpload(pixels, true, numberOfPixels); +#else + while (numberOfPixels > 8) { + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + send_data(*pixels++); + numberOfPixels -= 8; + } + while (numberOfPixels--) { + send_data(*pixels++); + } +#endif +} + +void pushColor(KDColor color, size_t numberOfPixels) { + send_command(Command::MemoryWrite); +#if USE_DMA_FOR_PUSH_COLOR + /* The "color" variable lives on the stack. We cannot take its address because + * it will stop being valid as soon as we return. An easy workaround is to + * duplicate the content in a static variable, whose value is guaranteed to be + * kept until the next pushColor call. */ + static KDColor staticColor; + staticColor = color; + startDMAUpload(&staticColor, false, (numberOfPixels > 64000 ? 64000 : numberOfPixels)); +#else + while (numberOfPixels--) { + send_data(color); + } +#endif +} + +void pullPixels(KDColor * pixels, size_t numberOfPixels) { + if (numberOfPixels == 0) { + return; + } + send_command(Command::PixelFormatSet, 0x06); + send_command(Command::MemoryRead); + + receive_data(); // First read is dummy data, per datasheet + while (true) { + if (numberOfPixels == 0) { + break; + } + uint16_t one = receive_data(); + uint16_t two = receive_data(); + uint16_t firstPixel = (one & 0xF800) | (one & 0xFC) << 3 | (two & 0xF800) >> 11; + *pixels++ = KDColor::RGB16(firstPixel); + numberOfPixels--; + + if (numberOfPixels == 0) { + break; + } + uint16_t three = receive_data(); + uint16_t secondPixel = (two & 0xF8) << 8 | (three & 0xFC00) >> 5 | (three & 0xF8) >> 3; + *pixels++ = KDColor::RGB16(secondPixel); + numberOfPixels--; + } + send_command(Command::PixelFormatSet, 0x05); +} + +} +} +} diff --git a/ion/src/f730/display.h b/ion/src/f730/display.h new file mode 100644 index 000000000..e9147b90a --- /dev/null +++ b/ion/src/f730/display.h @@ -0,0 +1,110 @@ +#ifndef ION_DEVICE_DISPLAY_H +#define ION_DEVICE_DISPLAY_H + +#include +#include +extern "C" { +#include +} +#include "regs/regs.h" + +namespace Ion { +namespace Display { +namespace Device { + +/* Pin | Role | Mode | Function | Note + * -----+-------------------+-----------------------+----------|------ + * PA2 | LCD D4 | Alternate Function 12 | FSMC_D4 | + * PA3 | LCD D5 | Alternate Function 12 | FSMC_D5 | + * PA4 | LCD D6 | Alternate Function 12 | FSMC_D6 | + * PB12 | LCD D13 | Alternate Function 12 | FSMC_D13 | + * PB14 | LCD power | Output | | LCD controller is powered directly from GPIO + * PD0 | LCD D2 | Alternate Function 12 | FSMC_D2 | + * PD1 | LCD D3 | Alternate Function 12 | FSMC_D3 | + * PD4 | LCD read signal | Alternate Function 12 | FSMC_NOE | + * PD5 | LCD write signal | Alternate Function 12 | FSMC_NWE + * PD7 | LCD chip select | Alternate Function 12 | FSMC_NE1 | Memory bank 1 + * PD9 | LCD D14 | Alternate Function 12 | FSMC_D14 | + * PD10 | LCD D15 | Alternate Function 12 | FSMC_D15 | + * PD11 | LCD data/command | Alternate Function 12 | FSMC_A16 | Data/Command is address bit 16 + * PD14 | LCD D0 | Alternate Function 12 | FSMC_D0 | + * PD15 | LCD D1 | Alternate Function 12 | FSMC_D1 | + * PE9 | LCD reset | Output | | + * PE10 | LCD D7 | Alternate Function 12 | FSMC_D7 | + * PE11 | LCD D8 | Alternate Function 12 | FSMC_D8 | + * PE12 | LCD D9 | Alternate Function 12 | FSMC_D9 | + * PE13 | LCD D10 | Alternate Function 12 | FSMC_D10 | + * PE14 | LCD D11 | Alternate Function 12 | FSMC_D11 | + * PE15 | LCD D12 | Alternate Function 12 | FSMC_D12 | + */ + +void init(); +void shutdown(); + +void initDMA(); +void initGPIO(); +void shutdownGPIO(); +void initFSMC(); +void shutdownFSMC(); +void initPanel(); +void shutdownPanel(); + +enum class Orientation { + Landscape = 0, + Portrait = 1 +}; + +void setDrawingArea(KDRect r, Orientation o); +void waitForPendingDMAUploadCompletion(); +void pushPixels(const KDColor * pixels, size_t numberOfPixels); +void pushColor(KDColor color, size_t numberOfPixels); +void pullPixels(KDColor * pixels, size_t numberOfPixels); + +enum class Command : uint16_t { + Nop = 0x00, + Reset = 0x01, + SleepIn = 0x10, + SleepOut = 0x11, + DisplayOff = 0x28, + DisplayOn = 0x29, + ColumnAddressSet = 0x2A, + PageAddressSet = 0x2B, + MemoryWrite = 0x2C, + MemoryRead = 0x2E, + TearingEffectLineOn = 0x35, + MemoryAccessControl = 0x36, + PixelFormatSet = 0x3A, + FrameRateControl = 0xC6 +}; + +constexpr static GPIOPin FSMCPins[] = { + GPIOPin(GPIOA, 2), GPIOPin(GPIOA, 3), GPIOPin(GPIOA, 4), GPIOPin(GPIOB, 12), + GPIOPin(GPIOB, 12), GPIOPin(GPIOD, 0), GPIOPin(GPIOD, 1), GPIOPin(GPIOD, 4), + GPIOPin(GPIOD, 5), GPIOPin(GPIOD, 7), GPIOPin(GPIOD, 9), GPIOPin(GPIOD, 10), + GPIOPin(GPIOD, 11), GPIOPin(GPIOD, 14), GPIOPin(GPIOD, 15), GPIOPin(GPIOE, 10), + GPIOPin(GPIOE, 11), GPIOPin(GPIOE, 12), GPIOPin(GPIOE, 13), GPIOPin(GPIOE, 14), + GPIOPin(GPIOE, 15) +}; + +constexpr static GPIOPin PowerPin = GPIOPin(GPIOB, 14); +constexpr static GPIOPin ResetPin = GPIOPin(GPIOE, 9); +constexpr static GPIOPin ExtendedCommandPin = GPIOPin(GPIOB, 13); +constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 10); + +constexpr static int FSMCMemoryBank = 1; +constexpr static int FSMCDataCommandAddressBit = 16; + +constexpr static uint32_t FSMCBaseAddress = 0x60000000; +constexpr static uint32_t FSMCBankAddress = FSMCBaseAddress + (FSMCMemoryBank-1)*0x04000000; + +constexpr static DMA DMAEngine = DMA2; +constexpr static int DMAStream = 0; + +static volatile Command * const CommandAddress = (Command *)(FSMCBankAddress); +static volatile uint16_t * const DataAddress = (uint16_t *)(FSMCBankAddress | (1<<(FSMCDataCommandAddressBit+1))); + +} +} +} + +#endif diff --git a/ion/src/f730/events.cpp b/ion/src/f730/events.cpp new file mode 100644 index 000000000..03de2de53 --- /dev/null +++ b/ion/src/f730/events.cpp @@ -0,0 +1,93 @@ +#include +#include + +namespace Ion { +namespace Events { + +static bool sleepWithTimeout(int duration, int * timeout) { + if (*timeout >= duration) { + Timing::msleep(duration); + *timeout -= duration; + return false; + } else { + Timing::msleep(*timeout); + *timeout = 0; + return true; + } +} + +Event sLastEvent = Events::None; +Keyboard::State sLastKeyboardState; +bool sLastUSBPlugged = false; +bool sLastUSBEnumerated = false; +bool sEventIsRepeating = 0; +constexpr int delayBeforeRepeat = 200; +constexpr int delayBetweenRepeat = 50; + +bool canRepeatEvent(Event e) { + return (e == Events::Left || e == Events::Up || e == Events::Down || e == Events::Right || e == Events::Backspace); +} + +Event getEvent(int * timeout) { + assert(*timeout > delayBeforeRepeat); + assert(*timeout > delayBetweenRepeat); + int time = 0; + uint64_t keysSeenUp = 0; + uint64_t keysSeenTransitionningFromUpToDown = 0; + while (true) { + // First, check if the USB plugged status has changed + bool usbPlugged = USB::isPlugged(); + if (usbPlugged != sLastUSBPlugged) { + sLastUSBPlugged = usbPlugged; + return Events::USBPlug; + } + + // Second, check if the USB device has been connected to an USB host + bool usbEnumerated = USB::isEnumerated(); + if (usbEnumerated != sLastUSBEnumerated) { + sLastUSBEnumerated = usbEnumerated; + if (usbEnumerated) { + return Events::USBEnumeration; + } + } + + Keyboard::State state = Keyboard::scan(); + keysSeenUp |= ~state; + keysSeenTransitionningFromUpToDown = keysSeenUp & state; + + if (keysSeenTransitionningFromUpToDown != 0) { + sEventIsRepeating = false; + /* The key that triggered the event corresponds to the first non-zero bit + * in "match". This is a rather simple logic operation for the which many + * processors have an instruction (ARM thumb uses CLZ). + * Unfortunately there's no way to express this in standard C, so we have + * to resort to using a builtin function. */ + Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitionningFromUpToDown)); + Event event(key, isShiftActive(), isAlphaActive()); + updateModifiersFromEvent(event); + sLastEvent = event; + sLastKeyboardState = state; + return event; + } + + if (sleepWithTimeout(10, timeout)) { + // Timeout occured + return Events::None; + } + time += 10; + + // At this point, we know that keysSeenTransitionningFromUpToDown has *always* been zero + // In other words, no new key has been pressed + if (canRepeatEvent(sLastEvent) && (state == sLastKeyboardState)) { + int delay = (sEventIsRepeating ? delayBetweenRepeat : delayBeforeRepeat); + if (time >= delay) { + sEventIsRepeating = true; + sLastKeyboardState = state; + return sLastEvent; + } + } + } +} + +} +} diff --git a/ion/src/f730/flash.cpp b/ion/src/f730/flash.cpp new file mode 100644 index 000000000..b5b0bae8e --- /dev/null +++ b/ion/src/f730/flash.cpp @@ -0,0 +1,233 @@ +#include "flash.h" +#include + +namespace Ion { +namespace Flash { +namespace Device { + +static inline void wait() { + // Wait for pending Flash operations to complete + while (FLASH.SR()->getBSY()) { + } +} + +static void open() { + // Unlock the Flash configuration register if needed + if (FLASH.CR()->getLOCK()) { + FLASH.KEYR()->set(0x45670123); + FLASH.KEYR()->set(0xCDEF89AB); + } + assert(FLASH.CR()->getLOCK() == false); + + // Set the programming parallelism + FLASH.CR()->setPSIZE(MemoryAccessWidth); +} + +static void close() { + // Lock the Flash configuration register + assert(!FLASH.CR()->getMER()); + assert(!FLASH.CR()->getSER()); + assert(!FLASH.CR()->getPG()); + FLASH.CR()->setLOCK(true); + + // Purge Data and instruction cache + if (FLASH.ACR()->getDCEN()) { + FLASH.ACR()->setDCEN(false); + FLASH.ACR()->setDCRST(true); + FLASH.ACR()->setDCRST(false); + FLASH.ACR()->setDCEN(true); + } + if (FLASH.ACR()->getICEN()) { + FLASH.ACR()->setICEN(false); + FLASH.ACR()->setICRST(true); + FLASH.ACR()->setICRST(false); + FLASH.ACR()->setICEN(true); + } +} + +// Compile-time log2 +static inline constexpr size_t clog2(size_t input) { + return (input == 1) ? 0 : clog2(input/2)+1; +} + +// Align a pointer to a given type's boundaries +// Returns a value that is lower or equal to input +template +static inline T * align(void * input) { + size_t k = clog2(sizeof(T)); + return reinterpret_cast(reinterpret_cast(input) & ~((1< +static inline T eat(void * ptr) { + T * pointer = *reinterpret_cast(ptr); + T result = *pointer; + *reinterpret_cast(ptr) = pointer+1; + return result; +} + +static inline ptrdiff_t byte_offset(void * p1, void * p2) { + return reinterpret_cast(p2) - reinterpret_cast(p1); +} + +template +static inline T min(T i, T j) { + return (i| + * |-- HeaderDelta ->| + */ + + MemoryAccessType * alignedDestination = align(destination); + ptrdiff_t headerDelta = byte_offset(alignedDestination, destination); + assert(headerDelta >= 0 && headerDelta < static_cast(sizeof(MemoryAccessType))); + + if (headerDelta > 0) { + // At this point, alignedDestination < destination + // We'll then retrieve the current value at alignedDestination, fill it with + // bytes from source, and write it back at alignedDestination. + + // First, retrieve the current value at alignedDestination + MemoryAccessType header = *alignedDestination; + + // Then copy headerLength bytes from source and put them in the header + uint8_t * headerStart = reinterpret_cast(&header); + // Here's where source data shall start being copied in the header + uint8_t * headerDataStart = headerStart + headerDelta; + // And here's where it should end + uint8_t * headerDataEnd = min( + headerStart + sizeof(MemoryAccessType), // Either at the end of the header + headerDataStart + length // or whenever src runs out of data + ); + for (uint8_t * h = headerDataStart; h(&source); + } + + // Then eventually write the header back into the aligned destination + *alignedDestination++ = header; + } + + /* Step 2 - Copy the bulk of the data + * At this point, we can use aligned MemoryAccessType pointers. */ + + MemoryAccessType * lastAlignedDestination = align(destination + length); + while (alignedDestination < lastAlignedDestination) { + *alignedDestination++ = eat(&source); + } + + /* Step 3 - Copy a footer if needed + * Some unaligned data can be pending at the end. Let's take care of it like + * we did for the header. + * + * _alignedDst _Destination+length + * | | + * --+--------+--------+--------+--------+--------+--------+-- + * | || | | | || | + *---+--------+--------+--------+--------+--------+--------+-- + * |<------------ Footer ------------->| + * |- footerLength ->| + */ + + ptrdiff_t footerLength = byte_offset(alignedDestination, destination + length); + assert(footerLength < static_cast(sizeof(MemoryAccessType))); + if (footerLength > 0) { + assert(alignedDestination == lastAlignedDestination); + + // First, retrieve the current value at alignedDestination + MemoryAccessType footer = *alignedDestination; + + /* Then copy footerLength bytes from source and put them at the beginning of + * the footer */ + uint8_t * footerPointer = reinterpret_cast(&footer); + for (ptrdiff_t i=0; i(&source); + } + + // Then eventually write the footer back into the aligned destination + *alignedDestination = footer; + } +} + +int SectorAtAddress(uint32_t address) { + uint32_t sectorAddresses[NumberOfSectors+1] = { + 0x08000000, 0x08004000, 0x08008000, 0x0800C000, + 0x08010000, 0x08020000, 0x08040000, 0x08060000, + 0x08080000, 0x080A0000, 0x080C0000, 0x080E0000, + 0x08100000 + }; + for (int i=0; i= sectorAddresses[i] && address < sectorAddresses[i+1]) { + return i; + } + } + return -1; +} + +void MassErase() { + open(); + FLASH.CR()->setMER(true); + FLASH.CR()->setSTRT(true); + wait(); + FLASH.CR()->setMER(false); + close(); +} + +void EraseSector(int i) { + assert(i >= 0 && i < NumberOfSectors); + open(); + FLASH.CR()->setSNB(i); + FLASH.CR()->setSER(true); + FLASH.CR()->setSTRT(true); + wait(); + FLASH.CR()->setSNB(0); + FLASH.CR()->setSER(false); + close(); +} + +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { + open(); + FLASH.CR()->setPG(true); + flash_memcpy(source, destination, length); + wait(); + FLASH.CR()->setPG(false); + close(); +} + +} +} +} diff --git a/ion/src/f730/flash.h b/ion/src/f730/flash.h new file mode 100644 index 000000000..6d8c9b657 --- /dev/null +++ b/ion/src/f730/flash.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_FLASH_H +#define ION_DEVICE_FLASH_H + +#include +#include "regs/flash.h" + +namespace Ion { +namespace Flash { +namespace Device { + +void MassErase(); + +constexpr int NumberOfSectors = 12; +int SectorAtAddress(uint32_t address); +void EraseSector(int i); + +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); + +/* The Device is powered by a 2.8V LDO. This allows us to perform writes to the + * Flash 32 bits at once. */ +constexpr FLASH::CR::PSIZE MemoryAccessWidth = FLASH::CR::PSIZE::X32; +typedef uint32_t MemoryAccessType; + +} +} +} + +#endif diff --git a/ion/src/f730/keyboard.cpp b/ion/src/f730/keyboard.cpp new file mode 100644 index 000000000..91c634dae --- /dev/null +++ b/ion/src/f730/keyboard.cpp @@ -0,0 +1,112 @@ +/* Keyboard initialization code + * + * The job of this code is to implement the "ion_key_state" function. + * + * The keyboard is a matrix that is laid out as follow: + * + * | PC0 | PC1 | PC2 | PC3 | PC4 | PC5 | + * -----+------+------+------+------+------+------+ + * PE0 | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 | + * -----+------+------+------+------+------+------+ + * PE1 | K_B1 | K_B2 | | | | | + * -----+------+------+------+------+------+------+ + * PE2 | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 | + * -----+------+------+------+------+------+------+ + * PE3 | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 | + * -----+------+------+------+------+------+------+ + * PE4 | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 | + * -----+------+------+------+------+------+------+ + * PE5 | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | | + * -----+------+------+------+------+------+------+ + * PE6 | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | | + * -----+------+------+------+------+------+------+ + * PE7 | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | | + * -----+------+------+------+------+------+------+ + * PE8 | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | | + * -----+------+------+------+------+------+------| + * + * We decide to drive the rows (PE0-8) and read the columns (PC0-5). + * + * To avoid short-circuits, the pins E0-E8 will not be standard outputs but + * only open-drain. Open drain means the pin is either driven low or left + * floating. + * When a user presses multiple keys, a connection between two rows can happen. + * If we don't use open drain outputs, this situation could trigger a short + * circuit between an output driving high and another driving low. + * + * If the outputs are open-drain, this means that the input must be pulled up. + * So if the input reads "1", this means the key is in fact *not* pressed, and + * if it reads "0" it means that there's a short to an open-drain output. Which + * means the corresponding key is pressed. + */ + +#include "keyboard.h" + +// Public Ion::Keyboard methods + +namespace Ion { +namespace Keyboard { + +State scan() { + uint64_t state = 0; + + for (uint8_t i=0; igetBitRange(5,0); + + /* The key is down if the input is brought low by the output. In other + * words, we want to return true if the input is low (false). So we need to + * append 6 bits of (not columns) to state. */ + state = (state << 6) | (~columns & 0x3F); + } + + /* Last but not least, keys number 8, 9, 10, 11, 35, 41, 47 and 53 are not + * defined. Therefore we want to make sure those bits are forced to zero in + * whatever value we return. */ + state = state & 0x1F7DF7FFFFF0FF; + + return State(state); +} + +} +} + +// Private Ion::Keyboard::Device methods + +namespace Ion { +namespace Keyboard { +namespace Device { + +void init() { + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Output); + RowGPIO.OTYPER()->setType(pin, GPIO::OTYPER::Type::OpenDrain); + } + + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Input); + ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::Up); + } +} + +void shutdown() { + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Analog); + RowGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None); + } + + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Analog); + ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None); + } +} + +} +} +} diff --git a/ion/src/f730/keyboard.h b/ion/src/f730/keyboard.h new file mode 100644 index 000000000..fa69ab39f --- /dev/null +++ b/ion/src/f730/keyboard.h @@ -0,0 +1,70 @@ +#ifndef ION_DEVICE_KEYBOARD_H +#define ION_DEVICE_KEYBOARD_H + +#include +#include +#include "regs/regs.h" + +namespace Ion { +namespace Keyboard { +namespace Device { + +/* 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 + * PE0 | Keyboard row A | Output, open drain + * PE1 | Keyboard row B | Output, open drain + * PE2 | Keyboard row C | Output, open drain + * PE3 | Keyboard row D | Output, open drain + * PE4 | Keyboard row E | Output, open drain + * PE5 | Keyboard row F | Output, open drain + * PE6 | Keyboard row G | Output, open drain + * PE7 | Keyboard row H | Output, open drain + * PE8 | Keyboard row I | Output, open drain + */ + +void init(); +void shutdown(); + +constexpr GPIO RowGPIO = GPIOE; +constexpr uint8_t numberOfRows = 9; +constexpr uint8_t RowPins[numberOfRows] = {0, 1, 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}; + +inline uint8_t rowForKey(Key key) { + return (int)key/numberOfColumns; +} +inline uint8_t columnForKey(Key key) { + return (int)key%numberOfColumns; +} + +inline void activateRow(uint8_t row) { + /* In open-drain mode, a 0 in the register drives the pin low, and a 1 lets + * the pin floating (Hi-Z). So we want to set the current row to zero and all + * the others to 1. */ + uint16_t rowState = ~(1<setBitRange(9, 0, rowState); + + // TODO: 100 us seems to work, but wasn't really calculated + Timing::usleep(100); +} + +inline bool columnIsActive(uint8_t column) { + return !(Device::ColumnGPIO.IDR()->getBitRange(column,column)); +} + +} +} +} + +#endif diff --git a/ion/src/f730/led.cpp b/ion/src/f730/led.cpp new file mode 100644 index 000000000..028e8e846 --- /dev/null +++ b/ion/src/f730/led.cpp @@ -0,0 +1,133 @@ +#include +#include +#include "device.h" +#include "led.h" +#include "regs/regs.h" + +// Public Ion::LED methods + +static KDColor sLedColor = KDColorBlack; + +KDColor Ion::LED::getColor() { + return sLedColor; +} + +void Ion::LED::setColor(KDColor c) { + sLedColor = c; + + /* Active all RGB colors */ + TIM3.CCMR()->setOC2M(TIM::CCMR::OCM::PWM1); + TIM3.CCMR()->setOC4M(TIM::CCMR::OCM::PWM1); + TIM3.CCMR()->setOC3M(TIM::CCMR::OCM::PWM1); + + /* Set the PWM duty cycles to display the right color */ + constexpr float maxColorValue = (float)((1 << 8) -1); + Device::setPeriodAndDutyCycles(Device::Mode::PWM, c.red()/maxColorValue, c.green()/maxColorValue, c.blue()/maxColorValue); +} + +void Ion::LED::setBlinking(uint16_t period, float dutyCycle) { + /* We want to use the PWM at a slow rate to display a seeable blink. + * Consequently, we do not use PWM to display the right color anymore but to + * blink. We cannot use the PWM to display the exact color so we 'project the + * color on 3 bits' : all colors have 2 states - active or not. */ + TIM3.CCMR()->setOC2M(sLedColor.red() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOC4M(sLedColor.green() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOC3M(sLedColor.blue() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + + Device::setPeriodAndDutyCycles(Device::Mode::Blink, dutyCycle, dutyCycle, dutyCycle, period); +} + +// Private Ion::Device::LED methods + +namespace Ion { +namespace LED { +namespace Device { + +void init() { + initGPIO(); + initTimer(); +} + +void shutdown() { + shutdownTimer(); + shutdownGPIO(); +} + +void initGPIO() { + /* RED_LED(PC7), GREEN_LED(PB1), and BLUE_LED(PB0) are driven using a timer, + * which is an alternate function. More precisely, we will use AF2, which maps + * PB0 to TIM3_CH2, PB1 to TIM3_CH4, and PC7 to TIM3_CH2. */ + for(const GPIOPin & g : RGBPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF2); + } +} + +void shutdownGPIO() { + for(const GPIOPin & g : RGBPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } +} + +void initTimer() { + // Output preload enable for channels 2-4 + TIM3.CCMR()->setOC2PE(true); + TIM3.CCMR()->setOC3PE(true); + TIM3.CCMR()->setOC4PE(true); + + // Auto-reload preload enable + TIM3.CR1()->setARPE(true); + + // Enable Capture/Compare for channel 2 to 4 + TIM3.CCER()->setCC2E(true); + TIM3.CCER()->setCC3E(true); + TIM3.CCER()->setCC4E(true); + + TIM3.BDTR()->setMOE(true); + + TIM3.CR1()->setCEN(true); +} + +void shutdownTimer() { + TIM3.CCMR()->setOC2M(TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOC4M(TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOC3M(TIM::CCMR::OCM::ForceInactive); +} + +/* Pulse width modulation mode allows you to generate a signal with a + * frequency determined by the value of the TIMx_ARR register and a duty cycle + * determined by the value of the TIMx_CCRx register. */ + +void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period) { + switch (mode) { + case Mode::PWM: + /* Let's set the prescaler to 1. Increasing the prescaler would slow down + * the modulation, which can be useful when debugging or when we want an + * actual blinking. */ + TIM3.PSC()->set(1); + TIM3.ARR()->set(PWMPeriod); + period = PWMPeriod; + break; + case Mode::Blink: + int systemClockFreq = 96; + /* We still want to do PWM, but at a rate slow enough to blink. Ideally, + * we want to pre-scale the period to be able to set it in milliseconds; + * however, as the prescaler is cap by 2^16-1, we divide it by a factor + * and correct the period consequently. */ + int factor = 2; + // TODO: explain the 2 ? + TIM3.PSC()->set(systemClockFreq*1000/factor); + period *= factor; + TIM3.ARR()->set(period); + break; + } + + TIM3.CCR2()->set(dutyCycleRed*period); + TIM3.CCR3()->set(dutyCycleBlue*period); + TIM3.CCR4()->set(dutyCycleGreen*period); +} + +} +} +} diff --git a/ion/src/f730/led.h b/ion/src/f730/led.h new file mode 100644 index 000000000..fe46994d9 --- /dev/null +++ b/ion/src/f730/led.h @@ -0,0 +1,48 @@ +#ifndef ION_DEVICE_LED_H +#define ION_DEVICE_LED_H + +#include "regs/regs.h" + +namespace Ion { +namespace LED { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PB0 | LED blue | Alternate Function 2 | TIM3_CH3 + * PB1 | LED green | Alternate Function 2 | TIM3_CH4 + * PC7 | LED red | Alternate Function 2 | TIM3_CH2 + */ + +enum class Mode { + PWM, + Blink +}; + +enum class Color { + Red, + Green, + Blue +}; + +void init(); +void shutdown(); +void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period = 0); + +void initGPIO(); +void shutdownGPIO(); +void initTimer(); +void shutdownTimer(); + +constexpr static GPIOPin RGBPins[] = { + GPIOPin(GPIOC, 7), GPIOPin(GPIOB, 1), GPIOPin(GPIOB, 0) +}; + + +constexpr uint16_t PWMPeriod = 40000; + +} +} +} + +#endif diff --git a/ion/src/f730/log.cpp b/ion/src/f730/log.cpp new file mode 100644 index 000000000..25ee1e7e2 --- /dev/null +++ b/ion/src/f730/log.cpp @@ -0,0 +1,15 @@ +#include +#include "regs/itm.h" + +// We're printing using SWO. +// This is achieved by writing to the ITM register, which is sent through the +// Cortex Debug bus + +void ion_log_string(const char * message) { + char character = 0; + while ((character = *message++) != 0) { + if (ITM.TER()->get(0)) { + ITM.STIM(0)->set(character); + } + } +} diff --git a/ion/src/f730/power.cpp b/ion/src/f730/power.cpp new file mode 100644 index 000000000..021d75cc6 --- /dev/null +++ b/ion/src/f730/power.cpp @@ -0,0 +1,69 @@ +#include +#include "battery.h" +#include "device.h" +#include "display.h" +#include "keyboard.h" +#include "led.h" +#include "usb.h" +#include "wakeup.h" +#include "regs/regs.h" + +void Ion::Power::suspend(bool checkIfPowerKeyReleased) { + bool isLEDActive = Ion::LED::getColor() != KDColorBlack; + if (checkIfPowerKeyReleased) { + /* Wait until power is released to avoid restarting just after suspending */ + bool isPowerDown = true; + while (isPowerDown) { + Keyboard::State scan = Keyboard::scan(); + isPowerDown = scan.keyDown(Keyboard::Key::B2); + } + } + Device::shutdownPeripherals(isLEDActive); + + PWR.CR()->setLPDS(true); // Turn the regulator off. Takes longer to wake up. + PWR.CR()->setFPDS(true); // Put the flash to sleep. Takes longer to wake up. + CM4.SCR()->setSLEEPDEEP(!isLEDActive); + + while (1) { +#if EPSILON_LED_WHILE_CHARGING + /* Update LEDS + * if the standby mode was stopped due to a "stop charging" event, we wait + * a while to be sure that the plug state of the USB is up-to-date. */ + msleep(200); + //Ion::LED::setCharging(Ion::USB::isPlugged(), Ion::Battery::isCharging()); +#endif + + WakeUp::Device::onPowerKeyDown(); + WakeUp::Device::onUSBPlugging(); +#if EPSILON_LED_WHILE_CHARGING + WakeUp::Device::onChargingEvent(); +#endif + + Device::shutdownClocks(isLEDActive); + + /* To enter sleep, we need to issue a WFE instruction, which waits for the + * event flag to be set and then clears it. However, the event flag might + * already be on. So the safest way to make sure we actually wait for a new + * event is to force the event flag to on (SEV instruction), use a first WFE + * to clear it, and then a second WFE to wait for a _new_ event. */ + asm("sev"); + asm("wfe"); + asm("nop"); + asm("wfe"); + + Device::initClocks(); + + Keyboard::Device::init(); + Keyboard::State scan = Keyboard::scan(); + Keyboard::Device::shutdown(); + + Ion::Keyboard::State OnlyPowerKeyDown = Keyboard::State(Keyboard::Key::B2); + if (scan == OnlyPowerKeyDown || USB::isPlugged()) { + // Wake up + break; + } + } + Device::initClocks(); + + Device::initPeripherals(); +} diff --git a/ion/src/f730/regs/adc.h b/ion/src/f730/regs/adc.h new file mode 100644 index 000000000..2881cf23b --- /dev/null +++ b/ion/src/f730/regs/adc.h @@ -0,0 +1,73 @@ +#ifndef REGS_ADC_H +#define REGS_ADC_H + +#include "register.h" + +class ADC { +public: + class SR : Register32 { + public: + REGS_BOOL_FIELD(EOC, 1); + }; + + class CR1 : Register32 { + public: + }; + + class CR2 : Register32 { + public: + REGS_BOOL_FIELD(ADON, 0); + REGS_BOOL_FIELD(SWSTART, 30); + }; + + class SMPR : Register64 { + /* The SMPR register doesn't exist per-se in the documentation. It is the + * consolidation of SMPR1 and SMPR2 which are two 32-bit registers. */ + public: + enum class SamplingTime { + Cycles3 = 0, + Cycles15 = 1, + Cycles28 = 2, + Cycles56 = 3, + Cycles84 = 4, + Cycles112 = 5, + Cycles144 = 6, + Cycles480 = 7 + }; + SamplingTime getSamplingTime(int channel) { + return (SamplingTime)getBitRange(3*channel+2, 3*channel); + } + void setSamplingTime(int channel, SamplingTime t) volatile { + setBitRange(3*channel+2, 3*channel, (uint64_t)t); + } + }; + + class SQR1 : Register32 { + public: + REGS_FIELD(L, uint8_t, 23, 20); + }; + + class SQR3 : Register32 { + public: + REGS_FIELD(SQ1, uint8_t, 4, 0); + }; + + class DR : public Register16 { + }; + + constexpr ADC() {}; + REGS_REGISTER_AT(SR, 0x0); + REGS_REGISTER_AT(CR2, 0x08); + REGS_REGISTER_AT(SMPR, 0x0C); + REGS_REGISTER_AT(SQR1, 0x2C); + REGS_REGISTER_AT(SQR3, 0x34); + REGS_REGISTER_AT(DR, 0x4C); +private: + constexpr uint32_t Base() const { + return 0x40012000; + }; +}; + +constexpr ADC ADC; + +#endif diff --git a/ion/src/f730/regs/cm4.h b/ion/src/f730/regs/cm4.h new file mode 100644 index 000000000..58c5af1cb --- /dev/null +++ b/ion/src/f730/regs/cm4.h @@ -0,0 +1,81 @@ +#ifndef REGS_CM4_H +#define REGS_CM4_H + +#include "register.h" + +class CM4 { +public: + // Vector table offset register + // http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf + class VTOR : Register32 { + public: + void setVTOR(void *address) volatile { setBitRange(29, 9, (uint32_t)address); } + }; + + // Coprocessor Access Control Register + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BEHBJHIG.html + class CPACR : public Register32 { + public: + enum class Access { + Denied = 0, + PrivilegedOnly = 1, + Full = 3 + }; + void setAccess(int index, Access a) volatile { setBitRange(2*index+1, 2*index, (uint32_t)a); } + }; + + + // Application Interrupt and Reset Control Register + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Cihehdge.html + class AIRCR : public Register32 { + public: + void requestReset() volatile { + set(0x5FA<<16 |(1<<2)); + } + }; + + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/Cihhjgdh.html + class SCR : public Register32 { + public: + REGS_BOOL_FIELD(SLEEPDEEP, 2); + }; + + class SYST_CSR : public Register32 { + public: + enum class CLKSOURCE : uint8_t { + AHB_DIV8 = 0, + AHB = 1 + }; + REGS_BOOL_FIELD(COUNTFLAG, 16); + REGS_TYPE_FIELD(CLKSOURCE, 2, 2); + REGS_BOOL_FIELD(TICKINT, 1); + REGS_BOOL_FIELD(ENABLE, 0); + }; + + class SYST_RVR : public Register32 { + public: + REGS_FIELD(RELOAD, uint32_t, 23, 0); + }; + + class SYST_CVR : public Register32 { + public: + REGS_FIELD(CURRENT, uint32_t, 23, 0); + }; + + constexpr CM4() {}; + REGS_REGISTER_AT(SYST_CSR, 0x10); + REGS_REGISTER_AT(SYST_RVR, 0x14); + REGS_REGISTER_AT(SYST_CVR, 0x18); + REGS_REGISTER_AT(VTOR, 0xD08); + REGS_REGISTER_AT(AIRCR, 0xD0C); + REGS_REGISTER_AT(SCR, 0xD10); + REGS_REGISTER_AT(CPACR, 0xD88); +private: + constexpr uint32_t Base() const { + return 0xE000E000; + } +}; + +constexpr CM4 CM4; + +#endif diff --git a/ion/src/f730/regs/crc.h b/ion/src/f730/regs/crc.h new file mode 100644 index 000000000..6ae4a2c10 --- /dev/null +++ b/ion/src/f730/regs/crc.h @@ -0,0 +1,27 @@ +#ifndef REGS_CRC_H +#define REGS_CRC_H + +#include "register.h" + +class CRC { +public: + class DR : public Register32 { + }; + + class CR : Register32 { + public: + REGS_BOOL_FIELD(RESET, 0); + }; + + constexpr CRC() {}; + REGS_REGISTER_AT(DR, 0x00); + REGS_REGISTER_AT(CR, 0x08); +private: + constexpr uint32_t Base() const { + return 0x40023000; + }; +}; + +constexpr CRC CRC; + +#endif diff --git a/ion/src/f730/regs/dma.h b/ion/src/f730/regs/dma.h new file mode 100644 index 000000000..de2acc702 --- /dev/null +++ b/ion/src/f730/regs/dma.h @@ -0,0 +1,63 @@ +#ifndef REGS_DMA_H +#define REGS_DMA_H + +#include "register.h" + +class DMA { +public: + class LIFCR : public Register32 { + }; + class SCR : Register32 { + public: + enum class Burst : uint8_t { + Single = 0, + Incremental4 = 1, + Incremental8 = 2, + Incremental16 = 3 + }; + enum class DataSize : uint8_t { + Byte = 0, + HalfWord = 1, + Word = 2 + }; + enum class Direction : uint8_t { + PeripheralToMemory = 0, + MemoryToPeripheral = 1, + MemoryToMemory = 2 + }; + REGS_FIELD(CHSEL, uint8_t, 27, 25); + REGS_FIELD(MBURST, Burst, 24, 23); + REGS_FIELD(PBURST, Burst, 22, 21); + REGS_FIELD(MSIZE, DataSize, 14, 13); + REGS_FIELD(PSIZE, DataSize, 12, 11); + REGS_BOOL_FIELD(MINC, 10); + REGS_BOOL_FIELD(PINC, 9); + REGS_BOOL_FIELD(CIRC, 8); + REGS_FIELD(DIR, Direction, 7, 6); + REGS_BOOL_FIELD(EN, 0); + }; + class SNDTR : public Register32 { + }; + class SPAR : public Register32 { + }; + class SM0AR : public Register32 { + }; + + constexpr DMA(int i) : m_index(i) {} + //constexpr operator int() const { return m_index; } + REGS_REGISTER_AT(LIFCR, 0x08); + volatile SCR * SCR(int i ) const { return (class SCR *)(Base() + 0x10 + 0x18*i); }; + volatile SNDTR * SNDTR(int i ) const { return (class SNDTR *)(Base() + 0x14 + 0x18*i); }; + volatile SPAR * SPAR(int i ) const { return (class SPAR *)(Base() + 0x18 + 0x18*i); }; + volatile SM0AR * SM0AR(int i ) const { return (class SM0AR *)(Base() + 0x1C + 0x18*i); }; +private: + constexpr uint32_t Base() const { + return 0x40026000 + 0x400*m_index; + }; + int m_index; +}; + +constexpr DMA DMA1(0); +constexpr DMA DMA2(1); + +#endif diff --git a/ion/src/f730/regs/exti.h b/ion/src/f730/regs/exti.h new file mode 100644 index 000000000..46ee8550b --- /dev/null +++ b/ion/src/f730/regs/exti.h @@ -0,0 +1,34 @@ +#ifndef REGS_EXTI_H +#define REGS_EXTI_H + +#include "register.h" + +class EXTI { +public: + class MaskRegister : Register32 { + public: + bool get(int index) { return (bool)getBitRange(index, index); } + void set(int index, bool state) volatile { setBitRange(index, index, state); } + }; + + class IMR : public MaskRegister { }; + class EMR : public MaskRegister { }; + class RTSR : public MaskRegister { }; + class FTSR : public MaskRegister { }; + class PR : public MaskRegister { }; + + constexpr EXTI() {}; + REGS_REGISTER_AT(IMR, 0x00); + REGS_REGISTER_AT(EMR, 0x04); + REGS_REGISTER_AT(RTSR, 0x08); + REGS_REGISTER_AT(FTSR, 0x0C); + REGS_REGISTER_AT(PR, 0x14); +private: + constexpr uint32_t Base() const { + return 0x40013C00; + } +}; + +constexpr EXTI EXTI; + +#endif diff --git a/ion/src/f730/regs/flash.h b/ion/src/f730/regs/flash.h new file mode 100644 index 000000000..218b17e4b --- /dev/null +++ b/ion/src/f730/regs/flash.h @@ -0,0 +1,56 @@ +#ifndef REGS_FLASH_H +#define REGS_FLASH_H + +#include "register.h" + +class FLASH { +public: + class ACR : public Register32 { + public: + REGS_FIELD(LATENCY, uint8_t, 3, 0); + REGS_BOOL_FIELD(PRFTEN, 8); + REGS_BOOL_FIELD(ICEN, 9); + REGS_BOOL_FIELD(DCEN, 10); + REGS_BOOL_FIELD(ICRST, 11); + REGS_BOOL_FIELD(DCRST, 12); + }; + + class KEYR : public Register32 { + }; + + class CR : public Register32 { + public: + enum class PSIZE : uint8_t { + X8 = 0, + X16 = 1, + X32 = 2, + X64 = 3 + }; + REGS_BOOL_FIELD(PG, 0); + REGS_BOOL_FIELD(SER, 1); + REGS_BOOL_FIELD(MER, 2); + REGS_FIELD(SNB, uint8_t, 6, 3); + REGS_TYPE_FIELD(PSIZE, 9, 8); + REGS_BOOL_FIELD(STRT, 16); + REGS_BOOL_FIELD(LOCK, 31); + }; + + class SR : public Register32 { + public: + REGS_BOOL_FIELD(BSY, 16); + }; + + constexpr FLASH() {}; + REGS_REGISTER_AT(ACR, 0x00); + REGS_REGISTER_AT(KEYR, 0x04); + REGS_REGISTER_AT(SR, 0x0C); + REGS_REGISTER_AT(CR, 0x10); +private: + constexpr uint32_t Base() const { + return 0x40023C00; + } +}; + +constexpr FLASH FLASH; + +#endif diff --git a/ion/src/f730/regs/fsmc.h b/ion/src/f730/regs/fsmc.h new file mode 100644 index 000000000..52bfcbdac --- /dev/null +++ b/ion/src/f730/regs/fsmc.h @@ -0,0 +1,79 @@ +#ifndef REGS_FSMC_H +#define REGS_FSMC_H + +#include "register.h" + +class FSMC { +public: + class BCR : Register32 { + public: + enum class MTYP : uint8_t { + SRAM = 0, + PSRAM = 1, + NOR = 2 + }; + enum class MWID : uint8_t { + EIGHT_BITS = 0, + SIXTEEN_BITS = 1 + }; + REGS_BOOL_FIELD(MBKEN, 0); + REGS_BOOL_FIELD(MUXEN, 1); + REGS_TYPE_FIELD(MTYP, 3, 2); + REGS_TYPE_FIELD(MWID, 5, 4); + REGS_BOOL_FIELD(WREN, 12); + REGS_BOOL_FIELD(EXTMOD, 14); + }; + + class BTR : Register32 { + public: + enum class ACCMOD : uint8_t { + A = 0, + B = 1, + C = 2, + D = 3 + }; + REGS_FIELD(ADDSET, uint8_t, 3, 0); + REGS_FIELD(ADDHLD, uint8_t, 7, 4); + REGS_FIELD(DATAST, uint8_t, 15, 8); + REGS_FIELD(BUSTURN, uint8_t, 19, 16); + REGS_FIELD(CLKDIV, uint8_t, 23, 20); + REGS_FIELD(DATLAT, uint8_t, 27, 24); + REGS_TYPE_FIELD(ACCMOD, 29, 28); + }; + + class BWTR : Register32 { + public: + enum class ACCMOD : uint8_t { + A = 0, + B = 1, + C = 2, + D = 3 + }; + REGS_FIELD(ADDSET, uint8_t, 3, 0); + REGS_FIELD(ADDHLD, uint8_t, 7, 4); + REGS_FIELD(DATAST, uint8_t, 15, 8); + REGS_FIELD(BUSTURN, uint8_t, 19, 16); + REGS_FIELD(CLKDIV, uint8_t, 23, 20); + REGS_FIELD(DATLAT, uint8_t, 27, 24); + REGS_TYPE_FIELD(ACCMOD, 29, 28); + }; + + constexpr FSMC() {} + volatile BCR * BCR(int index) const { + return (class BCR *)(Base() + 8*(index-1)); + } + volatile BTR * BTR(int index) const { + return (class BTR *)(Base() + 4 + 8*(index-1)); + } + volatile BWTR * BWTR(int index) const { + return (class BWTR *)(Base() + 0x104 + 8*(index-1)); + } +private: + constexpr uint32_t Base() const { + return 0xA0000000; + }; +}; + +constexpr FSMC FSMC; + +#endif diff --git a/ion/src/f730/regs/gpio.h b/ion/src/f730/regs/gpio.h new file mode 100644 index 000000000..2371ac0eb --- /dev/null +++ b/ion/src/f730/regs/gpio.h @@ -0,0 +1,104 @@ +#ifndef REGS_GPIO_H +#define REGS_GPIO_H + +#include "register.h" + +class GPIO { +public: + class MODER : public Register32 { + public: + enum class Mode { + Input = 0, + Output = 1, + AlternateFunction = 2, + Analog = 3 + }; + Mode getMode(int index) { return (Mode)getBitRange(2*index+1, 2*index); } + void setMode(int index, Mode mode) volatile { setBitRange(2*index+1, 2*index, (uint32_t)mode); } + }; + + class OTYPER : Register32 { + public: + enum class Type { + PushPull = 0, + OpenDrain = 1 + }; + Type getType(int index) { return (Type)getBitRange(index, index); } + void setType(int index, Type type) volatile { setBitRange(index, index, (uint32_t)type); } + }; + + class PUPDR : public Register32 { + public: + enum class Pull { + None = 0, + Up = 1, + Down = 2, + Reserved = 3 + }; + Pull getPull(int index) { return (Pull)getBitRange(2*index+1, 2*index); } + void setPull(int index, Pull pull) volatile { setBitRange(2*index+1, 2*index, (uint32_t)pull); } + }; + + class IDR : public Register32 { + public: + bool get(int index) volatile { return (bool)getBitRange(index, index); } + }; + + class ODR : public Register32 { + public: + bool get(int index) volatile { return (bool)getBitRange(index, index); } + void set(int index, bool state) volatile { setBitRange(index, index, state); } + }; + + class AFR : Register64 { + /* The AFR register doesn't exist per-se in the documentation. It is the + * consolidation of AFRL and AFRH which are two 32 bits registers. */ + public: + enum class AlternateFunction { + AF0 = 0, AF1 = 1, AF2 = 2, AF3 = 3, + AF4 = 4, AF5 = 5, AF6 = 6, AF7 = 7, + AF8 = 8, AF9 = 9, AF10 = 10, AF11 = 11, + AF12 = 12, AF13 = 13, AF14 = 14, AF15 = 15 + }; + AlternateFunction getAlternateFunction(int index) { + return (AlternateFunction)getBitRange(4*index+3, 4*index); + } + void setAlternateFunction(int index, AlternateFunction af) volatile { + setBitRange(4*index+3, 4*index, (uint64_t)af); + } + }; + + constexpr GPIO(int i) : m_index(i) {} + constexpr operator int() const { return m_index; } + REGS_REGISTER_AT(MODER, 0x00); + REGS_REGISTER_AT(OTYPER, 0x04); + REGS_REGISTER_AT(PUPDR, 0x0C); + REGS_REGISTER_AT(IDR, 0x10); + REGS_REGISTER_AT(ODR, 0x14); + REGS_REGISTER_AT(AFR, 0x20); +private: + constexpr uint32_t Base() const { + return 0x40020000 + 0x400*m_index; + }; + int m_index; +}; + +constexpr GPIO GPIOA(0); +constexpr GPIO GPIOB(1); +constexpr GPIO GPIOC(2); +constexpr GPIO GPIOD(3); +constexpr GPIO GPIOE(4); +constexpr GPIO GPIOF(5); +constexpr GPIO GPIOG(6); +constexpr GPIO GPIOH(7); + +class GPIOPin { +public: + constexpr GPIOPin(GPIO group, uint8_t pin) : m_data(((group&0xF) << 4) | (pin&0xF)) {} + GPIO group() const { return GPIO(m_data>>4); } + uint8_t pin() const { return m_data & 0xF; } +private: + uint8_t m_data; +}; + +#endif diff --git a/ion/src/f730/regs/itm.h b/ion/src/f730/regs/itm.h new file mode 100644 index 000000000..196737ca3 --- /dev/null +++ b/ion/src/f730/regs/itm.h @@ -0,0 +1,32 @@ +#ifndef REGS_ITM_H +#define REGS_ITM_H + +#include "register.h" + +// See ARM Cortex M4 TRM + +class ITM { +public: + class STIM : public Register8 { + }; + + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0314h/Chdbicac.html + class TER : Register32 { + public: + bool get(int index) volatile { return (bool)getBitRange(index, index); } + }; + + constexpr ITM() {}; + volatile STIM * STIM(int i) const { + return (class STIM *)(Base() + 4*i); + }; + REGS_REGISTER_AT(TER, 0xE00); +private: + constexpr uint32_t Base() const { + return 0xE0000000; + } +}; + +constexpr ITM ITM; + +#endif diff --git a/ion/src/f730/regs/mpu.h b/ion/src/f730/regs/mpu.h new file mode 100644 index 000000000..f9c2049f4 --- /dev/null +++ b/ion/src/f730/regs/mpu.h @@ -0,0 +1,71 @@ +#ifndef REGS_MPU_H +#define REGS_MPU_H + +#include "register.h" + +class MPU { +public: + class TYPER : Register32 { + public: + REGS_FIELD_R(IREGION, uint8_t, 23, 16); + REGS_FIELD_R(DREGION, uint8_t, 15, 8); + REGS_BOOL_FIELD_R(SEPARATE, 0); + }; + + class CTRL : Register32 { + public: + REGS_BOOL_FIELD(PRIVDEFENA, 2); + REGS_BOOL_FIELD(HFNMIENA, 1); + REGS_BOOL_FIELD(ENABLE, 0); + }; + + class RNR : Register32 { + public: + REGS_FIELD(REGION, uint8_t, 7, 0); + }; + + class RBAR : Register32 { + public: + void setADDR(void * address) volatile { assert(((uint32_t)address & 0b11111) == 0); setBitRange(31, 5, (uint32_t)address >> 5); } + REGS_BOOL_FIELD(VALID, 4); + REGS_FIELD(REGION, uint8_t, 3, 0); + }; + + class RASR : Register32 { + public: + REGS_BOOL_FIELD(XN, 28); + REGS_FIELD(AP, uint8_t, 26, 24); + REGS_FIELD(TEX, uint8_t, 21, 19); + REGS_BOOL_FIELD(S, 18); + REGS_BOOL_FIELD(C, 17); + REGS_BOOL_FIELD(B, 16); + REGS_FIELD(SRD, uint8_t, 15, 8); + enum class RegionSize : uint8_t { + Bytes32 = 0b00100, + Bytes64 = 0b00101, + Bytes128 = 0b00110, + KyloBytes1 = 0b01001, + MegaBytes1 = 0b10011, + GigaBytes1 = 0b11101, + GigaBytes4 = 0b11111 + }; + + REGS_FIELD(SIZE, RegionSize, 5, 1); + REGS_BOOL_FIELD(ENABLE, 0); + }; + + constexpr MPU() {}; + REGS_REGISTER_AT(TYPER, 0x0); + REGS_REGISTER_AT(CTRL, 0x04); + REGS_REGISTER_AT(RNR, 0x08); + REGS_REGISTER_AT(RBAR, 0x0C); + REGS_REGISTER_AT(RASR, 0x10); +private: + constexpr uint32_t Base() const { + return 0xE000ED90; + }; +}; + +constexpr MPU MPU; + +#endif diff --git a/ion/src/f730/regs/nvic.h b/ion/src/f730/regs/nvic.h new file mode 100644 index 000000000..dd72ea0d5 --- /dev/null +++ b/ion/src/f730/regs/nvic.h @@ -0,0 +1,37 @@ +#ifndef REGS_NVIC_H +#define REGS_NVIC_H + +#include "register.h" + +// http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf +class NVIC { +public: + class MaskRegister : Register32 { + public: + bool get(int index) { return (bool)getBitRange(index, index); } + void set(int index, bool state) volatile { setBitRange(index, index, state); } + }; + + class NVIC_ISER0 : public MaskRegister { }; + class NVIC_ISER1 : public MaskRegister { }; + class NVIC_ISER2 : public MaskRegister { }; + class NVIC_ICER0 : public MaskRegister { }; + class NVIC_ICER1 : public MaskRegister { }; + class NVIC_ICER2 : public MaskRegister { }; + + constexpr NVIC() {}; + REGS_REGISTER_AT(NVIC_ISER0, 0x00); + REGS_REGISTER_AT(NVIC_ISER1, 0x04); + REGS_REGISTER_AT(NVIC_ISER2, 0x08); + REGS_REGISTER_AT(NVIC_ICER0, 0x80); + REGS_REGISTER_AT(NVIC_ICER1, 0x84); + REGS_REGISTER_AT(NVIC_ICER2, 0x88); +private: + constexpr uint32_t Base() const { + return 0xE000E100; + } +}; + +constexpr NVIC NVIC; + +#endif diff --git a/ion/src/f730/regs/otg.h b/ion/src/f730/regs/otg.h new file mode 100644 index 000000000..5c4f0c89e --- /dev/null +++ b/ion/src/f730/regs/otg.h @@ -0,0 +1,195 @@ +#ifndef REGS_OTG_H +#define REGS_OTG_H + +#include "register.h" + +class OTG { +public: + class GAHBCFG : public Register32 { + public: + REGS_BOOL_FIELD(GINTMSK, 0); + }; + + class GUSBCFG : public Register32 { + public: + REGS_BOOL_FIELD(PHYSEL, 6); + REGS_FIELD(TRDT, uint8_t, 13, 10); + REGS_BOOL_FIELD(FDMOD, 30); + }; + + class GRSTCTL : public Register32 { + public: + REGS_BOOL_FIELD(CSRST, 0); + REGS_BOOL_FIELD(RXFFLSH, 4); + REGS_BOOL_FIELD(TXFFLSH, 5); + REGS_FIELD(TXFNUM, uint8_t, 10, 6); + REGS_BOOL_FIELD(AHBIDL, 31); + }; + + class GINTSTS : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(MMIS, 1); + REGS_BOOL_FIELD(SOF, 3); + REGS_BOOL_FIELD(RXFLVL, 4); + REGS_BOOL_FIELD(USBSUSP, 11); + REGS_BOOL_FIELD(USBRST, 12); + REGS_BOOL_FIELD(ENUMDNE, 13); + REGS_BOOL_FIELD(IEPINT, 18); + REGS_BOOL_FIELD(WKUPINT, 31); + }; + + class GINTMSK : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(RXFLVLM, 4); + REGS_BOOL_FIELD(USBSUSPM, 11); + REGS_BOOL_FIELD(USBRST, 12); + REGS_BOOL_FIELD(ENUMDNEM, 13); + REGS_BOOL_FIELD(IEPINT, 18); + REGS_BOOL_FIELD(WUIM, 31); + }; + + class GRXSTSP : public Register32 { + public: + using Register32::Register32; + enum class PKTSTS { + GlobalOutNAK = 1, + OutReceived = 2, + OutTransferCompleted = 3, // After each Out Transaction + SetupTransactionCompleted = 4, // Supposed to be after each SETUP transaction + SetupReceived = 6 + }; + REGS_FIELD(EPNUM, uint8_t, 3, 0); + REGS_FIELD(BCNT, uint16_t, 14, 4); + PKTSTS getPKTSTS() volatile { return (PKTSTS)getBitRange(20, 17); } + }; + + class GRXFSIZ : public Register32 { + public: + REGS_FIELD(RXFD, uint16_t, 15, 0); + }; + + class DIEPTXF0 : public Register32 { + public: + REGS_FIELD(TX0FSA, uint16_t, 15, 0); + REGS_FIELD(TX0FD, uint16_t, 31, 16); + }; + + class GCCFG : public Register32 { + public: + REGS_BOOL_FIELD(PWRDWN, 16); + REGS_BOOL_FIELD(VBDEN, 21); + }; + + class DCFG : public Register32 { + public: + enum class DSPD { + FullSpeed = 3, + }; + void setDSPD(DSPD s) volatile { setBitRange(1, 0, (uint8_t)s); } + REGS_FIELD(DAD, uint8_t, 10, 4); + }; + + class DCTL : public Register32 { + public: + REGS_BOOL_FIELD(SDIS, 1); + }; + + class DIEPMSK : public Register32 { + public: + REGS_BOOL_FIELD(XFRCM, 0); + }; + + class DAINTMSK : public Register32 { + public: + REGS_FIELD(IEPM, uint16_t, 15, 0); + REGS_FIELD(OEPM, uint16_t, 31, 16); + }; + + class DIEPCTL0 : public Register32 { + public: + enum class MPSIZ { + Size64 = 0, + Size32 = 1, + Size16 = 2, + Size8 = 3 + }; + using Register32::Register32; + void setMPSIZ(MPSIZ s) volatile { setBitRange(1, 0, (uint8_t)s); } + REGS_BOOL_FIELD(STALL, 21); + REGS_FIELD(TXFNUM, uint8_t, 25, 22); + REGS_BOOL_FIELD(CNAK, 26); + REGS_BOOL_FIELD(SNAK, 27); + REGS_BOOL_FIELD(EPENA, 31); + }; + + class DOEPCTL0 : public Register32 { + public: + REGS_BOOL_FIELD(CNAK, 26); + REGS_BOOL_FIELD(SNAK, 27); + REGS_BOOL_FIELD(EPENA, 31); + }; + + class DIEPINT : public Register32 { + public: + REGS_BOOL_FIELD(XFRC, 0); + REGS_BOOL_FIELD(INEPNE, 6); + }; + + class DIEPTSIZ0 : public Register32 { + public: + using Register32::Register32; + REGS_FIELD(XFRSIZ, uint8_t, 6, 0); + REGS_FIELD(PKTCNT, uint8_t, 20, 19); + }; + + class DOEPTSIZ0 : public Register32 { + public: + using Register32::Register32; + REGS_FIELD(XFRSIZ, uint8_t, 6, 0); + REGS_BOOL_FIELD(PKTCNT, 19); + REGS_FIELD(STUPCNT, uint8_t, 30, 29); + }; + + class PCGCCTL : public Register32 { + public: + REGS_BOOL_FIELD(STPPCLK, 0); + REGS_BOOL_FIELD(GATEHCLK, 1); + }; + + class DFIFO0 : public Register32 { + }; + + constexpr OTG() {}; + REGS_REGISTER_AT(GAHBCFG, 0x008); + REGS_REGISTER_AT(GUSBCFG, 0x00C); + REGS_REGISTER_AT(GRSTCTL, 0x010); + REGS_REGISTER_AT(GINTSTS, 0x014); + REGS_REGISTER_AT(GINTMSK, 0x018); + REGS_REGISTER_AT(GRXSTSP, 0x020); + REGS_REGISTER_AT(GRXFSIZ, 0x024); + REGS_REGISTER_AT(DIEPTXF0, 0x28); + REGS_REGISTER_AT(GCCFG, 0x038); + REGS_REGISTER_AT(DCFG, 0x800); + REGS_REGISTER_AT(DCTL, 0x804); + REGS_REGISTER_AT(DIEPMSK, 0x810); + REGS_REGISTER_AT(DAINTMSK, 0x81C); + REGS_REGISTER_AT(DIEPCTL0, 0x900); + REGS_REGISTER_AT(DIEPTSIZ0, 0x910); + REGS_REGISTER_AT(DOEPCTL0, 0xB00); + REGS_REGISTER_AT(DOEPTSIZ0, 0xB10); + REGS_REGISTER_AT(PCGCCTL, 0xE00); + REGS_REGISTER_AT(DFIFO0, 0x1000); + constexpr volatile DIEPINT * DIEPINT(int i) const { + return (class DIEPINT *)(Base() + 0x908 + i*0x20); + } +private: + constexpr uint32_t Base() const { + return 0x50000000; + } +}; + +constexpr OTG OTG; + +#endif diff --git a/ion/src/f730/regs/pwr.h b/ion/src/f730/regs/pwr.h new file mode 100644 index 000000000..089d72e18 --- /dev/null +++ b/ion/src/f730/regs/pwr.h @@ -0,0 +1,25 @@ +#ifndef REGS_PWR_H +#define REGS_PWR_H + +#include "register.h" + +class PWR { +public: + class CR : Register32 { + public: + REGS_BOOL_FIELD(LPDS, 0); + REGS_BOOL_FIELD(PPDS, 1); + REGS_BOOL_FIELD(FPDS, 9); + }; + + constexpr PWR() {}; + REGS_REGISTER_AT(CR, 0x00); +private: + constexpr uint32_t Base() const { + return 0x40007000; + }; +}; + +constexpr PWR PWR; + +#endif diff --git a/ion/src/f730/regs/rcc.h b/ion/src/f730/regs/rcc.h new file mode 100644 index 000000000..0536441d4 --- /dev/null +++ b/ion/src/f730/regs/rcc.h @@ -0,0 +1,149 @@ +#ifndef REGS_RCC_H +#define REGS_RCC_H + +#include "register.h" + +class RCC { +public: + class CR : public Register32 { + public: + REGS_BOOL_FIELD(HSION, 0); + REGS_BOOL_FIELD(HSEON, 16); + REGS_BOOL_FIELD_R(HSERDY, 17); + REGS_BOOL_FIELD(PLLON, 24); + REGS_BOOL_FIELD(PLLRDY, 25); + }; + + class PLLCFGR : public Register32 { + public: + REGS_FIELD(PLLM, uint8_t, 5, 0); + REGS_FIELD(PLLN, uint16_t, 14, 6); + REGS_FIELD(PLLP, uint8_t, 17, 16); + enum class PLLSRC { + HSI = 0, + HSE = 1 + }; + void setPLLSRC(PLLSRC s) volatile { setBitRange(22, 22, (uint8_t)s); } + REGS_FIELD(PLLQ, uint8_t, 27, 24); + REGS_FIELD(PLLR, uint8_t, 30, 28); + }; + + class CFGR : public Register32 { + public: + enum class SW { + HSI = 0, + HSE = 1, + PLL = 2 + }; + void setSW(SW s) volatile { setBitRange(1, 0, (uint8_t)s); } + SW getSWS() volatile { return (SW)getBitRange(3,2); } + enum class AHBPrescaler { + SysClk = 0, + SysClkDividedBy2 = 8, + SysClkDividedBy4 = 9, + SysClkDividedBy8 = 10, + SysClkDividedBy16 = 11, + SysClkDividedBy64 = 12, + SysClkDividedBy128 = 13, + SysClkDividedBy256 = 14, + SysClkDividedBy512 = 15 + }; + void setHPRE(AHBPrescaler p) volatile { setBitRange(7, 4, (uint32_t)p); } + enum class APBPrescaler{ + AHB = 0, + AHBDividedBy2 = 4, + AHBDividedBy4 = 5, + AHBDividedBy8 = 6, + AHBDividedBy16 = 7 + }; + void setPPRE1(APBPrescaler r) volatile { setBitRange(12, 10, (uint32_t)r); } + }; + + class AHB1ENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(GPIOAEN, 0); + REGS_BOOL_FIELD(GPIOBEN, 1); + REGS_BOOL_FIELD(GPIOCEN, 2); + REGS_BOOL_FIELD(GPIODEN, 3); + REGS_BOOL_FIELD(GPIOEEN, 4); + REGS_BOOL_FIELD(GPIOFEN, 5); + REGS_BOOL_FIELD(GPIOGEN, 6); + REGS_BOOL_FIELD(GPIOHEN, 7); + REGS_BOOL_FIELD(CRCEN, 12); + REGS_BOOL_FIELD(DMA1EN, 21); + REGS_BOOL_FIELD(DMA2EN, 22); + }; + + class AHB2ENR : Register32 { + public: + REGS_BOOL_FIELD(RNGEN, 6); + REGS_BOOL_FIELD(OTGFSEN, 7); + }; + + class AHB3ENR : Register32 { + public: + REGS_BOOL_FIELD(FSMCEN, 0); + REGS_BOOL_FIELD(QSPIEN, 1); + }; + + class APB1ENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(TIM3EN, 1); + REGS_BOOL_FIELD(SPI3EN, 15); + REGS_BOOL_FIELD(USART3EN, 18); + REGS_BOOL_FIELD(PWREN, 28); + }; + + class APB2ENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(TIM1EN, 0); + REGS_BOOL_FIELD(USART1EN, 4); + REGS_BOOL_FIELD(ADC1EN, 8); + REGS_BOOL_FIELD(SDIOEN, 11); + REGS_BOOL_FIELD(SPI1EN, 12); + REGS_BOOL_FIELD(SYSCFGEN, 14); + }; + + class AHB1LPENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(GPIOBLPEN, 1); + REGS_BOOL_FIELD(GPIOCLPEN, 2); + }; + + class APB1LPENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(TIM3LPEN, 1); + }; + + class DCKCFGR2 : Register32 { + public: + REGS_BOOL_FIELD(CK48MSEL, 27); + REGS_BOOL_FIELD(CKSDIOSEL, 28); + }; + + constexpr RCC() {}; + REGS_REGISTER_AT(CR, 0x00); + REGS_REGISTER_AT(PLLCFGR, 0x04); + REGS_REGISTER_AT(CFGR, 0x08); + REGS_REGISTER_AT(AHB1ENR, 0x30); + REGS_REGISTER_AT(AHB2ENR, 0x34); + REGS_REGISTER_AT(AHB3ENR, 0x38); + REGS_REGISTER_AT(APB1ENR, 0x40); + REGS_REGISTER_AT(APB2ENR, 0x44); + REGS_REGISTER_AT(AHB1LPENR, 0x50); + REGS_REGISTER_AT(APB1LPENR, 0x60); + REGS_REGISTER_AT(DCKCFGR2, 0x94); +private: + constexpr uint32_t Base() const { + return 0x40023800; + } +}; + +constexpr RCC RCC; + +#endif diff --git a/ion/src/f730/regs/register.h b/ion/src/f730/regs/register.h new file mode 100644 index 000000000..5ed38d779 --- /dev/null +++ b/ion/src/f730/regs/register.h @@ -0,0 +1,61 @@ +#ifndef REGS_REGISTER_H +#define REGS_REGISTER_H + +#include +#include + +template +class Register { +public: + Register() = delete; + Register(T v) : m_value(v) {} + void set(Register value) volatile { + m_value = value.m_value; + } + void set(T value) volatile { + m_value = value; + } + T get() volatile { + return m_value; + } + void setBitRange(uint8_t high, uint8_t low, T value) volatile { + m_value = bit_range_set_value(high, low, m_value, value); + } + T getBitRange(uint8_t high, uint8_t low) volatile { + /* "Shift behavior is undefined if the right operand is negative, or greater + * than or equal to the length in bits of the promoted left operand" according + * to C++ spec. */ + assert(low < 8*sizeof(T)); + return (m_value & bit_range_mask(high,low)) >> low; + } +protected: + static constexpr T bit_range_mask(uint8_t high, uint8_t low) { + // Same comment as for getBitRange: we should assert (high-low+1) < 8*sizeof(T) + return ((((T)1)<<(high-low+1))-1)< Register8; +typedef Register Register16; +typedef Register Register32; +typedef Register Register64; + +#define REGS_FIELD_R(name,type,high,low) type get##name() volatile { return (type)getBitRange(high,low); }; +#define REGS_FIELD_W(name,type,high,low) void set##name(type v) volatile { static_assert(sizeof(type) <= 4, "Invalid size"); setBitRange(high, low, static_cast(v)); }; +#define REGS_FIELD(name,type,high,low) REGS_FIELD_R(name,type,high,low); REGS_FIELD_W(name,type,high,low); +#define REGS_TYPE_FIELD(name,high,low) REGS_FIELD(name,name,high,low) +#define REGS_BOOL_FIELD(name,bit) REGS_FIELD(name,bool,bit,bit) +#define REGS_BOOL_FIELD_R(name,bit) REGS_FIELD_R(name,bool,bit,bit) +#define REGS_BOOL_FIELD_W(name,bit) REGS_FIELD_W(name,bool,bit,bit) +#define REGS_REGISTER_AT(name, offset) constexpr volatile name * name() const { return (class name *)(Base() + offset); }; + +#endif diff --git a/ion/src/f730/regs/regs.h b/ion/src/f730/regs/regs.h new file mode 100644 index 000000000..b5fad9e8a --- /dev/null +++ b/ion/src/f730/regs/regs.h @@ -0,0 +1,25 @@ +#ifndef REGS_REGS_H +#define REGS_REGS_H + +#include "adc.h" +#include "cm4.h" +#include "crc.h" +#include "dma.h" +#include "exti.h" +#include "flash.h" +#include "fsmc.h" +#include "gpio.h" +#include "itm.h" +#include "mpu.h" +#include "nvic.h" +#include "pwr.h" +#include "rcc.h" +#include "rng.h" +#include "otg.h" +#include "sdio.h" +#include "spi.h" +#include "syscfg.h" +#include "tim.h" +#include "usart.h" + +#endif diff --git a/ion/src/f730/regs/rng.h b/ion/src/f730/regs/rng.h new file mode 100644 index 000000000..6aa2d6505 --- /dev/null +++ b/ion/src/f730/regs/rng.h @@ -0,0 +1,33 @@ +#ifndef REGS_RNG_H +#define REGS_RNG_H + +#include "register.h" + +class RNG { +public: + class CR : Register32 { + public: + REGS_BOOL_FIELD(RNGEN, 2); + }; + + class SR : Register32 { + public: + REGS_BOOL_FIELD(DRDY, 0); + }; + + class DR : public Register32 { + }; + + constexpr RNG() {}; + REGS_REGISTER_AT(CR, 0x00); + REGS_REGISTER_AT(SR, 0x04); + REGS_REGISTER_AT(DR, 0x08); +private: + constexpr uint32_t Base() const { + return 0x50060800; + } +}; + +constexpr RNG RNG; + +#endif diff --git a/ion/src/f730/regs/sdio.h b/ion/src/f730/regs/sdio.h new file mode 100644 index 000000000..bb8149175 --- /dev/null +++ b/ion/src/f730/regs/sdio.h @@ -0,0 +1,112 @@ +#ifndef REGS_SDIO_H +#define REGS_SDIO_H + +#include "register.h" + +class SDIO { +public: + class POWER : Register32 { + public: + enum class PWRCTRL : uint8_t { + Off = 0, + On = 3 + }; + REGS_TYPE_FIELD(PWRCTRL, 1, 0); + }; + + class CLKCR : Register32 { + public: + enum class WIDBUS : uint8_t { + Default = 0, + FourBits = 1, + EightBits = 2 + }; + REGS_FIELD(CLKDIV, uint8_t, 7, 0); + REGS_BOOL_FIELD(CLKEN, 8); + REGS_BOOL_FIELD(PWRSAV, 9); + REGS_BOOL_FIELD(BYPASS, 10); + REGS_TYPE_FIELD(WIDBUS, 12, 11); + REGS_BOOL_FIELD(NEGEDGE, 13); + REGS_BOOL_FIELD(HWFC_EN, 14); + }; + + class ARG : public Register32 { + }; + + class CMD : public Register32 { + public: + using Register32::Register32; + enum class WAITRESP : uint8_t { + None = 0, + Short = 1, + Long = 3 + }; + REGS_FIELD(CMDINDEX, uint8_t, 5, 0); + REGS_TYPE_FIELD(WAITRESP, 7, 6); + REGS_BOOL_FIELD(WAITINT, 8); + REGS_BOOL_FIELD(WAITPEND, 9); + REGS_BOOL_FIELD(CPSMEN, 10); + }; + + class RESP : public Register32 { + }; + + class STA : Register32 { + public: + REGS_BOOL_FIELD_R(CCRCFAIL, 0); + REGS_BOOL_FIELD_R(DCRCFAIL, 1); + REGS_BOOL_FIELD_R(CTIMEOUT, 2); + REGS_BOOL_FIELD_R(DTIMEOUT, 3); + REGS_BOOL_FIELD_R(TXUNDERR, 4); + REGS_BOOL_FIELD_R(RXOVERR, 5); + REGS_BOOL_FIELD_R(CMDREND, 6); + REGS_BOOL_FIELD_R(CMDSENT, 7); + REGS_BOOL_FIELD_R(DATAEND, 8); + REGS_BOOL_FIELD_R(DBCKEND, 10); + REGS_BOOL_FIELD_R(CMDACT, 11); + REGS_BOOL_FIELD_R(TXACT, 12); + REGS_BOOL_FIELD_R(RXACT, 13); + REGS_BOOL_FIELD_R(TXFIFOHE, 14); + REGS_BOOL_FIELD_R(RXFIFOHF, 15); + REGS_BOOL_FIELD_R(TXFIFOF, 16); + REGS_BOOL_FIELD_R(RXFIFOF, 17); + REGS_BOOL_FIELD_R(TXFIFOE, 18); + REGS_BOOL_FIELD_R(RXFIFOE, 19); + REGS_BOOL_FIELD_R(TXDAVL, 20); + REGS_BOOL_FIELD_R(RXDAVL, 21); + REGS_BOOL_FIELD_R(SDIOIT, 22); + }; + + class ICR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD_W(CCRCFAILC, 0); + REGS_BOOL_FIELD_W(DCRCFAILC, 1); + REGS_BOOL_FIELD_W(CTIMEOUTC, 2); + REGS_BOOL_FIELD_W(DTIMEOUTC, 3); + REGS_BOOL_FIELD_W(TXUNDERRC, 4); + REGS_BOOL_FIELD_W(RXOVERRC, 5); + REGS_BOOL_FIELD_W(CMDRENDC, 6); + REGS_BOOL_FIELD_W(CMDSENTC, 7); + REGS_BOOL_FIELD_W(DATAENDC, 8); + REGS_BOOL_FIELD_W(DBCKENDC, 10); + REGS_BOOL_FIELD_W(SDIOITC, 22); + }; + + constexpr SDIO() {}; + REGS_REGISTER_AT(POWER, 0x00); + REGS_REGISTER_AT(CLKCR, 0x04); + REGS_REGISTER_AT(ARG, 0x08); + REGS_REGISTER_AT(CMD, 0x0C); + volatile RESP * RESP(int i ) const { return (class RESP *)(Base() + 0x10+4*i); }; + REGS_REGISTER_AT(STA, 0x34); + REGS_REGISTER_AT(ICR, 0x38); +private: + constexpr uint32_t Base() const { + return 0x40012C00; + } +}; + +constexpr SDIO SDIO; + +#endif diff --git a/ion/src/f730/regs/spi.h b/ion/src/f730/regs/spi.h new file mode 100644 index 000000000..abe050b97 --- /dev/null +++ b/ion/src/f730/regs/spi.h @@ -0,0 +1,45 @@ +#ifndef REGS_SPI_H +#define REGS_SPI_H + +#include "register.h" + +class SPI { +public: + class CR1 : Register16 { + public: + REGS_BOOL_FIELD(MSTR, 2); + REGS_BOOL_FIELD(SPE, 6); + REGS_BOOL_FIELD(LSBFIRST, 7); + REGS_BOOL_FIELD(SSI, 8); + REGS_BOOL_FIELD(SSM, 9); + REGS_BOOL_FIELD(RXONLY, 10); + REGS_BOOL_FIELD(DFF, 11); + }; + class CR2 : Register16 { + public: + REGS_BOOL_FIELD(RXDMAEN, 0); + }; + class SR : Register16 { + public: + REGS_BOOL_FIELD(RXNE, 0); + REGS_BOOL_FIELD(TXE, 1); + }; + class DR : public Register16 { + }; + + constexpr SPI(int i) : m_index(i) {} + constexpr operator int() const { return m_index; } + REGS_REGISTER_AT(CR1, 0x00); + REGS_REGISTER_AT(CR2, 0x04); + REGS_REGISTER_AT(SR, 0x08); + REGS_REGISTER_AT(DR, 0x0C); +private: + constexpr uint32_t Base() const { + return ((uint32_t []){0x40013000, 0x40003800, 0x40003C00})[m_index-1]; + }; + int m_index; +}; + +constexpr SPI SPI1(1); + +#endif diff --git a/ion/src/f730/regs/syscfg.h b/ion/src/f730/regs/syscfg.h new file mode 100644 index 000000000..0690b9430 --- /dev/null +++ b/ion/src/f730/regs/syscfg.h @@ -0,0 +1,44 @@ +#ifndef REGS_SYSCFG_H +#define REGS_SYSCFG_H + +#include "register.h" + +#include "gpio.h" + +class SYSCFG { +public: + class MEMRMP : Register32 { + public: + enum class MemMode : uint8_t { + MainFlashmemory = 0, + SystemFlashmemory = 1, + EmbeddedSRAM = 3 + }; + REGS_FIELD(MEM_MODE, MemMode, 1, 0); + }; + class EXTICR1 : Register32 { + public: + void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*index+3, 4*index, (uint32_t)gpio); } + }; + class EXTICR2 : Register32 { + public: + void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*(index-4)+3, 4*(index-4), (uint32_t)gpio); } + }; + class EXTICR3 : Register32 { + public: + void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*(index-8)+3, 4*(index-8), (uint32_t)gpio); } + }; + constexpr SYSCFG() {}; + REGS_REGISTER_AT(MEMRMP, 0x00); + REGS_REGISTER_AT(EXTICR1, 0x08); + REGS_REGISTER_AT(EXTICR2, 0x0C); + REGS_REGISTER_AT(EXTICR3, 0x10); +private: + constexpr uint32_t Base() const { + return 0x40013800; + } +}; + +constexpr SYSCFG SYSCFG; + +#endif diff --git a/ion/src/f730/regs/tim.h b/ion/src/f730/regs/tim.h new file mode 100644 index 000000000..d76f8e293 --- /dev/null +++ b/ion/src/f730/regs/tim.h @@ -0,0 +1,100 @@ +#ifndef REGS_TIM_H +#define REGS_TIM_H + +#include "register.h" + +template +class TIM { +public: + class CR1 : Register16 { + public: + REGS_BOOL_FIELD(CEN, 0); + REGS_BOOL_FIELD(ARPE, 7); + }; + + class CCMR : Register64 { + /* We're declaring CCMR as a 64 bits register. CCMR doesn't exsist per se, + * it is in fact the consolidation of CCMR1 and CCMR2. Both are 16 bits + * registers, so one could expect the consolidation to be 32 bits. However, + * both CCMR1 and CCMR2 live on 32-bits boundaries, so the consolidation has + * to be 64 bits wide, even though we'll only use 32 bits out of 64. */ + public: + enum class CC1S : uint8_t { + OUTPUT = 0, + INPUT_TI2 = 1, + INPUT_TI1 = 2, + INPUT_TRC = 3 + }; + enum class OCM : uint8_t { + Frozen = 0, + ActiveOnMatch = 1, + InactiveOnMatch = 2, + Toggle = 3, + ForceInactive = 4, + ForceActive = 5, + PWM1 = 6, + PWM2 = 7 + }; + typedef OCM OC1M; + typedef OCM OC2M; + typedef OCM OC3M; + typedef OCM OC4M; + REGS_BOOL_FIELD(OC1PE, 3); + REGS_TYPE_FIELD(OC1M, 6, 4); + REGS_BOOL_FIELD(OC2PE, 11); + REGS_TYPE_FIELD(OC2M, 14, 12); + REGS_BOOL_FIELD(OC3PE, 35); + REGS_TYPE_FIELD(OC3M, 38, 36); + REGS_BOOL_FIELD(OC4PE, 43); + REGS_TYPE_FIELD(OC4M, 46, 44); + }; + + class CCER : Register16 { + public: + REGS_BOOL_FIELD(CC1E, 0); + REGS_BOOL_FIELD(CC2E, 4); + REGS_BOOL_FIELD(CC3E, 8); + REGS_BOOL_FIELD(CC4E, 12); + }; + + class BDTR : Register16 { + public: + REGS_BOOL_FIELD(MOE, 15); + }; + + class PSC : public Register16 {}; + class ARR : public Register16 {}; + class CCR1 : public RegisterWidth {}; + class CCR2 : public RegisterWidth {}; + class CCR3 : public RegisterWidth {}; + class CCR4 : public RegisterWidth {}; + + constexpr TIM(int i) : m_index(i) {} + REGS_REGISTER_AT(CR1, 0x0); + REGS_REGISTER_AT(CCMR, 0x18); + REGS_REGISTER_AT(CCER, 0x20); + REGS_REGISTER_AT(PSC, 0x28); + REGS_REGISTER_AT(ARR, 0x2C); + REGS_REGISTER_AT(CCR1, 0x34); + REGS_REGISTER_AT(CCR2, 0x38); + REGS_REGISTER_AT(CCR3, 0x3C); + REGS_REGISTER_AT(CCR4, 0x40); + REGS_REGISTER_AT(BDTR, 0x44); +private: + constexpr uint32_t Base() const { + return (m_index == 1 ? 0x40010000 : + (m_index <= 7 ? 0x40000000 + 0x400*(m_index-2) : + (m_index == 8 ? 0x40010400 : + (m_index <= 11 ? 0x40014000 + 0x400*(m_index-9) : + 0x40001800 + 0x400*(m_index-12) + ) + ) + ) + ); + } + int m_index; +}; + +constexpr TIM TIM3(3); + +#endif diff --git a/ion/src/f730/regs/usart.h b/ion/src/f730/regs/usart.h new file mode 100644 index 000000000..cb454c2d6 --- /dev/null +++ b/ion/src/f730/regs/usart.h @@ -0,0 +1,50 @@ +#ifndef REGS_USART_H +#define REGS_USART_H + +#include "register.h" + +class USART { +public: + class SR : Register32 { + public: + REGS_BOOL_FIELD(RXNE, 5); + REGS_BOOL_FIELD(TXE, 7); + }; + + class DR : Register32 { + public: + uint16_t get() volatile { + return (uint16_t)getBitRange(8, 0); + } + void set(uint16_t v) volatile { + setBitRange(8, 0, v); + } + }; + + class BRR : Register32 { + public: + REGS_FIELD(DIV_FRAC, uint8_t, 3, 0); + REGS_FIELD(DIV_MANTISSA, uint16_t, 15, 4); + }; + + class CR1 : Register32 { + public: + REGS_BOOL_FIELD(UE, 13); + REGS_BOOL_FIELD(TE, 3); + REGS_BOOL_FIELD(RE, 2); + }; + + constexpr USART(int i) : m_index(i) {} + constexpr operator int() const { return m_index; } + REGS_REGISTER_AT(SR, 0x00); + REGS_REGISTER_AT(DR, 0x04); + REGS_REGISTER_AT(BRR, 0x08); + REGS_REGISTER_AT(CR1, 0x0C); +private: + constexpr uint32_t Base() const { + return ((uint32_t []){0x40011000, 0x40004400, 0x40004800})[m_index-1]; + }; + int m_index; +}; + +#endif diff --git a/ion/src/f730/sd_card.cpp b/ion/src/f730/sd_card.cpp new file mode 100644 index 000000000..e677c63be --- /dev/null +++ b/ion/src/f730/sd_card.cpp @@ -0,0 +1,95 @@ +#include "sd_card.h" +#include "regs/regs.h" +extern "C" { +#include +} + +namespace Ion { +namespace SDCard { +namespace Device { + +void init() { + initGPIO(); + initCard(); +} + +void initGPIO() { + // Configure GPIOs to use AF + GPIOA.MODER()->setMode(8, GPIO::MODER::Mode::AlternateFunction); + GPIOB.MODER()->setMode(4, GPIO::MODER::Mode::AlternateFunction); + GPIOB.MODER()->setMode(5, GPIO::MODER::Mode::AlternateFunction); + GPIOB.MODER()->setMode(15, GPIO::MODER::Mode::AlternateFunction); + GPIOC.MODER()->setMode(10, GPIO::MODER::Mode::AlternateFunction); + GPIOD.MODER()->setMode(2, GPIO::MODER::Mode::AlternateFunction); + + // More precisely, AF12 which correspond to SDIO alternate functions + GPIOA.AFR()->setAlternateFunction(8, GPIO::AFR::AlternateFunction::AF12); + GPIOB.AFR()->setAlternateFunction(4, GPIO::AFR::AlternateFunction::AF12); + GPIOB.AFR()->setAlternateFunction(5, GPIO::AFR::AlternateFunction::AF12); + GPIOB.AFR()->setAlternateFunction(15, GPIO::AFR::AlternateFunction::AF12); + GPIOC.AFR()->setAlternateFunction(10, GPIO::AFR::AlternateFunction::AF12); + GPIOD.AFR()->setAlternateFunction(2, GPIO::AFR::AlternateFunction::AF12); +} + +void initCard() { + + // Power on + SDIO.POWER()->setPWRCTRL(SDIO::POWER::PWRCTRL::On); + while (SDIO.POWER()->getPWRCTRL() != SDIO::POWER::PWRCTRL::On) { + } + + // Clock set + SDIO.CLKCR()->setCLKDIV(254); + SDIO.CLKCR()->setCLKEN(true); + + sendCommand(0, 0); + // CMD8 : 0b0001 = 2.7 - 3.6V + // 0xB7 = Pattern to see back in response + sendCommand(8, 0x1B7); + + assert(SDIO.RESP(1)->get() == 0x1B7); +} + +void sendCommand(uint32_t cmd, uint32_t arg) { + class SDIO::ICR icr(0); + icr.setCCRCFAILC(true); + icr.setCTIMEOUTC(true); + icr.setCMDRENDC(true); + icr.setCMDSENTC(true); + SDIO.ICR()->set(icr); + + SDIO.ARG()->set(arg); + + SDIO::CMD::WAITRESP responseType = SDIO::CMD::WAITRESP::Short; + switch (cmd) { + case 0: + responseType = SDIO::CMD::WAITRESP::None; + break; + case 2: + case 9: + case 10: + responseType = SDIO::CMD::WAITRESP::Long; + default: + break; + } + + class SDIO::CMD command(0); + command.setCMDINDEX(cmd); + command.setCPSMEN(true); + command.setWAITRESP(responseType); + SDIO.CMD()->set(command); + + if (responseType == SDIO::CMD::WAITRESP::None) { + // Wait for timeout or command sent + while (!SDIO.STA()->getCTIMEOUT() && !SDIO.STA()->getCMDSENT()) { + } + } else { + while (!SDIO.STA()->getCTIMEOUT() && !SDIO.STA()->getCMDREND() && !SDIO.STA()->getCCRCFAIL()) { + } + } + +} + +} +} +} diff --git a/ion/src/f730/sd_card.h b/ion/src/f730/sd_card.h new file mode 100644 index 000000000..1a2babfb7 --- /dev/null +++ b/ion/src/f730/sd_card.h @@ -0,0 +1,33 @@ +#ifndef ION_DEVICE_SD_CARD_H +#define ION_DEVICE_SD_CARD_H + +extern "C" { +#include +} + +namespace Ion { +namespace SDCard { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PA8 | SDIO D1 | Alternate Function 12 | SDIO_D1 + * PB4 | SDIO D0 | Alternate Function 12 | SDIO_D0 + * PB5 | SDIO D3 | Alternate Function 12 | SDIO_D3 + * PB15 | SDIO CLK | Alternate Function 12 | SDIO_CK + * PC10 | SDIO D2 | Alternate Function 12 | SDIO_D2 + * PD2 | SDIO CMD | Alternate Function 12 | SDIO_CMD + */ + +void init(); +void initGPIO(); + +void initCard(); + +void sendCommand(uint32_t cmd, uint32_t arg); + +} +} +} + +#endif diff --git a/ion/src/f730/stack.cpp b/ion/src/f730/stack.cpp new file mode 100644 index 000000000..141e222b5 --- /dev/null +++ b/ion/src/f730/stack.cpp @@ -0,0 +1,10 @@ +#include + +extern const void * _stack_start; +extern const void * _stack_end; + +bool Ion::stackSafe() { + volatile int stackDummy; + volatile void * stackPointer = &stackDummy; + return (stackPointer >= &_stack_end && stackPointer <= &_stack_start); +} diff --git a/ion/src/f730/swd.cpp b/ion/src/f730/swd.cpp new file mode 100644 index 000000000..008dc8110 --- /dev/null +++ b/ion/src/f730/swd.cpp @@ -0,0 +1,24 @@ +#include "swd.h" +#include "regs/regs.h" + +namespace Ion { +namespace SWD { +namespace Device { + +void init() { + for(const GPIOPin & g : Pins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF0); + } +} + +void shutdown() { + for(const GPIOPin & g : Pins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } +} + +} +} +} diff --git a/ion/src/f730/swd.h b/ion/src/f730/swd.h new file mode 100644 index 000000000..49ea414c8 --- /dev/null +++ b/ion/src/f730/swd.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_SWD_H +#define ION_DEVICE_SWD_H + +#include "regs/regs.h" + +namespace Ion { +namespace SWD { +namespace Device { + +/* Pin | Role | Mode + * -----+-------------------+--------------------- + * PA13 | SWDIO | Alternate Function 0 + * PA14 | SWCLK | Alternate Function 0 + * PB3 | SWO | Alternate Function 0 + */ + +void init(); +void shutdown(); + +constexpr static GPIOPin Pins[] = { + GPIOPin(GPIOA, 13), GPIOPin(GPIOA, 14), GPIOPin(GPIOB, 3) +}; + +} +} +} + +#endif diff --git a/ion/src/f730/timing.cpp b/ion/src/f730/timing.cpp new file mode 100644 index 000000000..c22d425f7 --- /dev/null +++ b/ion/src/f730/timing.cpp @@ -0,0 +1,36 @@ +#include "timing.h" + +namespace Ion { +namespace Timing { + +/* TODO: The delay methods 'msleep' and 'usleep' are currently dependent on the + * optimizations chosen by the compiler. To prevent that and to gain in + * precision, we could use the controller cycle counter (Systick). */ + +void msleep(uint32_t ms) { + for (volatile uint32_t i=0; i<8852*ms; i++) { + __asm volatile("nop"); + } +} +void usleep(uint32_t us) { + for (volatile uint32_t i=0; i<9*us; i++) { + __asm volatile("nop"); + } +} + +uint64_t millis() { + return Ion::Timing::Device::MillisElapsed; +} + +} +} + +namespace Ion { +namespace Timing { +namespace Device { + +volatile uint64_t MillisElapsed = 0; + +} +} +} diff --git a/ion/src/f730/timing.h b/ion/src/f730/timing.h new file mode 100644 index 000000000..017bbd31a --- /dev/null +++ b/ion/src/f730/timing.h @@ -0,0 +1,16 @@ +#ifndef ION_DEVICE_TIMING_H +#define ION_DEVICE_TIMING_H + +#include + +namespace Ion { +namespace Timing { +namespace Device { + +extern volatile uint64_t MillisElapsed; + +} +} +} + +#endif diff --git a/ion/src/f730/usb.cpp b/ion/src/f730/usb.cpp new file mode 100644 index 000000000..1a76cc4e1 --- /dev/null +++ b/ion/src/f730/usb.cpp @@ -0,0 +1,187 @@ +#include +#include "usb.h" +#include +#include "device.h" +#include "display.h" +#include "regs/regs.h" +#include + +namespace Ion { +namespace USB { + +bool isPlugged() { + return Device::VbusPin.group().IDR()->get(Device::VbusPin.pin()); +} + +bool isEnumerated() { + /* Note: This implementation is not perfect. One would assume isEnumerated to + * return true for as long as the device is enumerated. But the GINTSTS + * register will be cleared in the poll() routine. */ + return OTG.GINTSTS()->getENUMDNE(); +} + +void clearEnumerationInterrupt() { + OTG.GINTSTS()->setENUMDNE(true); +} + +void enable() { + // Get out of soft-disconnected state + OTG.DCTL()->setSDIS(false); +} + +void disable() { + // Get into soft-disconnected state + OTG.DCTL()->setSDIS(true); +} + +} +} + +namespace Ion { +namespace USB { +namespace Device { + +void init() { + initGPIO(); + initOTG(); +} + +void shutdown() { + shutdownOTG(); + shutdownGPIO(); +} + +static inline void DEBUGTOGGLE() { + bool state = GPIOC.ODR()->get(11); + GPIOC.ODR()->set(11, !state); +} + +#include + +void initGPIO() { + + // DEBUG GPIO pin + GPIOC.MODER()->setMode(11, GPIO::MODER::Mode::Output); + GPIOC.ODR()->set(11, false); + + /* Configure the GPIO + * The VBUS pin is connected to the USB VBUS port. To read if the USB is + * plugged, the pin must be pulled down. */ + // FIXME: Understand how the Vbus pin really works! +#if 0 + VbusPin.group().MODER()->setMode(VbusPin.pin(), GPIO::MODER::Mode::Input); + VbusPin.group().PUPDR()->setPull(VbusPin.pin(), GPIO::PUPDR::Pull::Down); +#else + VbusPin.group().MODER()->setMode(VbusPin.pin(), GPIO::MODER::Mode::AlternateFunction); + VbusPin.group().AFR()->setAlternateFunction(VbusPin.pin(), GPIO::AFR::AlternateFunction::AF10); +#endif + + DmPin.group().MODER()->setMode(DmPin.pin(), GPIO::MODER::Mode::AlternateFunction); + DmPin.group().AFR()->setAlternateFunction(DmPin.pin(), GPIO::AFR::AlternateFunction::AF10); + + DpPin.group().MODER()->setMode(DpPin.pin(), GPIO::MODER::Mode::AlternateFunction); + DpPin.group().AFR()->setAlternateFunction(DpPin.pin(), GPIO::AFR::AlternateFunction::AF10); +} + +void shutdownGPIO() { + constexpr static GPIOPin USBPins[] = {DpPin, DmPin, VbusPin}; + for (const GPIOPin & g : USBPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } +} + +void initOTG() { + // Wait for AHB idle + while (!OTG.GRSTCTL()->getAHBIDL()) { + } + + /* Core soft reset: Clears the interrupts and many of the CSR register bits, + * resets state machines, flushes the FIFOs and terminates USB transactions.*/ + OTG.GRSTCTL()->setCSRST(true); + while (OTG.GRSTCTL()->getCSRST()) { + } + + /* Enable the transceiver module of the PHY. It must be done to allow any USB + * operation */ + OTG.GCCFG()->setPWRDWN(true); + + /* Enable VBUS sensing comparators to detect valid levels for USB operation. + * This is used for instance to end the session if the host is switched off.*/ + OTG.GCCFG()->setVBDEN(true); + + // Force peripheral only mode + OTG.GUSBCFG()->setFDMOD(true); + + // Configure the USB turnaround time, depending on the AHB clock speed (96MHz) + OTG.GUSBCFG()->setTRDT(0x6); + + // Clear the interrupts + OTG.GINTSTS()->set(0); + + // Full speed device + OTG.DCFG()->setDSPD(OTG::DCFG::DSPD::FullSpeed); + + /* RxFIFO size. The value is in terms of 32-bit words. + * According to the reference manual, it should be, at minimum: + * (4 * number of control endpoints + 6) + * To receive SETUP packets on control endpoint + * + ((largest USB packet used / 4) + 1) + * To receive 1 USB packet + 1 packet status + * + (2 * number of OUT endpoints) + * Transfer complete status information + * + 1 for Global NAK + * So, for the calculator: (4*1+6) + (64/4 + 1) + (2*1) + 1 = 30 + * As the RAM size is 1.25kB, the size should be at most 320, minus the space + * for the Tx FIFOs. + * However, we tested and found that only values between 40 and 255 actually + * work. We arbitrarily chose 128. */ + OTG.GRXFSIZ()->setRXFD(128); + + // Unmask the interrupt line assertions + OTG.GAHBCFG()->setGINTMSK(true); + + // Restart the PHY clock + OTG.PCGCCTL()->setSTPPCLK(false); + + // Pick which interrupts we're interested in + class OTG::GINTMSK intMask(0); // Reset value + intMask.setENUMDNEM(true); // Speed enumeration done + intMask.setRXFLVLM(true); // Receive FIFO non empty + intMask.setIEPINT(true); // IN endpoint interrupt + OTG.GINTMSK()->set(intMask); + + // Unmask IN interrupts for endpoint 0 only + OTG.DAINTMSK()->setIEPM(1); + + /* Unmask the IN transfer completed interrupt for all endpoints. This + * interrupt warns that a IN transaction happened on the endpoint. */ + OTG.DIEPMSK()->setXFRCM(true); + + /* To communicate with a USB host, the device still needs to get out of soft- + * disconnected state (SDIS in the DCTL register). We do this when we detect + * that the USB cable is plugged. */ +} + +void shutdownOTG() { + // Core soft reset + OTG.GRSTCTL()->setCSRST(true); + while (OTG.GRSTCTL()->getCSRST()) { + } + + // Get into soft-disconnected state + OTG.DCTL()->setSDIS(true); + + // Stop the PHY clock + OTG.PCGCCTL()->setSTPPCLK(true); + + // Stop VBUS sensing + OTG.GCCFG()->setVBDEN(false); + + // Disable the transceiver module of the PHY + OTG.GCCFG()->setPWRDWN(false); +} + +} +} +} diff --git a/ion/src/f730/usb.h b/ion/src/f730/usb.h new file mode 100644 index 000000000..7c462cd47 --- /dev/null +++ b/ion/src/f730/usb.h @@ -0,0 +1,34 @@ +#ifndef ION_DEVICE_USB_H +#define ION_DEVICE_USB_H + +#include "regs/regs.h" +#include "ion.h" +#include "usb/calculator.h" + +namespace Ion { +namespace USB { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PA9 | VBUS | Input, pulled down//TODO | Low = unplugged, high = plugged + * PA11 | USB D- | Alternate Function 10 | + * PA12 | USB D+ | Alternate Function 10 | + */ + +constexpr static GPIOPin VbusPin = GPIOPin(GPIOA, 9); +constexpr static GPIOPin DmPin = GPIOPin(GPIOA, 11); +constexpr static GPIOPin DpPin = GPIOPin(GPIOA, 12); + +void init(); +void shutdown(); +void initGPIO(); +void shutdownGPIO(); +void initOTG(); +void shutdownOTG(); + +} +} +} + +#endif diff --git a/ion/src/f730/usb/Makefile b/ion/src/f730/usb/Makefile new file mode 100644 index 000000000..a2d840eef --- /dev/null +++ b/ion/src/f730/usb/Makefile @@ -0,0 +1,67 @@ +usb_objs += $(addprefix ion/src/device/usb/, \ + calculator.o \ + dfu_interface.o\ +) + +usb_objs += $(addprefix ion/src/device/usb/stack/, \ + device.o\ + endpoint0.o \ + interface.o\ + request_recipient.o\ + setup_packet.o\ + streamable.o\ +) + +usb_objs += $(addprefix ion/src/device/usb/stack/descriptor/, \ + bos_descriptor.o\ + configuration_descriptor.o \ + descriptor.o\ + device_descriptor.o\ + device_capability_descriptor.o\ + dfu_functional_descriptor.o\ + extended_compat_id_descriptor.o \ + interface_descriptor.o\ + language_id_string_descriptor.o \ + microsoft_os_string_descriptor.o\ + platform_device_capability_descriptor.o\ + string_descriptor.o\ + url_descriptor.o\ + webusb_platform_descriptor.o\ +) + +EPSILON_USB_DFU_XIP ?= 0 + +ifeq ($(EPSILON_USB_DFU_XIP),1) + +objs += ion/src/device/usb/dfu_xip.o +objs += $(usb_objs) + +else + +dfu_objs += liba/src/assert.o +dfu_objs += liba/src/strlen.o +dfu_objs += liba/src/strlcpy.o +dfu_objs += liba/src/memset.o +dfu_objs += liba/src/memcpy.o +dfu_objs += libaxx/src/cxxabi/pure_virtual.o +dfu_objs += ion/src/device/usb/boot.o +dfu_objs += ion/src/device/keyboard.o +dfu_objs += ion/src/device/device.o +dfu_objs += ion/src/device/usb.o +dfu_objs += ion/src/device/base64.o +dfu_objs += ion/src/device/flash.o +dfu_objs += ion/src/device/timing.o + +ion/src/device/usb/dfu.elf: LDSCRIPT = ion/src/device/usb/dfu.ld +ion/src/device/usb/dfu.elf: $(usb_objs) $(dfu_objs) + +ion/src/device/usb/dfu.o: ion/src/device/usb/dfu.bin + @echo "OBJCOPY $@" + $(Q) $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata --redefine-sym _binary_ion_src_device_usb_dfu_bin_start=_dfu_bootloader_flash_start --redefine-sym _binary_ion_src_device_usb_dfu_bin_end=_dfu_bootloader_flash_end $< $@ + +objs += ion/src/device/usb/dfu.o +objs += ion/src/device/usb/dfu_relocated.o + +products += $(usb_objs) $(addprefix ion/src/device/usb/dfu, .elf .bin) + +endif diff --git a/ion/src/f730/usb/boot.cpp b/ion/src/f730/usb/boot.cpp new file mode 100644 index 000000000..0eb8d71d6 --- /dev/null +++ b/ion/src/f730/usb/boot.cpp @@ -0,0 +1,2 @@ +extern "C" void abort() { +} diff --git a/ion/src/f730/usb/calculator.cpp b/ion/src/f730/usb/calculator.cpp new file mode 100644 index 000000000..fc23c1512 --- /dev/null +++ b/ion/src/f730/usb/calculator.cpp @@ -0,0 +1,94 @@ +#include "calculator.h" +#include +#include +#include +#include + +namespace Ion { +namespace USB { +namespace Device { + +void Calculator::PollAndReset(bool exitWithKeyboard) { + char serialNumber[Ion::Device::SerialNumberLength+1]; + Ion::Device::copySerialNumber(serialNumber); + Calculator c(serialNumber); + + /* Leave DFU mode if the Back key is pressed, the calculator unplugged or the + * USB core soft-disconnected. */ + Ion::Keyboard::Key exitKey = Ion::Keyboard::Key::A6; + uint8_t exitKeyRow = Ion::Keyboard::Device::rowForKey(exitKey); + uint8_t exitKeyColumn = Ion::Keyboard::Device::columnForKey(exitKey); + + Ion::Keyboard::Device::activateRow(exitKeyRow); + + while (!(exitWithKeyboard && Ion::Keyboard::Device::columnIsActive(exitKeyColumn)) && + Ion::USB::isPlugged() && + !c.isSoftDisconnected()) { + c.poll(); + } + if (!c.isSoftDisconnected()) { + c.detach(); + } + if (c.resetOnDisconnect()) { + /* We don't perform a core reset because at this point in time the USB cable + * is most likely plugged in. Doing a full core reset would be the clean + * thing to do but would therefore result in the device entering the ROMed + * DFU bootloader, which we want to avoid. By performing a jump-reset, we + * will enter the newly flashed firmware. */ + Ion::Device::jumpReset(); + } +} + +Descriptor * Calculator::descriptor(uint8_t type, uint8_t index) { + /* Special case: Microsoft OS String Descriptor should be returned when + * searching for string descriptor at index 0xEE. */ + if (type == m_microsoftOSStringDescriptor.type() && index == 0xEE) { + return &m_microsoftOSStringDescriptor; + } + int typeCount = 0; + for (size_t i=0; itype() != type) { + continue; + } + if (typeCount == index) { + return descriptor; + } else { + typeCount++; + } + } + return nullptr; +} + +bool Calculator::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (Device::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + return true; + } + if (request->requestType() == SetupPacket::RequestType::Vendor) { + if (request->bRequest() == k_webUSBVendorCode && request->wIndex() == 2) { + // This is a WebUSB, GET_URL request + assert(request->wValue() == k_webUSBLandingPageIndex); + return getURLCommand(transferBuffer, transferBufferLength, transferBufferMaxLength); + } + if (request->bRequest() == k_microsoftOSVendorCode && request->wIndex() == 0x0004) { + // This is a Microsoft OS descriptor, Extended Compat ID request + assert(request->wValue() == 0); + return getExtendedCompatIDCommand(transferBuffer, transferBufferLength, transferBufferMaxLength); + } + } + return false; +} + +bool Calculator::getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = m_workshopURLDescriptor.copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool Calculator::getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = m_extendedCompatIdDescriptor.copy(transferBuffer, transferBufferMaxLength); + return true; +} + +} +} +} diff --git a/ion/src/f730/usb/calculator.h b/ion/src/f730/usb/calculator.h new file mode 100644 index 000000000..d3383e419 --- /dev/null +++ b/ion/src/f730/usb/calculator.h @@ -0,0 +1,166 @@ +#ifndef ION_DEVICE_USB_CALCULATOR_H +#define ION_DEVICE_USB_CALCULATOR_H + +#include +#include +#include "dfu_interface.h" +#include "stack/device.h" +#include "stack/descriptor/bos_descriptor.h" +#include "stack/descriptor/configuration_descriptor.h" +#include "stack/descriptor/descriptor.h" +#include "stack/descriptor/device_descriptor.h" +#include "stack/descriptor/dfu_functional_descriptor.h" +#include "stack/descriptor/extended_compat_id_descriptor.h" +#include "stack/descriptor/interface_descriptor.h" +#include "stack/descriptor/language_id_string_descriptor.h" +#include "stack/descriptor/microsoft_os_string_descriptor.h" +#include "stack/descriptor/string_descriptor.h" +#include "stack/descriptor/url_descriptor.h" +#include "stack/descriptor/webusb_platform_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class Calculator : public Device { +public: + static void PollAndReset(bool exitWithKeyboard) + __attribute__((section(".dfu_entry_point"))) // Needed to pinpoint this symbol in the linker script + __attribute__((used)) // Make sure this symbol is not discarded at link time + ; // Return true if reset is needed + Calculator(const char * serialNumber) : + Device(&m_dfuInterface), + m_deviceDescriptor( + 0x0210, /* bcdUSB: USB Specification Number which the device complies + * to. Must be greater than 0x0200 to use the BOS. */ + 0, // bDeviceClass: The class is defined by the interface. + 0, // bDeviceSUBClass: The subclass is defined by the interface. + 0, // bDeviceProtocol: The protocol is defined by the interface. + 64, // bMaxPacketSize0: Maximum packet size for endpoint 0 + 0x0483, // idVendor + 0xA291, // idProduct + 0x0100, // bcdDevice: Device Release Number + 1, // iManufacturer: Index of the manufacturer name string, see m_descriptor + 2, // iProduct: Index of the product name string, see m_descriptor + 3, // iSerialNumber: Index of the SerialNumber string, see m_descriptor + 1), // bNumConfigurations + m_dfuFunctionalDescriptor( + 0b0011, /* bmAttributes: + * - bitWillDetach: If true, the device will perform a bus + * detach-attach sequence when it receives a DFU_DETACH + * request. The host must not issue a USB Reset. + * - bitManifestationTolerant: if true, the device is able to + * communicate via USB after Manifestation phase. The + * manifestation phase implies a reset in the calculator, so, + * even if the device is still plugged, it needs to be + * re-enumerated to communicate. + * - bitCanUpload + * - bitCanDnload */ + 0, /* wDetachTimeOut: Time, in milliseconds, that the device in APP + * mode will wait after receipt of the DFU_DETACH request before + * switching to DFU mode. It does not apply to the calculator.*/ + 2048, // wTransferSize: Maximum number of bytes that the device can accept per control-write transaction + 0x0100),// bcdDFUVersion + m_interfaceDescriptor( + 0, // bInterfaceNumber + k_dfuInterfaceAlternateSetting, // bAlternateSetting + 0, // bNumEndpoints: Other than endpoint 0 + 0xFE, // bInterfaceClass: DFU (http://www.usb.org/developers/defined_class) + 1, // bInterfaceSubClass: DFU + 2, // bInterfaceProtocol: DFU Mode (not DFU Runtime, which would be 1) + 4, // iInterface: Index of the Interface string, see m_descriptor + &m_dfuFunctionalDescriptor), + m_configurationDescriptor( + 9 + 9 + 9, // wTotalLength: configuration descriptor + interface descriptor + dfu functional descriptor lengths + 1, // bNumInterfaces + k_bConfigurationValue, // bConfigurationValue + 0, // iConfiguration: No string descriptor for the configuration + 0x80, /* bmAttributes: + * Bit 7: Reserved, set to 1 + * Bit 6: Self Powered + * Bit 5: Remote Wakeup (allows the device to wake up the host when the host is in suspend) + * Bit 4..0: Reserved, set to 0 */ + 0x32, // bMaxPower: half of the Maximum Power Consumption + &m_interfaceDescriptor), + m_webUSBPlatformDescriptor( + k_webUSBVendorCode, + k_webUSBLandingPageIndex), + m_bosDescriptor( + 5 + 24, // wTotalLength: BOS descriptor + webusb platform descriptor lengths + 1, // bNumDeviceCapabilities + &m_webUSBPlatformDescriptor), + m_languageStringDescriptor(), + m_manufacturerStringDescriptor("NumWorks"), + m_productStringDescriptor("NumWorks Calculator"), + m_serialNumberStringDescriptor(serialNumber), + m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg"), + //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), + /* Switch to this descriptor to use dfu-util to write in the SRAM. + * FIXME Should be an alternate Interface. */ + m_microsoftOSStringDescriptor(k_microsoftOSVendorCode), + m_workshopURLDescriptor(URLDescriptor::Scheme::HTTPS, "workshop.numworks.com"), + m_extendedCompatIdDescriptor("WINUSB"), + m_descriptors{ + &m_deviceDescriptor, // Type = Device, Index = 0 + &m_configurationDescriptor, // Type = Configuration, Index = 0 + &m_languageStringDescriptor, // Type = String, Index = 0 + &m_manufacturerStringDescriptor, // Type = String, Index = 1 + &m_productStringDescriptor, // Type = String, Index = 2 + &m_serialNumberStringDescriptor, // Type = String, Index = 3 + &m_interfaceStringDescriptor, // Type = String, Index = 4 + &m_bosDescriptor // Type = BOS, Index = 0 + }, + m_dfuInterface(this, &m_ep0, k_dfuInterfaceAlternateSetting) + { + } +protected: + virtual Descriptor * descriptor(uint8_t type, uint8_t index) override; + virtual void setActiveConfiguration(uint8_t configurationIndex) override { + assert(configurationIndex == k_bConfigurationValue); + } + virtual uint8_t getActiveConfiguration() override { + return k_bConfigurationValue; + } + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + +private: + static constexpr uint8_t k_bConfigurationValue = 1; + static constexpr uint8_t k_dfuInterfaceAlternateSetting = 0; + static constexpr uint8_t k_webUSBVendorCode = 1; + static constexpr uint8_t k_webUSBLandingPageIndex = 1; + static constexpr uint8_t k_microsoftOSVendorCode = 2; + + // WebUSB and MicrosoftOSDescriptor commands + bool getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + + // Descriptors + DeviceDescriptor m_deviceDescriptor; + DFUFunctionalDescriptor m_dfuFunctionalDescriptor; + InterfaceDescriptor m_interfaceDescriptor; + ConfigurationDescriptor m_configurationDescriptor; + WebUSBPlatformDescriptor m_webUSBPlatformDescriptor; + BOSDescriptor m_bosDescriptor; + LanguageIDStringDescriptor m_languageStringDescriptor; + StringDescriptor m_manufacturerStringDescriptor; + StringDescriptor m_productStringDescriptor; + StringDescriptor m_serialNumberStringDescriptor; + StringDescriptor m_interfaceStringDescriptor; + MicrosoftOSStringDescriptor m_microsoftOSStringDescriptor; + URLDescriptor m_workshopURLDescriptor; + ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor; + + Descriptor * m_descriptors[8]; + /* m_descriptors contains only descriptors that sould be returned via the + * method descriptor(uint8_t type, uint8_t index), so do not count descriptors + * included in other descriptors or returned by other functions. */ + + // Interface + DFUInterface m_dfuInterface; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/dfu.ld b/ion/src/f730/usb/dfu.ld new file mode 100644 index 000000000..934772384 --- /dev/null +++ b/ion/src/f730/usb/dfu.ld @@ -0,0 +1,50 @@ +/* DFU transfers can serve two purposes: + * - Transfering RAM data between the machine and the host, e.g. Python scripts + * - Upgrading the flash memory to perform a software update + * + * The second case raises a huge issue: code cannot be executed from memory that + * is being modified. We're solving this issue by copying the DFU code in RAM. + * + * This linker script will generate some code that expects to be executed from a + * fixed address in RAM. The corresponding instructions will be embedded in the + * main Epsilon ELF file, and copied to that address before execution. + * + * This address needs to live in RAM, and needs to be temporarily overwriteable + * when the program is being run. Epsilon has a large stack to allow deeply + * recursive code to run. But when doing DFU transfers it is safe to assume we + * will need very little stack space. We're therefore using the topmost 8K of + * the stack reserved by Epsilon. + * + * Last but not least, we'll want to jump to a known entry point when running + * the DFU code (namely, Ion::USB::Device::Calculator::Poll). We're simply + * making sure this is the first symbol output. */ + +EPSILON_STACK_END = 0x20000000 + 256K - 32K; + +MEMORY { + RAM_BUFFER (rw) : ORIGIN = EPSILON_STACK_END, LENGTH = 8K +} + +SECTIONS { + .text : { + . = ALIGN(4); + KEEP(*(.dfu_entry_point)) + *(.text) + *(.text.*) + } >RAM_BUFFER + + .rodata : { + *(.rodata) + *(.rodata.*) + } >RAM_BUFFER + + /DISCARD/ : { + /* For now, we do not need .bss and .data sections. This allows us to simply + * skip any rt0-style initialization and jump straight into the PollAndReset + * routine. */ + *(.bss) + *(.bss.*) + *(.data) + *(.data.*) + } +} diff --git a/ion/src/f730/usb/dfu_interface.cpp b/ion/src/f730/usb/dfu_interface.cpp new file mode 100644 index 000000000..029cce703 --- /dev/null +++ b/ion/src/f730/usb/dfu_interface.cpp @@ -0,0 +1,278 @@ +#include "dfu_interface.h" +#include +#include + +namespace Ion { +namespace USB { +namespace Device { + +static inline uint32_t min(uint32_t x, uint32_t y) { return (xpush(m_bStatus); + c->push(m_bwPollTimeout[2]); + c->push(m_bwPollTimeout[1]); + c->push(m_bwPollTimeout[0]); + c->push(m_bState); + c->push(m_iString); +} + +void DFUInterface::StateData::push(Channel * c) const { + c->push(m_bState); +} + +void DFUInterface::wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::Download) { + // Handle a download request + if (request->wValue() == 0) { + // The request is a special command + switch (transferBuffer[0]) { + case (uint8_t) DFUDownloadCommand::SetAddressPointer: + setAddressPointerCommand(request, transferBuffer, *transferBufferLength); + return; + case (uint8_t) DFUDownloadCommand::Erase: + eraseCommand(transferBuffer, *transferBufferLength); + return; + default: + m_state = State::dfuERROR; + m_status = Status::errSTALLEDPKT; + return; + } + } + if (request->wValue() == 1) { + m_ep0->stallTransaction(); + return; + } + if (request->wLength() > 0) { + // The request is a "real" download. Compute the writing address. + m_writeAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; + // Store the received data until we copy it on the flash. + memcpy(m_largeBuffer, transferBuffer, *transferBufferLength); + m_largeBufferLength = *transferBufferLength; + m_state = State::dfuDNLOADSYNC; + } + } +} + +void DFUInterface::wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { + if (request->bRequest() == (uint8_t) DFURequest::GetStatus) { + // Do any needed action after the GetStatus request. + if (m_state == State::dfuMANIFEST) { + // Leave DFU routine: Leave DFU, reset device, jump to application code + leaveDFUAndReset(); + } else if (m_state == State::dfuDNBUSY) { + if (m_largeBufferLength != 0) { + // Here, copy the data from the transfer buffer to the flash memory + writeOnMemory(); + } + changeAddressPointerIfNeeded(); + eraseMemoryIfNeeded(); + m_state = State::dfuDNLOADIDLE; + } + } +} + +bool DFUInterface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (Interface::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + return true; + } + switch (request->bRequest()) { + case (uint8_t) DFURequest::Detach: + m_device->detach(); + return true; + case (uint8_t) DFURequest::Download: + return processDownloadRequest(request->wLength(), transferBufferLength); + case (uint8_t) DFURequest::Upload: + return processUploadRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::GetStatus: + return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::ClearStatus: + return clearStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::GetState: + return getState(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (uint8_t) DFURequest::Abort: + return dfuAbort(transferBufferLength); + } + return false; +} + +bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuDNLOADIDLE) { + m_state = State::dfuERROR; + m_status = Status::errNOTDONE; + m_ep0->stallTransaction(); + return false; + } + if (wLength == 0) { + // Leave DFU routine: Reset the device and jump to application code + m_state = State::dfuMANIFESTSYNC; + } else { + // Prepare to receive the download data + m_ep0->clearForOutTransactions(wLength); + m_state = State::dfuDNLOADSYNC; + } + return true; +} + +bool DFUInterface::processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) { + m_ep0->stallTransaction(); + return false; + } + if (request->wValue() == 0) { + /* The host requests to read the commands supported by the bootloader. After + * receiving this command, the device should returns N bytes representing + * the command codes for : + * Get command / Set Address Pointer / Erase / Read Unprotect + * We no not need it for now. */ + return false; + } else if (request->wValue() == 1) { + m_ep0->stallTransaction(); + return false; + } else { + /* We decided to never protect Read operation. Else we would have to check + * here it is not protected before reading. */ + + // Compute the reading address + uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; + // Copy the requested memory zone into the transfer buffer. + uint16_t copySize = min(transferBufferMaxLength, request->wLength()); + memcpy(transferBuffer, (void *)readAddress, copySize); + *transferBufferLength = copySize; + } + m_state = State::dfuUPLOADIDLE; + return true; +} + +void DFUInterface::setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength) { + assert(transferBufferLength == 5); + // Compute the new address but change it after the next getStatus request. + m_potentialNewAddressPointer = transferBuffer[1] + + (transferBuffer[2] << 8) + + (transferBuffer[3] << 16) + + (transferBuffer[4] << 24); + m_state = State::dfuDNLOADSYNC; +} + +void DFUInterface::changeAddressPointerIfNeeded() { + if (m_potentialNewAddressPointer == 0) { + // There was no address change waiting. + return; + } + // If there is a new address pointer waiting, change the pointer address. + m_addressPointer = m_potentialNewAddressPointer; + m_potentialNewAddressPointer = 0; + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + +void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength) { + /* We determine whether the commands asks for a mass erase or which sector to + * erase. The erase must be done after the next getStatus request. */ + m_state = State::dfuDNLOADSYNC; + + if (transferBufferLength == 1) { + // Mass erase + m_erasePage = Flash::Device::NumberOfSectors; + return; + } + + // Sector erase + assert(transferBufferLength == 5); + + uint32_t eraseAddress = transferBuffer[1] + + (transferBuffer[2] << 8) + + (transferBuffer[3] << 16) + + (transferBuffer[4] << 24); + + m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); + + if (m_erasePage < 0) { + // Unrecognized sector + m_state = State::dfuERROR; + m_status = Status::errTARGET; + } +} + + +void DFUInterface::eraseMemoryIfNeeded() { + if (m_erasePage < 0) { + // There was no erase waiting. + return; + } + + if (m_erasePage == Ion::Flash::Device::NumberOfSectors) { + Flash::Device::MassErase(); + } else { + Flash::Device::EraseSector(m_erasePage); + } + + /* Put an out of range value in m_erasePage to indicate that no erase is + * waiting. */ + m_erasePage = -1; + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + +void DFUInterface::writeOnMemory() { + if (m_writeAddress >= k_flashStartAddress && m_writeAddress <= k_flashEndAddress) { + // Write to the Flash memory + Flash::Device::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress), m_largeBufferLength); + } else if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) { + // Write on SRAM + // FIXME We should check that we are not overriding the current instructions. + memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); + } else { + // Invalid write address + m_largeBufferLength = 0; + m_state = State::dfuERROR; + m_status = Status::errTARGET; + return; + } + + // Reset the buffer length + m_largeBufferLength = 0; + // Change the interface state and status + m_state = State::dfuDNLOADIDLE; + m_status = Status::OK; +} + + +bool DFUInterface::getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + // Change the status if needed + if (m_state == State::dfuMANIFESTSYNC) { + m_state = State::dfuMANIFEST; + } else if (m_state == State::dfuDNLOADSYNC) { + m_state = State::dfuDNBUSY; + } + // Copy the status on the TxFifo + *transferBufferLength = StatusData(m_status, m_state).copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool DFUInterface::clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + m_status = Status::OK; + m_state = State::dfuIDLE; + return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); +} + +bool DFUInterface::getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize) { + *transferBufferLength = StateData(m_state).copy(transferBuffer, maxSize); + return true; +} + +bool DFUInterface::dfuAbort(uint16_t * transferBufferLength) { + m_status = Status::OK; + m_state = State::dfuIDLE; + *transferBufferLength = 0; + return true; +} + +void DFUInterface::leaveDFUAndReset() { + m_device->setResetOnDisconnect(true); + m_device->detach(); +} + +} +} +} diff --git a/ion/src/f730/usb/dfu_interface.h b/ion/src/f730/usb/dfu_interface.h new file mode 100644 index 000000000..6284ce951 --- /dev/null +++ b/ion/src/f730/usb/dfu_interface.h @@ -0,0 +1,172 @@ +#ifndef ION_DEVICE_USB_DFU_INTERFACE_H +#define ION_DEVICE_USB_DFU_INTERFACE_H + +#include +#include +#include "stack/device.h" +#include "stack/interface.h" +#include "stack/endpoint0.h" +#include "stack/setup_packet.h" +#include "stack/streamable.h" + +namespace Ion { +namespace USB { +namespace Device { + +class DFUInterface : public Interface { + +public: + DFUInterface(Device * device, Endpoint0 * ep0, uint8_t bInterfaceAlternateSetting) : + Interface(ep0), + m_device(device), + m_status(Status::OK), + m_state(State::dfuIDLE), + m_addressPointer(0), + m_potentialNewAddressPointer(0), + m_erasePage(-1), + m_largeBuffer{0}, + m_largeBufferLength(0), + m_writeAddress(0), + m_bInterfaceAlternateSetting(bInterfaceAlternateSetting) + { + } + void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; + void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; + +protected: + void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) override { + assert(interfaceAlternativeIndex == m_bInterfaceAlternateSetting); + } + uint8_t getActiveInterfaceAlternative() override { + return m_bInterfaceAlternateSetting; + } + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + +private: + // DFU Request Codes + enum class DFURequest { + Detach = 0, + Download = 1, + Upload = 2, + GetStatus = 3, + ClearStatus = 4, + GetState = 5, + Abort = 6 + }; + + // DFU Download Commmand Codes + enum class DFUDownloadCommand { + GetCommand = 0x00, + SetAddressPointer = 0x21, + Erase = 0x41, + ReadUnprotect = 0x92 + }; + + enum class Status : uint8_t { + OK = 0x00, + errTARGET = 0x01, + errFILE = 0x02, + errWRITE = 0x03, + errERASE = 0x04, + errCHECK_ERASED = 0x05, + errPROG = 0x06, + errVERIFY = 0x07, + errADDRESS = 0x08, + errNOTDONE = 0x09, + errFIRMWARE = 0x0A, + errVENDOR = 0x0B, + errUSBR = 0x0C, + errPOR = 0x0D, + errUNKNOWN = 0x0E, + errSTALLEDPKT = 0x0F + }; + + enum class State : uint8_t { + appIDLE = 0, + appDETACH = 1, + dfuIDLE = 2, + dfuDNLOADSYNC = 3, + dfuDNBUSY = 4, + dfuDNLOADIDLE = 5, + dfuMANIFESTSYNC = 6, + dfuMANIFEST = 7, + dfuMANIFESTWAITRESET = 8, + dfuUPLOADIDLE = 9, + dfuERROR = 10 + }; + + class StatusData : public Streamable { + public: + StatusData(Status status, State state, uint32_t pollTimeout = 1) : + /* We put a default pollTimeout value of 1ms: if the device is busy, the + * host has to wait 1ms before sending a getStatus Request. */ + m_bStatus((uint8_t)status), + m_bwPollTimeout{uint8_t((pollTimeout>>16) & 0xFF), uint8_t((pollTimeout>>8) & 0xFF), uint8_t(pollTimeout & 0xFF)}, + m_bState((uint8_t)state), + m_iString(0) + { + } + protected: + void push(Channel * c) const override; + private: + uint8_t m_bStatus; // Status resulting from the execution of the most recent request + uint8_t m_bwPollTimeout[3]; // m_bwPollTimeout is 24 bits + uint8_t m_bState; // State of the device immediately following transmission of this response + uint8_t m_iString; + }; + + class StateData : public Streamable { + public: + StateData(State state) : m_bState((uint8_t)state) {} + protected: + void push(Channel * c) const override; + private: + uint8_t m_bState; // Current state of the device + }; + + /* The Flash and SRAM addresses are in flash.ld. However, dfu_interface is + * linked with dfu.ld, so we cannot access the values. */ + constexpr static uint32_t k_flashStartAddress = 0x08000000; + constexpr static uint32_t k_flashEndAddress = 0x08100000; + constexpr static uint32_t k_sramStartAddress = 0x20000000; + constexpr static uint32_t k_sramEndAddress = 0x20040000; + + // Download and upload + bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); + bool processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + // Address pointer + void setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength); + void changeAddressPointerIfNeeded(); + // Access memory + void eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength); + void eraseMemoryIfNeeded(); + void writeOnMemory(); + void unlockFlashMemory(); + void lockFlashMemoryAndPurgeCaches(); + // Status + bool getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + // State + bool getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize); + // Abort + bool dfuAbort(uint16_t * transferBufferLength); + // Leave DFU + void leaveDFUAndReset(); + + Device * m_device; + Status m_status; + State m_state; + uint32_t m_addressPointer; + uint32_t m_potentialNewAddressPointer; + int32_t m_erasePage; + uint8_t m_largeBuffer[Endpoint0::MaxTransferSize]; + uint16_t m_largeBufferLength; + uint32_t m_writeAddress; + uint8_t m_bInterfaceAlternateSetting; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/dfu_relocated.cpp b/ion/src/f730/usb/dfu_relocated.cpp new file mode 100644 index 000000000..41566e2a0 --- /dev/null +++ b/ion/src/f730/usb/dfu_relocated.cpp @@ -0,0 +1,80 @@ +#include +#include +#include +#include + +extern char _stack_end; +extern char _dfu_bootloader_flash_start; +extern char _dfu_bootloader_flash_end; + +namespace Ion { +namespace USB { + +typedef void (*PollFunctionPointer)(bool exitWithKeyboard); + +void DFU() { + + /* DFU transfers can serve two purposes: + * - Transfering RAM data between the machine and a host, e.g. Python scripts + * - Upgrading the flash memory to perform a software update + * + * The second case raises a huge issue: code cannot be executed from memory + * that is being modified. We're solving this issue by copying the DFU code in + * RAM. + * + * The new DFU address in RAM needs to be temporarily overwriteable when the + * program is being run. Epsilon has a large stack to allow deeply recursive + * code to run, but when doing DFU transfers it is safe to assume we will need + * very little stack space. We're therefore using the topmost 8K of the stack + * reserved by Epsilon. */ + + /* 1- The stack being in reverse order, the end of the stack will be the + * beginning of the DFU bootloader copied in RAM. */ + + size_t dfu_bootloader_size = &_dfu_bootloader_flash_end - &_dfu_bootloader_flash_start; + char * dfu_bootloader_ram_start = reinterpret_cast(&_stack_end); + assert(&_stack_end == (void *)(0x20000000 + 256*1024 - 32*1024)); + + /* 2- Verify there is enough free space on the stack to copy the DFU code. */ + + char foo; + char * stackPointer = &foo; + if (dfu_bootloader_ram_start + dfu_bootloader_size > stackPointer) { + // There is not enough room on the stack to copy the DFU bootloader. + return; + } + + /* 3- Copy the DFU bootloader from Flash to RAM. */ + + memcpy(dfu_bootloader_ram_start, &_dfu_bootloader_flash_start, dfu_bootloader_size); + + /* 4- Disable all interrupts + * The interrupt service routines live in the Flash and could be overwritten + * by garbage during a firmware upgrade opration, so we disable them. */ + Device::shutdownSysTick(); + + /* 5- Jump to DFU bootloader code. We made sure in the linker script that the + * first function we want to call is at the beginning of the DFU code. */ + + PollFunctionPointer dfu_bootloader_entry = reinterpret_cast(dfu_bootloader_ram_start); + + /* To have the right debug symbols for the reallocated code, break here and: + * - Get the address of the new .text section + * In a terminal: arm-none-eabi-readelf -a ion/src/device/usb/dfu.elf + * - Delete the current symbol table + * symbol-file + * - Add the new symbol table, with the address of the new .text section + * add-symbol-file ion/src/device/usb/dfu.elf 0x20038000 + */ + + dfu_bootloader_entry(true); + + /* 5- Restore interrupts */ + Device::initSysTick(); + + /* 6- That's all. The DFU bootloader on the stack is now dead code that will + * be overwritten when the stack grows. */ +} + +} +} diff --git a/ion/src/f730/usb/dfu_xip.cpp b/ion/src/f730/usb/dfu_xip.cpp new file mode 100644 index 000000000..ad2ef68bb --- /dev/null +++ b/ion/src/f730/usb/dfu_xip.cpp @@ -0,0 +1,12 @@ +#include "calculator.h" +#include "../device.h" + +namespace Ion { +namespace USB { + +void DFU() { + Ion::USB::Device::Calculator::PollAndReset(true); +} + +} +} diff --git a/ion/src/f730/usb/flasher.cpp b/ion/src/f730/usb/flasher.cpp new file mode 100644 index 000000000..71cebf261 --- /dev/null +++ b/ion/src/f730/usb/flasher.cpp @@ -0,0 +1,13 @@ +#include "../regs/regs.h" +#include "../usb/calculator.h" +#include + +void ion_main(int argc, char * argv[]) { + Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0xFFFF00)); + while (true) { + Ion::USB::enable(); + while (!Ion::USB::isEnumerated()) { + } + Ion::USB::Device::Calculator::PollAndReset(false); + } +} diff --git a/ion/src/f730/usb/flasher.ld b/ion/src/f730/usb/flasher.ld new file mode 100644 index 000000000..5621192b3 --- /dev/null +++ b/ion/src/f730/usb/flasher.ld @@ -0,0 +1,77 @@ +/* Create a USB DFU firmware that runs from RAM. + * Flashing using ST's ROMed DFU bootloader is reliable but very slow. To make + * flashing faster, we can leverage ST's bootloader to copy a small "flasher" in + * RAM, and run it from there. + * Caution: ST's bootloader uses some RAM, so we want to stay off of that memory + * region. Per AN2606, section 47, it's using 0x20003000 - 0x2003FFFF; We'll try + * to play safe and avoid the first 32KB of RAM. */ + +MEMORY { + RAM_BUFFER (rw) : ORIGIN = 0x20000000 + 32K, LENGTH = 256K - 32K +} + +/* The stack is quite large, and should really be equal to Epsilon's. Indeed, + * we're making the Calculator object live on the stack, and it's quite large + * (about 4K just for this single object). Using a stack too small will result + * in some memory being overwritten (for instance, vtables that live in the + * .rodata section). */ + +STACK_SIZE = 32K; + +SECTIONS { + .isr_vector_table ORIGIN(RAM_BUFFER) : { + KEEP(*(.isr_vector_table)) + } >RAM_BUFFER + + .text : { + . = ALIGN(4); + *(.text) + *(.text.*) + } >RAM_BUFFER + + .init_array : { + . = ALIGN(4); + _init_array_start = .; + KEEP (*(.init_array*)) + _init_array_end = .; + } >RAM_BUFFER + + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata.*) + } >RAM_BUFFER + + .data : { + . = ALIGN(4); + *(.data) + *(.data.*) + } >RAM_BUFFER + + .bss : { + . = ALIGN(4); + _bss_section_start_ram = .; + *(.bss) + *(.bss.*) + *(COMMON) + _bss_section_end_ram = .; + } >RAM_BUFFER + + .stack : { + . = ALIGN(8); + _stack_end = .; + . += (STACK_SIZE - 8); + . = ALIGN(8); + _stack_start = .; + } >RAM_BUFFER + + .phony : { + /* We won't do dynamic memory allocation */ + _heap_start = .; + _heap_end = .; + /* Effectively bypass copying .data to RAM */ + _data_section_start_flash = .; + _data_section_start_ram = .; + _data_section_end_ram = .; + } >RAM_BUFFER +} diff --git a/ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp new file mode 100644 index 000000000..069e6d56e --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp @@ -0,0 +1,22 @@ +#include "bos_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void BOSDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_wTotalLength); + c->push(m_bNumDeviceCaps); + for (uint8_t i = 0; i < m_bNumDeviceCaps; i++) { + m_deviceCapabilities[i].push(c); + } +} + +uint8_t BOSDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/bos_descriptor.h b/ion/src/f730/usb/stack/descriptor/bos_descriptor.h new file mode 100644 index 000000000..ce2626099 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/bos_descriptor.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_USB_STACK_BOS_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_BOS_DESCRIPTOR_H + +#include "descriptor.h" +#include "device_capability_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class BOSDescriptor : public Descriptor { +public: + constexpr BOSDescriptor( + uint16_t wTotalLength, + uint8_t bNumDeviceCapabilities, + const DeviceCapabilityDescriptor * deviceCapabilities) : + Descriptor(0x0F), + m_wTotalLength(wTotalLength), + m_bNumDeviceCaps(bNumDeviceCapabilities), + m_deviceCapabilities(deviceCapabilities) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint16_t m_wTotalLength; + uint8_t m_bNumDeviceCaps; + const DeviceCapabilityDescriptor * m_deviceCapabilities; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp new file mode 100644 index 000000000..3981837b5 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp @@ -0,0 +1,26 @@ +#include "configuration_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void ConfigurationDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_wTotalLength); + c->push(m_bNumInterfaces); + c->push(m_bConfigurationValue); + c->push(m_iConfiguration); + c->push(m_bmAttributes); + c->push(m_bMaxPower); + for (uint8_t i = 0; i < m_bNumInterfaces; i++) { + m_interfaces[i].push(c); + } +} + +uint8_t ConfigurationDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + 5*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/configuration_descriptor.h b/ion/src/f730/usb/stack/descriptor/configuration_descriptor.h new file mode 100644 index 000000000..fc43d59fb --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/configuration_descriptor.h @@ -0,0 +1,48 @@ +#ifndef ION_DEVICE_USB_STACK_CONFIGURATION_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_CONFIGURATION_DESCRIPTOR_H + +#include "descriptor.h" +#include "interface_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class ConfigurationDescriptor : public Descriptor { +public: + constexpr ConfigurationDescriptor( + uint16_t wTotalLength, + uint8_t bNumInterfaces, + uint8_t bConfigurationValue, + uint8_t iConfiguration, + uint8_t bmAttributes, + uint8_t bMaxPower, + const InterfaceDescriptor * interfaces) : + Descriptor(0x02), + m_wTotalLength(wTotalLength), + m_bNumInterfaces(bNumInterfaces), + m_bConfigurationValue(bConfigurationValue), + m_iConfiguration(iConfiguration), + m_bmAttributes(bmAttributes), + m_bMaxPower(bMaxPower), + m_interfaces(interfaces) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint16_t m_wTotalLength; + uint8_t m_bNumInterfaces; + uint8_t m_bConfigurationValue; + uint8_t m_iConfiguration; + uint8_t m_bmAttributes; + uint8_t m_bMaxPower; + const InterfaceDescriptor * m_interfaces; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/descriptor.cpp b/ion/src/f730/usb/stack/descriptor/descriptor.cpp new file mode 100644 index 000000000..cda9e7a77 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/descriptor.cpp @@ -0,0 +1,15 @@ +#include "descriptor.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +void Descriptor::push(Channel * c) const { + c->push(bLength()); + c->push(m_bDescriptorType); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/descriptor.h b/ion/src/f730/usb/stack/descriptor/descriptor.h new file mode 100644 index 000000000..9af940211 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/descriptor.h @@ -0,0 +1,32 @@ +#ifndef ION_DEVICE_USB_STACK_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_DESCRIPTOR_H + +#include "../streamable.h" + +namespace Ion { +namespace USB { +namespace Device { + +class InterfaceDescriptor; + +class Descriptor : public Streamable { + friend class InterfaceDescriptor; +public: + constexpr Descriptor(uint8_t bDescriptorType) : + m_bDescriptorType(bDescriptorType) + { + } + uint8_t type() const { return m_bDescriptorType; } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const { return 2*sizeof(uint8_t); } +private: + uint8_t m_bDescriptorType; +}; + + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp new file mode 100644 index 000000000..e3ab162d4 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp @@ -0,0 +1,18 @@ +#include "device_capability_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void DeviceCapabilityDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bDeviceCapabilityType); +} + +uint8_t DeviceCapabilityDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h new file mode 100644 index 000000000..cf600c9f1 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h @@ -0,0 +1,31 @@ +#ifndef ION_DEVICE_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class BOSDescriptor; + +class DeviceCapabilityDescriptor : public Descriptor { + friend class BOSDescriptor; +public: + constexpr DeviceCapabilityDescriptor(uint8_t bDeviceCapabilityType) : + Descriptor(0x10), + m_bDeviceCapabilityType(bDeviceCapabilityType) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint8_t m_bDeviceCapabilityType; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/device_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/device_descriptor.cpp new file mode 100644 index 000000000..1ce33847a --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/device_descriptor.cpp @@ -0,0 +1,29 @@ +#include "device_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void DeviceDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bcdUSB); + c->push(m_bDeviceClass); + c->push(m_bDeviceSubClass); + c->push(m_bDeviceProtocol); + c->push(m_bMaxPacketSize0); + c->push(m_idVendor); + c->push(m_idProduct); + c->push(m_bcdDevice); + c->push(m_iManufacturer); + c->push(m_iProduct); + c->push(m_iSerialNumber); + c->push(m_bNumConfigurations); +} + +uint8_t DeviceDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t) + 4*sizeof(uint8_t) + 3*sizeof(uint16_t) + 4*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/device_descriptor.h b/ion/src/f730/usb/stack/descriptor/device_descriptor.h new file mode 100644 index 000000000..d41241348 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/device_descriptor.h @@ -0,0 +1,62 @@ +#ifndef ION_DEVICE_USB_STACK_DEVICE_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_DEVICE_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class DeviceDescriptor : public Descriptor { +public: + constexpr DeviceDescriptor( + uint16_t bcdUSB, + uint8_t bDeviceClass, + uint8_t bDeviceSubClass, + uint8_t bDeviceProtocol, + uint8_t bMaxPacketSize0, + uint16_t idVendor, + uint16_t idProduct, + uint16_t bcdDevice, + uint8_t iManufacturer, + uint8_t iProduct, + uint8_t iSerialNumber, + uint8_t bNumConfigurations) : + Descriptor(0x01), + m_bcdUSB(bcdUSB), + m_bDeviceClass(bDeviceClass), + m_bDeviceSubClass(bDeviceSubClass), + m_bDeviceProtocol(bDeviceProtocol), + m_bMaxPacketSize0(bMaxPacketSize0), + m_idVendor(idVendor), + m_idProduct(idProduct), + m_bcdDevice(bcdDevice), + m_iManufacturer(iManufacturer), + m_iProduct(iProduct), + m_iSerialNumber(iSerialNumber), + m_bNumConfigurations(bNumConfigurations) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint16_t m_bcdUSB; + uint8_t m_bDeviceClass; + uint8_t m_bDeviceSubClass; + uint8_t m_bDeviceProtocol; + uint8_t m_bMaxPacketSize0; + uint16_t m_idVendor; + uint16_t m_idProduct; + uint16_t m_bcdDevice; + uint8_t m_iManufacturer; + uint8_t m_iProduct; + uint8_t m_iSerialNumber; + uint8_t m_bNumConfigurations; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp new file mode 100644 index 000000000..0d531965a --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp @@ -0,0 +1,21 @@ +#include "dfu_functional_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void DFUFunctionalDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bmAttributes); + c->push(m_wDetachTimeOut); + c->push(m_wTransferSize); + c->push(m_bcdDFUVersion); +} + +uint8_t DFUFunctionalDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint8_t) + 3*sizeof(uint16_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h b/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h new file mode 100644 index 000000000..5d51caa5a --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h @@ -0,0 +1,38 @@ +#ifndef ION_DEVICE_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class DFUFunctionalDescriptor : public Descriptor { +public: + constexpr DFUFunctionalDescriptor( + uint8_t bmAttributes, + uint16_t wDetachTimeOut, + uint16_t wTransferSize, + uint16_t bcdDFUVersion) : + Descriptor(0x21), + m_bmAttributes(bmAttributes), + m_wDetachTimeOut(wDetachTimeOut), + m_wTransferSize(wTransferSize), + m_bcdDFUVersion(bcdDFUVersion) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint8_t m_bmAttributes; + uint16_t m_wDetachTimeOut; + uint16_t m_wTransferSize; + uint16_t m_bcdDFUVersion; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp new file mode 100644 index 000000000..287ac8dd7 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp @@ -0,0 +1,61 @@ +#include "extended_compat_id_descriptor.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +ExtendedCompatIDDescriptor::ExtendedCompatIDDescriptor(const char * compatibleID) : + m_dwLength(sizeof(uint32_t) + + 2*sizeof(uint16_t) + + sizeof(uint8_t) + + k_reserved1Size * sizeof(uint8_t) + + 2*sizeof(uint8_t) + + k_compatibleIDSize * sizeof(uint8_t) + + k_compatibleIDSize * sizeof(uint8_t) + + k_reserved2Size * sizeof(uint8_t)), + m_bcdVersion(0x0100), // Microsoft OS Descriptors version 1 + m_wIndex(Index), + m_bCount(1), // We assume one function only. + m_reserved1{0, 0, 0, 0, 0, 0, 0}, + m_bFirstInterfaceNumber(0), + m_bReserved(1), + m_subCompatibleID{0, 0, 0, 0, 0, 0, 0, 0}, + m_reserved2{0, 0, 0, 0, 0, 0} +{ + /* Compatible ID has size k_compatibleIDSize, and any unused bytes should be + * filled with 0. */ + size_t compatibleIDSize = strlen(compatibleID); + size_t compatibleIDCopySize = k_compatibleIDSize < compatibleIDSize ? k_compatibleIDSize : compatibleIDSize; + for (size_t i = 0; i < compatibleIDCopySize; i++) { + m_compatibleID[i] = compatibleID[i]; + } + for (size_t i = compatibleIDCopySize; i < k_compatibleIDSize; i++) { + m_compatibleID[i] = 0; + } +} + +void ExtendedCompatIDDescriptor::push(Channel * c) const { + c->push(m_dwLength); + c->push(m_bcdVersion); + c->push(m_wIndex); + c->push(m_bCount); + for (uint8_t i = 0; i < k_reserved1Size; i++) { + c->push(m_reserved1[i]); + } + c->push(m_bFirstInterfaceNumber); + c->push(m_bReserved); + for (uint8_t i = 0; i < k_compatibleIDSize; i++) { + c->push(m_compatibleID[i]); + } + for (uint8_t i = 0; i < k_compatibleIDSize; i++) { + c->push(m_subCompatibleID[i]); + } + for (uint8_t i = 0; i < k_reserved2Size; i++) { + c->push(m_reserved2[i]); + } +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h b/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h new file mode 100644 index 000000000..199c39640 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h @@ -0,0 +1,43 @@ +#ifndef ION_DEVICE_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H + +#include "../streamable.h" + +namespace Ion { +namespace USB { +namespace Device { + +/* We use this descriptor to tell the Windows OS that the device should be + * treated as a WinUSB device. The Extended Compat ID Descriptor can set + * differents compat IDs according to the interface and function of the device, + * but we assume there is only one. */ + +class ExtendedCompatIDDescriptor : public Streamable { +public: + static constexpr uint8_t Index = 0x0004; + ExtendedCompatIDDescriptor(const char * compatibleID); +protected: + void push(Channel * c) const override; +private: + constexpr static uint8_t k_reserved1Size = 7; + constexpr static uint8_t k_compatibleIDSize = 8; + constexpr static uint8_t k_reserved2Size = 6; + // Header + uint32_t m_dwLength; // The length, in bytes, of the complete extended compat ID descriptor + uint16_t m_bcdVersion; // The descriptor’s version number, in binary coded decimal format + uint16_t m_wIndex; // An index that identifies the particular OS feature descriptor + uint8_t m_bCount; // The number of function sections + uint8_t m_reserved1[k_reserved1Size]; + // Function + uint8_t m_bFirstInterfaceNumber; // The interface or function number + uint8_t m_bReserved; + uint8_t m_compatibleID[k_compatibleIDSize]; + uint8_t m_subCompatibleID[k_compatibleIDSize]; + uint8_t m_reserved2[k_reserved2Size]; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp new file mode 100644 index 000000000..f4bb7d739 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp @@ -0,0 +1,27 @@ +#include "interface_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void InterfaceDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bInterfaceNumber); + c->push(m_bAlternateSetting); + c->push(m_bNumEndpoints); + c->push(m_bInterfaceClass); + c->push(m_bInterfaceSubClass); + c->push(m_bInterfaceProtocol); + c->push(m_iInterface); + if (m_additionalDescriptor != nullptr) { + m_additionalDescriptor->push(c); + } +} + +uint8_t InterfaceDescriptor::bLength() const { + return Descriptor::bLength() + 7*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/interface_descriptor.h b/ion/src/f730/usb/stack/descriptor/interface_descriptor.h new file mode 100644 index 000000000..2d1418c0d --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/interface_descriptor.h @@ -0,0 +1,55 @@ +#ifndef ION_DEVICE_USB_STACK_INTERFACE_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_INTERFACE_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class ConfigurationDescriptor; + +class InterfaceDescriptor : public Descriptor { + friend class ConfigurationDescriptor; +public: + constexpr InterfaceDescriptor( + uint8_t bInterfaceNumber, + uint8_t bAlternateSetting, + uint8_t bNumEndpoints, + uint8_t bInterfaceClass, + uint8_t bInterfaceSubClass, + uint8_t bInterfaceProtocol, + uint8_t iInterface, + Descriptor * additionalDescriptor) : + Descriptor(0x04), + m_bInterfaceNumber(bInterfaceNumber), + m_bAlternateSetting(bAlternateSetting), + m_bNumEndpoints(bNumEndpoints), + m_bInterfaceClass(bInterfaceClass), + m_bInterfaceSubClass(bInterfaceSubClass), + m_bInterfaceProtocol(bInterfaceProtocol), + m_iInterface(iInterface), + m_additionalDescriptor(additionalDescriptor) + /* There could be more than one additional descriptor, but we do not need + * this for now. */ + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint8_t m_bInterfaceNumber; + uint8_t m_bAlternateSetting; + uint8_t m_bNumEndpoints; + uint8_t m_bInterfaceClass; + uint8_t m_bInterfaceSubClass; + uint8_t m_bInterfaceProtocol; + uint8_t m_iInterface; + const Descriptor * m_additionalDescriptor; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp new file mode 100644 index 000000000..49dec8e10 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp @@ -0,0 +1,19 @@ +#include "language_id_string_descriptor.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +void LanguageIDStringDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push((uint16_t)(0x0409)); +} + +uint8_t LanguageIDStringDescriptor::bLength() const { + return Descriptor::bLength() + sizeof(uint16_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h b/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h new file mode 100644 index 000000000..69533bdf5 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h @@ -0,0 +1,25 @@ +#ifndef ION_DEVICE_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +// For now this LanguageIDStringDescriptor only ever returns American English + +class LanguageIDStringDescriptor : public Descriptor { +public: + constexpr LanguageIDStringDescriptor() : + Descriptor(0x03) { } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp new file mode 100644 index 000000000..2d119dc09 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp @@ -0,0 +1,19 @@ +#include "microsoft_os_string_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void MicrosoftOSStringDescriptor::push(Channel * c) const { + StringDescriptor::push(c); + c->push(m_bMSVendorCode); + c->push(m_bPad); +} + +uint8_t MicrosoftOSStringDescriptor::bLength() const { + return StringDescriptor::bLength() + 2 * sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h b/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h new file mode 100644 index 000000000..9bf2295b6 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h @@ -0,0 +1,30 @@ +#ifndef ION_DEVICE_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H + +#include "string_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class MicrosoftOSStringDescriptor : public StringDescriptor { +public: + constexpr MicrosoftOSStringDescriptor(uint8_t bMSVendorCode) : + StringDescriptor("MSFT100"), + m_bMSVendorCode(bMSVendorCode), + m_bPad(0) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint8_t m_bMSVendorCode; + uint8_t m_bPad; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp new file mode 100644 index 000000000..b3739ec6e --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp @@ -0,0 +1,21 @@ +#include "platform_device_capability_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +void PlatformDeviceCapabilityDescriptor::push(Channel * c) const { + DeviceCapabilityDescriptor::push(c); + c->push(m_bReserved); + for (int i = 0; i < k_platformCapabilityUUIDSize; i++) { + c->push(m_platformCapabilityUUID[i]); + } +} + +uint8_t PlatformDeviceCapabilityDescriptor::bLength() const { + return DeviceCapabilityDescriptor::bLength() + sizeof(uint8_t) + k_platformCapabilityUUIDSize*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h new file mode 100644 index 000000000..76087b565 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h @@ -0,0 +1,47 @@ +#ifndef ION_DEVICE_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H + +#include "device_capability_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class PlatformDeviceCapabilityDescriptor : public DeviceCapabilityDescriptor { +public: + constexpr PlatformDeviceCapabilityDescriptor(const uint8_t platformCapabilityUUID[]) : + DeviceCapabilityDescriptor(0x05), + m_bReserved(0), + m_platformCapabilityUUID{ + platformCapabilityUUID[0], + platformCapabilityUUID[1], + platformCapabilityUUID[2], + platformCapabilityUUID[3], + platformCapabilityUUID[4], + platformCapabilityUUID[5], + platformCapabilityUUID[6], + platformCapabilityUUID[7], + platformCapabilityUUID[8], + platformCapabilityUUID[9], + platformCapabilityUUID[10], + platformCapabilityUUID[11], + platformCapabilityUUID[12], + platformCapabilityUUID[13], + platformCapabilityUUID[14], + platformCapabilityUUID[15]} + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + constexpr static uint8_t k_platformCapabilityUUIDSize = 16; + uint8_t m_bReserved; + uint8_t m_platformCapabilityUUID[k_platformCapabilityUUIDSize]; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/string_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/string_descriptor.cpp new file mode 100644 index 000000000..c4ee5068e --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/string_descriptor.cpp @@ -0,0 +1,25 @@ +#include "string_descriptor.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +void StringDescriptor::push(Channel * c) const { + Descriptor::push(c); + const char * stringPointer = m_string; + while (*stringPointer != 0) { + uint16_t stringAsUTF16CodePoint = *stringPointer; + c->push(stringAsUTF16CodePoint); + stringPointer++; + } +} + +uint8_t StringDescriptor::bLength() const { + // The script is returned in UTF-16, hence the multiplication. + return Descriptor::bLength() + 2*strlen(m_string); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/string_descriptor.h b/ion/src/f730/usb/stack/descriptor/string_descriptor.h new file mode 100644 index 000000000..296ac79ca --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/string_descriptor.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_USB_STACK_STRING_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_STRING_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class StringDescriptor : public Descriptor { +public: + constexpr StringDescriptor(const char * string) : + Descriptor(0x03), + m_string(string) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + const char * m_string; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/url_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/url_descriptor.cpp new file mode 100644 index 000000000..3001d8143 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/url_descriptor.cpp @@ -0,0 +1,25 @@ +#include "url_descriptor.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +void URLDescriptor::push(Channel * c) const { + Descriptor::push(c); + c->push(m_bScheme); + const char * stringPointer = m_string; + while (*stringPointer != 0) { + c->push(*stringPointer); + stringPointer++; + } +} + +uint8_t URLDescriptor::bLength() const { + // The script is returned in UTF-8. + return Descriptor::bLength() + sizeof(uint8_t) + strlen(m_string); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/url_descriptor.h b/ion/src/f730/usb/stack/descriptor/url_descriptor.h new file mode 100644 index 000000000..d9e39af29 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/url_descriptor.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_USB_STACK_URL_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_URL_DESCRIPTOR_H + +#include "descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class URLDescriptor : public Descriptor { +public: + enum class Scheme { + HTTP = 0, + HTTPS = 1, + IncludedInURL = 255 + }; + + constexpr URLDescriptor(Scheme scheme, const char * url) : + Descriptor(0x03), + m_bScheme((uint8_t)scheme), + m_string(url) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + uint8_t m_bScheme; + const char * m_string; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp new file mode 100644 index 000000000..e4a4ff548 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp @@ -0,0 +1,22 @@ +#include "webusb_platform_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +constexpr uint8_t WebUSBPlatformDescriptor::k_webUSBUUID[]; + +void WebUSBPlatformDescriptor::push(Channel * c) const { + PlatformDeviceCapabilityDescriptor::push(c); + c->push(m_bcdVersion); + c->push(m_bVendorCode); + c->push(m_iLandingPage); +} + +uint8_t WebUSBPlatformDescriptor::bLength() const { + return PlatformDeviceCapabilityDescriptor::bLength() + sizeof(uint16_t) + 2*sizeof(uint8_t); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h b/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h new file mode 100644 index 000000000..89d39c712 --- /dev/null +++ b/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h @@ -0,0 +1,37 @@ +#ifndef ION_DEVICE_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H +#define ION_DEVICE_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H + +#include "platform_device_capability_descriptor.h" + +namespace Ion { +namespace USB { +namespace Device { + +class WebUSBPlatformDescriptor : public PlatformDeviceCapabilityDescriptor { +public: + constexpr WebUSBPlatformDescriptor(uint8_t bVendorCode, uint8_t iLandingPage) : + PlatformDeviceCapabilityDescriptor(k_webUSBUUID), + m_bcdVersion(0x0100), + m_bVendorCode(bVendorCode), + m_iLandingPage(iLandingPage) + { + } +protected: + void push(Channel * c) const override; + virtual uint8_t bLength() const override; +private: + /* Little-endian encoding of {3408B638-09A9-47A0-8BFD-A0768815B665}. + * See https://wicg.github.io/webusb/#webusb-platform-capability-descriptor */ + constexpr static uint8_t k_webUSBUUID[] = { + 0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, + 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65}; + uint16_t m_bcdVersion; + uint8_t m_bVendorCode; + uint8_t m_iLandingPage; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/device.cpp b/ion/src/f730/usb/stack/device.cpp new file mode 100644 index 000000000..27bf6d5de --- /dev/null +++ b/ion/src/f730/usb/stack/device.cpp @@ -0,0 +1,156 @@ +#include "device.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +static inline uint16_t min(uint16_t x, uint16_t y) { return (xget()); + + /* SETUP or OUT transaction + * If the Rx FIFO is not empty, there is a SETUP or OUT transaction. + * The interrupt is done AFTER THE HANSDHAKE of the transaction. */ + if (intsts.getRXFLVL()) { + class OTG::GRXSTSP grxstsp(OTG.GRXSTSP()->get()); + + // Store the packet status + OTG::GRXSTSP::PKTSTS pktsts = grxstsp.getPKTSTS(); + + // We only use endpoint 0 + assert(grxstsp.getEPNUM() == 0); + + if (pktsts == OTG::GRXSTSP::PKTSTS::OutTransferCompleted || pktsts == OTG::GRXSTSP::PKTSTS::SetupTransactionCompleted) { + // There is no data associated with this interrupt. + return; + } + + assert(pktsts != OTG::GRXSTSP::PKTSTS::GlobalOutNAK); + /* We did not enable the GONAKEFFM (Global OUT NAK effective mask) bit in + * GINTSTS, so we should never get this interrupt. */ + + assert(pktsts == OTG::GRXSTSP::PKTSTS::OutReceived || pktsts == OTG::GRXSTSP::PKTSTS::SetupReceived); + + TransactionType type = (pktsts == OTG::GRXSTSP::PKTSTS::OutReceived) ? TransactionType::Out : TransactionType::Setup; + + if (type == TransactionType::Setup && OTG.DIEPTSIZ0()->getPKTCNT()) { + // SETUP received but there is a packet in the Tx FIFO. Flush it. + m_ep0.flushTxFifo(); + } + + // Save the received packet byte count + m_ep0.setReceivedPacketSize(grxstsp.getBCNT()); + + if (type == TransactionType::Setup) { + m_ep0.readAndDispatchSetupPacket(); + } else { + assert(type == TransactionType::Out); + m_ep0.processOUTpacket(); + } + + m_ep0.discardUnreadData(); + } + + /* IN transactions. + * The interrupt is done AFTER THE HANSDHAKE of the transaction. */ + if (OTG.DIEPINT(0)->getXFRC()) { // We only check endpoint 0. + m_ep0.processINpacket(); + // Clear the Transfer Completed Interrupt + OTG.DIEPINT(0)->setXFRC(true); + } + + // Handle USB RESET. ENUMDNE = **SPEED** Enumeration Done + if (intsts.getENUMDNE()) { + // Clear the ENUMDNE bit + OTG.GINTSTS()->setENUMDNE(true); + /* After a USB reset, the host talks to the device by sending messages to + * address 0; */ + setAddress(0); + // Flush the FIFOs + m_ep0.reset(); + m_ep0.setup(); + /* In setup(), we should set the MPSIZ field in OTG_DIEPCTL0 to the maximum + * packet size depending on the enumeration speed (found in OTG_DSTS). We + * should always get FullSpeed, so we set the packet size accordingly. */ + } +} + +bool Device::isSoftDisconnected() const { + return OTG.DCTL()->getSDIS(); +} + +void Device::detach() { + // Get in soft-disconnected state + OTG.DCTL()->setSDIS(true); +} + +bool Device::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + // Device only handles standard requests. + if (request->requestType() != SetupPacket::RequestType::Standard) { + return false; + } + switch (request->bRequest()) { + case (int) Request::GetStatus: + return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetAddress: + // Make sure the request is adress is valid. + assert(request->wValue() < 128); + /* According to the reference manual, the address should be set after the + * Status stage of the current transaction, but this is not true. + * It should be set here, after the Data stage. */ + setAddress(request->wValue()); + *transferBufferLength = 0; + return true; + case (int) Request::GetDescriptor: + return getDescriptor(request, transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetConfiguration: + *transferBufferLength = 0; + return setConfiguration(request); + case (int) Request::GetConfiguration: + return getConfiguration(transferBuffer, transferBufferLength); + } + return false; +} + +bool Device::getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = min(2, transferBufferMaxLength); + for (int i = 0; i<*transferBufferLength; i++) { + transferBuffer[i] = 0; // No remote wakeup, not self-powered. + } + return true; +} + +void Device::setAddress(uint8_t address) { + OTG.DCFG()->setDAD(address); +} + +bool Device::getDescriptor(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + Descriptor * wantedDescriptor = descriptor(request->descriptorType(), request->descriptorIndex()); + if (wantedDescriptor == nullptr) { + return false; + } + *transferBufferLength = wantedDescriptor->copy(transferBuffer, transferBufferMaxLength); + return true; +} + +bool Device::getConfiguration(uint8_t * transferBuffer, uint16_t * transferBufferLength) { + *transferBufferLength = 1; + transferBuffer[0] = getActiveConfiguration(); + return true; +} + +bool Device::setConfiguration(SetupPacket * request) { + // We support one configuration only + setActiveConfiguration(request->wValue()); + /* There is one configuration only, we no need to set it again, just reset the + * endpoint. */ + m_ep0.reset(); + return true; +} + +} +} +} diff --git a/ion/src/f730/usb/stack/device.h b/ion/src/f730/usb/stack/device.h new file mode 100644 index 000000000..6bc112940 --- /dev/null +++ b/ion/src/f730/usb/stack/device.h @@ -0,0 +1,66 @@ +#ifndef ION_DEVICE_USB_DEVICE_H +#define ION_DEVICE_USB_DEVICE_H + +#include "descriptor/descriptor.h" +#include "endpoint0.h" +#include "interface.h" +#include "request_recipient.h" +#include "setup_packet.h" + +namespace Ion { +namespace USB { +namespace Device { + +// We only handle control transfers, on EP0. +class Device : public RequestRecipient { +public: + Device(Interface * interface) : + RequestRecipient(&m_ep0), + m_ep0(this, interface), + m_resetOnDisconnect(false) + { + } + void poll(); + bool isSoftDisconnected() const; + void detach(); + bool resetOnDisconnect() { return m_resetOnDisconnect; } + void setResetOnDisconnect(bool reset) { m_resetOnDisconnect = reset; } +protected: + virtual Descriptor * descriptor(uint8_t type, uint8_t index) = 0; + virtual void setActiveConfiguration(uint8_t configurationIndex) = 0; + virtual uint8_t getActiveConfiguration() = 0; + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; + Endpoint0 m_ep0; +private: + // USB Standard Device Request Codes + enum class Request { + GetStatus = 0, + ClearFeature = 1, + SetFeature = 3, + SetAddress = 5, + GetDescriptor = 6, + SetDescriptor = 7, + GetConfiguration = 8, + SetConfiguration = 9, + }; + + enum class TransactionType { + Setup, + In, + Out + }; + + void setAddress(uint8_t address); + bool getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getDescriptor(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getConfiguration(uint8_t * transferBuffer, uint16_t * transferBufferLength); + bool setConfiguration(SetupPacket * request); + + bool m_resetOnDisconnect; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/endpoint0.cpp b/ion/src/f730/usb/stack/endpoint0.cpp new file mode 100644 index 000000000..df41e4bd3 --- /dev/null +++ b/ion/src/f730/usb/stack/endpoint0.cpp @@ -0,0 +1,344 @@ +#include "endpoint0.h" +#include +#include +#include "device.h" +#include "interface.h" +#include "request_recipient.h" + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +namespace Ion { +namespace USB { +namespace Device { + +void Endpoint0::setup() { + // Setup the IN direction + + // Reset the device IN endpoint 0 transfer size register + class OTG::DIEPTSIZ0 dieptsiz0(0); + /* Transfer size. The core interrupts the application only after it has + * exhausted the transfer size amount of data. The transfer size is set to the + * maximum packet size, to be interrupted at the end of each packet. */ + dieptsiz0.setXFRSIZ(k_maxPacketSize); + OTG.DIEPTSIZ0()->set(dieptsiz0); + + // Reset the device IN endpoint 0 control register + class OTG::DIEPCTL0 diepctl0(0); // Reset value + // Set the maximum packet size + diepctl0.setMPSIZ(OTG::DIEPCTL0::MPSIZ::Size64); + // Set the NAK bit: all IN transactions on endpoint 0 receive a NAK answer + diepctl0.setSNAK(true); + // Enable the endpoint + diepctl0.setEPENA(true); + OTG.DIEPCTL0()->set(diepctl0); + + // Setup the OUT direction + + setupOut(); + // Set the NAK bit + OTG.DOEPCTL0()->setSNAK(true); + // Enable the endpoint + enableOut(); + + // Setup the Tx FIFO + + /* Tx FIFO depth + * We process each packet as soon as it arrives, so we only need + * k_maxPacketSize bytes. TX0FD being in terms of 32-bit words, we divide + * k_maxPacketSize by 4. */ + OTG.DIEPTXF0()->setTX0FD(k_maxPacketSize/4); + /* Tx FIFO RAM start address. It starts just after the Rx FIFOso the value is + * Rx FIFO start address (0) + Rx FIFO depth. the Rx FIFO depth is set in + * usb.cpp, but because the code is linked separately, we cannot get it. */ + OTG.DIEPTXF0()->setTX0FSA(128); +} + +void Endpoint0::setupOut() { + class OTG::DOEPTSIZ0 doeptsiz0(0); + // Number of back-to-back SETUP data packets the endpoint can receive + doeptsiz0.setSTUPCNT(1); + // Packet count, false if a packet is written into the Rx FIFO + doeptsiz0.setPKTCNT(true); + /* Transfer size. The core interrupts the application only after it has + * exhausted the transfer size amount of data. The transfer size is set to the + * maximum packet size, to be interrupted at the end of each packet. */ + doeptsiz0.setXFRSIZ(64); + OTG.DOEPTSIZ0()->set(doeptsiz0); +} + +void Endpoint0::setOutNAK(bool nak) { + m_forceNAK = nak; + /* We need to keep track of the NAK state of the endpoint to use the value + * after a setupOut in poll() of device.cpp. */ + if (nak) { + OTG.DOEPCTL0()->setSNAK(true); + } else { + OTG.DOEPCTL0()->setCNAK(true); + } +} + +void Endpoint0::enableOut() { + OTG.DOEPCTL0()->setEPENA(true); +} + +void Endpoint0::reset() { + flushTxFifo(); + flushRxFifo(); +} + +void Endpoint0::readAndDispatchSetupPacket() { + setOutNAK(true); + + // Read the 8-bytes Setup packet + if (readPacket(m_largeBuffer, sizeof(SetupPacket)) != sizeof(SetupPacket)) { + stallTransaction(); + return; + }; + + m_request = SetupPacket(m_largeBuffer); + uint16_t maxBufferLength = MIN(m_request.wLength(), MaxTransferSize); + + // Forward the request to the request recipient + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->processSetupRequest(&m_request, m_largeBuffer, &m_transferBufferLength, maxBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->processSetupRequest(&m_request, m_largeBuffer, &m_transferBufferLength, maxBufferLength); + } +} + +void Endpoint0::processINpacket() { + switch (m_state) { + case State::DataIn: + sendSomeData(); + break; + case State::LastDataIn: + m_state = State::StatusOut; + // Prepare to receive the OUT Data[] transaction. + setOutNAK(false); + break; + case State::StatusIn: + { + m_state = State::Idle; + // All the data has been received. Callback the request recipient. + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->wholeDataReceivedCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->wholeDataReceivedCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } + } + break; + default: + stallTransaction(); + } +} + +void Endpoint0::processOUTpacket() { + switch (m_state) { + case State::DataOut: + if (receiveSomeData() < 0) { + break; + } + if ((m_request.wLength() - m_transferBufferLength) <= k_maxPacketSize) { + m_state = State::LastDataOut; + } + break; + case State::LastDataOut: + if (receiveSomeData() < 0) { + break; + } + // Send the DATA1[] to the host. + writePacket(NULL, 0); + m_state = State::StatusIn; + break; + case State::StatusOut: + { + // Read the DATA1[] sent by the host. + readPacket(NULL, 0); + m_state = State::Idle; + // All the data has been sent. Callback the request recipient. + uint8_t type = static_cast(m_request.recipientType()); + if (type == 0) { + // Device recipient + m_requestRecipients[0]->wholeDataSentCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } else { + // Interface recipient + m_requestRecipients[1]->wholeDataSentCallback(&m_request, m_largeBuffer, &m_transferBufferLength); + } + } + break; + default: + stallTransaction(); + } +} + +void Endpoint0::flushTxFifo() { + // Set IN endpoint NAK + OTG.DIEPCTL0()->setSNAK(true); + + // Wait for core to respond + while (!OTG.DIEPINT(0)->getINEPNE()) { + } + + // Get the Tx FIFO number + uint32_t fifo = OTG.DIEPCTL0()->getTXFNUM(); + + // Wait for AHB idle + while (!OTG.GRSTCTL()->getAHBIDL()) { + } + + // Flush Tx FIFO + OTG.GRSTCTL()->setTXFNUM(fifo); + OTG.GRSTCTL()->setTXFFLSH(true); + + // Reset packet counter + OTG.DIEPTSIZ0()->set(0); + + // Wait for the flush + while (OTG.GRSTCTL()->getTXFFLSH()) { + } +} + +void Endpoint0::flushRxFifo() { + // Set OUT endpoint NAK + OTG.DOEPCTL0()->setSNAK(true); + + // Wait for AHB idle + while (!OTG.GRSTCTL()->getAHBIDL()) { + } + + // Flush Rx FIFO + OTG.GRSTCTL()->setRXFFLSH(true); + + // Reset packet counter + OTG.DOEPTSIZ0()->set(0); + + // Wait for the flush + while (OTG.GRSTCTL()->getRXFFLSH()) { + } +} + +void Endpoint0::discardUnreadData() { + for (int i = 0; i < m_receivedPacketSize; i += 4) { + OTG.DFIFO0()->get(); + } + m_receivedPacketSize = 0; +} + +void Endpoint0::sendSomeData() { + if (k_maxPacketSize < m_transferBufferLength) { + // More than one packet needs to be sent + writePacket(m_largeBuffer + m_bufferOffset, k_maxPacketSize); + m_state = State::DataIn; + m_bufferOffset += k_maxPacketSize; + m_transferBufferLength -= k_maxPacketSize; + return; + } + // Last data packet sent + writePacket(m_largeBuffer + m_bufferOffset, m_transferBufferLength); + if (m_zeroLengthPacketNeeded) { + m_state = State::DataIn; + } else { + m_state = State::LastDataIn; + } + m_bufferOffset = 0; + m_zeroLengthPacketNeeded = false; + m_transferBufferLength = 0; +} + +void Endpoint0::clearForOutTransactions(uint16_t wLength) { + m_transferBufferLength = 0; + m_state = (wLength > k_maxPacketSize) ? State::DataOut : State::LastDataOut; + setOutNAK(false); +} + +uint16_t Endpoint0::receiveSomeData() { + // If it is the first chunk of data to be received, m_transferBufferLength is 0. + uint16_t packetSize = MIN(k_maxPacketSize, m_request.wLength() - m_transferBufferLength); + uint16_t sizeOfPacketRead = readPacket(m_largeBuffer + m_transferBufferLength, packetSize); + if (sizeOfPacketRead != packetSize) { + stallTransaction(); + return -1; + } + m_transferBufferLength += packetSize; + return packetSize; +} + +uint16_t Endpoint0::readPacket(void * buffer, uint16_t length) { + uint32_t * buffer32 = (uint32_t *) buffer; + uint16_t buffer32Length = MIN(length, m_receivedPacketSize); + + int i; + // The RX FIFO is read 4 bytes by 4 bytes + for (i = buffer32Length; i >= 4; i -= 4) { + *buffer32++ = OTG.DFIFO0()->get(); + m_receivedPacketSize -= 4; + } + + if (i) { + /* If there are remaining bytes that should be read, read the next 4 bytes + * and copy only the wanted bytes. */ + uint32_t extraData = OTG.DFIFO0()->get(); + memcpy(buffer32, &extraData, i); + if (m_receivedPacketSize < 4) { + m_receivedPacketSize = 0; + } else { + m_receivedPacketSize -= 4; + } + } + return buffer32Length; +} + +uint16_t Endpoint0::writePacket(const void * buffer, uint16_t length) { + const uint32_t * buffer32 = (uint32_t *) buffer; + + // Return if there is already a packet waiting to be read in the TX FIFO + if (OTG.DIEPTSIZ0()->getPKTCNT()) { + return 0; + } + + // Enable transmission + + class OTG::DIEPTSIZ0 dieptsiz0(0); + // Indicate that the Transfer Size is one packet + dieptsiz0.setPKTCNT(1); + // Indicate the length of the Transfer Size + dieptsiz0.setXFRSIZ(length); + OTG.DIEPTSIZ0()->set(dieptsiz0); + // Enable the endpoint + OTG.DIEPCTL0()->setEPENA(true); + // Clear the NAK bit + OTG.DIEPCTL0()->setCNAK(true); + + // Copy the buffer to the TX FIFO by writing data 32bits by 32 bits. + for (int i = length; i > 0; i -= 4) { + OTG.DFIFO0()->set(*buffer32++); + } + + return length; +} + +void Endpoint0::stallTransaction() { + OTG.DIEPCTL0()->setSTALL(true); + m_state = State::Idle; +} + +void Endpoint0::computeZeroLengthPacketNeeded() { + if (m_transferBufferLength + && m_transferBufferLength < m_request.wLength() + && m_transferBufferLength % k_maxPacketSize == 0) + { + m_zeroLengthPacketNeeded = true; + return; + } + m_zeroLengthPacketNeeded = false; +} + +} +} +} diff --git a/ion/src/f730/usb/stack/endpoint0.h b/ion/src/f730/usb/stack/endpoint0.h new file mode 100644 index 000000000..2088aaadd --- /dev/null +++ b/ion/src/f730/usb/stack/endpoint0.h @@ -0,0 +1,79 @@ +#ifndef ION_DEVICE_USB_ENDPOINT0_H +#define ION_DEVICE_USB_ENDPOINT0_H + +#include "setup_packet.h" + +namespace Ion { +namespace USB { +namespace Device { + +class RequestRecipient; + +class Endpoint0 { +public: + enum class State { + Idle, + Stalled, + DataIn, + LastDataIn, + StatusIn, + DataOut, + LastDataOut, + StatusOut, + }; + + constexpr static int k_maxPacketSize = 64; + constexpr static int MaxTransferSize = 2048; + + constexpr Endpoint0(RequestRecipient * device, RequestRecipient * interface) : + m_forceNAK(false), + m_bufferOffset(0), + m_transferBufferLength(0), + m_receivedPacketSize(0), + m_zeroLengthPacketNeeded(false), + m_request(), + m_requestRecipients{device, interface}, + m_state(State::Idle), + m_largeBuffer{0} + { + } + void setup(); + void setupOut(); + void setOutNAK(bool nak); + void enableOut(); + void reset(); + bool NAKForced() const { return m_forceNAK; } + void readAndDispatchSetupPacket(); + void processINpacket(); + void processOUTpacket(); + void flushTxFifo(); + void flushRxFifo(); + void setReceivedPacketSize(uint16_t size) { m_receivedPacketSize = size; } + void discardUnreadData(); + void stallTransaction(); + void computeZeroLengthPacketNeeded(); + void setState(State state) { m_state = state; } + void sendSomeData(); // Writes the next data packet and updates the state. + void clearForOutTransactions(uint16_t wLength); + +private: + uint16_t receiveSomeData(); + uint16_t readPacket(void * buffer, uint16_t length); + uint16_t writePacket(const void * buffer, uint16_t length); + + bool m_forceNAK; + int m_bufferOffset; // When sending large data stored in the buffer, the offset keeps tracks of which data packet should be sent next. + uint16_t m_transferBufferLength; + uint16_t m_receivedPacketSize; + bool m_zeroLengthPacketNeeded; + SetupPacket m_request; + RequestRecipient * m_requestRecipients[2]; + State m_state; + uint8_t m_largeBuffer[MaxTransferSize]; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/interface.cpp b/ion/src/f730/usb/stack/interface.cpp new file mode 100644 index 000000000..3fa95947e --- /dev/null +++ b/ion/src/f730/usb/stack/interface.cpp @@ -0,0 +1,66 @@ +#include "interface.h" + +namespace Ion { +namespace USB { +namespace Device { + +static inline uint16_t min(uint16_t x, uint16_t y) { return (xrequestType() != SetupPacket::RequestType::Standard) { + return false; + } + switch (request->bRequest()) { + case (int) Request::GetStatus: + return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::SetInterface: + return setInterface(request, transferBufferLength); + case (int) Request::GetInterface: + return getInterface(transferBuffer, transferBufferLength, transferBufferMaxLength); + case (int) Request::ClearFeature: + return clearFeature(transferBufferLength); + case (int) Request::SetFeature: + return setFeature(transferBufferLength); + } + return false; +} + +bool Interface::getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = min(2, transferBufferMaxLength); + for (int i = 0; i<*transferBufferLength; i++) { + transferBuffer[i] = 0; // Reserved, must be set to 0 + } + return true; +} + +bool Interface::getInterface(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + *transferBufferLength = min(1, transferBufferMaxLength);; + if (*transferBufferLength > 0) { + transferBuffer[0] = getActiveInterfaceAlternative(); + } + return true; +} + +bool Interface::setInterface(SetupPacket * request, uint16_t * transferBufferLength) { + // We support one interface only + setActiveInterfaceAlternative(request->wValue()); + // There is one interface alternative only, we no need to set it again. + *transferBufferLength = 0; + return true; +} + +bool Interface::clearFeature(uint16_t * transferBufferLength) { + // Not needed for now + *transferBufferLength = 0; + return true; +} + +bool Interface::setFeature(uint16_t * transferBufferLength) { + // Not needed for now + *transferBufferLength = 0; + return true; +} + +} +} +} diff --git a/ion/src/f730/usb/stack/interface.h b/ion/src/f730/usb/stack/interface.h new file mode 100644 index 000000000..40376e197 --- /dev/null +++ b/ion/src/f730/usb/stack/interface.h @@ -0,0 +1,42 @@ +#ifndef ION_DEVICE_USB_INTERFACE_H +#define ION_DEVICE_USB_INTERFACE_H + +#include "endpoint0.h" +#include "request_recipient.h" +#include "setup_packet.h" + +namespace Ion { +namespace USB { +namespace Device { + +class Interface : public RequestRecipient { +public: + Interface(Endpoint0 * ep0) : + RequestRecipient(ep0) + { + } +protected: + virtual void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) = 0; + virtual uint8_t getActiveInterfaceAlternative() = 0; + bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; +private: + // USB Standard Interface Request Codes + enum class Request { + GetStatus = 0, + ClearFeature = 1, + SetFeature = 3, + GetInterface = 10, + SetInterface = 11, + }; + bool getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool getInterface(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + bool setInterface(SetupPacket * request, uint16_t * transferBufferLength); + bool clearFeature(uint16_t * transferBufferLength); + bool setFeature(uint16_t * transferBufferLength); +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/request_recipient.cpp b/ion/src/f730/usb/stack/request_recipient.cpp new file mode 100644 index 000000000..c00745763 --- /dev/null +++ b/ion/src/f730/usb/stack/request_recipient.cpp @@ -0,0 +1,32 @@ +#include "request_recipient.h" + +namespace Ion { +namespace USB { +namespace Device { + +bool RequestRecipient::processSetupRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { + if (request->followingTransaction() == SetupPacket::TransactionType::InTransaction) { + // There is no data stage in this transaction, or the data stage will be in IN direction. + if (!processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { + m_ep0->stallTransaction(); + return false; + } + if (*transferBufferLength > 0) { + m_ep0->computeZeroLengthPacketNeeded(); + m_ep0->sendSomeData(); + } else { + m_ep0->sendSomeData(); + // On seeing a zero length packet, sendSomeData changed endpoint0 state to + // LastDataIn, but it should be StatusIn as there was no data stage. + m_ep0->setState(Endpoint0::State::StatusIn); + } + } else { + // The following transaction will be an OUT transaction. + m_ep0->clearForOutTransactions(request->wLength()); + } + return true; +} + +} +} +} diff --git a/ion/src/f730/usb/stack/request_recipient.h b/ion/src/f730/usb/stack/request_recipient.h new file mode 100644 index 000000000..7f0fc8819 --- /dev/null +++ b/ion/src/f730/usb/stack/request_recipient.h @@ -0,0 +1,29 @@ +#ifndef ION_DEVICE_USB_REQUEST_RECIPIENT_H +#define ION_DEVICE_USB_REQUEST_RECIPIENT_H + +#include "endpoint0.h" +#include "setup_packet.h" + +namespace Ion { +namespace USB { +namespace Device { + +class RequestRecipient { +public: + RequestRecipient(Endpoint0 * ep0): + m_ep0(ep0) + { + } + bool processSetupRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); + virtual void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {} + virtual void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {} +protected: + virtual bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) = 0; + Endpoint0 * m_ep0; +}; + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/setup_packet.cpp b/ion/src/f730/usb/stack/setup_packet.cpp new file mode 100644 index 000000000..d879b59e9 --- /dev/null +++ b/ion/src/f730/usb/stack/setup_packet.cpp @@ -0,0 +1,38 @@ +#include "setup_packet.h" +#include + +namespace Ion { +namespace USB { +namespace Device { + +SetupPacket::SetupPacket(void * buffer) { + memcpy(this, buffer, sizeof(SetupPacket)); +} + +SetupPacket::TransactionType SetupPacket::followingTransaction() const { + if (m_wLength == 0 || (m_bmRequestType & 0b10000000) != 0) { + return TransactionType::InTransaction; + } else { + return TransactionType::OutTransaction; + } +} + +SetupPacket::RequestType SetupPacket::requestType() const { + return (RequestType) ((m_bmRequestType & 0b01100000) >> 5); +} + +SetupPacket::RecipientType SetupPacket::recipientType() const { + return (RecipientType) (m_bmRequestType & 0b00001111); +} + +int SetupPacket::descriptorIndex() { + return m_wValue & 0xFF; +} + +int SetupPacket::descriptorType() { + return m_wValue >> 8; +} + +} +} +} diff --git a/ion/src/f730/usb/stack/setup_packet.h b/ion/src/f730/usb/stack/setup_packet.h new file mode 100644 index 000000000..054ff81e9 --- /dev/null +++ b/ion/src/f730/usb/stack/setup_packet.h @@ -0,0 +1,65 @@ +#ifndef ION_DEVICE_USB_SETUP_PACKET_H +#define ION_DEVICE_USB_SETUP_PACKET_H + +#include + +namespace Ion { +namespace USB { +namespace Device { + +class SetupPacket { +public: + enum class TransactionType { + SetupTransaction, + InTransaction, + OutTransaction + }; + + enum class RequestType { + Standard = 0, + Class = 1, + Vendor = 2 + }; + + enum class RecipientType { + Device = 0, + Interface = 1, + Endpoint = 2, + Other = 3 + }; + + constexpr SetupPacket() : + m_bmRequestType(0), + m_bRequest(0), + m_wValue(0), + m_wIndex(0), + m_wLength(0) + { + } + + SetupPacket(void * buffer); + TransactionType followingTransaction() const; + RequestType requestType() const; + RecipientType recipientType() const; + int descriptorIndex(); + int descriptorType(); + uint8_t bmRequestType() { return m_bmRequestType; } + uint8_t bRequest() { return m_bRequest; } + uint16_t wValue() { return m_wValue; } + uint16_t wIndex() { return m_wIndex; } + uint16_t wLength() { return m_wLength; } +private: + uint8_t m_bmRequestType; + uint8_t m_bRequest; + uint16_t m_wValue; + uint16_t m_wIndex; + uint16_t m_wLength; +}; + +static_assert(sizeof(SetupPacket) == 8, "SetupData must be packed"); + +} +} +} + +#endif diff --git a/ion/src/f730/usb/stack/streamable.cpp b/ion/src/f730/usb/stack/streamable.cpp new file mode 100644 index 000000000..3f4677df8 --- /dev/null +++ b/ion/src/f730/usb/stack/streamable.cpp @@ -0,0 +1,15 @@ +#include "streamable.h" + +namespace Ion { +namespace USB { +namespace Device { + +uint16_t Streamable::copy(void * target, size_t maxSize) const { + Channel c(target, maxSize); + push(&c); + return maxSize - c.sizeLeft(); +} + +} +} +} diff --git a/ion/src/f730/usb/stack/streamable.h b/ion/src/f730/usb/stack/streamable.h new file mode 100644 index 000000000..3a653ef1e --- /dev/null +++ b/ion/src/f730/usb/stack/streamable.h @@ -0,0 +1,44 @@ +#ifndef ION_DEVICE_USB_STREAMABLE_H +#define ION_DEVICE_USB_STREAMABLE_H + +#include +#include + +namespace Ion { +namespace USB { +namespace Device { + +class Streamable { +public: + uint16_t copy(void * target, size_t maxSize) const; +protected: + class Channel { + public: + Channel(void * pointer, size_t maxSize) : + m_pointer(pointer), + m_sizeLeft(maxSize) + { + } + template + void push(T data) { + if (m_sizeLeft >= sizeof(T)) { + T * typedPointer = static_cast(m_pointer); + *typedPointer++ = data; // Actually push the data + m_pointer = static_cast(typedPointer); + m_sizeLeft -= sizeof(T); + } + } + size_t sizeLeft() { return m_sizeLeft; } + private: + void * m_pointer; + size_t m_sizeLeft; + }; + virtual void push(Channel * c) const = 0; +}; + + +} +} +} + +#endif diff --git a/ion/src/f730/wakeup.cpp b/ion/src/f730/wakeup.cpp new file mode 100644 index 000000000..3582d6ad3 --- /dev/null +++ b/ion/src/f730/wakeup.cpp @@ -0,0 +1,69 @@ +#include "wakeup.h" +#include "regs/regs.h" +#include "battery.h" +#include "usb.h" +#include "keyboard.h" + +namespace Ion { +namespace WakeUp { +namespace Device { + +void onChargingEvent() { + Battery::Device::initGPIO(); + + /* Warning: pins with the same number in different groups cannot be set as + * source input for EXTI at the same time. Here, EXTICR1 register is filled + * between position 0-3 (charging pin = 0) with + * 0000 (ChargingGPIO = group A). */ + SYSCFG.EXTICR1()->setEXTI(Battery::Device::ChargingPin, Battery::Device::ChargingGPIO); + + EXTI.EMR()->set(Battery::Device::ChargingPin, true); + + /* We need to detect when the battery stops charging. We set the + * wake up event on the rising edge. */ + EXTI.RTSR()->set(Battery::Device::ChargingPin, true); +} + +void onUSBPlugging() { + USB::Device::initGPIO(); + /* Here, EXTICR3 register is filled between position 4-7 (Vbus pin = 9) with + * 0000 (Vbus GPIO = group A). */ + SYSCFG.EXTICR3()->setEXTI(USB::Device::VbusPin.pin(), USB::Device::VbusPin.group()); + + EXTI.EMR()->set(USB::Device::VbusPin.pin(), true); +#if EPSILON_LED_WHILE_CHARGING + EXTI.FTSR()->set(USB::Device::VbusPin.pin(), true); +#endif + EXTI.RTSR()->set(USB::Device::VbusPin.pin(), true); +} + + +void onPowerKeyDown() { + Keyboard::Key key = Keyboard::Key::B2; + uint8_t rowPin = Keyboard::Device::RowPins[Keyboard::Device::rowForKey(key)]; + Keyboard::Device::RowGPIO.MODER()->setMode(rowPin, GPIO::MODER::Mode::Output); + Keyboard::Device::RowGPIO.OTYPER()->setType(rowPin, GPIO::OTYPER::Type::OpenDrain); + Keyboard::Device::RowGPIO.ODR()->set(rowPin, 0); + + uint8_t column = Keyboard::Device::columnForKey(key); + uint8_t columnPin = Keyboard::Device::ColumnPins[column]; + + Keyboard::Device::ColumnGPIO.MODER()->setMode(columnPin, GPIO::MODER::Mode::Input); + Keyboard::Device::ColumnGPIO.PUPDR()->setPull(columnPin, GPIO::PUPDR::Pull::Up); + + /* Here, EXTICR1 register is filled between position 4-7 (column pin = 1) with + * 0010 (ColumnGPIO = group C). */ + + SYSCFG.EXTICR1()->setEXTI(columnPin, Keyboard::Device::ColumnGPIO); + + EXTI.EMR()->set(columnPin, true); + + /* When the key is pressed, it will go from 1 (because it's pulled up) to + * zero (because it's connected to the open-drain output. In other words, + * we're waiting for a falling edge. */ + EXTI.FTSR()->set(columnPin, true); +} + +} +} +} diff --git a/ion/src/f730/wakeup.h b/ion/src/f730/wakeup.h new file mode 100644 index 000000000..0c7d641b9 --- /dev/null +++ b/ion/src/f730/wakeup.h @@ -0,0 +1,41 @@ +#ifndef ION_DEVICE_WAKE_UP_H +#define ION_DEVICE_WAKE_UP_H + +#include "regs/regs.h" + +namespace Ion { +namespace WakeUp { +namespace Device { + +/* All wakeup functions can be called together without overwriting the same + * register. All togethed, they will set SYSCFG and EXTi registers as follow: + * + * GPIO Pin Number|EXTI_EMR|EXTI_FTSR|EXTI_RTSR|EXTICR1|EXTICR2|EXTICR3| Wake up + * ---------------+--------+---------+---------+-------+-------+-------+------------------------- + * 0 | 1 | 0 | 1 | A | ***** | ***** | Rising edge GPIO A pin 0 + * 1 | 1 | 1 | 0 | C | ***** | ***** | Falling edge GPIO C pin 1 + * 2 | 0 | 0 | 0 | A | ***** | ***** | + * 3 | 0 | 0 | 0 | A | ***** | ***** | + * 4 | 0 | 0 | 0 | ***** | A | ***** | + * 5 | 0 | 0 | 0 | ***** | A | ***** | + * 6 | 0 | 0 | 0 | ***** | A | ***** | + * 7 | 0 | 0 | 0 | ***** | A | ***** | + * 8 | 0 | 0 | 0 | ***** | ***** | A | + * 9 | 1 | 1 | 1 | ***** | ***** | A | Falling/Rising edge GPIO A pin 9 + * 10 | 0 | 0 | 0 | ***** | ***** | A | + * 11 | 0 | 0 | 0 | ***** | ***** | A | + * 12 | 0 | 0 | 0 | ***** | ***** | ***** | + * 13 | 0 | 0 | 0 | ***** | ***** | ***** | + * 14 | 0 | 0 | 0 | ***** | ***** | ***** | + * 15 | 0 | 0 | 0 | ***** | ***** | ***** | + */ + +void onChargingEvent(); +void onUSBPlugging(); +void onPowerKeyDown(); + +} +} +} + +#endif From fe9b2fec1c67f99b49f8efae52b2ef479e965c0f Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:08:31 +0100 Subject: [PATCH 0053/1750] [ion/f730] Switch to FMC --- ion/src/f730/regs/{fsmc.h => fmc.h} | 10 +++++----- ion/src/f730/regs/regs.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) rename ion/src/f730/regs/{fsmc.h => fmc.h} (94%) diff --git a/ion/src/f730/regs/fsmc.h b/ion/src/f730/regs/fmc.h similarity index 94% rename from ion/src/f730/regs/fsmc.h rename to ion/src/f730/regs/fmc.h index 52bfcbdac..302c5be74 100644 --- a/ion/src/f730/regs/fsmc.h +++ b/ion/src/f730/regs/fmc.h @@ -1,9 +1,9 @@ -#ifndef REGS_FSMC_H -#define REGS_FSMC_H +#ifndef REGS_FMC_H +#define REGS_FMC_H #include "register.h" -class FSMC { +class FMC { public: class BCR : Register32 { public: @@ -58,7 +58,7 @@ public: REGS_TYPE_FIELD(ACCMOD, 29, 28); }; - constexpr FSMC() {} + constexpr FMC() {} volatile BCR * BCR(int index) const { return (class BCR *)(Base() + 8*(index-1)); } @@ -74,6 +74,6 @@ private: }; }; -constexpr FSMC FSMC; +constexpr FMC FMC; #endif diff --git a/ion/src/f730/regs/regs.h b/ion/src/f730/regs/regs.h index b5fad9e8a..ed5b48bdb 100644 --- a/ion/src/f730/regs/regs.h +++ b/ion/src/f730/regs/regs.h @@ -7,7 +7,7 @@ #include "dma.h" #include "exti.h" #include "flash.h" -#include "fsmc.h" +#include "fmc.h" #include "gpio.h" #include "itm.h" #include "mpu.h" From e4af17c3cc107c2428c066efa342f618df9bd80d Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:36:38 +0100 Subject: [PATCH 0054/1750] [build] Add an f730 folder --- build/f730/gdb_script.gdb | 36 ++++++++++++++++++++++++++++++++++++ build/f730/openocd.cfg | 7 +++++++ build/platform.f730.mak | 1 + build/targets.device.mak | 4 ++-- build/targets.f730.mak | 1 + 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 build/f730/gdb_script.gdb create mode 100644 build/f730/openocd.cfg create mode 100644 build/platform.f730.mak create mode 100644 build/targets.f730.mak diff --git a/build/f730/gdb_script.gdb b/build/f730/gdb_script.gdb new file mode 100644 index 000000000..515e20021 --- /dev/null +++ b/build/f730/gdb_script.gdb @@ -0,0 +1,36 @@ +# Add a routine to debug ARM exceptions + +define armex + printf "SCB_CFSR 0x%x\n", *0xE000ED28 + printf "SCB_HFSR 0x%x\n", *0xE000ED2C + printf "SCB_HFSR 0x%x\n", *0xE000ED2C + printf "SCB_MMAR 0x%x\n", *0xE000ED34 + printf "SCB_BFAR 0x%x\n", *0xE000ED38 + printf "xPSR 0x%x\n", *(int *)($msp+28) + printf "ReturnAddress 0x%x\n", *(int *)($msp+24) + printf "LR (R14) 0x%x\n", *(int *)($msp+20) + printf "R12 0x%x\n", *(int *)($msp+16) + printf "R3 0x%x\n", *(int *)($msp+12) + printf "R2 0x%x\n", *(int *)($msp+8) + printf "R1 0x%x\n", *(int *)($msp+4) + printf "R0 0x%x\n", *(int *)($msp) + printf "Return instruction:\n" + x/i *(int *)($msp+24) +end + +document armex +ARMv7 Exception entry behavior. +xPSR, ReturnAddress, LR (R14), R12, R3, R2, R1, and R0 +end + +# Let's connect to OpenOCD +target remote localhost:3333 + +# GDB pagniation is annoying +set pagination off + +# Load our executable +load + +# Tell OpenOCD to reset and halt +monitor reset halt diff --git a/build/f730/openocd.cfg b/build/f730/openocd.cfg new file mode 100644 index 000000000..0645bf05a --- /dev/null +++ b/build/f730/openocd.cfg @@ -0,0 +1,7 @@ +source [find interface/stlink.cfg] + +transport select hla_swd + +source [find target/stm32f7x.cfg] + +reset_config none separate diff --git a/build/platform.f730.mak b/build/platform.f730.mak new file mode 100644 index 000000000..1c9bcd804 --- /dev/null +++ b/build/platform.f730.mak @@ -0,0 +1 @@ +include build/platform.device.mak diff --git a/build/targets.device.mak b/build/targets.device.mak index 5a33eea6f..4df3b5f38 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -25,7 +25,7 @@ products += $(patsubst %.$(EXE),%.map,$(filter %.$(EXE),$(products))) .PHONY: %_run %_run: %.$(EXE) - $(GDB) -x build/device/gdb_script.gdb $< + $(GDB) -x build/$(PLATFORM)/gdb_script.gdb $< %.map: %.elf @echo "LDMAP $@" @@ -48,7 +48,7 @@ products += $(patsubst %.$(EXE),%.map,$(filter %.$(EXE),$(products))) .PHONY: openocd openocd: - openocd -f build/device/openocd.cfg + openocd -f build/$(PLATFORM)/openocd.cfg ifeq ($(EPSILON_USB_DFU_XIP)$(EPSILON_DEVICE_BENCH),10) flasher.$(EXE): LDFLAGS = --gc-sections -T ion/src/device/usb/flasher.ld diff --git a/build/targets.f730.mak b/build/targets.f730.mak new file mode 100644 index 000000000..11effd8c7 --- /dev/null +++ b/build/targets.f730.mak @@ -0,0 +1 @@ +include build/targets.device.mak From 19ad5617284e0c94af3cf066938912aeaf5f2513 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:37:45 +0100 Subject: [PATCH 0055/1750] [ion/f730] Fix makefiles --- ion/src/f730/Makefile | 18 +++++++++--------- ion/src/f730/usb/Makefile | 34 +++++++++++++++++----------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/ion/src/f730/Makefile b/ion/src/f730/Makefile index 3d243bd47..4c5ffee55 100644 --- a/ion/src/f730/Makefile +++ b/ion/src/f730/Makefile @@ -1,6 +1,6 @@ -include ion/src/device/boot/Makefile -include ion/src/device/bench/Makefile -include ion/src/device/usb/Makefile +include ion/src/f730/boot/Makefile +include ion/src/f730/bench/Makefile +include ion/src/f730/usb/Makefile ion/src/shared/platform_info.o: SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" @@ -13,7 +13,7 @@ objs += $(addprefix ion/src/shared/, \ # If you need to profile execution, you can replace events_keyboard with # events_replay.o and dummy/events_modifier.o -objs += $(addprefix ion/src/device/, \ +objs += $(addprefix ion/src/f730/, \ backlight.o \ battery.o\ base64.o\ @@ -41,11 +41,11 @@ objs += $(addprefix ion/src/device/, \ ifneq ($(DEBUG),1) ifneq ($(COMPILER),llvm) -ion/src/device/led.o: SFLAGS+=-O3 -ion/src/device/console.o: SFLAGS+=-O3 -ion/src/device/display.o: SFLAGS+=-O3 -ion/src/device/swd.o: SFLAGS+=-O3 +ion/src/f730/led.o: SFLAGS+=-O3 +ion/src/f730/console.o: SFLAGS+=-O3 +ion/src/f730/display.o: SFLAGS+=-O3 +ion/src/f730/swd.o: SFLAGS+=-O3 endif endif -#objs += $(addprefix ion/src/device/keyboard/, keyboard.o) +#objs += $(addprefix ion/src/f730/keyboard/, keyboard.o) diff --git a/ion/src/f730/usb/Makefile b/ion/src/f730/usb/Makefile index a2d840eef..7d38b05d6 100644 --- a/ion/src/f730/usb/Makefile +++ b/ion/src/f730/usb/Makefile @@ -1,9 +1,9 @@ -usb_objs += $(addprefix ion/src/device/usb/, \ +usb_objs += $(addprefix ion/src/f730/usb/, \ calculator.o \ dfu_interface.o\ ) -usb_objs += $(addprefix ion/src/device/usb/stack/, \ +usb_objs += $(addprefix ion/src/f730/usb/stack/, \ device.o\ endpoint0.o \ interface.o\ @@ -12,7 +12,7 @@ usb_objs += $(addprefix ion/src/device/usb/stack/, \ streamable.o\ ) -usb_objs += $(addprefix ion/src/device/usb/stack/descriptor/, \ +usb_objs += $(addprefix ion/src/f730/usb/stack/descriptor/, \ bos_descriptor.o\ configuration_descriptor.o \ descriptor.o\ @@ -33,7 +33,7 @@ EPSILON_USB_DFU_XIP ?= 0 ifeq ($(EPSILON_USB_DFU_XIP),1) -objs += ion/src/device/usb/dfu_xip.o +objs += ion/src/f730/usb/dfu_xip.o objs += $(usb_objs) else @@ -44,24 +44,24 @@ dfu_objs += liba/src/strlcpy.o dfu_objs += liba/src/memset.o dfu_objs += liba/src/memcpy.o dfu_objs += libaxx/src/cxxabi/pure_virtual.o -dfu_objs += ion/src/device/usb/boot.o -dfu_objs += ion/src/device/keyboard.o -dfu_objs += ion/src/device/device.o -dfu_objs += ion/src/device/usb.o -dfu_objs += ion/src/device/base64.o -dfu_objs += ion/src/device/flash.o -dfu_objs += ion/src/device/timing.o +dfu_objs += ion/src/f730/usb/boot.o +dfu_objs += ion/src/f730/keyboard.o +dfu_objs += ion/src/f730/device.o +dfu_objs += ion/src/f730/usb.o +dfu_objs += ion/src/f730/base64.o +dfu_objs += ion/src/f730/flash.o +dfu_objs += ion/src/f730/timing.o -ion/src/device/usb/dfu.elf: LDSCRIPT = ion/src/device/usb/dfu.ld -ion/src/device/usb/dfu.elf: $(usb_objs) $(dfu_objs) +ion/src/f730/usb/dfu.elf: LDSCRIPT = ion/src/f730/usb/dfu.ld +ion/src/f730/usb/dfu.elf: $(usb_objs) $(dfu_objs) -ion/src/device/usb/dfu.o: ion/src/device/usb/dfu.bin +ion/src/f730/usb/dfu.o: ion/src/f730/usb/dfu.bin @echo "OBJCOPY $@" $(Q) $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata --redefine-sym _binary_ion_src_device_usb_dfu_bin_start=_dfu_bootloader_flash_start --redefine-sym _binary_ion_src_device_usb_dfu_bin_end=_dfu_bootloader_flash_end $< $@ -objs += ion/src/device/usb/dfu.o -objs += ion/src/device/usb/dfu_relocated.o +objs += ion/src/f730/usb/dfu.o +objs += ion/src/f730/usb/dfu_relocated.o -products += $(usb_objs) $(addprefix ion/src/device/usb/dfu, .elf .bin) +products += $(usb_objs) $(addprefix ion/src/f730/usb/dfu, .elf .bin) endif From 993edb72d0e3e1e81e54484655bc972c9695c1e2 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:38:30 +0100 Subject: [PATCH 0056/1750] [ion/f730] Rename FSMC to FMC --- ion/src/f730/device.cpp | 4 +- ion/src/f730/display.cpp | 58 +++++++++++++-------------- ion/src/f730/display.h | 87 ++++++++++++++++++++-------------------- ion/src/f730/regs/rcc.h | 2 +- 4 files changed, 76 insertions(+), 75 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index e25cc4106..ab6f47baf 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -285,7 +285,7 @@ void initClocks() { RCC.AHB2ENR()->setOTGFSEN(true); // AHB3 bus - RCC.AHB3ENR()->setFSMCEN(true); + RCC.AHB3ENR()->setFMCEN(true); // APB1 bus // We're using TIM3 for the LEDs @@ -318,7 +318,7 @@ void shutdownClocks(bool keepLEDAwake) { RCC.APB1ENR()->set(apb1enr); RCC.AHB1ENR()->set(ahb1enr); - RCC.AHB3ENR()->setFSMCEN(false); + RCC.AHB3ENR()->setFMCEN(false); } } diff --git a/ion/src/f730/display.cpp b/ion/src/f730/display.cpp index 1804a7b19..1fa38012d 100644 --- a/ion/src/f730/display.cpp +++ b/ion/src/f730/display.cpp @@ -7,7 +7,7 @@ extern "C" { /* This driver interfaces with the ST7789V LCD controller. * This chip keeps a whole frame in SRAM memory and feeds it to the LCD panel as - * needed. We use the STM32's FSMC to drive the bus between the ST7789V. Once + * needed. We use the STM32's FMC to drive the bus between the ST7789V. Once * configured, we only need to write in the address space of the MCU to actually * send some data to the LCD controller. */ @@ -94,13 +94,13 @@ void init() { initDMA(); #endif initGPIO(); - initFSMC(); + initFMC(); initPanel(); } void shutdown() { shutdownPanel(); - shutdownFSMC(); + shutdownFMC(); shutdownGPIO(); } @@ -140,8 +140,8 @@ static inline void startDMAUpload(const KDColor * src, bool incrementSrc, uint16 #endif void initGPIO() { - // All the FSMC GPIO pins use the alternate function number 12 - for(const GPIOPin & g : FSMCPins) { + // All the FMC GPIO pins use the alternate function number 12 + for(const GPIOPin & g : FMCPins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF12); } @@ -167,8 +167,8 @@ void initGPIO() { void shutdownGPIO() { - // All the FSMC GPIO pins use the alternate function number 12 - for(const GPIOPin & g : FSMCPins) { + // All the FMC GPIO pins use the alternate function number 12 + for(const GPIOPin & g : FMCPins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); } @@ -185,26 +185,26 @@ void shutdownGPIO() { TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Analog); } -void initFSMC() { - /* Set up the FSMC control registers. +void initFMC() { + /* Set up the FMC control registers. * We address the LCD panel as if it were an SRAM module, using a 16bits wide * bus, non-multiplexed. - * The STM32 FSMC supports two kinds of memory access modes : + * The STM32 FMC supports two kinds of memory access modes : * - Base modes (1 and 2), which use the same timings for reads and writes * - Extended modes (named A to D), which can be customized further. * The LCD panel can be written to faster than it can be read from, therefore * we want to use one of the extended modes. */ - FSMC.BCR(FSMCMemoryBank)->setEXTMOD(true); - FSMC.BCR(FSMCMemoryBank)->setWREN(true); - FSMC.BCR(FSMCMemoryBank)->setMWID(FSMC::BCR::MWID::SIXTEEN_BITS); - FSMC.BCR(FSMCMemoryBank)->setMTYP(FSMC::BCR::MTYP::SRAM); - FSMC.BCR(FSMCMemoryBank)->setMUXEN(false); - FSMC.BCR(FSMCMemoryBank)->setMBKEN(true); + FMC.BCR(FMCMemoryBank)->setEXTMOD(true); + FMC.BCR(FMCMemoryBank)->setWREN(true); + FMC.BCR(FMCMemoryBank)->setMWID(FMC::BCR::MWID::SIXTEEN_BITS); + FMC.BCR(FMCMemoryBank)->setMTYP(FMC::BCR::MTYP::SRAM); + FMC.BCR(FMCMemoryBank)->setMUXEN(false); + FMC.BCR(FMCMemoryBank)->setMBKEN(true); - /* We now need to set the actual timings. First, the FSMC and LCD specs don't + /* We now need to set the actual timings. First, the FMC and LCD specs don't * use the same names. Here's the mapping: * - * FSMC | LCD + * FMC | LCD * -----+----- * NOE | RDX * NWE | WRX @@ -228,21 +228,21 @@ void initFSMC() { */ // Read timing from the LCD - FSMC.BTR(FSMCMemoryBank)->setADDSET(2); - FSMC.BTR(FSMCMemoryBank)->setADDHLD(0); - FSMC.BTR(FSMCMemoryBank)->setDATAST(36); - FSMC.BTR(FSMCMemoryBank)->setBUSTURN(10); - FSMC.BTR(FSMCMemoryBank)->setACCMOD(FSMC::BTR::ACCMOD::A); + FMC.BTR(FMCMemoryBank)->setADDSET(2); + FMC.BTR(FMCMemoryBank)->setADDHLD(0); + FMC.BTR(FMCMemoryBank)->setDATAST(36); + FMC.BTR(FMCMemoryBank)->setBUSTURN(10); + FMC.BTR(FMCMemoryBank)->setACCMOD(FMC::BTR::ACCMOD::A); // Write timings for the LCD - FSMC.BWTR(FSMCMemoryBank)->setADDSET(2); - FSMC.BWTR(FSMCMemoryBank)->setADDHLD(0); - FSMC.BWTR(FSMCMemoryBank)->setDATAST(3); - FSMC.BWTR(FSMCMemoryBank)->setBUSTURN(3); - FSMC.BWTR(FSMCMemoryBank)->setACCMOD(FSMC::BWTR::ACCMOD::A); + FMC.BWTR(FMCMemoryBank)->setADDSET(2); + FMC.BWTR(FMCMemoryBank)->setADDHLD(0); + FMC.BWTR(FMCMemoryBank)->setDATAST(3); + FMC.BWTR(FMCMemoryBank)->setBUSTURN(3); + FMC.BWTR(FMCMemoryBank)->setACCMOD(FMC::BWTR::ACCMOD::A); } -void shutdownFSMC() { +void shutdownFMC() { } void initPanel() { diff --git a/ion/src/f730/display.h b/ion/src/f730/display.h index e9147b90a..03d08b492 100644 --- a/ion/src/f730/display.h +++ b/ion/src/f730/display.h @@ -12,30 +12,32 @@ namespace Ion { namespace Display { namespace Device { -/* Pin | Role | Mode | Function | Note - * -----+-------------------+-----------------------+----------|------ - * PA2 | LCD D4 | Alternate Function 12 | FSMC_D4 | - * PA3 | LCD D5 | Alternate Function 12 | FSMC_D5 | - * PA4 | LCD D6 | Alternate Function 12 | FSMC_D6 | - * PB12 | LCD D13 | Alternate Function 12 | FSMC_D13 | - * PB14 | LCD power | Output | | LCD controller is powered directly from GPIO - * PD0 | LCD D2 | Alternate Function 12 | FSMC_D2 | - * PD1 | LCD D3 | Alternate Function 12 | FSMC_D3 | - * PD4 | LCD read signal | Alternate Function 12 | FSMC_NOE | - * PD5 | LCD write signal | Alternate Function 12 | FSMC_NWE - * PD7 | LCD chip select | Alternate Function 12 | FSMC_NE1 | Memory bank 1 - * PD9 | LCD D14 | Alternate Function 12 | FSMC_D14 | - * PD10 | LCD D15 | Alternate Function 12 | FSMC_D15 | - * PD11 | LCD data/command | Alternate Function 12 | FSMC_A16 | Data/Command is address bit 16 - * PD14 | LCD D0 | Alternate Function 12 | FSMC_D0 | - * PD15 | LCD D1 | Alternate Function 12 | FSMC_D1 | - * PE9 | LCD reset | Output | | - * PE10 | LCD D7 | Alternate Function 12 | FSMC_D7 | - * PE11 | LCD D8 | Alternate Function 12 | FSMC_D8 | - * PE12 | LCD D9 | Alternate Function 12 | FSMC_D9 | - * PE13 | LCD D10 | Alternate Function 12 | FSMC_D10 | - * PE14 | LCD D11 | Alternate Function 12 | FSMC_D11 | - * PE15 | LCD D12 | Alternate Function 12 | FSMC_D12 | +/* Pin | Role | Mode | Function | Note + * -----+--------------------+-----------------------+----------|------ + * PB11 | LCD Tearing effect | Input | | + * PC8 | LCD power | Output | | LCD controller is powered directly from GPIO + * PD0 | LCD D2 | Alternate Function 12 | FMC_D2 | + * PD1 | LCD D3 | Alternate Function 12 | FMC_D3 | + * PD4 | LCD read signal | Alternate Function 12 | FMC_NOE | + * PD5 | LCD write signal | Alternate Function 12 | FMC_NWE | + * PD6 | LCD extended cmd | Output | | + * PD7 | LCD chip select | Alternate Function 12 | FMC_NE1 | Memory bank 1 + * PD8 | LCD D13 | Alternate Function 12 | FMC_D13 | + * PD9 | LCD D14 | Alternate Function 12 | FMC_D14 | + * PD10 | LCD D15 | Alternate Function 12 | FMC_D15 | + * PD11 | LCD data/command | Alternate Function 12 | FMC_A16 | Data/Command is address bit 16 + * PD14 | LCD D0 | Alternate Function 12 | FMC_D0 | + * PD15 | LCD D1 | Alternate Function 12 | FMC_D1 | + * PE1 | LCD reset | Output | | + * PE7 | LCD D4 | Alternate Function 12 | FMC_D4 | + * PE8 | LCD D5 | Alternate Function 12 | FMC_D5 | + * PE9 | LCD D6 | Alternate Function 12 | FMC_D6 | + * PE10 | LCD D7 | Alternate Function 12 | FMC_D7 | + * PE11 | LCD D8 | Alternate Function 12 | FMC_D8 | + * PE12 | LCD D9 | Alternate Function 12 | FMC_D9 | + * PE13 | LCD D10 | Alternate Function 12 | FMC_D10 | + * PE14 | LCD D11 | Alternate Function 12 | FMC_D11 | + * PE15 | LCD D12 | Alternate Function 12 | FMC_D12 | */ void init(); @@ -44,8 +46,8 @@ void shutdown(); void initDMA(); void initGPIO(); void shutdownGPIO(); -void initFSMC(); -void shutdownFSMC(); +void initFMC(); +void shutdownFMC(); void initPanel(); void shutdownPanel(); @@ -77,31 +79,30 @@ enum class Command : uint16_t { FrameRateControl = 0xC6 }; -constexpr static GPIOPin FSMCPins[] = { - GPIOPin(GPIOA, 2), GPIOPin(GPIOA, 3), GPIOPin(GPIOA, 4), GPIOPin(GPIOB, 12), - GPIOPin(GPIOB, 12), GPIOPin(GPIOD, 0), GPIOPin(GPIOD, 1), GPIOPin(GPIOD, 4), - GPIOPin(GPIOD, 5), GPIOPin(GPIOD, 7), GPIOPin(GPIOD, 9), GPIOPin(GPIOD, 10), - GPIOPin(GPIOD, 11), GPIOPin(GPIOD, 14), GPIOPin(GPIOD, 15), GPIOPin(GPIOE, 10), - GPIOPin(GPIOE, 11), GPIOPin(GPIOE, 12), GPIOPin(GPIOE, 13), GPIOPin(GPIOE, 14), - GPIOPin(GPIOE, 15) +constexpr static GPIOPin FMCPins[] = { + 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(GPIOB, 14); -constexpr static GPIOPin ResetPin = GPIOPin(GPIOE, 9); -constexpr static GPIOPin ExtendedCommandPin = GPIOPin(GPIOB, 13); -constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 10); +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 int FSMCMemoryBank = 1; -constexpr static int FSMCDataCommandAddressBit = 16; +constexpr static int FMCMemoryBank = 1; +constexpr static int FMCDataCommandAddressBit = 16; -constexpr static uint32_t FSMCBaseAddress = 0x60000000; -constexpr static uint32_t FSMCBankAddress = FSMCBaseAddress + (FSMCMemoryBank-1)*0x04000000; +constexpr static uint32_t FMCBaseAddress = 0x60000000; +constexpr static uint32_t FMCBankAddress = FMCBaseAddress + (FMCMemoryBank-1)*0x04000000; constexpr static DMA DMAEngine = DMA2; constexpr static int DMAStream = 0; -static volatile Command * const CommandAddress = (Command *)(FSMCBankAddress); -static volatile uint16_t * const DataAddress = (uint16_t *)(FSMCBankAddress | (1<<(FSMCDataCommandAddressBit+1))); +static volatile Command * const CommandAddress = (Command *)(FMCBankAddress); +static volatile uint16_t * const DataAddress = (uint16_t *)(FMCBankAddress | (1<<(FMCDataCommandAddressBit+1))); } } diff --git a/ion/src/f730/regs/rcc.h b/ion/src/f730/regs/rcc.h index 0536441d4..83841b007 100644 --- a/ion/src/f730/regs/rcc.h +++ b/ion/src/f730/regs/rcc.h @@ -83,7 +83,7 @@ public: class AHB3ENR : Register32 { public: - REGS_BOOL_FIELD(FSMCEN, 0); + REGS_BOOL_FIELD(FMCEN, 0); REGS_BOOL_FIELD(QSPIEN, 1); }; From 64ce406c833d1f4de5edb3420520cd9416a9de0b Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:38:55 +0100 Subject: [PATCH 0057/1750] [ion/f730] Fix the backlight --- ion/src/f730/backlight.cpp | 14 +++++++------- ion/src/f730/backlight.h | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/ion/src/f730/backlight.cpp b/ion/src/f730/backlight.cpp index efcd49b9d..2b52448b0 100644 --- a/ion/src/f730/backlight.cpp +++ b/ion/src/f730/backlight.cpp @@ -33,23 +33,23 @@ namespace Device { static uint8_t sLevel; void init() { - GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Output); + BacklightPin.group().MODER()->setMode(BacklightPin.pin(), GPIO::MODER::Mode::Output); sLevel = 0xF; resume(); } void shutdown() { - GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Analog); - GPIOC.PUPDR()->setPull(6, GPIO::PUPDR::Pull::None); + BacklightPin.group().MODER()->setMode(BacklightPin.pin(), GPIO::MODER::Mode::Analog); + BacklightPin.group().PUPDR()->setPull(BacklightPin.pin(), GPIO::PUPDR::Pull::None); } void suspend() { - GPIOC.ODR()->set(6, false); + BacklightPin.group().ODR()->set(BacklightPin.pin(), false); Timing::msleep(3); // Might not need to be blocking } void resume() { - GPIOC.ODR()->set(6, true); + BacklightPin.group().ODR()->set(BacklightPin.pin(), true); Timing::usleep(50); uint8_t level = sLevel; sLevel = 0xF; @@ -73,9 +73,9 @@ uint8_t level() { void sendPulses(int n) { for (int i=0; iset(6, false); + BacklightPin.group().ODR()->set(BacklightPin.pin(), false); Timing::usleep(20); - GPIOC.ODR()->set(6, true); + BacklightPin.group().ODR()->set(BacklightPin.pin(), true); Timing::usleep(20); } } diff --git a/ion/src/f730/backlight.h b/ion/src/f730/backlight.h index 15eab4fb0..026fcd537 100644 --- a/ion/src/f730/backlight.h +++ b/ion/src/f730/backlight.h @@ -9,7 +9,7 @@ namespace Device { /* Pin | Role | Mode | Function * -----+-------------------+-----------------------+---------- - * PC6 | Backlight Enable | Output | + * PE0 | Backlight Enable | Output | */ void init(); @@ -21,6 +21,8 @@ uint8_t level(); void sendPulses(int n); +constexpr static GPIOPin BacklightPin = GPIOPin(GPIOE, 0); + } } } From 7cc47a6ccc1b76e344acec011084672df4137b29 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:40:13 +0100 Subject: [PATCH 0058/1750] [ion/f730] Use the proper GPIOs for the keyboard --- ion/src/f730/keyboard.cpp | 22 +++++++++++----------- ion/src/f730/keyboard.h | 20 ++++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ion/src/f730/keyboard.cpp b/ion/src/f730/keyboard.cpp index 91c634dae..0ec131551 100644 --- a/ion/src/f730/keyboard.cpp +++ b/ion/src/f730/keyboard.cpp @@ -6,28 +6,28 @@ * * | PC0 | PC1 | PC2 | PC3 | PC4 | PC5 | * -----+------+------+------+------+------+------+ - * PE0 | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 | + * PA0 | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 | * -----+------+------+------+------+------+------+ - * PE1 | K_B1 | K_B2 | | | | | + * PA1 | K_B1 | K_B2 | | | | | * -----+------+------+------+------+------+------+ - * PE2 | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 | + * PA2 | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 | * -----+------+------+------+------+------+------+ - * PE3 | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 | + * PA3 | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 | * -----+------+------+------+------+------+------+ - * PE4 | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 | + * PA4 | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 | * -----+------+------+------+------+------+------+ - * PE5 | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | | + * PA5 | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | | * -----+------+------+------+------+------+------+ - * PE6 | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | | + * PA6 | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | | * -----+------+------+------+------+------+------+ - * PE7 | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | | + * PA7 | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | | * -----+------+------+------+------+------+------+ - * PE8 | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | | + * PA8 | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | | * -----+------+------+------+------+------+------| * - * We decide to drive the rows (PE0-8) and read the columns (PC0-5). + * We decide to drive the rows (PA0-8) and read the columns (PC0-5). * - * To avoid short-circuits, the pins E0-E8 will not be standard outputs but + * To avoid short-circuits, the pins PA0-PA8 will not be standard outputs but * only open-drain. Open drain means the pin is either driven low or left * floating. * When a user presses multiple keys, a connection between two rows can happen. diff --git a/ion/src/f730/keyboard.h b/ion/src/f730/keyboard.h index fa69ab39f..d00ef5632 100644 --- a/ion/src/f730/keyboard.h +++ b/ion/src/f730/keyboard.h @@ -11,27 +11,27 @@ namespace Device { /* Pin | Role | Mode * -----+-------------------+-------------------- + * PA0 | Keyboard row A | Output, open drain + * PA1 | 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 * 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 - * PE0 | Keyboard row A | Output, open drain - * PE1 | Keyboard row B | Output, open drain - * PE2 | Keyboard row C | Output, open drain - * PE3 | Keyboard row D | Output, open drain - * PE4 | Keyboard row E | Output, open drain - * PE5 | Keyboard row F | Output, open drain - * PE6 | Keyboard row G | Output, open drain - * PE7 | Keyboard row H | Output, open drain - * PE8 | Keyboard row I | Output, open drain */ void init(); void shutdown(); -constexpr GPIO RowGPIO = GPIOE; +constexpr GPIO RowGPIO = GPIOA; constexpr uint8_t numberOfRows = 9; constexpr uint8_t RowPins[numberOfRows] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; From 20c0ba999c1c5bd48e11b999f130d5a8512e7615 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:54:36 +0100 Subject: [PATCH 0059/1750] [ion/f730] Fix some makefiles --- ion/src/f730/bench/Makefile | 4 ++-- ion/src/f730/boot/Makefile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ion/src/f730/bench/Makefile b/ion/src/f730/bench/Makefile index 90b0c82b6..28e33f7fa 100644 --- a/ion/src/f730/bench/Makefile +++ b/ion/src/f730/bench/Makefile @@ -1,10 +1,10 @@ -objs += $(addprefix ion/src/device/bench/, \ +objs += $(addprefix ion/src/f730/bench/, \ bench.o \ command_handler.o \ command_list.o \ ) -objs += $(addprefix ion/src/device/bench/command/, \ +objs += $(addprefix ion/src/f730/bench/command/, \ command.o \ adc.o \ backlight.o \ diff --git a/ion/src/f730/boot/Makefile b/ion/src/f730/boot/Makefile index ded572acd..153e0b034 100644 --- a/ion/src/f730/boot/Makefile +++ b/ion/src/f730/boot/Makefile @@ -1,2 +1,2 @@ -objs += $(addprefix ion/src/device/boot/, isr.o rt0.o) -LDSCRIPT = ion/src/device/boot/flash.ld +objs += $(addprefix ion/src/f730/boot/, isr.o rt0.o) +LDSCRIPT = ion/src/f730/boot/flash.ld From 63180705fad3379216a8da0de32b852165cbc4d7 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:55:15 +0100 Subject: [PATCH 0060/1750] [ion/f730] Use the MPU to force FMC access width --- ion/src/f730/boot/rt0.cpp | 2 +- ion/src/f730/device.cpp | 23 ++++++++++++++++++++++- ion/src/f730/device.h | 2 +- ion/src/f730/regs/mpu.h | 20 +++++++++++++++----- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/ion/src/f730/boot/rt0.cpp b/ion/src/f730/boot/rt0.cpp index e4ea05f0f..852cd7edb 100644 --- a/ion/src/f730/boot/rt0.cpp +++ b/ion/src/f730/boot/rt0.cpp @@ -61,7 +61,7 @@ void start() { * For example, static C++ objects are very likely to manipulate float values */ Ion::Device::initFPU(); -#if 0 +#if 1 Ion::Device::initMPU(); #endif diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index ab6f47baf..50a9daa1d 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -85,8 +85,11 @@ void initFPU() { // FIXME: The pipeline should be flushed at this point } -#if 0 +#if 1 void initMPU() { + +# if 0 + // This prevent overflowing the stack /* Region 0 reprensents the last 128 bytes of the stack: accessing this * memory means we are really likely to overflow the stack very soon. */ MPU.RNR()->setREGION(0x00); @@ -96,6 +99,24 @@ void initMPU() { MPU.RASR()->setAP(0x000); // Forbid access MPU.CTRL()->setPRIVDEFENA(true); MPU.CTRL()->setENABLE(true); +#endif + + // Configure MPU settings for the FMC memory area + // This is needed for interfacing with the LCD + MPU.RNR()->setREGION(0x00); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32MB); + MPU.RASR()->setXN(true); + MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); + MPU.RASR()->setTEX(0); + MPU.RASR()->setC(0); + MPU.RASR()->setB(1); + MPU.RASR()->setENABLE(true); + + MPU.CTRL()->setPRIVDEFENA(true); + MPU.CTRL()->setENABLE(true); + + } #endif diff --git a/ion/src/f730/device.h b/ion/src/f730/device.h index 2bdccea28..e7e136f61 100644 --- a/ion/src/f730/device.h +++ b/ion/src/f730/device.h @@ -8,7 +8,7 @@ void init(); void shutdown(); void initFPU(); -#if 0 +#if 1 void initMPU(); #endif diff --git a/ion/src/f730/regs/mpu.h b/ion/src/f730/regs/mpu.h index f9c2049f4..2d82a5e10 100644 --- a/ion/src/f730/regs/mpu.h +++ b/ion/src/f730/regs/mpu.h @@ -26,7 +26,7 @@ public: class RBAR : Register32 { public: - void setADDR(void * address) volatile { assert(((uint32_t)address & 0b11111) == 0); setBitRange(31, 5, (uint32_t)address >> 5); } + void setADDR(uint32_t address) volatile { /* assert((address & 0b11111) == 0);*/ setBitRange(31, 5, address >> 5); } REGS_BOOL_FIELD(VALID, 4); REGS_FIELD(REGION, uint8_t, 3, 0); }; @@ -34,11 +34,19 @@ public: class RASR : Register32 { public: REGS_BOOL_FIELD(XN, 28); - REGS_FIELD(AP, uint8_t, 26, 24); + enum class AccessPermission : uint8_t { + NoAccess = 0, + PrivilegedRO = 5, + PrivilegedRW = 1, + PrivilegedRWUnprivilegedRO = 2, + RO = 6, + RW = 3 + }; + REGS_FIELD(AP, AccessPermission, 26, 24); REGS_FIELD(TEX, uint8_t, 21, 19); - REGS_BOOL_FIELD(S, 18); - REGS_BOOL_FIELD(C, 17); - REGS_BOOL_FIELD(B, 16); + REGS_BOOL_FIELD(S, 18); // Shareable + REGS_BOOL_FIELD(C, 17); // Cacheable + REGS_BOOL_FIELD(B, 16); // Buffereable REGS_FIELD(SRD, uint8_t, 15, 8); enum class RegionSize : uint8_t { Bytes32 = 0b00100, @@ -46,6 +54,8 @@ public: Bytes128 = 0b00110, KyloBytes1 = 0b01001, MegaBytes1 = 0b10011, + _1MB = 19, + _32MB = 24, GigaBytes1 = 0b11101, GigaBytes4 = 0b11111 }; From 9aaaf2c1937ebd208cd800d9125de19c3ecca92e Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:57:46 +0100 Subject: [PATCH 0061/1750] [ion/f730] Fix a reset value --- ion/src/f730/device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 50a9daa1d..764f80258 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -314,7 +314,7 @@ void initClocks() { RCC.APB1ENR()->setPWREN(true); // APB2 bus - class RCC::APB2ENR apb2enr(0x00008000); // Reset value + class RCC::APB2ENR apb2enr(0); // Reset value apb2enr.setADC1EN(true); apb2enr.setSYSCFGEN(true); #if USE_SD_CARD From 25c72749b5c2765adec707d40e6b4a1474ae6f8e Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 11:00:16 +0100 Subject: [PATCH 0062/1750] [ion/f730] Temporarily disable extra peripherals --- ion/src/f730/device.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 764f80258..3f111c497 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -201,6 +201,7 @@ void initPeripherals() { Keyboard::Device::init(); LED::Device::init(); Battery::Device::init(); + return; // FIXME, obviously! USB::Device::init(); #if USE_SD_CARD SDCard::Device::init(); From a1f5ea4f76e77b110cd97e0c41340d3c1dc1d83e Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 11:00:36 +0100 Subject: [PATCH 0063/1750] [ion/f730] Don't init clocks for now --- ion/src/f730/device.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 3f111c497..7cc714002 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -229,6 +229,7 @@ void shutdownPeripherals(bool keepLEDAwake) { } void initClocks() { +#if 0 /* System clock * Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */ @@ -288,6 +289,7 @@ void initClocks() { // Now that we don't need use it anymore, turn the HSI off RCC.CR()->setHSION(false); +#endif // Peripheral clocks From b9d511a464bc062eb0659ff099bd4965cca21cdc Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 11:00:49 +0100 Subject: [PATCH 0064/1750] [apps] Enable a DUMMY_MAIN option --- apps/main.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/apps/main.cpp b/apps/main.cpp index fb2c90dc1..1be40ff7d 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -2,6 +2,20 @@ #include "global_preferences.h" #include +#define DUMMY_MAIN 1 +#if DUMMY_MAIN + +void ion_main(int argc, char * argv[]) { + while (1) { + Ion::Display::pushRectUniform(KDRect(0,0,10,10), KDColorRed); + Ion::Timing::msleep(100); + Ion::Display::pushRectUniform(KDRect(0,0,10,10), KDColorBlue); + Ion::Timing::msleep(100); + } +} + +#else + void ion_main(int argc, char * argv[]) { // Initialize Poincare::TreePool::sharedPool Poincare::Init(); @@ -44,3 +58,5 @@ void ion_main(int argc, char * argv[]) { #endif AppsContainerStorage::sharedContainer()->run(); } + +#endif From e07c6ccc88ee85b2d4779b6c214bf7069dc7816e Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:36:33 +0100 Subject: [PATCH 0065/1750] [build] Allow proper mcpu/mfpu flags for different targets --- build/platform.device.mak | 2 +- build/platform.f730.mak | 1 + build/toolchain.arm-gcc-m4f.mak | 3 +++ build/toolchain.arm-gcc-m7f.mak | 3 +++ build/toolchain.arm-gcc.mak | 1 - 5 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 build/toolchain.arm-gcc-m4f.mak create mode 100644 build/toolchain.arm-gcc-m7f.mak diff --git a/build/platform.device.mak b/build/platform.device.mak index 4c6be1d4c..d435f6a06 100644 --- a/build/platform.device.mak +++ b/build/platform.device.mak @@ -1,4 +1,4 @@ -TOOLCHAIN ?= arm-gcc +TOOLCHAIN ?= arm-gcc-m4f USE_LIBA = 1 EXE = elf EPSILON_BOOT_PROMPT = update diff --git a/build/platform.f730.mak b/build/platform.f730.mak index 1c9bcd804..76511cbcd 100644 --- a/build/platform.f730.mak +++ b/build/platform.f730.mak @@ -1 +1,2 @@ +TOOLCHAIN ?= arm-gcc-m7f include build/platform.device.mak diff --git a/build/toolchain.arm-gcc-m4f.mak b/build/toolchain.arm-gcc-m4f.mak new file mode 100644 index 000000000..5f5ae4056 --- /dev/null +++ b/build/toolchain.arm-gcc-m4f.mak @@ -0,0 +1,3 @@ +include build/toolchain.arm-gcc.mak +SFLAGS += -mthumb -march=armv7e-m -mfloat-abi=hard +SFLAGS += -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 diff --git a/build/toolchain.arm-gcc-m7f.mak b/build/toolchain.arm-gcc-m7f.mak new file mode 100644 index 000000000..b23b86b9d --- /dev/null +++ b/build/toolchain.arm-gcc-m7f.mak @@ -0,0 +1,3 @@ +include build/toolchain.arm-gcc.mak +SFLAGS += -mthumb -march=armv7e-m -mfloat-abi=hard +SFLAGS += -mcpu=cortex-m7 -mfpu=fpv5-sp-d16 diff --git a/build/toolchain.arm-gcc.mak b/build/toolchain.arm-gcc.mak index 14995b2d1..09e057b50 100644 --- a/build/toolchain.arm-gcc.mak +++ b/build/toolchain.arm-gcc.mak @@ -25,5 +25,4 @@ SFLAGS += -fdata-sections -ffunction-sections LDFLAGS += -Wl,--gc-sections endif -SFLAGS += -mthumb -march=armv7e-m -mfloat-abi=hard -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 LDFLAGS += $(SFLAGS) -lgcc -Wl,-T,$(LDSCRIPT) From 3cc9b321864412c5470a9dc63fee2c8acec717b3 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:38:13 +0100 Subject: [PATCH 0066/1750] [ion/f730] Don't use DTCM for now --- ion/src/f730/boot/flash.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/boot/flash.ld b/ion/src/f730/boot/flash.ld index e5ddcc9e4..fbf7d5138 100644 --- a/ion/src/f730/boot/flash.ld +++ b/ion/src/f730/boot/flash.ld @@ -10,7 +10,7 @@ * be stored in Flash. */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K - SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K + SRAM (rw) : ORIGIN = 0x20010000, LENGTH = 176K } STACK_SIZE = 32K; From e755dbef380c436e286f25014ce45ca72a7cf2c5 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:39:23 +0100 Subject: [PATCH 0067/1750] [ion/f730] Enable HSE and PLL for a HCLK of 192 MHz --- ion/src/f730/device.cpp | 21 +++++++++++---------- ion/src/f730/regs/rcc.h | 1 + 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 7cc714002..e438f7ab2 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -229,16 +229,16 @@ void shutdownPeripherals(bool keepLEDAwake) { } void initClocks() { -#if 0 /* System clock * Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */ /* 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 90MHz the flash expects 3 WS. */ - FLASH.ACR()->setLATENCY(3); + * The spec tells us that at 2.8V and over 210MHz the flash expects 7 WS. */ + FLASH.ACR()->setLATENCY(7); +#if 0 /* Enable prefetching flash instructions */ /* Fetching instructions increases slightly the power consumption but the * increase is negligible compared to the screen consumption. */ @@ -247,6 +247,7 @@ void initClocks() { /* Set flash instruction and data cache */ FLASH.ACR()->setDCEN(true); FLASH.ACR()->setICEN(true); +#endif /* 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 @@ -260,16 +261,17 @@ void initClocks() { /* Given the crystal used on our device, the HSE will oscillate at 25 MHz. By * piping it through a phase-locked loop (PLL) we can derive other frequencies - * for use in different parts of the system. Combining the default PLL values - * with a PLLM of 25 and a PLLQ of 4 yields both a 96 MHz frequency for SYSCLK - * and the required 48 MHz USB clock. */ + * for use in different parts of the system. */ // Configure the PLL ratios and use HSE as a PLL input RCC.PLLCFGR()->setPLLM(25); - RCC.PLLCFGR()->setPLLQ(4); + RCC.PLLCFGR()->setPLLN(384); + RCC.PLLCFGR()->setPLLQ(8); RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); - // 96 MHz is too fast for APB1. Divide it by two to reach 48 MHz - RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy2); + // 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz + RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy4); + // 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz + RCC.CFGR()->setPPRE2(RCC::CFGR::APBPrescaler::AHBDividedBy2); /* If you want to considerably slow down the whole machine uniformely, which * can be very useful to diagnose performance issues, just uncomment the line @@ -289,7 +291,6 @@ void initClocks() { // Now that we don't need use it anymore, turn the HSI off RCC.CR()->setHSION(false); -#endif // Peripheral clocks diff --git a/ion/src/f730/regs/rcc.h b/ion/src/f730/regs/rcc.h index 83841b007..3306c0b07 100644 --- a/ion/src/f730/regs/rcc.h +++ b/ion/src/f730/regs/rcc.h @@ -57,6 +57,7 @@ public: AHBDividedBy16 = 7 }; void setPPRE1(APBPrescaler r) volatile { setBitRange(12, 10, (uint32_t)r); } + void setPPRE2(APBPrescaler r) volatile { setBitRange(15, 13, (uint32_t)r); } }; class AHB1ENR : public Register32 { From 1a3852ba482902f9cb86ea574274d7d98e49e066 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:39:59 +0100 Subject: [PATCH 0068/1750] [ion/f730] Enable USB controller --- ion/src/f730/device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index e438f7ab2..009883032 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -201,8 +201,8 @@ void initPeripherals() { Keyboard::Device::init(); LED::Device::init(); Battery::Device::init(); - return; // FIXME, obviously! USB::Device::init(); + return; // FIXME, obviously! #if USE_SD_CARD SDCard::Device::init(); #endif From 7a069260a6aaff3364edd29f1faf4eef1167f50a Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:40:19 +0100 Subject: [PATCH 0069/1750] [ion/f730] Double all FMC access times to account for HCLK from 96 to 192 MHz --- ion/src/f730/display.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ion/src/f730/display.cpp b/ion/src/f730/display.cpp index 1fa38012d..ff349cee5 100644 --- a/ion/src/f730/display.cpp +++ b/ion/src/f730/display.cpp @@ -228,17 +228,17 @@ void initFMC() { */ // Read timing from the LCD - FMC.BTR(FMCMemoryBank)->setADDSET(2); + FMC.BTR(FMCMemoryBank)->setADDSET(4); FMC.BTR(FMCMemoryBank)->setADDHLD(0); - FMC.BTR(FMCMemoryBank)->setDATAST(36); - FMC.BTR(FMCMemoryBank)->setBUSTURN(10); + FMC.BTR(FMCMemoryBank)->setDATAST(72); + FMC.BTR(FMCMemoryBank)->setBUSTURN(20); FMC.BTR(FMCMemoryBank)->setACCMOD(FMC::BTR::ACCMOD::A); // Write timings for the LCD - FMC.BWTR(FMCMemoryBank)->setADDSET(2); + FMC.BWTR(FMCMemoryBank)->setADDSET(4); FMC.BWTR(FMCMemoryBank)->setADDHLD(0); - FMC.BWTR(FMCMemoryBank)->setDATAST(3); - FMC.BWTR(FMCMemoryBank)->setBUSTURN(3); + FMC.BWTR(FMCMemoryBank)->setDATAST(6); + FMC.BWTR(FMCMemoryBank)->setBUSTURN(6); FMC.BWTR(FMCMemoryBank)->setACCMOD(FMC::BWTR::ACCMOD::A); } From 0fb08be2a9c8e0148e1bf4b8e3b999a186c8149f Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 17:41:45 +0100 Subject: [PATCH 0070/1750] [ion/f730] WIP: Temporarily increase delays for msleep/usleep --- ion/src/f730/timing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/f730/timing.cpp b/ion/src/f730/timing.cpp index c22d425f7..dc316fcb0 100644 --- a/ion/src/f730/timing.cpp +++ b/ion/src/f730/timing.cpp @@ -8,12 +8,12 @@ namespace Timing { * precision, we could use the controller cycle counter (Systick). */ void msleep(uint32_t ms) { - for (volatile uint32_t i=0; i<8852*ms; i++) { + for (volatile uint32_t i=0; i<28852*ms; i++) { __asm volatile("nop"); } } void usleep(uint32_t us) { - for (volatile uint32_t i=0; i<9*us; i++) { + for (volatile uint32_t i=0; i<28*us; i++) { __asm volatile("nop"); } } From bb98c5650526cd1789086d77ee713978cfc1efc3 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 10:14:55 +0100 Subject: [PATCH 0071/1750] [ion/f730] Fix RCC resst values --- ion/src/f730/device.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 009883032..d53097d9f 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -297,7 +297,7 @@ void initClocks() { // 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(0); // Reset value + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value ahb1enr.setGPIOAEN(true); ahb1enr.setGPIOBEN(true); ahb1enr.setGPIOCEN(true); @@ -329,12 +329,12 @@ void initClocks() { void shutdownClocks(bool keepLEDAwake) { // APB2 bus - RCC.APB2ENR()->set(0x00008000); // Reset value + RCC.APB2ENR()->set(0); // Reset value // APB1 - class RCC::APB1ENR apb1enr(0x00000400); // Reset value + class RCC::APB1ENR apb1enr(0); // Reset value // AHB1 bus - class RCC::AHB1ENR ahb1enr(0); // Reset value + class RCC::AHB1ENR ahb1enr(0x00100000); // Reset value if (keepLEDAwake) { apb1enr.setTIM3EN(true); ahb1enr.setGPIOBEN(true); From 9ecfc3c72c735602b6e2293639611088a0fe8778 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 10:15:37 +0100 Subject: [PATCH 0072/1750] [ion/f730] Restore use DTCM --- ion/src/f730/boot/flash.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/boot/flash.ld b/ion/src/f730/boot/flash.ld index fbf7d5138..e5ddcc9e4 100644 --- a/ion/src/f730/boot/flash.ld +++ b/ion/src/f730/boot/flash.ld @@ -10,7 +10,7 @@ * be stored in Flash. */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K - SRAM (rw) : ORIGIN = 0x20010000, LENGTH = 176K + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K } STACK_SIZE = 32K; From 00253140e5caf8c747e8901a646ccfd12ece62eb Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 10:16:43 +0100 Subject: [PATCH 0073/1750] [ion/f730] WIP: Rename SDIOEN to SDMMCEN, don't init it anymore --- ion/src/f730/device.cpp | 3 --- ion/src/f730/regs/rcc.h | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index d53097d9f..6ab3e8707 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -321,9 +321,6 @@ void initClocks() { class RCC::APB2ENR apb2enr(0); // Reset value apb2enr.setADC1EN(true); apb2enr.setSYSCFGEN(true); -#if USE_SD_CARD - apb2enr.setSDIOEN(true); -#endif RCC.APB2ENR()->set(apb2enr); } diff --git a/ion/src/f730/regs/rcc.h b/ion/src/f730/regs/rcc.h index 3306c0b07..576dea281 100644 --- a/ion/src/f730/regs/rcc.h +++ b/ion/src/f730/regs/rcc.h @@ -103,7 +103,7 @@ public: REGS_BOOL_FIELD(TIM1EN, 0); REGS_BOOL_FIELD(USART1EN, 4); REGS_BOOL_FIELD(ADC1EN, 8); - REGS_BOOL_FIELD(SDIOEN, 11); + REGS_BOOL_FIELD(SDMMC1EN, 11); REGS_BOOL_FIELD(SPI1EN, 12); REGS_BOOL_FIELD(SYSCFGEN, 14); }; From 75f99a63f5aa8beb827716a4464fd2346b817fd1 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 15:19:36 +0100 Subject: [PATCH 0074/1750] [ion/f730] Fix battery GPIO --- ion/src/f730/battery.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ion/src/f730/battery.h b/ion/src/f730/battery.h index ef558dc86..7299aa8d4 100644 --- a/ion/src/f730/battery.h +++ b/ion/src/f730/battery.h @@ -9,8 +9,8 @@ namespace Device { /* Pin | Role | Mode | Function * -----+-------------------+-----------------------+---------- - * PA0 | BAT_CHRG | Input, pulled up | Low = charging, high = full - * PA1 | VBAT_SNS | Analog | ADC1_1 + * PE3 | BAT_CHRG | Input, pulled up | Low = charging, high = full + * PB1 | VBAT_SNS | Analog | ADC1_1 */ void init(); @@ -18,10 +18,10 @@ void shutdown(); void initGPIO(); void initADC(); -constexpr GPIO ChargingGPIO = GPIOA; -constexpr uint8_t ChargingPin = 0; +constexpr GPIO ChargingGPIO = GPIOE; +constexpr uint8_t ChargingPin = 3; -constexpr GPIO ADCGPIO = GPIOA; +constexpr GPIO ADCGPIO = GPIOB; constexpr uint8_t ADCPin = 1; constexpr uint8_t ADCChannel = 1; From 5ed8c8b45106328699e49f9104a8e71b0abb4ca4 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 15:22:15 +0100 Subject: [PATCH 0075/1750] [ion/keyboard] Do not use magic numbers --- ion/src/f730/keyboard.cpp | 2 +- ion/src/f730/keyboard.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ion/src/f730/keyboard.cpp b/ion/src/f730/keyboard.cpp index 0ec131551..3fc9f9948 100644 --- a/ion/src/f730/keyboard.cpp +++ b/ion/src/f730/keyboard.cpp @@ -59,7 +59,7 @@ State scan() { /* The key is down if the input is brought low by the output. In other * words, we want to return true if the input is low (false). So we need to * append 6 bits of (not columns) to state. */ - state = (state << 6) | (~columns & 0x3F); + state = (state << Device::numberOfColumns) | (~columns & 0x3F); } /* Last but not least, keys number 8, 9, 10, 11, 35, 41, 47 and 53 are not diff --git a/ion/src/f730/keyboard.h b/ion/src/f730/keyboard.h index d00ef5632..d1537bc5a 100644 --- a/ion/src/f730/keyboard.h +++ b/ion/src/f730/keyboard.h @@ -52,8 +52,8 @@ inline void activateRow(uint8_t row) { * the others to 1. */ uint16_t rowState = ~(1<setBitRange(9, 0, rowState); + // TODO: Assert pin numbers are sequentials and dynamically find 8 and 0 + Device::RowGPIO.ODR()->setBitRange(numberOfRows-1, 0, rowState); // TODO: 100 us seems to work, but wasn't really calculated Timing::usleep(100); From a2ebdcdbc12db3092669f17fdfc1114579a8b1de Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 15:32:10 +0100 Subject: [PATCH 0076/1750] [ion/f730] Fix includes for the right PLATFORM --- ion/src/f730/backlight.h | 1 + ion/src/f730/bench/command/adc.cpp | 2 +- ion/src/f730/bench/command/backlight.cpp | 2 +- ion/src/f730/bench/command/display.cpp | 2 +- ion/src/f730/bench/command/led.cpp | 2 +- ion/src/f730/bench/command/print.cpp | 2 +- ion/src/f730/bench/command/vblank.cpp | 2 +- ion/src/f730/usb/calculator.cpp | 6 +++--- ion/src/f730/usb/dfu_interface.cpp | 2 +- ion/src/f730/usb/dfu_relocated.cpp | 6 +++--- ion/src/f730/usb/stack/device.cpp | 2 +- ion/src/f730/usb/stack/endpoint0.cpp | 2 +- 12 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ion/src/f730/backlight.h b/ion/src/f730/backlight.h index 026fcd537..527bc1344 100644 --- a/ion/src/f730/backlight.h +++ b/ion/src/f730/backlight.h @@ -2,6 +2,7 @@ #define ION_DEVICE_BACKLIGHT_H #include +#include "regs/regs.h" namespace Ion { namespace Backlight { diff --git a/ion/src/f730/bench/command/adc.cpp b/ion/src/f730/bench/command/adc.cpp index 8c3e677a4..26f86c0a9 100644 --- a/ion/src/f730/bench/command/adc.cpp +++ b/ion/src/f730/bench/command/adc.cpp @@ -1,7 +1,7 @@ #include "command.h" #include #include -#include +#include namespace Ion { namespace Device { diff --git a/ion/src/f730/bench/command/backlight.cpp b/ion/src/f730/bench/command/backlight.cpp index b4a7f2d9f..86e630c55 100644 --- a/ion/src/f730/bench/command/backlight.cpp +++ b/ion/src/f730/bench/command/backlight.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include namespace Ion { namespace Device { diff --git a/ion/src/f730/bench/command/display.cpp b/ion/src/f730/bench/command/display.cpp index 553e6e54a..61b74c322 100644 --- a/ion/src/f730/bench/command/display.cpp +++ b/ion/src/f730/bench/command/display.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include #include namespace Ion { diff --git a/ion/src/f730/bench/command/led.cpp b/ion/src/f730/bench/command/led.cpp index 43f7fcac1..4d6f80e81 100644 --- a/ion/src/f730/bench/command/led.cpp +++ b/ion/src/f730/bench/command/led.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include namespace Ion { namespace Device { diff --git a/ion/src/f730/bench/command/print.cpp b/ion/src/f730/bench/command/print.cpp index 8b5eb3bfc..fa73e6234 100644 --- a/ion/src/f730/bench/command/print.cpp +++ b/ion/src/f730/bench/command/print.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include #include namespace Ion { diff --git a/ion/src/f730/bench/command/vblank.cpp b/ion/src/f730/bench/command/vblank.cpp index 3c6971061..d292cab26 100644 --- a/ion/src/f730/bench/command/vblank.cpp +++ b/ion/src/f730/bench/command/vblank.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include namespace Ion { namespace Device { diff --git a/ion/src/f730/usb/calculator.cpp b/ion/src/f730/usb/calculator.cpp index fc23c1512..47150f053 100644 --- a/ion/src/f730/usb/calculator.cpp +++ b/ion/src/f730/usb/calculator.cpp @@ -1,8 +1,8 @@ #include "calculator.h" #include -#include -#include -#include +#include +#include +#include namespace Ion { namespace USB { diff --git a/ion/src/f730/usb/dfu_interface.cpp b/ion/src/f730/usb/dfu_interface.cpp index 029cce703..395576cc6 100644 --- a/ion/src/f730/usb/dfu_interface.cpp +++ b/ion/src/f730/usb/dfu_interface.cpp @@ -1,6 +1,6 @@ #include "dfu_interface.h" #include -#include +#include namespace Ion { namespace USB { diff --git a/ion/src/f730/usb/dfu_relocated.cpp b/ion/src/f730/usb/dfu_relocated.cpp index 41566e2a0..6f4191ea8 100644 --- a/ion/src/f730/usb/dfu_relocated.cpp +++ b/ion/src/f730/usb/dfu_relocated.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include extern char _stack_end; extern char _dfu_bootloader_flash_start; @@ -60,11 +60,11 @@ void DFU() { /* To have the right debug symbols for the reallocated code, break here and: * - Get the address of the new .text section - * In a terminal: arm-none-eabi-readelf -a ion/src/device/usb/dfu.elf + * In a terminal: arm-none-eabi-readelf -a ion/src/f730/usb/dfu.elf * - Delete the current symbol table * symbol-file * - Add the new symbol table, with the address of the new .text section - * add-symbol-file ion/src/device/usb/dfu.elf 0x20038000 + * add-symbol-file ion/src/f730/usb/dfu.elf 0x20038000 */ dfu_bootloader_entry(true); diff --git a/ion/src/f730/usb/stack/device.cpp b/ion/src/f730/usb/stack/device.cpp index 27bf6d5de..cd2d7fb5b 100644 --- a/ion/src/f730/usb/stack/device.cpp +++ b/ion/src/f730/usb/stack/device.cpp @@ -1,5 +1,5 @@ #include "device.h" -#include +#include namespace Ion { namespace USB { diff --git a/ion/src/f730/usb/stack/endpoint0.cpp b/ion/src/f730/usb/stack/endpoint0.cpp index df41e4bd3..957a577f2 100644 --- a/ion/src/f730/usb/stack/endpoint0.cpp +++ b/ion/src/f730/usb/stack/endpoint0.cpp @@ -1,6 +1,6 @@ #include "endpoint0.h" #include -#include +#include #include "device.h" #include "interface.h" #include "request_recipient.h" From 56e49e0aa4b83b36316eb79abb8e64371363799b Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 16:53:47 +0100 Subject: [PATCH 0077/1750] [ion/f730] WIP: Adapt linker script and DEBUG=1 to fit in flash --- build/defaults.mak | 2 +- ion/src/f730/boot/flash.ld | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/defaults.mak b/build/defaults.mak index e7b364888..e90a2b158 100644 --- a/build/defaults.mak +++ b/build/defaults.mak @@ -8,7 +8,7 @@ CXXFLAGS = -std=c++11 -fno-exceptions -fno-rtti -fno-threadsafe-statics # Flags - Optimizations ifeq ($(DEBUG),1) -SFLAGS = -O0 -g +SFLAGS = -Og -g else SFLAGS = -Os endif diff --git a/ion/src/f730/boot/flash.ld b/ion/src/f730/boot/flash.ld index e5ddcc9e4..a6179f9f4 100644 --- a/ion/src/f730/boot/flash.ld +++ b/ion/src/f730/boot/flash.ld @@ -9,7 +9,7 @@ * This will let us use shortcuts such as ">FLASH" to ask for a given section to * be stored in Flash. */ MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K } From e283f6eb7a0fd18efd1508bb60a8799469d4b2ef Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 16:55:55 +0100 Subject: [PATCH 0078/1750] [apps] WIP: Disable a DUMMY_MAIN option --- apps/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/main.cpp b/apps/main.cpp index 1be40ff7d..806c63d13 100644 --- a/apps/main.cpp +++ b/apps/main.cpp @@ -2,7 +2,7 @@ #include "global_preferences.h" #include -#define DUMMY_MAIN 1 +#define DUMMY_MAIN 0 #if DUMMY_MAIN void ion_main(int argc, char * argv[]) { From d0516136e97a3b02cf3cfb9f50b0ec98a18596e3 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 16:56:52 +0100 Subject: [PATCH 0079/1750] [escher] WIP: Temporarily comment Ion::Display::waitForVBlank() --- escher/src/window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escher/src/window.cpp b/escher/src/window.cpp index 618b6fba4..1b94c65c9 100644 --- a/escher/src/window.cpp +++ b/escher/src/window.cpp @@ -13,7 +13,7 @@ void Window::redraw(bool force) { if (force) { markRectAsDirty(bounds()); } - Ion::Display::waitForVBlank(); + //Ion::Display::waitForVBlank(); View::redraw(bounds()); } From c0fee0b735481b34c2f85a40ce30dfc3e0af775c Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 16:58:23 +0100 Subject: [PATCH 0080/1750] [ion/f730/battery] WIP: Temporary dummy Charge::FULL level --- ion/src/f730/battery.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/src/f730/battery.cpp b/ion/src/f730/battery.cpp index f3ed16364..212ab7214 100644 --- a/ion/src/f730/battery.cpp +++ b/ion/src/f730/battery.cpp @@ -18,6 +18,7 @@ bool isCharging() { } Charge level() { + return Charge::FULL; if (voltage() < 3.2f) { return Charge::EMPTY; } From 05bb07dd3ece552e945967b6e826c549a96cb58f Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 10 Jan 2019 17:00:50 +0100 Subject: [PATCH 0081/1750] [ion/f730] WIP: Temporary dummy serial number --- ion/src/f730/device.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 6ab3e8707..145b3e68f 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -56,9 +56,10 @@ uint32_t Ion::random() { } void Ion::Device::copySerialNumber(char * buffer) { - const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; - Base64::encode(rawUniqueID, 12, buffer); - buffer[SerialNumberLength] = 0; + //const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; + //Base64::encode(rawUniqueID, 12, buffer); + buffer[0]='A'; + buffer[1] = 0; } const char * Ion::serialNumber() { From 606d358c0fc2b1959ea3337d37795ac2513409e9 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Wed, 9 Jan 2019 14:58:22 +0100 Subject: [PATCH 0082/1750] [ion/f730] Port external flash --- ion/src/f730/Makefile | 1 + ion/src/f730/device.cpp | 2 + ion/src/f730/external_flash.cpp | 314 +++++++++++++++++++++++++++++ ion/src/f730/external_flash.h | 81 ++++++++ ion/src/f730/regs/gpio.h | 14 ++ ion/src/f730/regs/quadspi.h | 126 ++++++++++++ ion/src/f730/regs/regs.h | 1 + ion/src/f730/usb/Makefile | 1 + ion/src/f730/usb/calculator.h | 2 +- ion/src/f730/usb/dfu_interface.cpp | 20 +- ion/src/f730/usb/dfu_interface.h | 2 + 11 files changed, 557 insertions(+), 7 deletions(-) create mode 100644 ion/src/f730/external_flash.cpp create mode 100644 ion/src/f730/external_flash.h create mode 100644 ion/src/f730/regs/quadspi.h diff --git a/ion/src/f730/Makefile b/ion/src/f730/Makefile index 4c5ffee55..19c5e33da 100644 --- a/ion/src/f730/Makefile +++ b/ion/src/f730/Makefile @@ -21,6 +21,7 @@ objs += $(addprefix ion/src/f730/, \ device.o\ display.o\ events.o\ + external_flash.o\ flash.o\ keyboard.o\ led.o\ diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 145b3e68f..2dc37989c 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -15,6 +15,7 @@ extern "C" { #include "usb.h" #include "bench/bench.h" #include "base64.h" +#include "external_flash.h" #define USE_SD_CARD 0 @@ -210,6 +211,7 @@ void initPeripherals() { Console::Device::init(); SWD::Device::init(); initSysTick(); + ExternalFlash::Device::init(); } void shutdownPeripherals(bool keepLEDAwake) { diff --git a/ion/src/f730/external_flash.cpp b/ion/src/f730/external_flash.cpp new file mode 100644 index 000000000..5de68b64c --- /dev/null +++ b/ion/src/f730/external_flash.cpp @@ -0,0 +1,314 @@ +#include "external_flash.h" + +namespace Ion { +namespace ExternalFlash { +namespace Device { + +/* 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 --> | regsiter --> 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. */ + +class ExternalFlashStatusRegister { +public: + class StatusRegister1 : Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_R(BUSY, 0); + }; + class StatusRegister2 : Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_W(QE, 1); + }; +}; + +static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; +static constexpr int ClockFrequencyDivisor = 2; +static constexpr int ChipSelectHighTime = (ClockFrequencyDivisor == 1) ? 3 : (ClockFrequencyDivisor == 2) ? 2 : 1; + +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, 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, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + operatingMode, + c, + reinterpret_cast(FlashAddressSpaceSize), + 0, 0, + 0, + nullptr, 0 + ); +} + +static inline void send_write_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + operatingMode, + c, + address, + 0, 0, + 0, + data, dataLength + ); +} + +static inline void send_read_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + operatingMode, + c, + address, + 0, 0, + 0, + data, dataLength + ); +} + +static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + do { + send_read_command(Command::ReadStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); + } 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.) */ + constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : (ClockFrequencyDivisor > 1) ? 4 : 6; + send_command_full( + QUADSPI::CCR::FunctionalMode::MemoryMapped, + DefaultOperatingMode, + Command::FastReadQuadIO, + reinterpret_cast(FlashAddressSpaceSize), + 0xA0, 1, + 2, //FIXME + 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, + DefaultOperatingMode, + Command::FastReadQuadIO, + 0, + ~(0xA0), 1, + 2, //FIXME + &dummyData, 1 + ); +} + +void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { + class QUADSPI::CCR ccr(0); + ccr.setFMODE(functionalMode); + if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setDMODE(operatingMode); + } + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + } + ccr.setDCYC(dummyCycles); + if (numberOfAltBytes > 0) { + ccr.setABMODE(operatingMode); + ccr.setABSIZE(static_cast(numberOfAltBytes - 1)); + QUADSPI.ABR()->set(altBytes); + } + if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setADMODE(operatingMode); + ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); + } + ccr.setIMODE(operatingMode); + ccr.setINSTRUCTION(static_cast(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(FlashAddressSpaceSize)) { + QUADSPI.AR()->set(reinterpret_cast(address)); + } + + if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { + for (size_t i=0; iset(data[i]); + } + } else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + for (size_t i=0; iget(); + } + } + + /* 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." */ + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + while (QUADSPI.SR()->getBUSY()) { + } + } +} + +void init() { + initGPIO(); + initQSPI(); + initChip(); +} + +void initGPIO() { + for(const GPIOPin & g : QSPIPins) { + g.group().OSPEEDR()->setOutputSpeed(g.pin(), GPIO::OSPEEDR::OutputSpeed::High); + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), (g.pin() == 6 ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9)); + } +} + +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); + dcr.setCSHT(ChipSelectHighTime - 1); + dcr.setCKMODE(true); + QUADSPI.DCR()->set(dcr); + class QUADSPI::CR cr(0); + cr.setPRESCALER(ClockFrequencyDivisor - 1); + cr.setEN(true); + QUADSPI.CR()->set(cr); +} + +void initChip() { + /* The chip initially expects commands in SPI mode. We need to use SPI to tell + * it to switch to QPI. */ + if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { + send_command(Command::WriteEnable, QUADSPI::CCR::OperatingMode::Single); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + statusRegister2.setQE(true); + wait(QUADSPI::CCR::OperatingMode::Single); + send_write_command(Command::WriteStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); + wait(QUADSPI::CCR::OperatingMode::Single); + send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); + wait(); + if (ClockFrequencyDivisor == 1) { + class ReadParameters : Register8 { + public: + /* Parameters sent along with SetReadParameters instruction in order + * to configure the number of dummy cycles for the QPI Read instructions. */ + using Register8::Register8; + REGS_BOOL_FIELD_W(P5, 1); + }; + ReadParameters readParameters(0); + readParameters.setP5(true); + send_write_command(Command::SetReadParameters, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&readParameters), sizeof(readParameters)); + } + } + set_as_memory_mapped(); +} + +int SectorAtAddress(uint32_t address) { + int i = address >> NumberOfAddressBitsIn64KbyteBlock; + if (i >= NumberOfSectors) { + return -1; + } + return i; +} + +void MassErase() { + unset_memory_mapped_mode(); + send_command(Command::WriteEnable); + wait(); + send_command(Command::ChipErase); + wait(); + set_as_memory_mapped(); +} + +void EraseSector(int i) { + assert(i >= 0 && i < NumberOfSectors); + unset_memory_mapped_mode(); + send_command(Command::WriteEnable); + wait(); + send_write_command(Command::Erase64KbyteBlock, reinterpret_cast(i << NumberOfAddressBitsIn64KbyteBlock), nullptr, 0); + wait(); + set_as_memory_mapped(); +} + +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { + 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; + constexpr Command pageProgram = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? Command::PageProgram : Command::QuadPageProgram; + uint8_t offset = reinterpret_cast(destination) & (PageSize - 1); + size_t lengthThatFitsInPage = PageSize - offset; + while (length > 0) { + if (lengthThatFitsInPage > length) { + lengthThatFitsInPage = length; + } + send_command(Command::WriteEnable); + wait(); + send_write_command(pageProgram, destination, source, lengthThatFitsInPage); + length -= lengthThatFitsInPage; + destination += lengthThatFitsInPage; + source += lengthThatFitsInPage; + lengthThatFitsInPage = PageSize; + wait(); + } + set_as_memory_mapped(); +} + +} +} +} diff --git a/ion/src/f730/external_flash.h b/ion/src/f730/external_flash.h new file mode 100644 index 000000000..bde2678f8 --- /dev/null +++ b/ion/src/f730/external_flash.h @@ -0,0 +1,81 @@ +#ifndef ION_DEVICE_EXTERNAL_FLASH_H +#define ION_DEVICE_EXTERNAL_FLASH_H + +#include +#include +#include "regs/regs.h" + +// Quad-SPI on STM32 microcontroller +// https://www.st.com/resource/en/application_note/dm00227538.pdf + +// Adesto Technologies AT25SF641 +// https://www.adestotech.com/wp-content/uploads/AT25SF641_111.pdf + +/* External Flash Memory map Address space + * 8MiB chip 0x000000 - 0x7FFFFF + * 2^7 64KiB blocks 0x..0000 - 0x..FFFF + * 2^7 * 2 32KiB blocks 0x..0000 - 0x..7FFF or 0x..8000 - 0x..FFFF + * 2^7 * 2 * 2^3 4KiB blocks 0x...000 - 0x...FFF + * 2^7 * 2 * 2^3 * 2^4 256B pages 0x....00 - 0x....FF */ + +namespace Ion { +namespace ExternalFlash { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS + * PC8 | 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 + */ + +void init(); +void shutdown(); + +void initGPIO(); +void initQSPI(); +void initChip(); + +void MassErase(); + +constexpr int NumberOfSectors = 128; +int SectorAtAddress(uint32_t address); +void EraseSector(int i); + +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); + +enum class Command : uint8_t { + ReadStatusRegister = 0x05, + WriteStatusRegister2 = 0x31, + WriteEnable = 0x06, + ReadData = 0x03, + FastRead = 0x0B, + FastReadQuadIO = 0xEB, + // Program previously erased memory areas as being "0" + PageProgram = 0x02, + QuadPageProgram = 0x33, + EnableQPI = 0x38, + // Erase the whole chip or a 64-Kbyte block as being "1" + ChipErase = 0xC7, + Erase64KbyteBlock = 0xD8, + SetReadParameters = 0xC0 +}; + +constexpr static uint32_t QSPIBaseAddress = 0x90000000; +constexpr static uint8_t NumberOfAddressBitsInChip = 23; +constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; + +constexpr static GPIOPin QSPIPins[] = { + GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), + GPIOPin(GPIOC, 8), GPIOPin(GPIOD,13) +}; + +} +} +} + +#endif diff --git a/ion/src/f730/regs/gpio.h b/ion/src/f730/regs/gpio.h index 2371ac0eb..bf7300295 100644 --- a/ion/src/f730/regs/gpio.h +++ b/ion/src/f730/regs/gpio.h @@ -27,6 +27,19 @@ public: void setType(int index, Type type) volatile { setBitRange(index, index, (uint32_t)type); } }; + class OSPEEDR : Register32 { + // Datasheet, page 120, table 58 + public: + enum class OutputSpeed { + Low = 0, + Medium = 1, + Fast = 2, + High = 3 + }; + OutputSpeed getOutputSpeed(int index) { return (OutputSpeed)getBitRange(2*index+1, 2*index); } + void setOutputSpeed(int index, OutputSpeed speed) volatile { setBitRange(2*index+1, 2*index, (uint32_t)speed); } + }; + class PUPDR : public Register32 { public: enum class Pull { @@ -72,6 +85,7 @@ public: constexpr operator int() const { return m_index; } REGS_REGISTER_AT(MODER, 0x00); REGS_REGISTER_AT(OTYPER, 0x04); + REGS_REGISTER_AT(OSPEEDR, 0x08); REGS_REGISTER_AT(PUPDR, 0x0C); REGS_REGISTER_AT(IDR, 0x10); REGS_REGISTER_AT(ODR, 0x14); diff --git a/ion/src/f730/regs/quadspi.h b/ion/src/f730/regs/quadspi.h new file mode 100644 index 000000000..18d6c81f0 --- /dev/null +++ b/ion/src/f730/regs/quadspi.h @@ -0,0 +1,126 @@ +#ifndef REGS_QUADSPI_H +#define REGS_QUADSPI_H + +#include "register.h" + +// Quad-SPI register map on STM32 microcontroller +// See section 12 of the STM32F412 reference manual + +class QUADSPI { +public: + class CR : public Register32 { // Control register + public: + using Register32::Register32; + REGS_BOOL_FIELD(EN, 0); + REGS_BOOL_FIELD(TCEN, 3); // Lower-power timeout counter enable in memory-mapped mode + REGS_BOOL_FIELD(SSHIFT, 4); + REGS_FIELD(PRESCALER, uint8_t, 31, 24); + }; + + class DCR : public Register32 { // Device configuration register + public: + using Register32::Register32; + REGS_BOOL_FIELD(CKMODE, 0); // MODE 0 or 3 + REGS_FIELD(CSHT, uint8_t, 10, 8); // Chip select minimum high time + REGS_FIELD(FSIZE, uint8_t, 20, 16); + }; + + class SR : Register32 { // Status register + public: + REGS_BOOL_FIELD(TEF, 0); // Transfer error flag + REGS_BOOL_FIELD(TCF, 1); // Transfer complete flag + REGS_BOOL_FIELD(BUSY, 5); + }; + + class FCR : Register32 { // Flag clear register + }; + + class DLR : public Register32 { // Data length register + // In indirect and status-polling modes + // Number of bytes to be transferred = DLR + 1 + }; + + class CCR : public Register32 { // Communication configuration register + public: + using Register32::Register32; + enum class Size : uint8_t { + OneByte = 0, + TwoBytes = 1, + ThreeBytes = 2, + FourBytes = 3 + }; + enum class OperatingMode : uint8_t { + NoData = 0, + Single = 1, + Dual = 2, + Quad = 3 + }; + enum class FunctionalMode : uint8_t { + IndirectWrite = 0, + IndirectRead = 1, + StatusPolling = 2, + MemoryMapped = 3 + }; + REGS_FIELD(INSTRUCTION, uint8_t, 7, 0); + REGS_FIELD(IMODE, OperatingMode, 9, 8); + REGS_FIELD(ADMODE, OperatingMode, 11, 10); + REGS_FIELD(ADSIZE, Size, 13, 12); + REGS_FIELD(ABMODE, OperatingMode, 15, 14); + REGS_FIELD(ABSIZE, Size, 17, 16); + REGS_FIELD(DCYC, uint8_t, 22, 18); + REGS_FIELD(DMODE, OperatingMode, 25, 24); + REGS_FIELD(FMODE, FunctionalMode, 27, 26); + REGS_BOOL_FIELD(SIOO, 28); + }; + + class AR : public Register32 { // Address register + }; + + class ABR : public Register32 { // Alternate bytes register + }; + + class DR : public Register8 { // Data register + /* In indirect (read or write) mode, any data must be written to or read from + * the data register, which supports byte, halfword and word access + * (aligned to the bottom of the register). + * N bytes read/written remove/add N bytes from/to the FIFO. + */ + }; + + class PSMKR : Register32 { // Polling status mask register + }; + + class PSMAR : Register32 { // Polling status match register + }; + + class PIR : Register32 { // Polling interval register + }; + + class LPTR : public Register16 { // Low-power timeout register + // Period before putting the Flash memory in lower-power state (in memory-mapped mode) + }; + + constexpr QUADSPI() {}; + + REGS_REGISTER_AT(CR, 0x00); + REGS_REGISTER_AT(DCR, 0x04); + REGS_REGISTER_AT(SR, 0x08); + REGS_REGISTER_AT(FCR, 0x0C); + REGS_REGISTER_AT(DLR, 0x10); + REGS_REGISTER_AT(CCR, 0x14); + REGS_REGISTER_AT(AR, 0x18); + REGS_REGISTER_AT(ABR, 0x1C); + REGS_REGISTER_AT(DR, 0x20); + REGS_REGISTER_AT(PSMKR, 0x24); + REGS_REGISTER_AT(PSMAR, 0x28); + REGS_REGISTER_AT(PIR, 0x2C); + REGS_REGISTER_AT(LPTR, 0x30); +private: + constexpr uint32_t Base() const { + return 0xA0001000; + } +}; + +constexpr QUADSPI QUADSPI; + +#endif diff --git a/ion/src/f730/regs/regs.h b/ion/src/f730/regs/regs.h index ed5b48bdb..cfa644dd7 100644 --- a/ion/src/f730/regs/regs.h +++ b/ion/src/f730/regs/regs.h @@ -16,6 +16,7 @@ #include "rcc.h" #include "rng.h" #include "otg.h" +#include "quadspi.h" #include "sdio.h" #include "spi.h" #include "syscfg.h" diff --git a/ion/src/f730/usb/Makefile b/ion/src/f730/usb/Makefile index 7d38b05d6..d32478413 100644 --- a/ion/src/f730/usb/Makefile +++ b/ion/src/f730/usb/Makefile @@ -50,6 +50,7 @@ dfu_objs += ion/src/f730/device.o dfu_objs += ion/src/f730/usb.o dfu_objs += ion/src/f730/base64.o dfu_objs += ion/src/f730/flash.o +dfu_objs += ion/src/device/external_flash.o dfu_objs += ion/src/f730/timing.o ion/src/f730/usb/dfu.elf: LDSCRIPT = ion/src/f730/usb/dfu.ld diff --git a/ion/src/f730/usb/calculator.h b/ion/src/f730/usb/calculator.h index d3383e419..81b8c9b2a 100644 --- a/ion/src/f730/usb/calculator.h +++ b/ion/src/f730/usb/calculator.h @@ -93,7 +93,7 @@ public: m_manufacturerStringDescriptor("NumWorks"), m_productStringDescriptor("NumWorks Calculator"), m_serialNumberStringDescriptor(serialNumber), - m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg"), + m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg/0x90000000/64*064Kg,64*064Kg"), //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), /* Switch to this descriptor to use dfu-util to write in the SRAM. * FIXME Should be an alternate Interface. */ diff --git a/ion/src/f730/usb/dfu_interface.cpp b/ion/src/f730/usb/dfu_interface.cpp index 395576cc6..b6e6fde24 100644 --- a/ion/src/f730/usb/dfu_interface.cpp +++ b/ion/src/f730/usb/dfu_interface.cpp @@ -1,6 +1,7 @@ #include "dfu_interface.h" #include #include +#include namespace Ion { namespace USB { @@ -173,7 +174,7 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe if (transferBufferLength == 1) { // Mass erase - m_erasePage = Flash::Device::NumberOfSectors; + m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors; return; } @@ -185,9 +186,11 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe + (transferBuffer[3] << 16) + (transferBuffer[4] << 24); - m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); - - if (m_erasePage < 0) { + if (eraseAddress >= k_flashStartAddress && eraseAddress <= k_flashEndAddress) { + m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); + } else if (eraseAddress >= k_externalFlashStartAddress && eraseAddress <= k_externalFlashEndAddress) { + m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::SectorAtAddress(eraseAddress - k_externalFlashStartAddress); + } else { // Unrecognized sector m_state = State::dfuERROR; m_status = Status::errTARGET; @@ -201,10 +204,13 @@ void DFUInterface::eraseMemoryIfNeeded() { return; } - if (m_erasePage == Ion::Flash::Device::NumberOfSectors) { + if (m_erasePage == Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors) { Flash::Device::MassErase(); - } else { + ExternalFlash::Device::MassErase(); + } else if (m_erasePage < Flash::Device::NumberOfSectors) { Flash::Device::EraseSector(m_erasePage); + } else { + ExternalFlash::Device::EraseSector(m_erasePage - Flash::Device::NumberOfSectors); } /* Put an out of range value in m_erasePage to indicate that no erase is @@ -222,6 +228,8 @@ void DFUInterface::writeOnMemory() { // Write on SRAM // FIXME We should check that we are not overriding the current instructions. memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); + } else if (m_writeAddress >= k_externalFlashStartAddress && m_writeAddress <= k_externalFlashEndAddress) { + ExternalFlash::Device::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress) - k_externalFlashStartAddress, m_largeBufferLength); } else { // Invalid write address m_largeBufferLength = 0; diff --git a/ion/src/f730/usb/dfu_interface.h b/ion/src/f730/usb/dfu_interface.h index 6284ce951..7fce4bf40 100644 --- a/ion/src/f730/usb/dfu_interface.h +++ b/ion/src/f730/usb/dfu_interface.h @@ -130,6 +130,8 @@ private: constexpr static uint32_t k_flashEndAddress = 0x08100000; constexpr static uint32_t k_sramStartAddress = 0x20000000; constexpr static uint32_t k_sramEndAddress = 0x20040000; + constexpr static uint32_t k_externalFlashStartAddress = 0x90000000; + constexpr static uint32_t k_externalFlashEndAddress = 0x90800000; // Download and upload bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); From 378ec39f2ca49e3ddafd809c7f7309c5ae4250fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 11 Jan 2019 10:32:50 +0100 Subject: [PATCH 0083/1750] [ion/f730] ExternalFlash: fix QSPI GPIOs --- ion/src/f730/external_flash.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/f730/external_flash.h b/ion/src/f730/external_flash.h index bde2678f8..1b102027d 100644 --- a/ion/src/f730/external_flash.h +++ b/ion/src/f730/external_flash.h @@ -26,7 +26,7 @@ namespace Device { * -----+----------------------+-----------------------+----------------- * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS - * PC8 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI_BK1_IO2 + * 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 @@ -71,7 +71,7 @@ constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip constexpr static GPIOPin QSPIPins[] = { GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), - GPIOPin(GPIOC, 8), GPIOPin(GPIOD,13) + GPIOPin(GPIOE, 2), GPIOPin(GPIOD,13) }; } From aaaae0305918a595feb99bfe75b52a29360fda25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 11 Jan 2019 10:44:22 +0100 Subject: [PATCH 0084/1750] [ion/f730] ExternalFlash: slow down QUADSPI clock to minimum to ensure its working --- ion/src/f730/external_flash.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/external_flash.cpp b/ion/src/f730/external_flash.cpp index 5de68b64c..0ebe55c17 100644 --- a/ion/src/f730/external_flash.cpp +++ b/ion/src/f730/external_flash.cpp @@ -64,7 +64,7 @@ public: }; static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; -static constexpr int ClockFrequencyDivisor = 2; +static constexpr int ClockFrequencyDivisor = 256;// TODO: optimize me (value forSTM32F412: 2); static constexpr int ChipSelectHighTime = (ClockFrequencyDivisor == 1) ? 3 : (ClockFrequencyDivisor == 2) ? 2 : 1; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength); From c61a9783e6c7ebc66162c877e7844529fcb87c82 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 11 Jan 2019 11:41:38 +0100 Subject: [PATCH 0085/1750] [ion/f730] Enable L1 cache --- ion/src/f730/Makefile | 1 + ion/src/f730/cache.cpp | 21 +++++++++++++++++++++ ion/src/f730/cache.h | 38 ++++++++++++++++++++++++++++++++++++++ ion/src/f730/device.cpp | 7 +++++++ ion/src/f730/regs/cm4.h | 13 +++++++++++++ 5 files changed, 80 insertions(+) create mode 100644 ion/src/f730/cache.cpp create mode 100644 ion/src/f730/cache.h diff --git a/ion/src/f730/Makefile b/ion/src/f730/Makefile index 4c5ffee55..56524771e 100644 --- a/ion/src/f730/Makefile +++ b/ion/src/f730/Makefile @@ -17,6 +17,7 @@ objs += $(addprefix ion/src/f730/, \ backlight.o \ battery.o\ base64.o\ + cache.o \ console.o \ device.o\ display.o\ diff --git a/ion/src/f730/cache.cpp b/ion/src/f730/cache.cpp new file mode 100644 index 000000000..30979e114 --- /dev/null +++ b/ion/src/f730/cache.cpp @@ -0,0 +1,21 @@ +#ifndef ION_DEVICE_CACHE_H +#define ION_DEVICE_CACHE_H + +namespace Ion { +namespace Device { +namespace Cache { + +void dsb(); +void isb(); + +void enableDCache(); +void enableICache(); + +void invalidateDCache(); +void invalidateICache(); + +} +} +} + +#endif diff --git a/ion/src/f730/cache.h b/ion/src/f730/cache.h new file mode 100644 index 000000000..42f8ff9bb --- /dev/null +++ b/ion/src/f730/cache.h @@ -0,0 +1,38 @@ +#ifndef ION_DEVICE_CACHE_H +#define ION_DEVICE_CACHE_H + +namespace Ion { +namespace Device { +namespace Cache { + +void dsb() { + asm volatile("dsb 0xF":::"memory"); +} + +void isb() { + asm volatile("isb 0xF":::"memory"); +} + +void enableDCache() { + dsb(); + isb(); + // FIXME: Invalidate D-Cache + CM4.CCR()->setDC(true); + dsb(); + isb(); +} + +void enableICache() { + dsb(); + isb(); + CM4.ICIALLU->set(0); // Invalidate I-Cache + CM4.CCR()->setIC(true); + dsb(); + isb(); +} + +} +} +} + +#endif diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 145b3e68f..be8d9cf92 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -15,6 +15,7 @@ extern "C" { #include "usb.h" #include "bench/bench.h" #include "base64.h" +#include "cache.h" #define USE_SD_CARD 0 @@ -159,8 +160,14 @@ void jumpReset() { ); } +void initL1Cache() { + Cache::enableICache(); + Cache::enableDCache(); +} + void init() { initClocks(); + initL1Cache(); // Ensure right location of interrupt vectors // The bootloader leaves its own after flashing diff --git a/ion/src/f730/regs/cm4.h b/ion/src/f730/regs/cm4.h index 58c5af1cb..d183043d1 100644 --- a/ion/src/f730/regs/cm4.h +++ b/ion/src/f730/regs/cm4.h @@ -40,6 +40,12 @@ public: REGS_BOOL_FIELD(SLEEPDEEP, 2); }; + class CCR : public Register32 { + public: + REGS_BOOL_FIELD(IC, 17); + REGS_BOOL_FIELD(DC, 16); + }; + class SYST_CSR : public Register32 { public: enum class CLKSOURCE : uint8_t { @@ -62,6 +68,11 @@ public: REGS_FIELD(CURRENT, uint32_t, 23, 0); }; + class ICIALLU : public Register32 { + public: + using Register32::Register32; + }; + constexpr CM4() {}; REGS_REGISTER_AT(SYST_CSR, 0x10); REGS_REGISTER_AT(SYST_RVR, 0x14); @@ -69,7 +80,9 @@ public: REGS_REGISTER_AT(VTOR, 0xD08); REGS_REGISTER_AT(AIRCR, 0xD0C); REGS_REGISTER_AT(SCR, 0xD10); + REGS_REGISTER_AT(CCR, 0xD10); REGS_REGISTER_AT(CPACR, 0xD88); + REGS_REGISTER_AT(CPACR, 0xF50); private: constexpr uint32_t Base() const { return 0xE000E000; From 83588b780a695c1ca821b499b79ce1ea6403f7e3 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 11 Jan 2019 11:49:04 +0100 Subject: [PATCH 0086/1750] [ion/f730] Fix the cache registers --- ion/src/f730/cache.h | 2 +- ion/src/f730/regs/cm4.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/f730/cache.h b/ion/src/f730/cache.h index 42f8ff9bb..aca5a05c6 100644 --- a/ion/src/f730/cache.h +++ b/ion/src/f730/cache.h @@ -25,7 +25,7 @@ void enableDCache() { void enableICache() { dsb(); isb(); - CM4.ICIALLU->set(0); // Invalidate I-Cache + CM4.ICIALLU()->set(0); // Invalidate I-Cache CM4.CCR()->setIC(true); dsb(); isb(); diff --git a/ion/src/f730/regs/cm4.h b/ion/src/f730/regs/cm4.h index d183043d1..0fa181de9 100644 --- a/ion/src/f730/regs/cm4.h +++ b/ion/src/f730/regs/cm4.h @@ -82,7 +82,7 @@ public: REGS_REGISTER_AT(SCR, 0xD10); REGS_REGISTER_AT(CCR, 0xD10); REGS_REGISTER_AT(CPACR, 0xD88); - REGS_REGISTER_AT(CPACR, 0xF50); + REGS_REGISTER_AT(ICIALLU, 0xF50); private: constexpr uint32_t Base() const { return 0xE000E000; From e4b7d902949c983e01bad06b9b76af592e5de7c8 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Fri, 11 Jan 2019 12:24:18 +0100 Subject: [PATCH 0087/1750] [ion/f730] WIP: init ExternalFlash in initPeripherals() --- ion/src/f730/device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 2dc37989c..fa97a1c3f 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -204,6 +204,7 @@ void initPeripherals() { LED::Device::init(); Battery::Device::init(); USB::Device::init(); + ExternalFlash::Device::init(); return; // FIXME, obviously! #if USE_SD_CARD SDCard::Device::init(); @@ -211,7 +212,6 @@ void initPeripherals() { Console::Device::init(); SWD::Device::init(); initSysTick(); - ExternalFlash::Device::init(); } void shutdownPeripherals(bool keepLEDAwake) { From 1c662fd0d5dafe6c860c0dba04865e26f7b5150a Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 11 Jan 2019 13:51:44 +0100 Subject: [PATCH 0088/1750] [ion/f730] Enable FLASH prefetch --- ion/src/f730/device.cpp | 7 +++---- ion/src/f730/flash.cpp | 2 ++ ion/src/f730/regs/flash.h | 6 ++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index be8d9cf92..c406ba2af 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -246,15 +246,14 @@ void initClocks() { * The spec tells us that at 2.8V and over 210MHz the flash expects 7 WS. */ FLASH.ACR()->setLATENCY(7); -#if 0 +#if 1 /* 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); - /* Set flash instruction and data cache */ - FLASH.ACR()->setDCEN(true); - FLASH.ACR()->setICEN(true); + /* Enable the ART */ + FLASH.ACR()->setARTEN(true); #endif /* After reset, the device is using the high-speed internal oscillator (HSI) diff --git a/ion/src/f730/flash.cpp b/ion/src/f730/flash.cpp index b5b0bae8e..602ae7dd9 100644 --- a/ion/src/f730/flash.cpp +++ b/ion/src/f730/flash.cpp @@ -30,6 +30,7 @@ static void close() { assert(!FLASH.CR()->getPG()); FLASH.CR()->setLOCK(true); +#if 0 // Purge Data and instruction cache if (FLASH.ACR()->getDCEN()) { FLASH.ACR()->setDCEN(false); @@ -43,6 +44,7 @@ static void close() { FLASH.ACR()->setICRST(false); FLASH.ACR()->setICEN(true); } +#endif } // Compile-time log2 diff --git a/ion/src/f730/regs/flash.h b/ion/src/f730/regs/flash.h index 218b17e4b..29fed1e84 100644 --- a/ion/src/f730/regs/flash.h +++ b/ion/src/f730/regs/flash.h @@ -9,10 +9,8 @@ public: public: REGS_FIELD(LATENCY, uint8_t, 3, 0); REGS_BOOL_FIELD(PRFTEN, 8); - REGS_BOOL_FIELD(ICEN, 9); - REGS_BOOL_FIELD(DCEN, 10); - REGS_BOOL_FIELD(ICRST, 11); - REGS_BOOL_FIELD(DCRST, 12); + REGS_BOOL_FIELD(ARTEN, 9); + REGS_BOOL_FIELD(ARTRST, 11); }; class KEYR : public Register32 { From 9b371809abb095908ee0b222c87c09d618838aad Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 11 Jan 2019 14:13:19 +0100 Subject: [PATCH 0089/1750] [build] Fix the flasher build commands --- build/targets.device.mak | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/targets.device.mak b/build/targets.device.mak index 4df3b5f38..6ff86d987 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -51,8 +51,8 @@ openocd: openocd -f build/$(PLATFORM)/openocd.cfg ifeq ($(EPSILON_USB_DFU_XIP)$(EPSILON_DEVICE_BENCH),10) -flasher.$(EXE): LDFLAGS = --gc-sections -T ion/src/device/usb/flasher.ld -flasher.$(EXE): $(objs) $(usb_objs) ion/src/device/usb/flasher.o +flasher.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/usb/flasher.ld +flasher.$(EXE): $(objs) $(usb_objs) ion/src/$(PLATFORM)/usb/flasher.o else flasher.$(EXE): @echo "Error: flasher.elf requires EPSILON_DEVICE_BENCH=0 EPSILON_USB_DFU_XIP=1" From f4e2eaa3900c8cb2c9aa8dc66a419f5071b3e402 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 19 Nov 2018 15:23:17 +0100 Subject: [PATCH 0090/1750] [ion/device] Add QuadSPI regs --- ion/src/device/regs/quadspi.h | 126 ++++++++++++++++++++++++++++++++++ ion/src/device/regs/regs.h | 1 + 2 files changed, 127 insertions(+) create mode 100644 ion/src/device/regs/quadspi.h diff --git a/ion/src/device/regs/quadspi.h b/ion/src/device/regs/quadspi.h new file mode 100644 index 000000000..ed46c3b6b --- /dev/null +++ b/ion/src/device/regs/quadspi.h @@ -0,0 +1,126 @@ +#ifndef REGS_QUADSPI_H +#define REGS_QUADSPI_H + +#include "register.h" + +// Quad-SPI register map on STM32 microcontroller +// See section 12 of the STM32F412 reference manual + +class QUADSPI { +public: + class CR : public Register32 { // Control register + public: + using Register32::Register32; + REGS_BOOL_FIELD(EN, 0); + REGS_BOOL_FIELD(TCEN, 3); // Lower-power timeout counter enable in memory-mapped mode + REGS_BOOL_FIELD(SSHIFT, 4); + REGS_FIELD(PRESCALER, uint8_t, 31, 24); + }; + + class DCR : public Register32 { // Device configuration register + public: + using Register32::Register32; + REGS_BOOL_FIELD(CKMODE, 0); // MODE 0 or 3 + REGS_FIELD(CSHT, uint8_t, 10, 8); // Chip select minimum high time + REGS_FIELD(FSIZE, uint8_t, 20, 16); + }; + + class SR : Register32 { // Status register + public: + REGS_BOOL_FIELD(TEF, 0); // Transfer error flag + REGS_BOOL_FIELD(TCF, 1); // Transfer complete flag + REGS_BOOL_FIELD(BUSY, 5); + }; + + class FCR : Register32 { // Flag clear register + }; + + class DLR : public Register32 { // Data length register + // In indirect and status-polling modes + // Number of bytes to be transferred = DLR + 1 + }; + + class CCR : public Register32 { // Communication configuration register + public: + using Register32::Register32; + enum class Size : uint8_t { + OneByte = 0, + TwoBytes = 1, + ThreeBytes = 2, + FourBytes = 3 + }; + enum class OperatingMode : uint8_t { + NoData = 0, + Single = 1, + Dual = 2, + Quad = 3 + }; + enum class FunctionalMode : uint8_t { + IndirectWrite = 0, + IndirectRead = 1, + StatusPolling = 2, + MemoryMapped = 3 + }; + REGS_FIELD(INSTRUCTION, uint8_t, 7, 0); + REGS_FIELD(IMODE, OperatingMode, 9, 8); + REGS_FIELD(ADMODE, OperatingMode, 11, 10); + REGS_FIELD(ADSIZE, Size, 13, 12); + REGS_FIELD(ABMODE, OperatingMode, 15, 14); + REGS_FIELD(ABSIZE, Size, 17, 16); + REGS_FIELD(DCYC, uint8_t, 22, 18); + REGS_FIELD(DMODE, OperatingMode, 25, 24); + REGS_FIELD(FMODE, FunctionalMode, 27, 26); + REGS_BOOL_FIELD(SIOO, 28); + }; + + class AR : public Register32 { // Address register + }; + + class ABR : Register32 { // Alternate bytes register + }; + + class DR : public Register8 { // Data register + /* In indirect (read or write) mode, any data must be written to or read from + * the data register, which supports byte, halfword and word access + * (aligned to the bottom of the register). + * N bytes read/written remove/add N bytes from/to the FIFO. + */ + }; + + class PSMKR : Register32 { // Polling status mask register + }; + + class PSMAR : Register32 { // Polling status match register + }; + + class PIR : Register32 { // Polling interval register + }; + + class LPTR : public Register16 { // Low-power timeout register + // Period before putting the Flash memory in lower-power state (in memory-mapped mode) + }; + + constexpr QUADSPI() {}; + + REGS_REGISTER_AT(CR, 0x00); + REGS_REGISTER_AT(DCR, 0x04); + REGS_REGISTER_AT(SR, 0x08); + REGS_REGISTER_AT(FCR, 0x0C); + REGS_REGISTER_AT(DLR, 0x10); + REGS_REGISTER_AT(CCR, 0x14); + REGS_REGISTER_AT(AR, 0x18); + REGS_REGISTER_AT(ABR, 0x1C); + REGS_REGISTER_AT(DR, 0x20); + REGS_REGISTER_AT(PSMKR, 0x24); + REGS_REGISTER_AT(PSMAR, 0x28); + REGS_REGISTER_AT(PIR, 0x2C); + REGS_REGISTER_AT(LPTR, 0x30); +private: + constexpr uint32_t Base() const { + return 0xA0001000; + } +}; + +constexpr QUADSPI QUADSPI; + +#endif diff --git a/ion/src/device/regs/regs.h b/ion/src/device/regs/regs.h index b5fad9e8a..c77040ee7 100644 --- a/ion/src/device/regs/regs.h +++ b/ion/src/device/regs/regs.h @@ -16,6 +16,7 @@ #include "rcc.h" #include "rng.h" #include "otg.h" +#include "quadspi.h" #include "sdio.h" #include "spi.h" #include "syscfg.h" From a89115d6fdf94eea8d6a28f9df489d61e3da5251 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 20 Nov 2018 10:59:48 +0100 Subject: [PATCH 0091/1750] [ion/device] Start implement external flash API --- ion/src/device/external_flash.cpp | 209 ++++++++++++++++++++++++++++++ ion/src/device/external_flash.h | 36 +++++ 2 files changed, 245 insertions(+) create mode 100644 ion/src/device/external_flash.cpp create mode 100644 ion/src/device/external_flash.h diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp new file mode 100644 index 000000000..27a9902c7 --- /dev/null +++ b/ion/src/device/external_flash.cpp @@ -0,0 +1,209 @@ +#include "external_flash.h" +#include +#include +#include + +namespace Ion { +namespace ExternalFlash { +namespace Device { + +void waitController() { + while (QUADSPI.SR()->getBUSY()) { + } +} + +static enum DeviceStatusRegister : uint16_t { + // Status Register 1 + BUSY = 1 << 0, // Erase, Program, Write Status Register in Progress + WEL = 1 << 1, // Write Enable Latch + BP0 = 1 << 2, // Block Protect + BP1 = 1 << 3, // Block Protect + BP2 = 1 << 4, // Block Protect + TB = 1 << 5, // Top/Bottom Write Protect + SEC = 1 << 6, // Sector Protect + SRP = 1 << 7, // Status Register Protect 0 + // Status Register 2 + SRP1 = 1 << 8, // Status Register Protect 1 + QE = 1 << 9, // Quad-SPI Enable + // Bits 10, 11, 12, 13 are reserved + CMP = 1 << 14, // + SUS = 1 << 15 +}; + +/* +------------------------------------------------------------------------------------------------------------------------------------------------ + Bit Volatile? Reset value Short name Long name +------------------------------------------------------------------------------------------------------------------------------------------------ +Status Register-1 0 volatile BUSY Erase, Program, Write Status Register in Progress + + Any instruction, except for Read Register, will be ignored while BUSY=1. + It is recommended to check the BUSY bit before sending a new instruction, after a Program, Erase, Write Status Register operation. + TODO Status polling instead of while... + + 1 volatile WEL Write Enable Latch + + must be set before any Program, Erase or Write Status Register instruction, with the Write Enable instruction. + is automatically reset write-disable status of “0†after Powerâ€up and upon completion of any Program, Erase or Write Status Register instruction. + + 2 non-volatile BP0 Block Protect + 3 non-volatile BP1 Block Protect + 4 non-volatile BP2 Block Protect + 5 non-volatile TB Top/Bottom Write Protect + 6 non-volatile 0 SEC Sector Protect + + controls if the Block Protect Bits (BP2, BP1, BP0) protect 4KB Sectors (SEC=1) or 64KB Blocks (SEC=0) in the Top (TB=0) or the Bottom (TB=1) of the array. + + 7 non-volatile SRP Status Register Protect 0 +Status Register-2 8 non-volatile SRP1 Status Register Protect 1 + 9 non-volatile QE Quad SPI Enable + 14 non-volatile 0 CMP Complement Protect + + CMP=0: a top 4KB sector can be protected while the rest of the array is not + CMP=1: the top 4KB sector will become unprotected while the rest of the array become read-only. + + 15 volatile 0 SUS Suspend Status +------------------------------------------------------------------------------------------------------------------------------------------------ +*/ + +void waitDevice() { + do { + ReadStatusRegister1.write(); + } while(QUADSPI.DR()->get() & 1); +} + +void initGPIO() { + /* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI BK1_NCS + * PC9 | QUADSPI BK1_IO0/SO | Alternate Function 9 | QUADSPI BK1_IO0 + * PD12 | QUADSPI BK1_IO1/SI | Alternate Function 9 | QUADSPI BK1_IO1 + * PC8 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI BK1_IO2 + * PD13 | QUADSPI BK1_IO3/HOLD | Alternate Function 9 | QUADSPI BK1_IO3 */ + + // Enable GPIOB, C, D AHB1 peripheral clocks + RCC.AHB1ENR()->setGPIOBEN(true); + RCC.AHB1ENR()->setGPIOCEN(true); + RCC.AHB1ENR()->setGPIODEN(true); + + constexpr static GPIOPin QUADSPIPins[] = { + GPIOPin(GPIOB, 2), + GPIOPin(GPIOB, 6), + GPIOPin(GPIOC, 9), + GPIOPin(GPIOD,12), + GPIOPin(GPIOC, 8), + GPIOPin(GPIOD,13) + } + for(const GPIOPin & g : QUADSPIPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), (g == QUADSPIPins[1]) ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9); + } +} + +void init() { + initGPIO(); + + // Enable QUADSPI AHB3 peripheral clocks + RCC.AHB3ENR()->setQSPIEN(true); + // Configure controller for target device + QUADSPI.DCR()->setFSIZE(22); // 2^(22+1) bytes in device + /* TODO CKMODE, CSHT + * Instructions are initiated on the falling edge of CS and are completed on its rising edge. + * Mode 0: SCK is low when CS is high + * Mode 3: SCK is high when CS is high + * Input bits are sampled/latched on the rising edge of SCK. + * Output bits are shifted out on the falling edge of SCK. + * + * The IO pins should be at high impedance prior to the falling edge of the first data out clock. + * Data bytes are shifted with Most Significant Bit first. + * + * Configure and enable QUADSPI peripheral + * All instructions, except Read Data, supported SPI clock frequencies of up to 104MHz. + * Read Data supports up to 50Mhz. + * TODO PRESCALER + * TODO trade-off ? consumption? */ + QUADSPI.CR()->setPRESCALER(255); + QUADSPI.CR()->setEN(true); +} + +class Instruction { +public: + using QUADSPI::CCR::OperatingMode; + using QUADSPI::CCR::Size; + using QUADSPI::CCR::FunctionalMode; + constexpr Instruction( + uint8_t instructionCode, OperatingMode IMode, bool SIOO, + OperatingMode ADMode, + uint8_t DCyc, + OperatingMode DMode, uint32_t DLength, + FunctionalMode FMode + ) { + m_ccr = QUADSPI::CCR((uint32_t)0); + m_ccr.setINSTRUCTION(instructionCode); + m_ccr.setIMODE (IMode); + m_ccr.setSIOO (SIOO); + m_ccr.setADMODE(ADMode); + m_ccr.setADSIZE(Size::ThreeBytes); + m_ccr.setDCYC (DCyc); + m_ccr.setDMODE (DMode); + m_ccr.setFMODE (FMode); + // DDR not supported by Adesto + } + void setAlternateBytes(OperatingMode ABMode, Size ABSize) { + m_ccr.setABMODE(ABMode); + m_ccr.setABSIZE(ABSize); + } + void write() { + QUADSPI.CCR()->set(m_ccr); + QUADSPI.DLR()->set(0); + } +private: + class QUADSPI::CCR m_ccr; + uint32_t m_dlr; +}; + +//TODO default ccr +/* Modes IMODE ADMODE ABMODE DMODE + * SPIStandard 1-1-1-1 + * SPIDualOutput 1-1-1-2 + * SPIDualIO 1-2-2-2 + * SPIQuadOutput 1-1-1-4 + * SPIQuadIO 1-4-4-4 + * Quad SPI instructions require the QE bit in Status Register-2 to be set. + * QPI 4-4-4-4 + * Enable/Disable QPI instruction + * ADSIZE is always ThreeBytes + * MODE may be NoData*/ + +/* All Read instructions can be completed after any clocked bit. + * However, all Write, Program or Erase instructions must complete on a byte (CS driven high + * after a full byte has been clocked) otherwise the instruction will be terminated. */ + +using QUADSPI::CCR::OperatingMode; +using QUADSPI::CCR::Size; +using QUADSPI::CCR::FunctionalMode; + +static enum InstructionSet : Instruction { + ReadStatusRegister1 = Instruction(0x05, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectRead), + WriteEnable = Instruction(0x06, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectWrite), + // Before any Program, Erase or Write Status Register instruction + EraseChip = Instruction(0x60, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectWrite) + /* + ReadStatusRegister2 = Instruction(0x35), + ReadData = Instruction(0x03), + Erase4kBlock = Instruction(0x20), + PageProgram = Instruction(0x02) + */ +}; + +void eraseChip() { + WriteEnable.write(); + EraseChip.write(); + waitDevice(); +} + +} +} +} + +#endif diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h new file mode 100644 index 000000000..f651f4635 --- /dev/null +++ b/ion/src/device/external_flash.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_EXTERNAL_FLASH_H +#define ION_DEVICE_EXTERNAL_FLASH_H + +// Quad-SPI on STM32 microcontroller +// https://www.st.com/resource/en/application_note/dm00227538.pdf + +// Adesto Technologies AT25SF641 +// https://www.adestotech.com/wp-content/uploads/AT25SF641_111.pdf + +/* External Flash Memory map Address space + * 8MiB chip 0x000000 - 0x7FFFFF + * 2^7 64KiB blocks 0x..0000 - 0x..FFFF + * 2^7 * 2 32KiB blocks 0x..0000 - 0x..7FFF or 0x..8000 - 0x..FFFF + * 2^7 * 2 * 2^3 4KiB blocks 0x...000 - 0x...FFF + * 2^7 * 2 * 2^3 * 2^4 256B pages 0x....00 - 0x....FF */ + +namespace Ion { +namespace ExternalFlash { +namespace Device { + +// Read, Erase, Write +// at the fastest possible speed + +void eraseChip(); //block? + +void program(uint32_t * source, uint32_t * destination, size_t length); + +void read(); // in indirect read mode + +// memory-mapped mode + +} +} +} + +#endif From 2270ebc1f378eea97103c7236ef3b2a30bc4e13d Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 20 Nov 2018 15:25:23 +0100 Subject: [PATCH 0092/1750] [ion/device] Clean the external flash init code --- ion/src/device/Makefile | 1 + ion/src/device/device.cpp | 2 + ion/src/device/external_flash.cpp | 298 +++++++++++++----------------- ion/src/device/external_flash.h | 42 ++++- 4 files changed, 166 insertions(+), 177 deletions(-) diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 3d243bd47..31a060382 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -21,6 +21,7 @@ objs += $(addprefix ion/src/device/, \ device.o\ display.o\ events.o\ + external_flash.o\ flash.o\ keyboard.o\ led.o\ diff --git a/ion/src/device/device.cpp b/ion/src/device/device.cpp index e25cc4106..d01941f32 100644 --- a/ion/src/device/device.cpp +++ b/ion/src/device/device.cpp @@ -15,6 +15,7 @@ extern "C" { #include "usb.h" #include "bench/bench.h" #include "base64.h" +#include "external_flash.h" #define USE_SD_CARD 0 @@ -187,6 +188,7 @@ void initPeripherals() { Console::Device::init(); SWD::Device::init(); initSysTick(); + ExternalFlash::Device::init(); } void shutdownPeripherals(bool keepLEDAwake) { diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 27a9902c7..90c17f9e8 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -1,209 +1,165 @@ #include "external_flash.h" -#include -#include -#include namespace Ion { namespace ExternalFlash { namespace Device { -void waitController() { - while (QUADSPI.SR()->getBUSY()) { - } +/* Explain here how QuadSPI and QPI are different. */ + +static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Single; + +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t * data, size_t dataLength); + +static inline void send_command(Command c) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + DefaultOperatingMode, + c, + 0, nullptr, 0 + ); } -static enum DeviceStatusRegister : uint16_t { - // Status Register 1 - BUSY = 1 << 0, // Erase, Program, Write Status Register in Progress - WEL = 1 << 1, // Write Enable Latch - BP0 = 1 << 2, // Block Protect - BP1 = 1 << 3, // Block Protect - BP2 = 1 << 4, // Block Protect - TB = 1 << 5, // Top/Bottom Write Protect - SEC = 1 << 6, // Sector Protect - SRP = 1 << 7, // Status Register Protect 0 - // Status Register 2 - SRP1 = 1 << 8, // Status Register Protect 1 - QE = 1 << 9, // Quad-SPI Enable - // Bits 10, 11, 12, 13 are reserved - CMP = 1 << 14, // - SUS = 1 << 15 -}; +static inline void send_command_single(Command c) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + QUADSPI::CCR::OperatingMode::Single, + c, + 0, nullptr, 0 + ); +} -/* ------------------------------------------------------------------------------------------------------------------------------------------------- - Bit Volatile? Reset value Short name Long name ------------------------------------------------------------------------------------------------------------------------------------------------- -Status Register-1 0 volatile BUSY Erase, Program, Write Status Register in Progress +static inline void send_write_command(Command c, uint32_t address, uint8_t * data, size_t dataLength) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + DefaultOperatingMode, + c, + address, data, dataLength + ); +} - Any instruction, except for Read Register, will be ignored while BUSY=1. - It is recommended to check the BUSY bit before sending a new instruction, after a Program, Erase, Write Status Register operation. - TODO Status polling instead of while... +static inline void send_read_command(Command c, uint32_t address, uint8_t * data, size_t dataLength) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + DefaultOperatingMode, + c, + address, data, dataLength + ); +} - 1 volatile WEL Write Enable Latch - - must be set before any Program, Erase or Write Status Register instruction, with the Write Enable instruction. - is automatically reset write-disable status of “0†after Powerâ€up and upon completion of any Program, Erase or Write Status Register instruction. - - 2 non-volatile BP0 Block Protect - 3 non-volatile BP1 Block Protect - 4 non-volatile BP2 Block Protect - 5 non-volatile TB Top/Bottom Write Protect - 6 non-volatile 0 SEC Sector Protect - - controls if the Block Protect Bits (BP2, BP1, BP0) protect 4KB Sectors (SEC=1) or 64KB Blocks (SEC=0) in the Top (TB=0) or the Bottom (TB=1) of the array. - - 7 non-volatile SRP Status Register Protect 0 -Status Register-2 8 non-volatile SRP1 Status Register Protect 1 - 9 non-volatile QE Quad SPI Enable - 14 non-volatile 0 CMP Complement Protect - - CMP=0: a top 4KB sector can be protected while the rest of the array is not - CMP=1: the top 4KB sector will become unprotected while the rest of the array become read-only. - - 15 volatile 0 SUS Suspend Status ------------------------------------------------------------------------------------------------------------------------------------------------- -*/ - -void waitDevice() { +static inline void wait() { + class ExternalFlashStatusRegister : Register16 { + public: + using Register16::Register16; + REGS_BOOL_FIELD(BUSY, 0); + }; + ExternalFlashStatusRegister statusRegister(0); do { - ReadStatusRegister1.write(); - } while(QUADSPI.DR()->get() & 1); + send_read_command(Command::ReadStatusRegister, 0, reinterpret_cast(&statusRegister), sizeof(statusRegister)); + } while (statusRegister.getBUSY()); } -void initGPIO() { - /* Pin | Role | Mode | Function - * -----+----------------------+-----------------------+----------------- - * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI CLK - * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI BK1_NCS - * PC9 | QUADSPI BK1_IO0/SO | Alternate Function 9 | QUADSPI BK1_IO0 - * PD12 | QUADSPI BK1_IO1/SI | Alternate Function 9 | QUADSPI BK1_IO1 - * PC8 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI BK1_IO2 - * PD13 | QUADSPI BK1_IO3/HOLD | Alternate Function 9 | QUADSPI BK1_IO3 */ +static void set_as_memory_mapped() { + send_command_full( + QUADSPI::CCR::FunctionalMode::MemoryMapped, + DefaultOperatingMode, + Command::FastRead, + 0, nullptr, 0 + ); +} - // Enable GPIOB, C, D AHB1 peripheral clocks - RCC.AHB1ENR()->setGPIOBEN(true); - RCC.AHB1ENR()->setGPIOCEN(true); - RCC.AHB1ENR()->setGPIODEN(true); - - constexpr static GPIOPin QUADSPIPins[] = { - GPIOPin(GPIOB, 2), - GPIOPin(GPIOB, 6), - GPIOPin(GPIOC, 9), - GPIOPin(GPIOD,12), - GPIOPin(GPIOC, 8), - GPIOPin(GPIOD,13) +void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t * data, size_t dataLength) { + class QUADSPI::CCR ccr(0); + ccr.setFMODE(functionalMode); + if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setDMODE(operatingMode); } - for(const GPIOPin & g : QUADSPIPins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), (g == QUADSPIPins[1]) ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9); + if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setADMODE(operatingMode); + ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); + QUADSPI.AR()->set(address); + } + ccr.setIMODE(operatingMode); + ccr.setINSTRUCTION(static_cast(c)); + QUADSPI.CCR()->set(ccr); + + // FIXME: Handle access sizes + if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { + for (size_t i=0; iset(data[i]); + } + } else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + for (size_t i=0; iget(); + } + } + + /* 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." */ + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + while (QUADSPI.SR()->getBUSY()) { + } } } void init() { initGPIO(); + initQSPI(); + initChip(); +} +void initGPIO() { + for(const GPIOPin & g : QSPIPins) { + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), (g.pin() == 6 ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9)); + } +} + +void initQSPI() { // Enable QUADSPI AHB3 peripheral clocks RCC.AHB3ENR()->setQSPIEN(true); // Configure controller for target device QUADSPI.DCR()->setFSIZE(22); // 2^(22+1) bytes in device - /* TODO CKMODE, CSHT - * Instructions are initiated on the falling edge of CS and are completed on its rising edge. - * Mode 0: SCK is low when CS is high - * Mode 3: SCK is high when CS is high - * Input bits are sampled/latched on the rising edge of SCK. - * Output bits are shifted out on the falling edge of SCK. - * - * The IO pins should be at high impedance prior to the falling edge of the first data out clock. - * Data bytes are shifted with Most Significant Bit first. - * - * Configure and enable QUADSPI peripheral - * All instructions, except Read Data, supported SPI clock frequencies of up to 104MHz. - * Read Data supports up to 50Mhz. - * TODO PRESCALER - * TODO trade-off ? consumption? */ - QUADSPI.CR()->setPRESCALER(255); + + QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal + QUADSPI.CR()->setPRESCALER(255); // Highest value. TODO: make it optimal + QUADSPI.CR()->setEN(true); } -class Instruction { -public: - using QUADSPI::CCR::OperatingMode; - using QUADSPI::CCR::Size; - using QUADSPI::CCR::FunctionalMode; - constexpr Instruction( - uint8_t instructionCode, OperatingMode IMode, bool SIOO, - OperatingMode ADMode, - uint8_t DCyc, - OperatingMode DMode, uint32_t DLength, - FunctionalMode FMode - ) { - m_ccr = QUADSPI::CCR((uint32_t)0); - m_ccr.setINSTRUCTION(instructionCode); - m_ccr.setIMODE (IMode); - m_ccr.setSIOO (SIOO); - m_ccr.setADMODE(ADMode); - m_ccr.setADSIZE(Size::ThreeBytes); - m_ccr.setDCYC (DCyc); - m_ccr.setDMODE (DMode); - m_ccr.setFMODE (FMode); - // DDR not supported by Adesto +void initChip() { + /* The chip initially expects commands in SPI mode. We need to use SPI to tell + * it to switch to QPI, hence the "_single". */ + if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { + send_command_single(Command::EnableQPI); } - void setAlternateBytes(OperatingMode ABMode, Size ABSize) { - m_ccr.setABMODE(ABMode); - m_ccr.setABSIZE(ABSize); - } - void write() { - QUADSPI.CCR()->set(m_ccr); - QUADSPI.DLR()->set(0); - } -private: - class QUADSPI::CCR m_ccr; - uint32_t m_dlr; -}; + set_as_memory_mapped(); +} -//TODO default ccr -/* Modes IMODE ADMODE ABMODE DMODE - * SPIStandard 1-1-1-1 - * SPIDualOutput 1-1-1-2 - * SPIDualIO 1-2-2-2 - * SPIQuadOutput 1-1-1-4 - * SPIQuadIO 1-4-4-4 - * Quad SPI instructions require the QE bit in Status Register-2 to be set. - * QPI 4-4-4-4 - * Enable/Disable QPI instruction - * ADSIZE is always ThreeBytes - * MODE may be NoData*/ +void MassErase() { + send_command(Command::WriteEnable); + send_command(Command::ChipErase); + //send_command(ReadStatusRegister); + wait(); + set_as_memory_mapped(); +} -/* All Read instructions can be completed after any clocked bit. - * However, all Write, Program or Erase instructions must complete on a byte (CS driven high - * after a full byte has been clocked) otherwise the instruction will be terminated. */ +void EraseSector() { + send_command(Command::WriteEnable); + //send_command(Command::BlockErase /* PICK SIZE */, addressOfSector); + wait(); + set_as_memory_mapped(); +} -using QUADSPI::CCR::OperatingMode; -using QUADSPI::CCR::Size; -using QUADSPI::CCR::FunctionalMode; - -static enum InstructionSet : Instruction { - ReadStatusRegister1 = Instruction(0x05, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectRead), - WriteEnable = Instruction(0x06, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectWrite), - // Before any Program, Erase or Write Status Register instruction - EraseChip = Instruction(0x60, OperatingMode::Single, false, OperatingMode::NoData, 0, OperatingMode::NoData, FunctionalMode::IndirectWrite) - /* - ReadStatusRegister2 = Instruction(0x35), - ReadData = Instruction(0x03), - Erase4kBlock = Instruction(0x20), - PageProgram = Instruction(0x02) - */ -}; - -void eraseChip() { - WriteEnable.write(); - EraseChip.write(); - waitDevice(); +void WriteMemory(uint32_t * source, uint32_t * destination, size_t length) { + send_command(Command::WriteEnable); + //send_write_command(Command::QuadPageProgram, (destination-reinterpret_cast(QSPIBaseAddress)), source, length); + // TODO: Apprently, here we must issue a new send_command every 256 byte + wait(); + set_as_memory_mapped(); } } } } - -#endif diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index f651f4635..659a4f8dc 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -1,6 +1,10 @@ #ifndef ION_DEVICE_EXTERNAL_FLASH_H #define ION_DEVICE_EXTERNAL_FLASH_H +#include +#include +#include "regs/regs.h" + // Quad-SPI on STM32 microcontroller // https://www.st.com/resource/en/application_note/dm00227538.pdf @@ -18,16 +22,42 @@ namespace Ion { namespace ExternalFlash { namespace Device { -// Read, Erase, Write -// at the fastest possible speed +/* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS + * PC8 | 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 + */ -void eraseChip(); //block? +void init(); +void shutdown(); -void program(uint32_t * source, uint32_t * destination, size_t length); +void initGPIO(); +void initQSPI(); +void initChip(); -void read(); // in indirect read mode +void MassErase(); +void EraseSector(int sector); +void WriteMemory(uint32_t * source, uint32_t * destination, size_t length); -// memory-mapped mode +enum class Command : uint8_t { + ReadStatusRegister = 0x05, + WriteEnable = 0x06, + FastRead = 0x0B, + QuadPageProgram = 0x33, + EnableQPI = 0x38, + ChipErase = 0xC7 +}; + +constexpr static uint32_t QSPIBaseAddress = 0x90000000; + +constexpr static GPIOPin QSPIPins[] = { + GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), + GPIOPin(GPIOC, 8), GPIOPin(GPIOD,13) +}; } } From 3d4fa6dea607179ffa8383a88673114261f46a37 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Wed, 21 Nov 2018 17:06:12 +0100 Subject: [PATCH 0093/1750] [ion/device] Read Data from external flash in memory-mapped mode --- ion/src/device/external_flash.cpp | 31 +++++++++++++++++++++++++++---- ion/src/device/external_flash.h | 3 +++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 90c17f9e8..179ec4c3b 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -4,7 +4,27 @@ namespace Ion { namespace ExternalFlash { namespace Device { -/* Explain here how QuadSPI and QPI are different. */ +/* 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. + * + * After the external flash receives a Read instructions and shifts a byte out, + * it automatically increments the provided address and shifts out the corresponding byte, + * and so on as long as the clock continues, allowing for a continuous stream of data. + */ static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Single; @@ -62,7 +82,7 @@ static void set_as_memory_mapped() { send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, - Command::FastRead, + Command::ReadData, 0, nullptr, 0 ); } @@ -73,14 +93,17 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setDMODE(operatingMode); } + QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); - QUADSPI.AR()->set(address); } ccr.setIMODE(operatingMode); ccr.setINSTRUCTION(static_cast(c)); QUADSPI.CCR()->set(ccr); + if (address != 0) { + QUADSPI.AR()->set(address); + } // FIXME: Handle access sizes if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { @@ -120,7 +143,7 @@ void initQSPI() { // Enable QUADSPI AHB3 peripheral clocks RCC.AHB3ENR()->setQSPIEN(true); // Configure controller for target device - QUADSPI.DCR()->setFSIZE(22); // 2^(22+1) bytes in device + QUADSPI.DCR()->setFSIZE(FlashNumberOfAddressBits-1); QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal QUADSPI.CR()->setPRESCALER(255); // Highest value. TODO: make it optimal diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index 659a4f8dc..ae78f42f4 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -46,6 +46,7 @@ void WriteMemory(uint32_t * source, uint32_t * destination, size_t length); enum class Command : uint8_t { ReadStatusRegister = 0x05, WriteEnable = 0x06, + ReadData = 0x03, FastRead = 0x0B, QuadPageProgram = 0x33, EnableQPI = 0x38, @@ -53,6 +54,8 @@ enum class Command : uint8_t { }; constexpr static uint32_t QSPIBaseAddress = 0x90000000; +constexpr static uint32_t FlashNumberOfAddressBits = 23; +constexpr static uint32_t FlashAddressSpaceSize = 1 << FlashNumberOfAddressBits; constexpr static GPIOPin QSPIPins[] = { GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), From 865fff316522fdab11442918dbcf355ed6653292 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Wed, 21 Nov 2018 18:18:00 +0100 Subject: [PATCH 0094/1750] [ion/device] Add dummyCycles parameter for ExternalFlash commands --- ion/src/device/external_flash.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 179ec4c3b..0c201a3ec 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -28,14 +28,14 @@ namespace Device { static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Single; -static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t * data, size_t dataLength); +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); static inline void send_command(Command c) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, DefaultOperatingMode, c, - 0, nullptr, 0 + 0, 0, nullptr, 0 ); } @@ -44,7 +44,7 @@ static inline void send_command_single(Command c) { QUADSPI::CCR::FunctionalMode::IndirectWrite, QUADSPI::CCR::OperatingMode::Single, c, - 0, nullptr, 0 + 0, 0, nullptr, 0 ); } @@ -53,7 +53,7 @@ static inline void send_write_command(Command c, uint32_t address, uint8_t * dat QUADSPI::CCR::FunctionalMode::IndirectWrite, DefaultOperatingMode, c, - address, data, dataLength + address, 0, data, dataLength ); } @@ -62,7 +62,7 @@ static inline void send_read_command(Command c, uint32_t address, uint8_t * data QUADSPI::CCR::FunctionalMode::IndirectRead, DefaultOperatingMode, c, - address, data, dataLength + address, 0, data, dataLength ); } @@ -83,17 +83,18 @@ static void set_as_memory_mapped() { QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::ReadData, - 0, nullptr, 0 + 0, 0, nullptr, 0 ); } -void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t * data, size_t dataLength) { +void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { class QUADSPI::CCR ccr(0); ccr.setFMODE(functionalMode); if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setDMODE(operatingMode); } QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + ccr.setDCYC(dummyCycles); if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); From 47358588c9695e13c970cd25ef347c51d4823b09 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 22 Nov 2018 09:44:32 +0100 Subject: [PATCH 0095/1750] [ion/device] Fast Read external flash in SPI mode --- ion/src/device/external_flash.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 0c201a3ec..8e94ef89d 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -82,8 +82,8 @@ static void set_as_memory_mapped() { send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, - Command::ReadData, - 0, 0, nullptr, 0 + Command::FastRead, + 0, 8, nullptr, 0 ); } From 1039c5cefd74571d5c6776e5481a8c7ec8b1ead8 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 22 Nov 2018 10:15:43 +0100 Subject: [PATCH 0096/1750] [ion/device] Fast Read external flash in QPI mode --- ion/src/device/external_flash.cpp | 68 ++++++++++++++++++------------- ion/src/device/external_flash.h | 1 + 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 8e94ef89d..81c4eda7d 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -26,64 +26,65 @@ namespace Device { * and so on as long as the clock continues, allowing for a continuous stream of data. */ -static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Single; +class ExternalFlashStatusRegister { +public: + class StatusRegister1 : Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_R(BUSY, 0); + }; + class StatusRegister2 : Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_W(QE, 1); + }; +}; + +static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); -static inline void send_command(Command c) { +static inline void send_command(Command c, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, - DefaultOperatingMode, + operatingMode, c, 0, 0, nullptr, 0 ); } -static inline void send_command_single(Command c) { +static inline void send_write_command(Command c, uint32_t address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, - QUADSPI::CCR::OperatingMode::Single, - c, - 0, 0, nullptr, 0 - ); -} - -static inline void send_write_command(Command c, uint32_t address, uint8_t * data, size_t dataLength) { - send_command_full( - QUADSPI::CCR::FunctionalMode::IndirectWrite, - DefaultOperatingMode, + operatingMode, c, address, 0, data, dataLength ); } -static inline void send_read_command(Command c, uint32_t address, uint8_t * data, size_t dataLength) { +static inline void send_read_command(Command c, uint32_t address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectRead, - DefaultOperatingMode, + operatingMode, c, address, 0, data, dataLength ); } -static inline void wait() { - class ExternalFlashStatusRegister : Register16 { - public: - using Register16::Register16; - REGS_BOOL_FIELD(BUSY, 0); - }; - ExternalFlashStatusRegister statusRegister(0); +static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); do { - send_read_command(Command::ReadStatusRegister, 0, reinterpret_cast(&statusRegister), sizeof(statusRegister)); - } while (statusRegister.getBUSY()); + send_read_command(Command::ReadStatusRegister, 0, reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); + } while (statusRegister1.getBUSY()); } static void set_as_memory_mapped() { + constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : 4; send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::FastRead, - 0, 8, nullptr, 0 + 0, FastReadDummyCycles, nullptr, 0 ); } @@ -93,7 +94,9 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setDMODE(operatingMode); } - QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + } ccr.setDCYC(dummyCycles); if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); @@ -154,9 +157,16 @@ void initQSPI() { void initChip() { /* The chip initially expects commands in SPI mode. We need to use SPI to tell - * it to switch to QPI, hence the "_single". */ + * it to switch to QPI. */ if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { - send_command_single(Command::EnableQPI); + send_command(Command::WriteEnable, QUADSPI::CCR::OperatingMode::Single); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + statusRegister2.setQE(true); + wait(QUADSPI::CCR::OperatingMode::Single); + send_write_command(Command::WriteStatusRegister2, 0, reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); + wait(QUADSPI::CCR::OperatingMode::Single); + send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); + wait(); } set_as_memory_mapped(); } diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index ae78f42f4..17f170b8f 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -45,6 +45,7 @@ void WriteMemory(uint32_t * source, uint32_t * destination, size_t length); enum class Command : uint8_t { ReadStatusRegister = 0x05, + WriteStatusRegister2 = 0x31, WriteEnable = 0x06, ReadData = 0x03, FastRead = 0x0B, From f0323c85ebf25a38ab1c5c65f371bb081ef0d96a Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 22 Nov 2018 15:51:54 +0100 Subject: [PATCH 0097/1750] [ion/device] Convert external flash addresses from uint32_t to uint8_t * --- ion/src/device/external_flash.cpp | 24 ++++++++++++------------ ion/src/device/external_flash.h | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 81c4eda7d..04233a00f 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -42,18 +42,18 @@ public: static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; -static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); static inline void send_command(Command c, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, c, - 0, 0, nullptr, 0 + nullptr, 0, nullptr, 0 ); } -static inline void send_write_command(Command c, uint32_t address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { +static inline void send_write_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, @@ -62,7 +62,7 @@ static inline void send_write_command(Command c, uint32_t address, uint8_t * dat ); } -static inline void send_read_command(Command c, uint32_t address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { +static inline void send_read_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectRead, operatingMode, @@ -74,7 +74,7 @@ static inline void send_read_command(Command c, uint32_t address, uint8_t * data static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); do { - send_read_command(Command::ReadStatusRegister, 0, reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); + send_read_command(Command::ReadStatusRegister, nullptr, reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); } while (statusRegister1.getBUSY()); } @@ -84,11 +84,11 @@ static void set_as_memory_mapped() { QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::FastRead, - 0, FastReadDummyCycles, nullptr, 0 + nullptr, FastReadDummyCycles, nullptr, 0 ); } -void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint32_t address, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { +void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { class QUADSPI::CCR ccr(0); ccr.setFMODE(functionalMode); if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { @@ -98,15 +98,15 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); } ccr.setDCYC(dummyCycles); - if (address != 0 || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + if (address != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); } ccr.setIMODE(operatingMode); ccr.setINSTRUCTION(static_cast(c)); QUADSPI.CCR()->set(ccr); - if (address != 0) { - QUADSPI.AR()->set(address); + if (address != nullptr) { + QUADSPI.AR()->set(reinterpret_cast(address)); } // FIXME: Handle access sizes @@ -163,7 +163,7 @@ void initChip() { ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); statusRegister2.setQE(true); wait(QUADSPI::CCR::OperatingMode::Single); - send_write_command(Command::WriteStatusRegister2, 0, reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); + send_write_command(Command::WriteStatusRegister2, nullptr, reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); wait(QUADSPI::CCR::OperatingMode::Single); send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); wait(); @@ -186,7 +186,7 @@ void EraseSector() { set_as_memory_mapped(); } -void WriteMemory(uint32_t * source, uint32_t * destination, size_t length) { +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { send_command(Command::WriteEnable); //send_write_command(Command::QuadPageProgram, (destination-reinterpret_cast(QSPIBaseAddress)), source, length); // TODO: Apprently, here we must issue a new send_command every 256 byte diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index 17f170b8f..123ab07e6 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -41,7 +41,7 @@ void initChip(); void MassErase(); void EraseSector(int sector); -void WriteMemory(uint32_t * source, uint32_t * destination, size_t length); +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); enum class Command : uint8_t { ReadStatusRegister = 0x05, @@ -55,7 +55,7 @@ enum class Command : uint8_t { }; constexpr static uint32_t QSPIBaseAddress = 0x90000000; -constexpr static uint32_t FlashNumberOfAddressBits = 23; +constexpr static uint8_t FlashNumberOfAddressBits = 23; constexpr static uint32_t FlashAddressSpaceSize = 1 << FlashNumberOfAddressBits; constexpr static GPIOPin QSPIPins[] = { From 5be7ed836d644dd116003b96cd2ebcb99dfd68aa Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 22 Nov 2018 17:03:11 +0100 Subject: [PATCH 0098/1750] [ion/device] External flash WriteMemory --- ion/src/device/external_flash.cpp | 26 +++++++++++++++++++++----- ion/src/device/external_flash.h | 2 ++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 04233a00f..76eb2ebee 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -174,7 +174,6 @@ void initChip() { void MassErase() { send_command(Command::WriteEnable); send_command(Command::ChipErase); - //send_command(ReadStatusRegister); wait(); set_as_memory_mapped(); } @@ -187,10 +186,27 @@ void EraseSector() { } void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { - send_command(Command::WriteEnable); - //send_write_command(Command::QuadPageProgram, (destination-reinterpret_cast(QSPIBaseAddress)), source, length); - // TODO: Apprently, here we must issue a new send_command every 256 byte - wait(); + /* 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; + constexpr Command pageProgram = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? Command::PageProgram : Command::QuadPageProgram; + uint8_t offset = reinterpret_cast(destination) & (PageSize - 1); + size_t lengthThatFitsInPage = PageSize - offset; + while (length > 0) { + if (lengthThatFitsInPage > length) { + lengthThatFitsInPage = length; + } + send_command(Command::WriteEnable); + wait(); + send_write_command(pageProgram, destination, source, lengthThatFitsInPage); + length -= lengthThatFitsInPage; + destination += lengthThatFitsInPage; + source += lengthThatFitsInPage; + lengthThatFitsInPage = PageSize; + wait(); + } set_as_memory_mapped(); } diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index 123ab07e6..1c12a7f19 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -49,6 +49,8 @@ enum class Command : uint8_t { WriteEnable = 0x06, ReadData = 0x03, FastRead = 0x0B, + // Program previously erased memory areas as being "0" + PageProgram = 0x02, QuadPageProgram = 0x33, EnableQPI = 0x38, ChipErase = 0xC7 From 5d62ef933aa8ac418e8eafb5fe3c6d7812013e4f Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 26 Nov 2018 14:18:18 +0100 Subject: [PATCH 0099/1750] [ion/device] Add explanatory comments to external flash --- ion/src/device/external_flash.cpp | 41 +++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 76eb2ebee..cc4dbc3c7 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -21,10 +21,33 @@ namespace Device { * The external flash supports clock frequencies up to 104MHz for all instructions, * except for Read Data (0x03) which is supported up to 50Mhz. * - * After the external flash receives a Read instructions and shifts a byte out, - * it automatically increments the provided address and shifts out the corresponding byte, - * and so on as long as the clock continues, allowing for a continuous stream of data. - */ + * + * Quad-SPI block diagram + * + * +----------------------+ +------------+ + * | Quad-SPI | | | + * | peripheral | | External | + * | | read | flash | + * AHB <-- | data <-- 32-byte | <-- | memory | + * matrix --> | regsiter --> 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. */ class ExternalFlashStatusRegister { public: @@ -79,6 +102,15 @@ static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOpera } 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.) */ constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : 4; send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, @@ -109,7 +141,6 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR QUADSPI.AR()->set(reinterpret_cast(address)); } - // FIXME: Handle access sizes if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { for (size_t i=0; iset(data[i]); From fc6aede57fcbcca4338c83c3bd5f0dda5fed9f32 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Wed, 28 Nov 2018 18:09:30 +0100 Subject: [PATCH 0100/1750] [ion/device] External flash erase routines --- ion/src/device/external_flash.cpp | 17 ++++++++++++++--- ion/src/device/external_flash.h | 15 +++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index cc4dbc3c7..1f6a5c4b2 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -178,7 +178,7 @@ void initQSPI() { // Enable QUADSPI AHB3 peripheral clocks RCC.AHB3ENR()->setQSPIEN(true); // Configure controller for target device - QUADSPI.DCR()->setFSIZE(FlashNumberOfAddressBits-1); + QUADSPI.DCR()->setFSIZE(NumberOfAddressBitsInChip - 1); QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal QUADSPI.CR()->setPRESCALER(255); // Highest value. TODO: make it optimal @@ -202,16 +202,27 @@ void initChip() { set_as_memory_mapped(); } +int SectorAtAddress(uint32_t address) { + int i = address >> NumberOfAddressBitsIn64KbyteBlock; + if (i >= NumberOfSectors) { + return -1; + } + return i; +} + void MassErase() { send_command(Command::WriteEnable); + wait(); send_command(Command::ChipErase); wait(); set_as_memory_mapped(); } -void EraseSector() { +void EraseSector(int i) { + assert(i >= 0 && i < NumberOfSectors); send_command(Command::WriteEnable); - //send_command(Command::BlockErase /* PICK SIZE */, addressOfSector); + wait(); + send_write_command(Command::Erase64KbyteBlock, reinterpret_cast(i << NumberOfAddressBitsIn64KbyteBlock), nullptr, 0); wait(); set_as_memory_mapped(); } diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index 1c12a7f19..ff4d9e7b8 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -40,7 +40,11 @@ void initQSPI(); void initChip(); void MassErase(); -void EraseSector(int sector); + +constexpr int NumberOfSectors = 128; +int SectorAtAddress(uint32_t address); +void EraseSector(int i); + void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); enum class Command : uint8_t { @@ -53,12 +57,15 @@ enum class Command : uint8_t { PageProgram = 0x02, QuadPageProgram = 0x33, EnableQPI = 0x38, - ChipErase = 0xC7 + // Erase the whole chip or a 64-Kbyte block as being "1" + ChipErase = 0xC7, + Erase64KbyteBlock = 0xD8, }; constexpr static uint32_t QSPIBaseAddress = 0x90000000; -constexpr static uint8_t FlashNumberOfAddressBits = 23; -constexpr static uint32_t FlashAddressSpaceSize = 1 << FlashNumberOfAddressBits; +constexpr static uint8_t NumberOfAddressBitsInChip = 23; +constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; constexpr static GPIOPin QSPIPins[] = { GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), From 0d6317117c39645fd0c851a85ff40cf5b6e1e21b Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 29 Nov 2018 15:56:26 +0100 Subject: [PATCH 0101/1750] [ion/device] External flash: replace nullptr address by an out-of-range address --- ion/src/device/external_flash.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 1f6a5c4b2..5aa124d3b 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -72,7 +72,7 @@ static inline void send_command(Command c, QUADSPI::CCR::OperatingMode operating QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, c, - nullptr, 0, nullptr, 0 + reinterpret_cast(FlashAddressSpaceSize), 0, nullptr, 0 ); } @@ -97,7 +97,7 @@ static inline void send_read_command(Command c, uint8_t * address, uint8_t * dat static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); do { - send_read_command(Command::ReadStatusRegister, nullptr, reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); + send_read_command(Command::ReadStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); } while (statusRegister1.getBUSY()); } @@ -116,7 +116,7 @@ static void set_as_memory_mapped() { QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::FastRead, - nullptr, FastReadDummyCycles, nullptr, 0 + reinterpret_cast(FlashAddressSpaceSize), FastReadDummyCycles, nullptr, 0 ); } @@ -130,14 +130,14 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); } ccr.setDCYC(dummyCycles); - if (address != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); } ccr.setIMODE(operatingMode); ccr.setINSTRUCTION(static_cast(c)); QUADSPI.CCR()->set(ccr); - if (address != nullptr) { + if (address != reinterpret_cast(FlashAddressSpaceSize)) { QUADSPI.AR()->set(reinterpret_cast(address)); } @@ -194,7 +194,7 @@ void initChip() { ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); statusRegister2.setQE(true); wait(QUADSPI::CCR::OperatingMode::Single); - send_write_command(Command::WriteStatusRegister2, nullptr, reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); + send_write_command(Command::WriteStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); wait(QUADSPI::CCR::OperatingMode::Single); send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); wait(); From e4f70c7be722f9896d55915f685fe4b43bfe3963 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 3 Dec 2018 09:58:35 +0100 Subject: [PATCH 0102/1750] [ion/device] Access external flash through DFU --- ion/src/device/usb/Makefile | 1 + ion/src/device/usb/calculator.h | 2 +- ion/src/device/usb/dfu_interface.cpp | 20 ++++++++++++++------ ion/src/device/usb/dfu_interface.h | 2 ++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/ion/src/device/usb/Makefile b/ion/src/device/usb/Makefile index a2d840eef..6a859fcc9 100644 --- a/ion/src/device/usb/Makefile +++ b/ion/src/device/usb/Makefile @@ -50,6 +50,7 @@ dfu_objs += ion/src/device/device.o dfu_objs += ion/src/device/usb.o dfu_objs += ion/src/device/base64.o dfu_objs += ion/src/device/flash.o +dfu_objs += ion/src/device/external_flash.o dfu_objs += ion/src/device/timing.o ion/src/device/usb/dfu.elf: LDSCRIPT = ion/src/device/usb/dfu.ld diff --git a/ion/src/device/usb/calculator.h b/ion/src/device/usb/calculator.h index d3383e419..81b8c9b2a 100644 --- a/ion/src/device/usb/calculator.h +++ b/ion/src/device/usb/calculator.h @@ -93,7 +93,7 @@ public: m_manufacturerStringDescriptor("NumWorks"), m_productStringDescriptor("NumWorks Calculator"), m_serialNumberStringDescriptor(serialNumber), - m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg"), + m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg/0x90000000/64*064Kg,64*064Kg"), //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), /* Switch to this descriptor to use dfu-util to write in the SRAM. * FIXME Should be an alternate Interface. */ diff --git a/ion/src/device/usb/dfu_interface.cpp b/ion/src/device/usb/dfu_interface.cpp index 029cce703..8b30e7ea2 100644 --- a/ion/src/device/usb/dfu_interface.cpp +++ b/ion/src/device/usb/dfu_interface.cpp @@ -1,6 +1,7 @@ #include "dfu_interface.h" #include #include +#include namespace Ion { namespace USB { @@ -173,7 +174,7 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe if (transferBufferLength == 1) { // Mass erase - m_erasePage = Flash::Device::NumberOfSectors; + m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors; return; } @@ -185,9 +186,11 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe + (transferBuffer[3] << 16) + (transferBuffer[4] << 24); - m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); - - if (m_erasePage < 0) { + if (eraseAddress >= k_flashStartAddress && eraseAddress <= k_flashEndAddress) { + m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); + } else if (eraseAddress >= k_externalFlashStartAddress && eraseAddress <= k_externalFlashEndAddress) { + m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::SectorAtAddress(eraseAddress - k_externalFlashStartAddress); + } else { // Unrecognized sector m_state = State::dfuERROR; m_status = Status::errTARGET; @@ -201,10 +204,13 @@ void DFUInterface::eraseMemoryIfNeeded() { return; } - if (m_erasePage == Ion::Flash::Device::NumberOfSectors) { + if (m_erasePage == Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors) { Flash::Device::MassErase(); - } else { + ExternalFlash::Device::MassErase(); + } else if (m_erasePage < Flash::Device::NumberOfSectors) { Flash::Device::EraseSector(m_erasePage); + } else { + ExternalFlash::Device::EraseSector(m_erasePage - Flash::Device::NumberOfSectors); } /* Put an out of range value in m_erasePage to indicate that no erase is @@ -222,6 +228,8 @@ void DFUInterface::writeOnMemory() { // Write on SRAM // FIXME We should check that we are not overriding the current instructions. memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); + } else if (m_writeAddress >= k_externalFlashStartAddress && m_writeAddress <= k_externalFlashEndAddress) { + ExternalFlash::Device::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress) - k_externalFlashStartAddress, m_largeBufferLength); } else { // Invalid write address m_largeBufferLength = 0; diff --git a/ion/src/device/usb/dfu_interface.h b/ion/src/device/usb/dfu_interface.h index 6284ce951..7fce4bf40 100644 --- a/ion/src/device/usb/dfu_interface.h +++ b/ion/src/device/usb/dfu_interface.h @@ -130,6 +130,8 @@ private: constexpr static uint32_t k_flashEndAddress = 0x08100000; constexpr static uint32_t k_sramStartAddress = 0x20000000; constexpr static uint32_t k_sramEndAddress = 0x20040000; + constexpr static uint32_t k_externalFlashStartAddress = 0x90000000; + constexpr static uint32_t k_externalFlashEndAddress = 0x90800000; // Download and upload bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); From c6e5b4590c6e5062580d01e88d1334523605fbb4 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 6 Dec 2018 13:56:04 +0100 Subject: [PATCH 0103/1750] [ion/device] Add external flash tests --- ion/Makefile | 8 ++ ion/test/external_flash.cpp | 165 ++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 ion/test/external_flash.cpp diff --git a/ion/Makefile b/ion/Makefile index e123b4595..611e42916 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -32,3 +32,11 @@ tests += $(addprefix ion/test/,\ keyboard.cpp\ storage.cpp\ ) + +TEST_EXT_FLASH ?= 0 +ifeq ($(TEST_EXT_FLASH),1) +SFLAGS += -DTEST_EXT_FLASH=1 +tests += ion/test/external_flash.cpp +TEST_EXT_FLASH_REPROGRAM ?= 0 +SFLAGS += -DTEST_EXT_FLASH_REPROGRAM=$(TEST_EXT_FLASH_REPROGRAM) +endif diff --git a/ion/test/external_flash.cpp b/ion/test/external_flash.cpp new file mode 100644 index 000000000..392a058da --- /dev/null +++ b/ion/test/external_flash.cpp @@ -0,0 +1,165 @@ +#include +#include +#include +#include "ion/src/device/external_flash.h" +#include "ion/include/ion/timing.h" + +// Choose some not too uniform data to program and test the external flash memory with. + +static inline uint8_t expected_value_at(uint8_t * ptr) { + uint32_t address = reinterpret_cast(ptr) - Ion::ExternalFlash::Device::QSPIBaseAddress; + return (address / 0x10000) + (address / 0x100) + address; + // Example: the value expected at the address 0x123456 is 0x12 + 0x34 + 0x56. +} + +static inline uint16_t expected_value_at(uint16_t * ptr) { + uint8_t * ptr8 = reinterpret_cast(ptr); + return (static_cast(expected_value_at(ptr8+1)) << 8) | static_cast(expected_value_at(ptr8)); +} + +static inline uint32_t expected_value_at(uint32_t * ptr) { + uint16_t * ptr16 = reinterpret_cast(ptr); + return (static_cast(expected_value_at(ptr16+1)) << 16) + static_cast(expected_value_at(ptr16)); +} + +template +static inline void check(volatile T * p, int repeat) { + for (int i = 0; i < repeat; i++) { + quiz_assert(*p == expected_value_at(const_cast(p))); + } +} + +template +void test(int accessType, int repeat) { + uint8_t * start = reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress); + uint8_t * end = reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress + Ion::ExternalFlash::Device::FlashAddressSpaceSize); + + // Forward sequential access + if (accessType == 0) { + for (uint8_t * p = start; p <= end-sizeof(T); p++) { + volatile T * q = reinterpret_cast(p); + check(q, repeat); + } + } + + // Backward sequential access + if (accessType == 1) { + for (uint8_t * p = end - sizeof(T); p >= start; p--) { + volatile T * q = reinterpret_cast(p); + check(q, repeat); + } + } + + // Random access + if (accessType == 2) { + T * endT = reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress + Ion::ExternalFlash::Device::FlashAddressSpaceSize); + for (size_t i=0; i> (32 - Ion::ExternalFlash::Device::NumberOfAddressBitsInChip); + volatile T * q = reinterpret_cast(randomAddr + Ion::ExternalFlash::Device::QSPIBaseAddress); + if (q <= endT - 1) { + check(q, repeat); + } + } + } +} + +static size_t uint64ToString(uint64_t n, char buffer[]) { + size_t len = 0; + do { + buffer[len++] = (n % 10) + '0'; + } while ((n /= 10) > 0); + int i = 0; + int j = len - 1; + while (i < j) { + char c = buffer[i]; + buffer[i++] = buffer[j]; + buffer[j--] = c; + } + return len; +} + +static void printElapsedTime(uint64_t startTime) { + char buffer[7+26+2] = " time: "; + size_t len = uint64ToString((Ion::Timing::millis() - startTime)/1000, buffer+7); + buffer[7+len] = 's'; + buffer[7+len+1] = 0; + quiz_print(buffer); +} + +QUIZ_CASE(ion_ext_flash_erase) { +#if TEST_EXT_FLASH_REPROGRAM + uint64_t startTime = Ion::Timing::millis(); + Ion::ExternalFlash::Device::MassErase(); + printElapsedTime(startTime); +#endif +} + +QUIZ_CASE(ion_ext_flash_program) { +#if TEST_EXT_FLASH_REPROGRAM + // Program separately each page of the flash memory + uint64_t startTime = Ion::Timing::millis(); + for (int page = 0; page < (1<<15); page++) { + uint8_t buffer[256]; + for (int byte = 0; byte < 256; byte++) { + buffer[byte] = expected_value_at(reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress + page * 256 + byte)); + } + Ion::ExternalFlash::Device::WriteMemory(buffer, reinterpret_cast(page * 256), 256); + } + printElapsedTime(startTime); +#endif +} + +QUIZ_CASE(ion_extflash_read_byte_fwd) { + uint64_t startTime = Ion::Timing::millis(); + test(0, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_byte_bck) { + uint64_t startTime = Ion::Timing::millis(); + test(1, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_byte_rand) { + uint64_t startTime = Ion::Timing::millis(); + test(2, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_half_fwd) { + uint64_t startTime = Ion::Timing::millis(); + test(0, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_half_bck) { + uint64_t startTime = Ion::Timing::millis(); + test(1, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_half_rand) { + uint64_t startTime = Ion::Timing::millis(); + test(2, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_word_fwd) { + uint64_t startTime = Ion::Timing::millis(); + test(0, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_word_bck) { + uint64_t startTime = Ion::Timing::millis(); + test(1, 1); + printElapsedTime(startTime); +} + +QUIZ_CASE(ion_extflash_read_word_rand) { + uint64_t startTime = Ion::Timing::millis(); + test(2, 1); + printElapsedTime(startTime); + Ion::Timing::msleep(3000); +} From 8a9f0d1f1f51b01d47e64c8179d1e0b26263e56d Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 10 Dec 2018 09:57:22 +0100 Subject: [PATCH 0104/1750] [ion/device] Define GPIO OSPEEDR register --- ion/src/device/regs/gpio.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ion/src/device/regs/gpio.h b/ion/src/device/regs/gpio.h index 2371ac0eb..bf7300295 100644 --- a/ion/src/device/regs/gpio.h +++ b/ion/src/device/regs/gpio.h @@ -27,6 +27,19 @@ public: void setType(int index, Type type) volatile { setBitRange(index, index, (uint32_t)type); } }; + class OSPEEDR : Register32 { + // Datasheet, page 120, table 58 + public: + enum class OutputSpeed { + Low = 0, + Medium = 1, + Fast = 2, + High = 3 + }; + OutputSpeed getOutputSpeed(int index) { return (OutputSpeed)getBitRange(2*index+1, 2*index); } + void setOutputSpeed(int index, OutputSpeed speed) volatile { setBitRange(2*index+1, 2*index, (uint32_t)speed); } + }; + class PUPDR : public Register32 { public: enum class Pull { @@ -72,6 +85,7 @@ public: constexpr operator int() const { return m_index; } REGS_REGISTER_AT(MODER, 0x00); REGS_REGISTER_AT(OTYPER, 0x04); + REGS_REGISTER_AT(OSPEEDR, 0x08); REGS_REGISTER_AT(PUPDR, 0x0C); REGS_REGISTER_AT(IDR, 0x10); REGS_REGISTER_AT(ODR, 0x14); From 6aa49513028cb5617742293cece267e071e51248 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 17 Dec 2018 14:56:41 +0100 Subject: [PATCH 0105/1750] [ion/device] External flash: add constexpr ClockFrequencyDivisor --- ion/src/device/external_flash.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 5aa124d3b..7b754a2ec 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -64,6 +64,7 @@ public: }; static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; +static constexpr int ClockFrequencyDivisor = 256; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); @@ -111,7 +112,7 @@ static void set_as_memory_mapped() { * * It goes low, only if the low-power timeout counter is enabled. * (Flash memories tend to consume more when nCS is held low.) */ - constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : 4; + constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : (ClockFrequencyDivisor > 1) ? 4 : 6; send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, @@ -181,7 +182,7 @@ void initQSPI() { QUADSPI.DCR()->setFSIZE(NumberOfAddressBitsInChip - 1); QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal - QUADSPI.CR()->setPRESCALER(255); // Highest value. TODO: make it optimal + QUADSPI.CR()->setPRESCALER(ClockFrequencyDivisor - 1); // Highest value. TODO: make it optimal QUADSPI.CR()->setEN(true); } From 409edb923dce6ff8277cc5582a5dc6b480dd4896 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 17 Dec 2018 16:32:04 +0100 Subject: [PATCH 0106/1750] [ion/device] External flash: SetReadParameters (dummy cycles) according to clock frequency --- ion/src/device/external_flash.cpp | 12 ++++++++++++ ion/src/device/external_flash.h | 1 + 2 files changed, 13 insertions(+) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 7b754a2ec..d39e084f8 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -199,6 +199,18 @@ void initChip() { wait(QUADSPI::CCR::OperatingMode::Single); send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); wait(); + if (ClockFrequencyDivisor == 1) { + class ReadParameters : Register8 { + public: + /* Parameters sent along with SetReadParameters instruction in order + * to configure the number of dummy cycles for the QPI Read instructions. */ + using Register8::Register8; + REGS_BOOL_FIELD_W(P5, 1); + }; + ReadParameters readParameters(0); + readParameters.setP5(true); + send_write_command(Command::SetReadParameters, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&readParameters), sizeof(readParameters)); + } } set_as_memory_mapped(); } diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index ff4d9e7b8..f05bcf476 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -60,6 +60,7 @@ enum class Command : uint8_t { // Erase the whole chip or a 64-Kbyte block as being "1" ChipErase = 0xC7, Erase64KbyteBlock = 0xD8, + SetReadParameters = 0xC0 }; constexpr static uint32_t QSPIBaseAddress = 0x90000000; From 4f1eba412d9f2d4b990b00a8b64dd4de899393dd Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 10 Dec 2018 10:00:26 +0100 Subject: [PATCH 0107/1750] [ion/device] Optimize external flash initQSPI --- ion/src/device/external_flash.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index d39e084f8..96f2538cf 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -64,7 +64,8 @@ public: }; static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; -static constexpr int ClockFrequencyDivisor = 256; +static constexpr int ClockFrequencyDivisor = 2; +static constexpr int ChipSelectHighTime = (ClockFrequencyDivisor == 1) ? 3 : (ClockFrequencyDivisor == 2) ? 2 : 1; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); @@ -170,21 +171,25 @@ void init() { void initGPIO() { for(const GPIOPin & g : QSPIPins) { + g.group().OSPEEDR()->setOutputSpeed(g.pin(), GPIO::OSPEEDR::OutputSpeed::High); g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), (g.pin() == 6 ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9)); } } void initQSPI() { - // Enable QUADSPI AHB3 peripheral clocks + // Enable QUADSPI AHB3 peripheral clock RCC.AHB3ENR()->setQSPIEN(true); // Configure controller for target device - QUADSPI.DCR()->setFSIZE(NumberOfAddressBitsInChip - 1); - - QUADSPI.DCR()->setCSHT(7); // Highest value. TODO: make it optimal - QUADSPI.CR()->setPRESCALER(ClockFrequencyDivisor - 1); // Highest value. TODO: make it optimal - - QUADSPI.CR()->setEN(true); + class QUADSPI::DCR dcr(0); + dcr.setFSIZE(NumberOfAddressBitsInChip - 1); + dcr.setCSHT(ChipSelectHighTime - 1); + dcr.setCKMODE(true); + QUADSPI.DCR()->set(dcr); + class QUADSPI::CR cr(0); + cr.setPRESCALER(ClockFrequencyDivisor - 1); + cr.setEN(true); + QUADSPI.CR()->set(cr); } void initChip() { From 677cf936b63f13c53f049d59b474a8002f0189db Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 11 Dec 2018 14:38:18 +0100 Subject: [PATCH 0108/1750] [quiz] Fix quiz_print to fit screen height --- quiz/src/runner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quiz/src/runner.cpp b/quiz/src/runner.cpp index b8f0e4ba7..454fd51f0 100644 --- a/quiz/src/runner.cpp +++ b/quiz/src/runner.cpp @@ -17,7 +17,7 @@ void quiz_print(const char * message) { int line_height = font->glyphSize().height(); ctx->drawString(message, KDPoint(0, line_y), font); line_y += line_height; - if (line_y > Ion::Display::Height) { + if (line_y + line_height > Ion::Display::Height) { line_y = 0; // Clear screen maybe? } From 8ce7be5ff2a5c507ce8d3b1daea327e333d5699b Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 18 Dec 2018 10:22:23 +0100 Subject: [PATCH 0109/1750] [ion/device] External flash: include alternate bytes in Quad-SPI instructions --- ion/src/device/external_flash.cpp | 29 +++++++++++++++++++++++------ ion/src/device/regs/quadspi.h | 2 +- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index 96f2538cf..ece42b00c 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -67,14 +67,17 @@ static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR static constexpr int ClockFrequencyDivisor = 2; static constexpr int ChipSelectHighTime = (ClockFrequencyDivisor == 1) ? 3 : (ClockFrequencyDivisor == 2) ? 2 : 1; -static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength); +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, 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, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, c, - reinterpret_cast(FlashAddressSpaceSize), 0, nullptr, 0 + reinterpret_cast(FlashAddressSpaceSize), + 0, 0, + 0, + nullptr, 0 ); } @@ -83,7 +86,10 @@ static inline void send_write_command(Command c, uint8_t * address, uint8_t * da QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, c, - address, 0, data, dataLength + address, + 0, 0, + 0, + data, dataLength ); } @@ -92,7 +98,10 @@ static inline void send_read_command(Command c, uint8_t * address, uint8_t * dat QUADSPI::CCR::FunctionalMode::IndirectRead, operatingMode, c, - address, 0, data, dataLength + address, + 0, 0, + 0, + data, dataLength ); } @@ -118,11 +127,14 @@ static void set_as_memory_mapped() { QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::FastRead, - reinterpret_cast(FlashAddressSpaceSize), FastReadDummyCycles, nullptr, 0 + reinterpret_cast(FlashAddressSpaceSize), + 0, 0, + FastReadDummyCycles, + nullptr, 0 ); } -void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { +void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { class QUADSPI::CCR ccr(0); ccr.setFMODE(functionalMode); if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { @@ -132,6 +144,11 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); } ccr.setDCYC(dummyCycles); + if (numberOfAltBytes > 0) { + ccr.setABMODE(operatingMode); + ccr.setABSIZE(static_cast(numberOfAltBytes - 1)); + QUADSPI.ABR()->set(altBytes); + } if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { ccr.setADMODE(operatingMode); ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); diff --git a/ion/src/device/regs/quadspi.h b/ion/src/device/regs/quadspi.h index ed46c3b6b..18d6c81f0 100644 --- a/ion/src/device/regs/quadspi.h +++ b/ion/src/device/regs/quadspi.h @@ -76,7 +76,7 @@ public: class AR : public Register32 { // Address register }; - class ABR : Register32 { // Alternate bytes register + class ABR : public Register32 { // Alternate bytes register }; class DR : public Register8 { // Data register From e11deaa8749e37dbe235264a20428e1976336d55 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 18 Dec 2018 11:45:41 +0100 Subject: [PATCH 0110/1750] [ion/device] External flash: Send instruction only once in memory-mapped mode --- ion/src/device/external_flash.cpp | 28 +++++++++++++++++++++++++--- ion/src/device/external_flash.h | 1 + 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/external_flash.cpp index ece42b00c..5de68b64c 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/external_flash.cpp @@ -126,14 +126,28 @@ static void set_as_memory_mapped() { send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, - Command::FastRead, + Command::FastReadQuadIO, reinterpret_cast(FlashAddressSpaceSize), - 0, 0, - FastReadDummyCycles, + 0xA0, 1, + 2, //FIXME 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, + DefaultOperatingMode, + Command::FastReadQuadIO, + 0, + ~(0xA0), 1, + 2, //FIXME + &dummyData, 1 + ); +} + void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { class QUADSPI::CCR ccr(0); ccr.setFMODE(functionalMode); @@ -155,6 +169,11 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR } ccr.setIMODE(operatingMode); ccr.setINSTRUCTION(static_cast(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(FlashAddressSpaceSize)) { QUADSPI.AR()->set(reinterpret_cast(address)); @@ -246,6 +265,7 @@ int SectorAtAddress(uint32_t address) { } void MassErase() { + unset_memory_mapped_mode(); send_command(Command::WriteEnable); wait(); send_command(Command::ChipErase); @@ -255,6 +275,7 @@ void MassErase() { void EraseSector(int i) { assert(i >= 0 && i < NumberOfSectors); + unset_memory_mapped_mode(); send_command(Command::WriteEnable); wait(); send_write_command(Command::Erase64KbyteBlock, reinterpret_cast(i << NumberOfAddressBitsIn64KbyteBlock), nullptr, 0); @@ -263,6 +284,7 @@ void EraseSector(int i) { } void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { + 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. diff --git a/ion/src/device/external_flash.h b/ion/src/device/external_flash.h index f05bcf476..bde2678f8 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/external_flash.h @@ -53,6 +53,7 @@ enum class Command : uint8_t { WriteEnable = 0x06, ReadData = 0x03, FastRead = 0x0B, + FastReadQuadIO = 0xEB, // Program previously erased memory areas as being "0" PageProgram = 0x02, QuadPageProgram = 0x33, From 6fa9e66549ad066ba763b49037df9a64f810fb40 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Wed, 9 Jan 2019 14:58:22 +0100 Subject: [PATCH 0111/1750] [ion/f730] Port external flash --- ion/src/f730/Makefile | 1 + ion/src/f730/device.cpp | 2 + ion/src/f730/external_flash.cpp | 314 +++++++++++++++++++++++++++++ ion/src/f730/external_flash.h | 81 ++++++++ ion/src/f730/regs/gpio.h | 14 ++ ion/src/f730/regs/quadspi.h | 126 ++++++++++++ ion/src/f730/regs/regs.h | 1 + ion/src/f730/usb/Makefile | 1 + ion/src/f730/usb/calculator.h | 2 +- ion/src/f730/usb/dfu_interface.cpp | 20 +- ion/src/f730/usb/dfu_interface.h | 2 + 11 files changed, 557 insertions(+), 7 deletions(-) create mode 100644 ion/src/f730/external_flash.cpp create mode 100644 ion/src/f730/external_flash.h create mode 100644 ion/src/f730/regs/quadspi.h diff --git a/ion/src/f730/Makefile b/ion/src/f730/Makefile index 56524771e..a94500d69 100644 --- a/ion/src/f730/Makefile +++ b/ion/src/f730/Makefile @@ -22,6 +22,7 @@ objs += $(addprefix ion/src/f730/, \ device.o\ display.o\ events.o\ + external_flash.o\ flash.o\ keyboard.o\ led.o\ diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index c406ba2af..54216c1cb 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -16,6 +16,7 @@ extern "C" { #include "bench/bench.h" #include "base64.h" #include "cache.h" +#include "external_flash.h" #define USE_SD_CARD 0 @@ -217,6 +218,7 @@ void initPeripherals() { Console::Device::init(); SWD::Device::init(); initSysTick(); + ExternalFlash::Device::init(); } void shutdownPeripherals(bool keepLEDAwake) { diff --git a/ion/src/f730/external_flash.cpp b/ion/src/f730/external_flash.cpp new file mode 100644 index 000000000..5de68b64c --- /dev/null +++ b/ion/src/f730/external_flash.cpp @@ -0,0 +1,314 @@ +#include "external_flash.h" + +namespace Ion { +namespace ExternalFlash { +namespace Device { + +/* 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 --> | regsiter --> 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. */ + +class ExternalFlashStatusRegister { +public: + class StatusRegister1 : Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_R(BUSY, 0); + }; + class StatusRegister2 : Register8 { + public: + using Register8::Register8; + REGS_BOOL_FIELD_W(QE, 1); + }; +}; + +static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; +static constexpr int ClockFrequencyDivisor = 2; +static constexpr int ChipSelectHighTime = (ClockFrequencyDivisor == 1) ? 3 : (ClockFrequencyDivisor == 2) ? 2 : 1; + +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, 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, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + operatingMode, + c, + reinterpret_cast(FlashAddressSpaceSize), + 0, 0, + 0, + nullptr, 0 + ); +} + +static inline void send_write_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectWrite, + operatingMode, + c, + address, + 0, 0, + 0, + data, dataLength + ); +} + +static inline void send_read_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { + send_command_full( + QUADSPI::CCR::FunctionalMode::IndirectRead, + operatingMode, + c, + address, + 0, 0, + 0, + data, dataLength + ); +} + +static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { + ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); + do { + send_read_command(Command::ReadStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); + } 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.) */ + constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : (ClockFrequencyDivisor > 1) ? 4 : 6; + send_command_full( + QUADSPI::CCR::FunctionalMode::MemoryMapped, + DefaultOperatingMode, + Command::FastReadQuadIO, + reinterpret_cast(FlashAddressSpaceSize), + 0xA0, 1, + 2, //FIXME + 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, + DefaultOperatingMode, + Command::FastReadQuadIO, + 0, + ~(0xA0), 1, + 2, //FIXME + &dummyData, 1 + ); +} + +void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { + class QUADSPI::CCR ccr(0); + ccr.setFMODE(functionalMode); + if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setDMODE(operatingMode); + } + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); + } + ccr.setDCYC(dummyCycles); + if (numberOfAltBytes > 0) { + ccr.setABMODE(operatingMode); + ccr.setABSIZE(static_cast(numberOfAltBytes - 1)); + QUADSPI.ABR()->set(altBytes); + } + if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { + ccr.setADMODE(operatingMode); + ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); + } + ccr.setIMODE(operatingMode); + ccr.setINSTRUCTION(static_cast(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(FlashAddressSpaceSize)) { + QUADSPI.AR()->set(reinterpret_cast(address)); + } + + if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { + for (size_t i=0; iset(data[i]); + } + } else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { + for (size_t i=0; iget(); + } + } + + /* 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." */ + if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { + while (QUADSPI.SR()->getBUSY()) { + } + } +} + +void init() { + initGPIO(); + initQSPI(); + initChip(); +} + +void initGPIO() { + for(const GPIOPin & g : QSPIPins) { + g.group().OSPEEDR()->setOutputSpeed(g.pin(), GPIO::OSPEEDR::OutputSpeed::High); + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); + g.group().AFR()->setAlternateFunction(g.pin(), (g.pin() == 6 ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9)); + } +} + +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); + dcr.setCSHT(ChipSelectHighTime - 1); + dcr.setCKMODE(true); + QUADSPI.DCR()->set(dcr); + class QUADSPI::CR cr(0); + cr.setPRESCALER(ClockFrequencyDivisor - 1); + cr.setEN(true); + QUADSPI.CR()->set(cr); +} + +void initChip() { + /* The chip initially expects commands in SPI mode. We need to use SPI to tell + * it to switch to QPI. */ + if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { + send_command(Command::WriteEnable, QUADSPI::CCR::OperatingMode::Single); + ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); + statusRegister2.setQE(true); + wait(QUADSPI::CCR::OperatingMode::Single); + send_write_command(Command::WriteStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); + wait(QUADSPI::CCR::OperatingMode::Single); + send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); + wait(); + if (ClockFrequencyDivisor == 1) { + class ReadParameters : Register8 { + public: + /* Parameters sent along with SetReadParameters instruction in order + * to configure the number of dummy cycles for the QPI Read instructions. */ + using Register8::Register8; + REGS_BOOL_FIELD_W(P5, 1); + }; + ReadParameters readParameters(0); + readParameters.setP5(true); + send_write_command(Command::SetReadParameters, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&readParameters), sizeof(readParameters)); + } + } + set_as_memory_mapped(); +} + +int SectorAtAddress(uint32_t address) { + int i = address >> NumberOfAddressBitsIn64KbyteBlock; + if (i >= NumberOfSectors) { + return -1; + } + return i; +} + +void MassErase() { + unset_memory_mapped_mode(); + send_command(Command::WriteEnable); + wait(); + send_command(Command::ChipErase); + wait(); + set_as_memory_mapped(); +} + +void EraseSector(int i) { + assert(i >= 0 && i < NumberOfSectors); + unset_memory_mapped_mode(); + send_command(Command::WriteEnable); + wait(); + send_write_command(Command::Erase64KbyteBlock, reinterpret_cast(i << NumberOfAddressBitsIn64KbyteBlock), nullptr, 0); + wait(); + set_as_memory_mapped(); +} + +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { + 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; + constexpr Command pageProgram = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? Command::PageProgram : Command::QuadPageProgram; + uint8_t offset = reinterpret_cast(destination) & (PageSize - 1); + size_t lengthThatFitsInPage = PageSize - offset; + while (length > 0) { + if (lengthThatFitsInPage > length) { + lengthThatFitsInPage = length; + } + send_command(Command::WriteEnable); + wait(); + send_write_command(pageProgram, destination, source, lengthThatFitsInPage); + length -= lengthThatFitsInPage; + destination += lengthThatFitsInPage; + source += lengthThatFitsInPage; + lengthThatFitsInPage = PageSize; + wait(); + } + set_as_memory_mapped(); +} + +} +} +} diff --git a/ion/src/f730/external_flash.h b/ion/src/f730/external_flash.h new file mode 100644 index 000000000..bde2678f8 --- /dev/null +++ b/ion/src/f730/external_flash.h @@ -0,0 +1,81 @@ +#ifndef ION_DEVICE_EXTERNAL_FLASH_H +#define ION_DEVICE_EXTERNAL_FLASH_H + +#include +#include +#include "regs/regs.h" + +// Quad-SPI on STM32 microcontroller +// https://www.st.com/resource/en/application_note/dm00227538.pdf + +// Adesto Technologies AT25SF641 +// https://www.adestotech.com/wp-content/uploads/AT25SF641_111.pdf + +/* External Flash Memory map Address space + * 8MiB chip 0x000000 - 0x7FFFFF + * 2^7 64KiB blocks 0x..0000 - 0x..FFFF + * 2^7 * 2 32KiB blocks 0x..0000 - 0x..7FFF or 0x..8000 - 0x..FFFF + * 2^7 * 2 * 2^3 4KiB blocks 0x...000 - 0x...FFF + * 2^7 * 2 * 2^3 * 2^4 256B pages 0x....00 - 0x....FF */ + +namespace Ion { +namespace ExternalFlash { +namespace Device { + +/* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS + * PC8 | 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 + */ + +void init(); +void shutdown(); + +void initGPIO(); +void initQSPI(); +void initChip(); + +void MassErase(); + +constexpr int NumberOfSectors = 128; +int SectorAtAddress(uint32_t address); +void EraseSector(int i); + +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); + +enum class Command : uint8_t { + ReadStatusRegister = 0x05, + WriteStatusRegister2 = 0x31, + WriteEnable = 0x06, + ReadData = 0x03, + FastRead = 0x0B, + FastReadQuadIO = 0xEB, + // Program previously erased memory areas as being "0" + PageProgram = 0x02, + QuadPageProgram = 0x33, + EnableQPI = 0x38, + // Erase the whole chip or a 64-Kbyte block as being "1" + ChipErase = 0xC7, + Erase64KbyteBlock = 0xD8, + SetReadParameters = 0xC0 +}; + +constexpr static uint32_t QSPIBaseAddress = 0x90000000; +constexpr static uint8_t NumberOfAddressBitsInChip = 23; +constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; + +constexpr static GPIOPin QSPIPins[] = { + GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), + GPIOPin(GPIOC, 8), GPIOPin(GPIOD,13) +}; + +} +} +} + +#endif diff --git a/ion/src/f730/regs/gpio.h b/ion/src/f730/regs/gpio.h index 2371ac0eb..bf7300295 100644 --- a/ion/src/f730/regs/gpio.h +++ b/ion/src/f730/regs/gpio.h @@ -27,6 +27,19 @@ public: void setType(int index, Type type) volatile { setBitRange(index, index, (uint32_t)type); } }; + class OSPEEDR : Register32 { + // Datasheet, page 120, table 58 + public: + enum class OutputSpeed { + Low = 0, + Medium = 1, + Fast = 2, + High = 3 + }; + OutputSpeed getOutputSpeed(int index) { return (OutputSpeed)getBitRange(2*index+1, 2*index); } + void setOutputSpeed(int index, OutputSpeed speed) volatile { setBitRange(2*index+1, 2*index, (uint32_t)speed); } + }; + class PUPDR : public Register32 { public: enum class Pull { @@ -72,6 +85,7 @@ public: constexpr operator int() const { return m_index; } REGS_REGISTER_AT(MODER, 0x00); REGS_REGISTER_AT(OTYPER, 0x04); + REGS_REGISTER_AT(OSPEEDR, 0x08); REGS_REGISTER_AT(PUPDR, 0x0C); REGS_REGISTER_AT(IDR, 0x10); REGS_REGISTER_AT(ODR, 0x14); diff --git a/ion/src/f730/regs/quadspi.h b/ion/src/f730/regs/quadspi.h new file mode 100644 index 000000000..18d6c81f0 --- /dev/null +++ b/ion/src/f730/regs/quadspi.h @@ -0,0 +1,126 @@ +#ifndef REGS_QUADSPI_H +#define REGS_QUADSPI_H + +#include "register.h" + +// Quad-SPI register map on STM32 microcontroller +// See section 12 of the STM32F412 reference manual + +class QUADSPI { +public: + class CR : public Register32 { // Control register + public: + using Register32::Register32; + REGS_BOOL_FIELD(EN, 0); + REGS_BOOL_FIELD(TCEN, 3); // Lower-power timeout counter enable in memory-mapped mode + REGS_BOOL_FIELD(SSHIFT, 4); + REGS_FIELD(PRESCALER, uint8_t, 31, 24); + }; + + class DCR : public Register32 { // Device configuration register + public: + using Register32::Register32; + REGS_BOOL_FIELD(CKMODE, 0); // MODE 0 or 3 + REGS_FIELD(CSHT, uint8_t, 10, 8); // Chip select minimum high time + REGS_FIELD(FSIZE, uint8_t, 20, 16); + }; + + class SR : Register32 { // Status register + public: + REGS_BOOL_FIELD(TEF, 0); // Transfer error flag + REGS_BOOL_FIELD(TCF, 1); // Transfer complete flag + REGS_BOOL_FIELD(BUSY, 5); + }; + + class FCR : Register32 { // Flag clear register + }; + + class DLR : public Register32 { // Data length register + // In indirect and status-polling modes + // Number of bytes to be transferred = DLR + 1 + }; + + class CCR : public Register32 { // Communication configuration register + public: + using Register32::Register32; + enum class Size : uint8_t { + OneByte = 0, + TwoBytes = 1, + ThreeBytes = 2, + FourBytes = 3 + }; + enum class OperatingMode : uint8_t { + NoData = 0, + Single = 1, + Dual = 2, + Quad = 3 + }; + enum class FunctionalMode : uint8_t { + IndirectWrite = 0, + IndirectRead = 1, + StatusPolling = 2, + MemoryMapped = 3 + }; + REGS_FIELD(INSTRUCTION, uint8_t, 7, 0); + REGS_FIELD(IMODE, OperatingMode, 9, 8); + REGS_FIELD(ADMODE, OperatingMode, 11, 10); + REGS_FIELD(ADSIZE, Size, 13, 12); + REGS_FIELD(ABMODE, OperatingMode, 15, 14); + REGS_FIELD(ABSIZE, Size, 17, 16); + REGS_FIELD(DCYC, uint8_t, 22, 18); + REGS_FIELD(DMODE, OperatingMode, 25, 24); + REGS_FIELD(FMODE, FunctionalMode, 27, 26); + REGS_BOOL_FIELD(SIOO, 28); + }; + + class AR : public Register32 { // Address register + }; + + class ABR : public Register32 { // Alternate bytes register + }; + + class DR : public Register8 { // Data register + /* In indirect (read or write) mode, any data must be written to or read from + * the data register, which supports byte, halfword and word access + * (aligned to the bottom of the register). + * N bytes read/written remove/add N bytes from/to the FIFO. + */ + }; + + class PSMKR : Register32 { // Polling status mask register + }; + + class PSMAR : Register32 { // Polling status match register + }; + + class PIR : Register32 { // Polling interval register + }; + + class LPTR : public Register16 { // Low-power timeout register + // Period before putting the Flash memory in lower-power state (in memory-mapped mode) + }; + + constexpr QUADSPI() {}; + + REGS_REGISTER_AT(CR, 0x00); + REGS_REGISTER_AT(DCR, 0x04); + REGS_REGISTER_AT(SR, 0x08); + REGS_REGISTER_AT(FCR, 0x0C); + REGS_REGISTER_AT(DLR, 0x10); + REGS_REGISTER_AT(CCR, 0x14); + REGS_REGISTER_AT(AR, 0x18); + REGS_REGISTER_AT(ABR, 0x1C); + REGS_REGISTER_AT(DR, 0x20); + REGS_REGISTER_AT(PSMKR, 0x24); + REGS_REGISTER_AT(PSMAR, 0x28); + REGS_REGISTER_AT(PIR, 0x2C); + REGS_REGISTER_AT(LPTR, 0x30); +private: + constexpr uint32_t Base() const { + return 0xA0001000; + } +}; + +constexpr QUADSPI QUADSPI; + +#endif diff --git a/ion/src/f730/regs/regs.h b/ion/src/f730/regs/regs.h index ed5b48bdb..cfa644dd7 100644 --- a/ion/src/f730/regs/regs.h +++ b/ion/src/f730/regs/regs.h @@ -16,6 +16,7 @@ #include "rcc.h" #include "rng.h" #include "otg.h" +#include "quadspi.h" #include "sdio.h" #include "spi.h" #include "syscfg.h" diff --git a/ion/src/f730/usb/Makefile b/ion/src/f730/usb/Makefile index 7d38b05d6..d32478413 100644 --- a/ion/src/f730/usb/Makefile +++ b/ion/src/f730/usb/Makefile @@ -50,6 +50,7 @@ dfu_objs += ion/src/f730/device.o dfu_objs += ion/src/f730/usb.o dfu_objs += ion/src/f730/base64.o dfu_objs += ion/src/f730/flash.o +dfu_objs += ion/src/device/external_flash.o dfu_objs += ion/src/f730/timing.o ion/src/f730/usb/dfu.elf: LDSCRIPT = ion/src/f730/usb/dfu.ld diff --git a/ion/src/f730/usb/calculator.h b/ion/src/f730/usb/calculator.h index d3383e419..81b8c9b2a 100644 --- a/ion/src/f730/usb/calculator.h +++ b/ion/src/f730/usb/calculator.h @@ -93,7 +93,7 @@ public: m_manufacturerStringDescriptor("NumWorks"), m_productStringDescriptor("NumWorks Calculator"), m_serialNumberStringDescriptor(serialNumber), - m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg"), + m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg/0x90000000/64*064Kg,64*064Kg"), //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), /* Switch to this descriptor to use dfu-util to write in the SRAM. * FIXME Should be an alternate Interface. */ diff --git a/ion/src/f730/usb/dfu_interface.cpp b/ion/src/f730/usb/dfu_interface.cpp index 395576cc6..b6e6fde24 100644 --- a/ion/src/f730/usb/dfu_interface.cpp +++ b/ion/src/f730/usb/dfu_interface.cpp @@ -1,6 +1,7 @@ #include "dfu_interface.h" #include #include +#include namespace Ion { namespace USB { @@ -173,7 +174,7 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe if (transferBufferLength == 1) { // Mass erase - m_erasePage = Flash::Device::NumberOfSectors; + m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors; return; } @@ -185,9 +186,11 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe + (transferBuffer[3] << 16) + (transferBuffer[4] << 24); - m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); - - if (m_erasePage < 0) { + if (eraseAddress >= k_flashStartAddress && eraseAddress <= k_flashEndAddress) { + m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); + } else if (eraseAddress >= k_externalFlashStartAddress && eraseAddress <= k_externalFlashEndAddress) { + m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::SectorAtAddress(eraseAddress - k_externalFlashStartAddress); + } else { // Unrecognized sector m_state = State::dfuERROR; m_status = Status::errTARGET; @@ -201,10 +204,13 @@ void DFUInterface::eraseMemoryIfNeeded() { return; } - if (m_erasePage == Ion::Flash::Device::NumberOfSectors) { + if (m_erasePage == Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors) { Flash::Device::MassErase(); - } else { + ExternalFlash::Device::MassErase(); + } else if (m_erasePage < Flash::Device::NumberOfSectors) { Flash::Device::EraseSector(m_erasePage); + } else { + ExternalFlash::Device::EraseSector(m_erasePage - Flash::Device::NumberOfSectors); } /* Put an out of range value in m_erasePage to indicate that no erase is @@ -222,6 +228,8 @@ void DFUInterface::writeOnMemory() { // Write on SRAM // FIXME We should check that we are not overriding the current instructions. memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); + } else if (m_writeAddress >= k_externalFlashStartAddress && m_writeAddress <= k_externalFlashEndAddress) { + ExternalFlash::Device::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress) - k_externalFlashStartAddress, m_largeBufferLength); } else { // Invalid write address m_largeBufferLength = 0; diff --git a/ion/src/f730/usb/dfu_interface.h b/ion/src/f730/usb/dfu_interface.h index 6284ce951..7fce4bf40 100644 --- a/ion/src/f730/usb/dfu_interface.h +++ b/ion/src/f730/usb/dfu_interface.h @@ -130,6 +130,8 @@ private: constexpr static uint32_t k_flashEndAddress = 0x08100000; constexpr static uint32_t k_sramStartAddress = 0x20000000; constexpr static uint32_t k_sramEndAddress = 0x20040000; + constexpr static uint32_t k_externalFlashStartAddress = 0x90000000; + constexpr static uint32_t k_externalFlashEndAddress = 0x90800000; // Download and upload bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); From 560974abc5393bb9bb5cd38d3568dea77ebc415c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 11 Jan 2019 10:32:50 +0100 Subject: [PATCH 0112/1750] [ion/f730] ExternalFlash: fix QSPI GPIOs --- ion/src/f730/external_flash.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/f730/external_flash.h b/ion/src/f730/external_flash.h index bde2678f8..1b102027d 100644 --- a/ion/src/f730/external_flash.h +++ b/ion/src/f730/external_flash.h @@ -26,7 +26,7 @@ namespace Device { * -----+----------------------+-----------------------+----------------- * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS - * PC8 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI_BK1_IO2 + * 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 @@ -71,7 +71,7 @@ constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip constexpr static GPIOPin QSPIPins[] = { GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), - GPIOPin(GPIOC, 8), GPIOPin(GPIOD,13) + GPIOPin(GPIOE, 2), GPIOPin(GPIOD,13) }; } From 546bddfcf40669866f76ef3130c81eda79d43fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 11 Jan 2019 10:44:22 +0100 Subject: [PATCH 0113/1750] [ion/f730] ExternalFlash: slow down QUADSPI clock to minimum to ensure its working --- ion/src/f730/external_flash.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/external_flash.cpp b/ion/src/f730/external_flash.cpp index 5de68b64c..0ebe55c17 100644 --- a/ion/src/f730/external_flash.cpp +++ b/ion/src/f730/external_flash.cpp @@ -64,7 +64,7 @@ public: }; static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; -static constexpr int ClockFrequencyDivisor = 2; +static constexpr int ClockFrequencyDivisor = 256;// TODO: optimize me (value forSTM32F412: 2); static constexpr int ChipSelectHighTime = (ClockFrequencyDivisor == 1) ? 3 : (ClockFrequencyDivisor == 2) ? 2 : 1; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength); From d3adefc243a22f33bfa3e462240747e7a3e22fc7 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Fri, 11 Jan 2019 12:24:18 +0100 Subject: [PATCH 0114/1750] [ion/f730] WIP: init ExternalFlash in initPeripherals() --- ion/src/f730/device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 54216c1cb..e90256a76 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -211,6 +211,7 @@ void initPeripherals() { LED::Device::init(); Battery::Device::init(); USB::Device::init(); + ExternalFlash::Device::init(); return; // FIXME, obviously! #if USE_SD_CARD SDCard::Device::init(); @@ -218,7 +219,6 @@ void initPeripherals() { Console::Device::init(); SWD::Device::init(); initSysTick(); - ExternalFlash::Device::init(); } void shutdownPeripherals(bool keepLEDAwake) { From ad9d89a7563826a493bfec4e2164f9df0e8e997b Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 14 Jan 2019 16:43:49 +0100 Subject: [PATCH 0115/1750] [ion/f730] Shutdown external flash --- ion/src/f730/device.cpp | 1 + ion/src/f730/external_flash.cpp | 33 +++++++++++++++++++++++++++++++-- ion/src/f730/external_flash.h | 7 ++++++- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 1958d8e4c..06956b4ed 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -228,6 +228,7 @@ void shutdownPeripherals(bool keepLEDAwake) { #if USE_SD_CARD SDCard::Device::shutdown(); #endif + ExternalFlash::Device::shutdown(); USB::Device::shutdown(); Battery::Device::shutdown(); if (!keepLEDAwake) { diff --git a/ion/src/f730/external_flash.cpp b/ion/src/f730/external_flash.cpp index 0ebe55c17..46a3c99b1 100644 --- a/ion/src/f730/external_flash.cpp +++ b/ion/src/f730/external_flash.cpp @@ -1,4 +1,5 @@ #include "external_flash.h" +#include "timing.h" namespace Ion { namespace ExternalFlash { @@ -205,6 +206,12 @@ void init() { initChip(); } +void shutdown() { + shutdownChip(); + shutdownQSPI(); + shutdownGPIO(); +} + void initGPIO() { for(const GPIOPin & g : QSPIPins) { g.group().OSPEEDR()->setOutputSpeed(g.pin(), GPIO::OSPEEDR::OutputSpeed::High); @@ -213,9 +220,17 @@ void initGPIO() { } } +void shutdownGPIO() { + for(const GPIOPin & g : QSPIPins) { + g.group().OSPEEDR()->setOutputSpeed(g.pin(), GPIO::OSPEEDR::OutputSpeed::Low); + g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); + g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + } +} + void initQSPI() { // Enable QUADSPI AHB3 peripheral clock - RCC.AHB3ENR()->setQSPIEN(true); + RCC.AHB3ENR()->setQSPIEN(true); // TODO: move in Device::initClocks // Configure controller for target device class QUADSPI::DCR dcr(0); dcr.setFSIZE(NumberOfAddressBitsInChip - 1); @@ -228,10 +243,15 @@ void initQSPI() { QUADSPI.CR()->set(cr); } +void shutdownQSPI() { + RCC.AHB3ENR()->setQSPIEN(false); // TODO: move in Device::shutdownClocks +} + void initChip() { + static bool firstPass = true; /* The chip initially expects commands in SPI mode. We need to use SPI to tell * it to switch to QPI. */ - if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { + if (firstPass && DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { send_command(Command::WriteEnable, QUADSPI::CCR::OperatingMode::Single); ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); statusRegister2.setQE(true); @@ -252,10 +272,19 @@ void initChip() { readParameters.setP5(true); send_write_command(Command::SetReadParameters, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&readParameters), sizeof(readParameters)); } + firstPass = false; + } else { + // TODO } set_as_memory_mapped(); } +void shutdownChip() { + unset_memory_mapped_mode(); + send_command(Command::DeepPowerDown); + Timing::usleep(100); // TODO should be 3us when usleep adjusted +} + int SectorAtAddress(uint32_t address) { int i = address >> NumberOfAddressBitsIn64KbyteBlock; if (i >= NumberOfSectors) { diff --git a/ion/src/f730/external_flash.h b/ion/src/f730/external_flash.h index 1b102027d..2f4af8b01 100644 --- a/ion/src/f730/external_flash.h +++ b/ion/src/f730/external_flash.h @@ -36,8 +36,11 @@ void init(); void shutdown(); void initGPIO(); +void shutdownGPIO(); void initQSPI(); +void shutdownQSPI(); void initChip(); +void shutdownChip(); void MassErase(); @@ -61,7 +64,9 @@ enum class Command : uint8_t { // Erase the whole chip or a 64-Kbyte block as being "1" ChipErase = 0xC7, Erase64KbyteBlock = 0xD8, - SetReadParameters = 0xC0 + SetReadParameters = 0xC0, + DeepPowerDown = 0xB9, + ReleaseDeepPowerDown = 0xAB }; constexpr static uint32_t QSPIBaseAddress = 0x90000000; From 6ef35fc634f577682da5157fea4653a4c7d65de6 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Mon, 14 Jan 2019 16:45:40 +0100 Subject: [PATCH 0116/1750] [ion/f730] Under-drive stop mode --- ion/src/f730/power.cpp | 6 ++++-- ion/src/f730/regs/pwr.h | 11 +++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ion/src/f730/power.cpp b/ion/src/f730/power.cpp index 021d75cc6..d18854bf5 100644 --- a/ion/src/f730/power.cpp +++ b/ion/src/f730/power.cpp @@ -20,8 +20,10 @@ void Ion::Power::suspend(bool checkIfPowerKeyReleased) { } Device::shutdownPeripherals(isLEDActive); - PWR.CR()->setLPDS(true); // Turn the regulator off. Takes longer to wake up. - PWR.CR()->setFPDS(true); // Put the flash to sleep. Takes longer to wake up. + PWR.CR1()->setLPDS(true); // Turn the regulator off. Takes longer to wake up. + PWR.CR1()->setFPDS(true); // Put the flash to sleep. Takes longer to wake up. + PWR.CR1()->setLPUDS(true); + PWR.CR1()->setUDEN(PWR::CR1::UnderDrive::Enable); CM4.SCR()->setSLEEPDEEP(!isLEDActive); while (1) { diff --git a/ion/src/f730/regs/pwr.h b/ion/src/f730/regs/pwr.h index 089d72e18..f3cef32d7 100644 --- a/ion/src/f730/regs/pwr.h +++ b/ion/src/f730/regs/pwr.h @@ -5,15 +5,22 @@ class PWR { public: - class CR : Register32 { + class CR1 : Register32 { public: + enum class UnderDrive { + Disable = 0, + Enable = 3 + }; REGS_BOOL_FIELD(LPDS, 0); REGS_BOOL_FIELD(PPDS, 1); REGS_BOOL_FIELD(FPDS, 9); + REGS_BOOL_FIELD(LPUDS, 10); + REGS_BOOL_FIELD(MRUDS, 11); + REGS_FIELD_W(UDEN, UnderDrive, 19, 18); }; constexpr PWR() {}; - REGS_REGISTER_AT(CR, 0x00); + REGS_REGISTER_AT(CR1, 0x00); private: constexpr uint32_t Base() const { return 0x40007000; From 5d2b16c08a0431c8ba9093577eb448a186c2853b Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Tue, 15 Jan 2019 11:44:40 +0100 Subject: [PATCH 0117/1750] [ion/f730] Underclock AHB in getEvent --- ion/src/f730/events.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ion/src/f730/events.cpp b/ion/src/f730/events.cpp index 03de2de53..fdc9c8ff8 100644 --- a/ion/src/f730/events.cpp +++ b/ion/src/f730/events.cpp @@ -1,4 +1,5 @@ #include +#include "regs/regs.h" #include namespace Ion { @@ -6,11 +7,13 @@ namespace Events { static bool sleepWithTimeout(int duration, int * timeout) { if (*timeout >= duration) { - Timing::msleep(duration); + Timing::usleep(duration*125); + //Timing::msleep(duration/8); *timeout -= duration; return false; } else { - Timing::msleep(*timeout); + //Timing::msleep(*timeout/8); + Timing::usleep(*timeout*125); *timeout = 0; return true; } @@ -70,10 +73,12 @@ Event getEvent(int * timeout) { return event; } + RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy8); if (sleepWithTimeout(10, timeout)) { // Timeout occured return Events::None; } + RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClk); time += 10; // At this point, we know that keysSeenTransitionningFromUpToDown has *always* been zero From 8c4ef3233a846c0a5a81b3a041d9ddbf6191dda8 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 15 Jan 2019 13:31:34 +0100 Subject: [PATCH 0118/1750] [ion/f730] Enable L1 cache --- ion/src/f730/Makefile | 1 - ion/src/f730/cache.cpp | 21 --------------------- ion/src/f730/cache.h | 24 ++++++++++++++++++++---- ion/src/f730/regs/cm4.h | 23 ++++++++++++++++++++++- 4 files changed, 42 insertions(+), 27 deletions(-) delete mode 100644 ion/src/f730/cache.cpp diff --git a/ion/src/f730/Makefile b/ion/src/f730/Makefile index a94500d69..19c5e33da 100644 --- a/ion/src/f730/Makefile +++ b/ion/src/f730/Makefile @@ -17,7 +17,6 @@ objs += $(addprefix ion/src/f730/, \ backlight.o \ battery.o\ base64.o\ - cache.o \ console.o \ device.o\ display.o\ diff --git a/ion/src/f730/cache.cpp b/ion/src/f730/cache.cpp deleted file mode 100644 index 30979e114..000000000 --- a/ion/src/f730/cache.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef ION_DEVICE_CACHE_H -#define ION_DEVICE_CACHE_H - -namespace Ion { -namespace Device { -namespace Cache { - -void dsb(); -void isb(); - -void enableDCache(); -void enableICache(); - -void invalidateDCache(); -void invalidateICache(); - -} -} -} - -#endif diff --git a/ion/src/f730/cache.h b/ion/src/f730/cache.h index aca5a05c6..5dfd4f587 100644 --- a/ion/src/f730/cache.h +++ b/ion/src/f730/cache.h @@ -5,18 +5,32 @@ namespace Ion { namespace Device { namespace Cache { -void dsb() { +inline void dsb() { asm volatile("dsb 0xF":::"memory"); } -void isb() { +inline void isb() { asm volatile("isb 0xF":::"memory"); } void enableDCache() { + CM4.CSSELR()->set(0); + dsb(); + + //Associativity = 6 + + uint32_t sets = CM4.CCSIDR()->getNUMSETS(); + do { + uint32_t ways = CM4.CCSIDR()->getASSOCIATIVITY(); + do { + class CM4::DCISW dcisw; + dcisw.setSET(sets); + dcisw.setWAY(ways); + CM4.DCISW()->set(dcisw); + } while (ways-- != 0); + } while (sets-- != 0); + dsb(); - isb(); - // FIXME: Invalidate D-Cache CM4.CCR()->setDC(true); dsb(); isb(); @@ -26,6 +40,8 @@ void enableICache() { dsb(); isb(); CM4.ICIALLU()->set(0); // Invalidate I-Cache + dsb(); + isb(); CM4.CCR()->setIC(true); dsb(); isb(); diff --git a/ion/src/f730/regs/cm4.h b/ion/src/f730/regs/cm4.h index 0fa181de9..d3b43d502 100644 --- a/ion/src/f730/regs/cm4.h +++ b/ion/src/f730/regs/cm4.h @@ -73,6 +73,24 @@ public: using Register32::Register32; }; + class CSSELR : public Register32 { + public: + REGS_BOOL_FIELD(IND, 0); + }; + + class CCSIDR : public Register32 { + public: + REGS_FIELD(ASSOCIATIVITY, uint16_t, 12, 3); + REGS_FIELD(NUMSETS, uint16_t, 27, 13); + }; + + class DCISW : public Register32 { + public: + DCISW() : Register32(0) {} + REGS_FIELD(SET, uint16_t, 13, 5); + REGS_FIELD(WAY, uint8_t, 31, 30); + }; + constexpr CM4() {}; REGS_REGISTER_AT(SYST_CSR, 0x10); REGS_REGISTER_AT(SYST_RVR, 0x14); @@ -80,9 +98,12 @@ public: REGS_REGISTER_AT(VTOR, 0xD08); REGS_REGISTER_AT(AIRCR, 0xD0C); REGS_REGISTER_AT(SCR, 0xD10); - REGS_REGISTER_AT(CCR, 0xD10); + REGS_REGISTER_AT(CCR, 0xD14); + REGS_REGISTER_AT(CCSIDR, 0xD80); + REGS_REGISTER_AT(CSSELR, 0xD84); REGS_REGISTER_AT(CPACR, 0xD88); REGS_REGISTER_AT(ICIALLU, 0xF50); + REGS_REGISTER_AT(DCISW, 0xF60); private: constexpr uint32_t Base() const { return 0xE000E000; From 4e54c5d579e0a7152e047195d26ad444251df3c7 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 15 Jan 2019 13:32:54 +0100 Subject: [PATCH 0119/1750] [ion/f730] Configure MPU for proper memory access on the FMC --- ion/src/f730/device.cpp | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 1958d8e4c..7552bc7a7 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -88,22 +88,9 @@ void initFPU() { // FIXME: The pipeline should be flushed at this point } -#if 1 void initMPU() { -# if 0 - // This prevent overflowing the stack - /* Region 0 reprensents the last 128 bytes of the stack: accessing this - * memory means we are really likely to overflow the stack very soon. */ - MPU.RNR()->setREGION(0x00); - MPU.RBAR()->setADDR(&_stack_end); - MPU.RASR()->setSIZE(MPU::RASR::RegionSize::Bytes128); - MPU.RASR()->setENABLE(true); - MPU.RASR()->setAP(0x000); // Forbid access - MPU.CTRL()->setPRIVDEFENA(true); - MPU.CTRL()->setENABLE(true); -#endif - +#if 1 // Configure MPU settings for the FMC memory area // This is needed for interfacing with the LCD MPU.RNR()->setREGION(0x00); @@ -111,17 +98,18 @@ void initMPU() { MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32MB); MPU.RASR()->setXN(true); MPU.RASR()->setAP(MPU::RASR::AccessPermission::RW); - MPU.RASR()->setTEX(0); + MPU.RASR()->setTEX(2); + MPU.RASR()->setS(0); MPU.RASR()->setC(0); - MPU.RASR()->setB(1); + MPU.RASR()->setB(0); MPU.RASR()->setENABLE(true); MPU.CTRL()->setPRIVDEFENA(true); MPU.CTRL()->setENABLE(true); +#endif } -#endif void initSysTick() { // CPU clock is 96 MHz, and systick clock source is divided by 8 @@ -167,8 +155,9 @@ void initL1Cache() { } void init() { - initClocks(); initL1Cache(); + initMPU(); + initClocks(); // Ensure right location of interrupt vectors // The bootloader leaves its own after flashing From a472b10b6843db8a21e9f4a26fda66355b9befd3 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 15 Jan 2019 13:35:07 +0100 Subject: [PATCH 0120/1750] [ion/f730] Add JUMP_TO_EXTERNAL_FLASH --- ion/src/f730/boot/rt0.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ion/src/f730/boot/rt0.cpp b/ion/src/f730/boot/rt0.cpp index 852cd7edb..6e10b70ac 100644 --- a/ion/src/f730/boot/rt0.cpp +++ b/ion/src/f730/boot/rt0.cpp @@ -57,12 +57,11 @@ void start() { size_t bssSectionLength = (&_bss_section_end_ram - &_bss_section_start_ram); memset(&_bss_section_start_ram, 0, bssSectionLength); +#define JUMP_TO_EXTERNAL_FLASH 1 +#if JUMP_TO_EXTERNAL_FLASH /* Initialize the FPU as early as possible. * For example, static C++ objects are very likely to manipulate float values */ Ion::Device::initFPU(); - -#if 1 - Ion::Device::initMPU(); #endif /* Call static C++ object constructors @@ -85,9 +84,18 @@ void start() { } #endif +#if JUMP_TO_EXTERNAL_FLASH Ion::Device::init(); + typedef void(*ISR)(void); + +// non_inlined_ion_main(); + + ISR externalFlashReset = *(ISR *)(0x90000004); + externalFlashReset(); +#else non_inlined_ion_main(); +#endif abort(); } From c030c7a2497858c2922ec593f60b8e590ccac970 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 15 Jan 2019 13:35:27 +0100 Subject: [PATCH 0121/1750] [ion/f730/display] Add an option for memory barriers (most likely useless) --- ion/src/f730/display.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ion/src/f730/display.cpp b/ion/src/f730/display.cpp index ff349cee5..7cba2e64a 100644 --- a/ion/src/f730/display.cpp +++ b/ion/src/f730/display.cpp @@ -1,6 +1,10 @@ #include #include "display.h" #include "regs/regs.h" +#define MEMORY_BARRIER 0 +#if MEMORY_BARRIER +#include "cache.h" +#endif extern "C" { #include } @@ -66,7 +70,13 @@ namespace Display { namespace Device { static inline void send_data(uint16_t d) { +#if MEMORY_BARRIER + Ion::Device::Cache::dsb(); +#endif *DataAddress = d; +#if MEMORY_BARRIER + Ion::Device::Cache::dsb(); +#endif } static inline uint16_t receive_data() { @@ -80,7 +90,13 @@ static inline void send_data(uint16_t d, Args... other) { } static inline void send_command(Command c) { +#if MEMORY_BARRIER + Ion::Device::Cache::dsb(); +#endif *CommandAddress = c; +#if MEMORY_BARRIER + Ion::Device::Cache::dsb(); +#endif } template From 3f79b5e1960a9e80f5f53680907c35c9b5c8c309 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Thu, 17 Jan 2019 11:09:03 +0100 Subject: [PATCH 0122/1750] [ion/f730] Fix serial number --- ion/src/f730/device.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index 35eb9b1da..c83bb6469 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -58,10 +58,9 @@ uint32_t Ion::random() { } void Ion::Device::copySerialNumber(char * buffer) { - //const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; - //Base64::encode(rawUniqueID, 12, buffer); - buffer[0]='A'; - buffer[1] = 0; + const unsigned char * rawUniqueID = (const unsigned char *)0x1FF07A10; + Base64::encode(rawUniqueID, 12, buffer); + buffer[SerialNumberLength] = 0; } const char * Ion::serialNumber() { From 37e70113d6671a9f718bb14380a6a219c6aa5311 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Fri, 18 Jan 2019 11:38:22 +0100 Subject: [PATCH 0123/1750] [ion/f730/battery] Fix charge level (correcting ADC channel) --- ion/src/f730/battery.cpp | 1 - ion/src/f730/battery.h | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ion/src/f730/battery.cpp b/ion/src/f730/battery.cpp index 212ab7214..f3ed16364 100644 --- a/ion/src/f730/battery.cpp +++ b/ion/src/f730/battery.cpp @@ -18,7 +18,6 @@ bool isCharging() { } Charge level() { - return Charge::FULL; if (voltage() < 3.2f) { return Charge::EMPTY; } diff --git a/ion/src/f730/battery.h b/ion/src/f730/battery.h index 7299aa8d4..5dac49de0 100644 --- a/ion/src/f730/battery.h +++ b/ion/src/f730/battery.h @@ -10,7 +10,7 @@ namespace Device { /* Pin | Role | Mode | Function * -----+-------------------+-----------------------+---------- * PE3 | BAT_CHRG | Input, pulled up | Low = charging, high = full - * PB1 | VBAT_SNS | Analog | ADC1_1 + * PB1 | VBAT_SNS | Analog | ADC1_IN9 */ void init(); @@ -23,7 +23,7 @@ constexpr uint8_t ChargingPin = 3; constexpr GPIO ADCGPIO = GPIOB; constexpr uint8_t ADCPin = 1; -constexpr uint8_t ADCChannel = 1; +constexpr uint8_t ADCChannel = 9; constexpr float ADCReferenceVoltage = 2.8f; constexpr float ADCDividerBridgeRatio = 2.0f; From df09059cc5146b148973a42ab92a2a93282f4d2a Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Fri, 18 Jan 2019 11:51:27 +0100 Subject: [PATCH 0124/1750] [ion/f730/battery] Redefine each pin as a GPIOPin instead of a GPIO and a uint8_t --- ion/src/f730/battery.cpp | 12 ++++++------ ion/src/f730/battery.h | 8 +++----- ion/src/f730/wakeup.cpp | 11 +++++++---- ion/src/f730/wakeup.h | 2 -- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/ion/src/f730/battery.cpp b/ion/src/f730/battery.cpp index f3ed16364..5e643f6b8 100644 --- a/ion/src/f730/battery.cpp +++ b/ion/src/f730/battery.cpp @@ -14,7 +14,7 @@ namespace Ion { namespace Battery { bool isCharging() { - return !Device::ChargingGPIO.IDR()->get(Device::ChargingPin); + return !Device::ChargingPin.group().IDR()->get(Device::ChargingPin.pin()); } Charge level() { @@ -52,7 +52,7 @@ void init() { /* The BAT_SNS pin is connected to Vbat through a divider bridge. It therefore * has a voltage of Vbat/2. We'll measure this using ADC channel 0. */ - ADCGPIO.MODER()->setMode(ADCPin, GPIO::MODER::Mode::Analog); + VoltagePin.group().MODER()->setMode(VoltagePin.pin(), GPIO::MODER::Mode::Analog); // Step 2 - Enable the ADC RCC.APB2ENR()->setADC1EN(true); @@ -70,13 +70,13 @@ void initGPIO() { * open-drain output. Open-drain output are either connected to ground or left * floating. To interact with such an output, our input must therefore be * pulled up. */ - ChargingGPIO.MODER()->setMode(ChargingPin, GPIO::MODER::Mode::Input); - ChargingGPIO.PUPDR()->setPull(ChargingPin, GPIO::PUPDR::Pull::Up); + ChargingPin.group().MODER()->setMode(ChargingPin.pin(), GPIO::MODER::Mode::Input); + ChargingPin.group().PUPDR()->setPull(ChargingPin.pin(), GPIO::PUPDR::Pull::Up); } void shutdown() { - ChargingGPIO.MODER()->setMode(ChargingPin, GPIO::MODER::Mode::Analog); - ChargingGPIO.PUPDR()->setPull(ChargingPin, GPIO::PUPDR::Pull::None); + ChargingPin.group().MODER()->setMode(ChargingPin.pin(), GPIO::MODER::Mode::Analog); + ChargingPin.group().PUPDR()->setPull(ChargingPin.pin(), GPIO::PUPDR::Pull::None); // Disable the ADC ADC.CR2()->setADON(false); diff --git a/ion/src/f730/battery.h b/ion/src/f730/battery.h index 5dac49de0..e9c402d44 100644 --- a/ion/src/f730/battery.h +++ b/ion/src/f730/battery.h @@ -1,7 +1,7 @@ #ifndef ION_DEVICE_BATTERY_H #define ION_DEVICE_BATTERY_H -#include "regs/regs.h" +#include "regs/gpio.h" namespace Ion { namespace Battery { @@ -18,11 +18,9 @@ void shutdown(); void initGPIO(); void initADC(); -constexpr GPIO ChargingGPIO = GPIOE; -constexpr uint8_t ChargingPin = 3; +constexpr GPIOPin ChargingPin = GPIOPin(GPIOE, 3); -constexpr GPIO ADCGPIO = GPIOB; -constexpr uint8_t ADCPin = 1; +constexpr GPIOPin VoltagePin = GPIOPin(GPIOB, 1); constexpr uint8_t ADCChannel = 9; constexpr float ADCReferenceVoltage = 2.8f; diff --git a/ion/src/f730/wakeup.cpp b/ion/src/f730/wakeup.cpp index 3582d6ad3..9486385d7 100644 --- a/ion/src/f730/wakeup.cpp +++ b/ion/src/f730/wakeup.cpp @@ -1,9 +1,12 @@ #include "wakeup.h" -#include "regs/regs.h" + #include "battery.h" #include "usb.h" #include "keyboard.h" +#include "regs/syscfg.h" +#include "regs/exti.h" + namespace Ion { namespace WakeUp { namespace Device { @@ -15,13 +18,13 @@ void onChargingEvent() { * source input for EXTI at the same time. Here, EXTICR1 register is filled * between position 0-3 (charging pin = 0) with * 0000 (ChargingGPIO = group A). */ - SYSCFG.EXTICR1()->setEXTI(Battery::Device::ChargingPin, Battery::Device::ChargingGPIO); + SYSCFG.EXTICR1()->setEXTI(Battery::Device::ChargingPin.pin(), Battery::Device::ChargingPin.group()); - EXTI.EMR()->set(Battery::Device::ChargingPin, true); + EXTI.EMR()->set(Battery::Device::ChargingPin.pin(), true); /* We need to detect when the battery stops charging. We set the * wake up event on the rising edge. */ - EXTI.RTSR()->set(Battery::Device::ChargingPin, true); + EXTI.RTSR()->set(Battery::Device::ChargingPin.pin(), true); } void onUSBPlugging() { diff --git a/ion/src/f730/wakeup.h b/ion/src/f730/wakeup.h index 0c7d641b9..36c35af70 100644 --- a/ion/src/f730/wakeup.h +++ b/ion/src/f730/wakeup.h @@ -1,8 +1,6 @@ #ifndef ION_DEVICE_WAKE_UP_H #define ION_DEVICE_WAKE_UP_H -#include "regs/regs.h" - namespace Ion { namespace WakeUp { namespace Device { From 22fe9871c04bbe1e58db4605d642ffb88d7e9783 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Fri, 18 Jan 2019 11:55:40 +0100 Subject: [PATCH 0125/1750] [ion/f730/battery] Do not enable peripheral clocks (done elsewhere) --- ion/src/f730/battery.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ion/src/f730/battery.cpp b/ion/src/f730/battery.cpp index 5e643f6b8..c613f0a27 100644 --- a/ion/src/f730/battery.cpp +++ b/ion/src/f730/battery.cpp @@ -1,6 +1,7 @@ #include #include "battery.h" -#include "regs/regs.h" + +#include "regs/adc.h" /* To measure the battery voltage, we're using the internal ADC. The ADC works * by comparing the input voltage to a reference voltage. The only fixed voltage @@ -54,8 +55,7 @@ void init() { * has a voltage of Vbat/2. We'll measure this using ADC channel 0. */ VoltagePin.group().MODER()->setMode(VoltagePin.pin(), GPIO::MODER::Mode::Analog); - // Step 2 - Enable the ADC - RCC.APB2ENR()->setADC1EN(true); + // Step 2 - Power on the ADC ADC.CR2()->setADON(true); // Configure the ADC channel @@ -78,9 +78,8 @@ void shutdown() { ChargingPin.group().MODER()->setMode(ChargingPin.pin(), GPIO::MODER::Mode::Analog); ChargingPin.group().PUPDR()->setPull(ChargingPin.pin(), GPIO::PUPDR::Pull::None); - // Disable the ADC + // Power down the ADC ADC.CR2()->setADON(false); - RCC.APB2ENR()->setADC1EN(false); } } From 44d683f41c5848246eea7e310ffc2cadbc0259cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 25 Jan 2019 10:10:44 +0100 Subject: [PATCH 0126/1750] [ion/f730] Fix LED GPIO and Timers --- ion/src/f730/led.cpp | 22 +++++++++++----------- ion/src/f730/led.h | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ion/src/f730/led.cpp b/ion/src/f730/led.cpp index 028e8e846..a7ff247db 100644 --- a/ion/src/f730/led.cpp +++ b/ion/src/f730/led.cpp @@ -16,8 +16,8 @@ void Ion::LED::setColor(KDColor c) { sLedColor = c; /* Active all RGB colors */ + TIM3.CCMR()->setOC1M(TIM::CCMR::OCM::PWM1); TIM3.CCMR()->setOC2M(TIM::CCMR::OCM::PWM1); - TIM3.CCMR()->setOC4M(TIM::CCMR::OCM::PWM1); TIM3.CCMR()->setOC3M(TIM::CCMR::OCM::PWM1); /* Set the PWM duty cycles to display the right color */ @@ -30,8 +30,8 @@ void Ion::LED::setBlinking(uint16_t period, float dutyCycle) { * Consequently, we do not use PWM to display the right color anymore but to * blink. We cannot use the PWM to display the exact color so we 'project the * color on 3 bits' : all colors have 2 states - active or not. */ - TIM3.CCMR()->setOC2M(sLedColor.red() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); - TIM3.CCMR()->setOC4M(sLedColor.green() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOC1M(sLedColor.red() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOC2M(sLedColor.green() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); TIM3.CCMR()->setOC3M(sLedColor.blue() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); Device::setPeriodAndDutyCycles(Device::Mode::Blink, dutyCycle, dutyCycle, dutyCycle, period); @@ -56,7 +56,7 @@ void shutdown() { void initGPIO() { /* RED_LED(PC7), GREEN_LED(PB1), and BLUE_LED(PB0) are driven using a timer, * which is an alternate function. More precisely, we will use AF2, which maps - * PB0 to TIM3_CH2, PB1 to TIM3_CH4, and PC7 to TIM3_CH2. */ + * PB0 to TIM3_CH3, PB5 to TIM3_CH2, and PB4 to TIM3_CH1. */ for(const GPIOPin & g : RGBPins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF2); @@ -71,18 +71,18 @@ void shutdownGPIO() { } void initTimer() { - // Output preload enable for channels 2-4 + // Output preload enable for channels 1 to 3 + TIM3.CCMR()->setOC1PE(true); TIM3.CCMR()->setOC2PE(true); TIM3.CCMR()->setOC3PE(true); - TIM3.CCMR()->setOC4PE(true); // Auto-reload preload enable TIM3.CR1()->setARPE(true); - // Enable Capture/Compare for channel 2 to 4 + // Enable Capture/Compare for channel 1 to 3 + TIM3.CCER()->setCC1E(true); TIM3.CCER()->setCC2E(true); TIM3.CCER()->setCC3E(true); - TIM3.CCER()->setCC4E(true); TIM3.BDTR()->setMOE(true); @@ -90,8 +90,8 @@ void initTimer() { } void shutdownTimer() { + TIM3.CCMR()->setOC1M(TIM::CCMR::OCM::ForceInactive); TIM3.CCMR()->setOC2M(TIM::CCMR::OCM::ForceInactive); - TIM3.CCMR()->setOC4M(TIM::CCMR::OCM::ForceInactive); TIM3.CCMR()->setOC3M(TIM::CCMR::OCM::ForceInactive); } @@ -123,9 +123,9 @@ void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, break; } - TIM3.CCR2()->set(dutyCycleRed*period); + TIM3.CCR1()->set(dutyCycleRed*period); TIM3.CCR3()->set(dutyCycleBlue*period); - TIM3.CCR4()->set(dutyCycleGreen*period); + TIM3.CCR2()->set(dutyCycleGreen*period); } } diff --git a/ion/src/f730/led.h b/ion/src/f730/led.h index fe46994d9..66574269d 100644 --- a/ion/src/f730/led.h +++ b/ion/src/f730/led.h @@ -10,8 +10,8 @@ namespace Device { /* Pin | Role | Mode | Function * -----+-------------------+-----------------------+---------- * PB0 | LED blue | Alternate Function 2 | TIM3_CH3 - * PB1 | LED green | Alternate Function 2 | TIM3_CH4 - * PC7 | LED red | Alternate Function 2 | TIM3_CH2 + * PB5 | LED green | Alternate Function 2 | TIM3_CH2 + * PB4 | LED red | Alternate Function 2 | TIM3_CH1 */ enum class Mode { @@ -35,7 +35,7 @@ void initTimer(); void shutdownTimer(); constexpr static GPIOPin RGBPins[] = { - GPIOPin(GPIOC, 7), GPIOPin(GPIOB, 1), GPIOPin(GPIOB, 0) + GPIOPin(GPIOB, 4), GPIOPin(GPIOB, 5), GPIOPin(GPIOB, 0) }; From 2e17db53d87bad0457fec8d7f093474e65c368a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 25 Jan 2019 10:11:17 +0100 Subject: [PATCH 0127/1750] [ion/f730] Timing: Roughly calibrate usleep and msleep --- ion/src/f730/timing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/f730/timing.cpp b/ion/src/f730/timing.cpp index dc316fcb0..98f3ff874 100644 --- a/ion/src/f730/timing.cpp +++ b/ion/src/f730/timing.cpp @@ -8,12 +8,12 @@ namespace Timing { * precision, we could use the controller cycle counter (Systick). */ void msleep(uint32_t ms) { - for (volatile uint32_t i=0; i<28852*ms; i++) { + for (volatile uint32_t i=0; i<39343*ms; i++) { __asm volatile("nop"); } } void usleep(uint32_t us) { - for (volatile uint32_t i=0; i<28*us; i++) { + for (volatile uint32_t i=0; i<39*us; i++) { __asm volatile("nop"); } } From f14478395c771ef3700e7aa6c17cd20de3cc3f8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 31 Jan 2019 10:19:14 +0100 Subject: [PATCH 0128/1750] [ion/f730] Device: LED use only GPIOB --- ion/src/f730/device.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp index c83bb6469..e0479e8de 100644 --- a/ion/src/f730/device.cpp +++ b/ion/src/f730/device.cpp @@ -331,7 +331,6 @@ void shutdownClocks(bool keepLEDAwake) { if (keepLEDAwake) { apb1enr.setTIM3EN(true); ahb1enr.setGPIOBEN(true); - ahb1enr.setGPIOCEN(true); } RCC.APB1ENR()->set(apb1enr); RCC.AHB1ENR()->set(ahb1enr); From aa6f574604041567165f96a10a43bb7f1dcf3787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 25 Jan 2019 14:55:38 +0100 Subject: [PATCH 0129/1750] [ion/f730] Fix wakeup events --- ion/src/f730/wakeup.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ion/src/f730/wakeup.cpp b/ion/src/f730/wakeup.cpp index 9486385d7..b67c95921 100644 --- a/ion/src/f730/wakeup.cpp +++ b/ion/src/f730/wakeup.cpp @@ -16,7 +16,7 @@ void onChargingEvent() { /* Warning: pins with the same number in different groups cannot be set as * source input for EXTI at the same time. Here, EXTICR1 register is filled - * between position 0-3 (charging pin = 0) with + * between position 12-15 (charging pin = 3) with * 0000 (ChargingGPIO = group A). */ SYSCFG.EXTICR1()->setEXTI(Battery::Device::ChargingPin.pin(), Battery::Device::ChargingPin.group()); @@ -28,15 +28,14 @@ void onChargingEvent() { } void onUSBPlugging() { - USB::Device::initGPIO(); + USB::Device::VbusPin.group().MODER()->setMode(USB::Device::VbusPin.pin(), GPIO::MODER::Mode::Input); + USB::Device::VbusPin.group().PUPDR()->setPull(USB::Device::VbusPin.pin(), GPIO::PUPDR::Pull::Down); /* Here, EXTICR3 register is filled between position 4-7 (Vbus pin = 9) with * 0000 (Vbus GPIO = group A). */ SYSCFG.EXTICR3()->setEXTI(USB::Device::VbusPin.pin(), USB::Device::VbusPin.group()); EXTI.EMR()->set(USB::Device::VbusPin.pin(), true); -#if EPSILON_LED_WHILE_CHARGING EXTI.FTSR()->set(USB::Device::VbusPin.pin(), true); -#endif EXTI.RTSR()->set(USB::Device::VbusPin.pin(), true); } @@ -54,7 +53,7 @@ void onPowerKeyDown() { Keyboard::Device::ColumnGPIO.MODER()->setMode(columnPin, GPIO::MODER::Mode::Input); Keyboard::Device::ColumnGPIO.PUPDR()->setPull(columnPin, GPIO::PUPDR::Pull::Up); - /* Here, EXTICR1 register is filled between position 4-7 (column pin = 1) with + /* Here, EXTICR1 register is filled between position 8-11 (column pin = 2) with * 0010 (ColumnGPIO = group C). */ SYSCFG.EXTICR1()->setEXTI(columnPin, Keyboard::Device::ColumnGPIO); From 7618318a905f8ade7f478c20428f13a30218d844 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 5 Feb 2019 11:23:55 +0100 Subject: [PATCH 0130/1750] [ion/device] Sort files --- build/platform.device.mak | 4 +- build/platform.device.n0100.mak | 1 + ion/src/device/Makefile | 29 +-- ion/src/device/boot/Makefile | 2 - ion/src/device/device.h | 36 ---- ion/src/device/n0100/Makefile | 5 + .../{device.cpp => n0100/drivers/board.cpp} | 171 +----------------- .../{ => n0100/drivers/config}/backlight.h | 23 +-- .../{ => n0100/drivers/config}/battery.h | 27 +-- .../{ => n0100/drivers/config}/console.h | 21 +-- .../{ => n0100/drivers/config}/display.h | 66 +------ .../n0100/drivers/config/external_flash.h | 31 ++++ .../{ => n0100/drivers/config}/keyboard.h | 41 +---- .../device/{ => n0100/drivers/config}/led.h | 37 +--- .../device/{ => n0100/drivers/config}/swd.h | 17 +- ion/src/device/n0100/drivers/config/timing.h | 24 +++ ion/src/device/n0100/drivers/config/usb.h | 20 ++ ion/src/device/{boot => n0100}/flash.ld | 0 ion/src/device/sd_card.cpp | 95 ---------- ion/src/device/sd_card.h | 33 ---- ion/src/device/shared/Makefile | 3 + ion/src/device/shared/boot/Makefile | 4 + ion/src/device/{ => shared}/boot/isr.c | 0 ion/src/device/{ => shared}/boot/isr.h | 0 ion/src/device/{ => shared}/boot/rt0.cpp | 17 +- ion/src/device/shared/drivers/Makefile | 22 +++ .../device/{ => shared/drivers}/backlight.cpp | 27 +-- ion/src/device/shared/drivers/backlight.h | 24 +++ .../device/{ => shared/drivers}/base64.cpp | 0 ion/src/device/{ => shared/drivers}/base64.h | 0 .../device/{ => shared/drivers}/battery.cpp | 27 +-- ion/src/device/shared/drivers/battery.h | 17 ++ ion/src/device/shared/drivers/board.cpp | 59 ++++++ ion/src/device/shared/drivers/board.h | 21 +++ .../device/{ => shared/drivers}/console.cpp | 41 +++-- ion/src/device/shared/drivers/console.h | 16 ++ ion/src/device/shared/drivers/crc32.cpp | 17 ++ .../device/{ => shared/drivers}/display.cpp | 100 +++++----- ion/src/device/shared/drivers/display.h | 67 +++++++ .../device/{ => shared/drivers}/events.cpp | 0 .../{ => shared/drivers}/external_flash.cpp | 5 +- .../{ => shared/drivers}/external_flash.h | 14 +- ion/src/device/{ => shared/drivers}/flash.cpp | 2 +- ion/src/device/{ => shared/drivers}/flash.h | 8 +- .../device/{ => shared/drivers}/keyboard.cpp | 46 +++-- ion/src/device/shared/drivers/keyboard.h | 45 +++++ ion/src/device/{ => shared/drivers}/led.cpp | 32 ++-- ion/src/device/shared/drivers/led.h | 36 ++++ ion/src/device/{ => shared/drivers}/power.cpp | 44 +++-- ion/src/device/shared/drivers/random.cpp | 18 ++ ion/src/device/shared/drivers/reset.cpp | 32 ++++ ion/src/device/shared/drivers/reset.h | 15 ++ .../device/shared/drivers/serial_number.cpp | 30 +++ ion/src/device/shared/drivers/serial_number.h | 19 ++ ion/src/device/{ => shared/drivers}/swd.cpp | 8 +- ion/src/device/shared/drivers/swd.h | 15 ++ ion/src/device/shared/drivers/timing.cpp | 53 ++++++ ion/src/device/shared/drivers/timing.h | 19 ++ ion/src/device/{ => shared/drivers}/usb.cpp | 32 ++-- ion/src/device/shared/drivers/usb.h | 19 ++ ion/src/device/shared/drivers/wakeup.cpp | 72 ++++++++ ion/src/device/{ => shared/drivers}/wakeup.h | 2 +- ion/src/device/{ => shared}/regs/adc.h | 0 ion/src/device/{ => shared}/regs/cm4.h | 0 ion/src/device/{ => shared}/regs/crc.h | 0 ion/src/device/{ => shared}/regs/dma.h | 0 ion/src/device/{ => shared}/regs/exti.h | 0 ion/src/device/{ => shared}/regs/flash.h | 0 ion/src/device/{ => shared}/regs/fsmc.h | 0 ion/src/device/{ => shared}/regs/gpio.h | 0 ion/src/device/{ => shared}/regs/itm.h | 0 ion/src/device/{ => shared}/regs/mpu.h | 0 ion/src/device/{ => shared}/regs/nvic.h | 0 ion/src/device/{ => shared}/regs/otg.h | 0 ion/src/device/{ => shared}/regs/pwr.h | 0 ion/src/device/{ => shared}/regs/quadspi.h | 0 ion/src/device/{ => shared}/regs/rcc.h | 0 ion/src/device/{ => shared}/regs/register.h | 0 ion/src/device/{ => shared}/regs/regs.h | 0 ion/src/device/{ => shared}/regs/rng.h | 0 ion/src/device/{ => shared}/regs/sdio.h | 0 ion/src/device/{ => shared}/regs/spi.h | 0 ion/src/device/{ => shared}/regs/syscfg.h | 0 ion/src/device/{ => shared}/regs/tim.h | 0 ion/src/device/{ => shared}/regs/usart.h | 0 ion/src/device/shared/usb/Makefile | 73 ++++++++ ion/src/device/{ => shared}/usb/boot.cpp | 0 .../device/{ => shared}/usb/calculator.cpp | 22 +-- ion/src/device/{ => shared}/usb/calculator.h | 6 +- ion/src/device/{ => shared}/usb/dfu.ld | 0 .../device/{ => shared}/usb/dfu_interface.cpp | 28 +-- .../device/{ => shared}/usb/dfu_interface.h | 6 +- .../device/{ => shared}/usb/dfu_relocated.cpp | 6 +- ion/src/device/{ => shared}/usb/dfu_xip.cpp | 3 +- ion/src/device/{ => shared}/usb/flasher.cpp | 0 ion/src/device/{ => shared}/usb/flasher.ld | 0 .../usb/stack/descriptor/bos_descriptor.cpp | 2 +- .../usb/stack/descriptor/bos_descriptor.h | 6 +- .../descriptor/configuration_descriptor.cpp | 2 +- .../descriptor/configuration_descriptor.h | 6 +- .../usb/stack/descriptor/descriptor.cpp | 2 +- .../usb/stack/descriptor/descriptor.h | 6 +- .../device_capability_descriptor.cpp | 2 +- .../descriptor/device_capability_descriptor.h | 6 +- .../stack/descriptor/device_descriptor.cpp | 2 +- .../usb/stack/descriptor/device_descriptor.h | 6 +- .../descriptor/dfu_functional_descriptor.cpp | 2 +- .../descriptor/dfu_functional_descriptor.h | 6 +- .../extended_compat_id_descriptor.cpp | 2 +- .../extended_compat_id_descriptor.h | 6 +- .../stack/descriptor/interface_descriptor.cpp | 2 +- .../stack/descriptor/interface_descriptor.h | 6 +- .../language_id_string_descriptor.cpp | 2 +- .../language_id_string_descriptor.h | 6 +- .../microsoft_os_string_descriptor.cpp | 2 +- .../microsoft_os_string_descriptor.h | 6 +- .../platform_device_capability_descriptor.cpp | 2 +- .../platform_device_capability_descriptor.h | 6 +- .../stack/descriptor/string_descriptor.cpp | 2 +- .../usb/stack/descriptor/string_descriptor.h | 6 +- .../usb/stack/descriptor/url_descriptor.cpp | 2 +- .../usb/stack/descriptor/url_descriptor.h | 6 +- .../descriptor/webusb_platform_descriptor.cpp | 2 +- .../descriptor/webusb_platform_descriptor.h | 6 +- .../device/{ => shared}/usb/stack/device.cpp | 4 +- .../device/{ => shared}/usb/stack/device.h | 6 +- .../{ => shared}/usb/stack/endpoint0.cpp | 4 +- .../device/{ => shared}/usb/stack/endpoint0.h | 6 +- .../{ => shared}/usb/stack/interface.cpp | 2 +- .../device/{ => shared}/usb/stack/interface.h | 6 +- .../usb/stack/request_recipient.cpp | 2 +- .../usb/stack/request_recipient.h | 6 +- .../{ => shared}/usb/stack/setup_packet.cpp | 2 +- .../{ => shared}/usb/stack/setup_packet.h | 6 +- .../{ => shared}/usb/stack/streamable.cpp | 2 +- .../{ => shared}/usb/stack/streamable.h | 6 +- ion/src/device/timing.cpp | 36 ---- ion/src/device/timing.h | 16 -- ion/src/device/usb.h | 34 ---- ion/src/device/usb/Makefile | 68 ------- ion/src/device/wakeup.cpp | 69 ------- 141 files changed, 1172 insertions(+), 1070 deletions(-) create mode 100644 build/platform.device.n0100.mak delete mode 100644 ion/src/device/boot/Makefile delete mode 100644 ion/src/device/device.h create mode 100644 ion/src/device/n0100/Makefile rename ion/src/device/{device.cpp => n0100/drivers/board.cpp} (51%) rename ion/src/device/{ => n0100/drivers/config}/backlight.h (54%) rename ion/src/device/{ => n0100/drivers/config}/battery.h (62%) rename ion/src/device/{ => n0100/drivers/config}/console.h (68%) rename ion/src/device/{ => n0100/drivers/config}/display.h (63%) create mode 100644 ion/src/device/n0100/drivers/config/external_flash.h rename ion/src/device/{ => n0100/drivers/config}/keyboard.h (56%) rename ion/src/device/{ => n0100/drivers/config}/led.h (53%) rename ion/src/device/{ => n0100/drivers/config}/swd.h (77%) create mode 100644 ion/src/device/n0100/drivers/config/timing.h create mode 100644 ion/src/device/n0100/drivers/config/usb.h rename ion/src/device/{boot => n0100}/flash.ld (100%) delete mode 100644 ion/src/device/sd_card.cpp delete mode 100644 ion/src/device/sd_card.h create mode 100644 ion/src/device/shared/Makefile create mode 100644 ion/src/device/shared/boot/Makefile rename ion/src/device/{ => shared}/boot/isr.c (100%) rename ion/src/device/{ => shared}/boot/isr.h (100%) rename ion/src/device/{ => shared}/boot/rt0.cpp (92%) create mode 100644 ion/src/device/shared/drivers/Makefile rename ion/src/device/{ => shared/drivers}/backlight.cpp (61%) create mode 100644 ion/src/device/shared/drivers/backlight.h rename ion/src/device/{ => shared/drivers}/base64.cpp (100%) rename ion/src/device/{ => shared/drivers}/base64.h (100%) rename ion/src/device/{ => shared/drivers}/battery.cpp (68%) create mode 100644 ion/src/device/shared/drivers/battery.h create mode 100644 ion/src/device/shared/drivers/board.cpp create mode 100644 ion/src/device/shared/drivers/board.h rename ion/src/device/{ => shared/drivers}/console.cpp (53%) create mode 100644 ion/src/device/shared/drivers/console.h create mode 100644 ion/src/device/shared/drivers/crc32.cpp rename ion/src/device/{ => shared/drivers}/display.cpp (74%) create mode 100644 ion/src/device/shared/drivers/display.h rename ion/src/device/{ => shared/drivers}/events.cpp (100%) rename ion/src/device/{ => shared/drivers}/external_flash.cpp (99%) rename ion/src/device/{ => shared/drivers}/external_flash.h (89%) rename ion/src/device/{ => shared/drivers}/flash.cpp (100%) rename ion/src/device/{ => shared/drivers}/flash.h (84%) rename ion/src/device/{ => shared/drivers}/keyboard.cpp (71%) create mode 100644 ion/src/device/shared/drivers/keyboard.h rename ion/src/device/{ => shared/drivers}/led.cpp (87%) create mode 100644 ion/src/device/shared/drivers/led.h rename ion/src/device/{ => shared/drivers}/power.cpp (71%) create mode 100644 ion/src/device/shared/drivers/random.cpp create mode 100644 ion/src/device/shared/drivers/reset.cpp create mode 100644 ion/src/device/shared/drivers/reset.h create mode 100644 ion/src/device/shared/drivers/serial_number.cpp create mode 100644 ion/src/device/shared/drivers/serial_number.h rename ion/src/device/{ => shared/drivers}/swd.cpp (78%) create mode 100644 ion/src/device/shared/drivers/swd.h create mode 100644 ion/src/device/shared/drivers/timing.cpp create mode 100644 ion/src/device/shared/drivers/timing.h rename ion/src/device/{ => shared/drivers}/usb.cpp (80%) create mode 100644 ion/src/device/shared/drivers/usb.h create mode 100644 ion/src/device/shared/drivers/wakeup.cpp rename ion/src/device/{ => shared/drivers}/wakeup.h (100%) rename ion/src/device/{ => shared}/regs/adc.h (100%) rename ion/src/device/{ => shared}/regs/cm4.h (100%) rename ion/src/device/{ => shared}/regs/crc.h (100%) rename ion/src/device/{ => shared}/regs/dma.h (100%) rename ion/src/device/{ => shared}/regs/exti.h (100%) rename ion/src/device/{ => shared}/regs/flash.h (100%) rename ion/src/device/{ => shared}/regs/fsmc.h (100%) rename ion/src/device/{ => shared}/regs/gpio.h (100%) rename ion/src/device/{ => shared}/regs/itm.h (100%) rename ion/src/device/{ => shared}/regs/mpu.h (100%) rename ion/src/device/{ => shared}/regs/nvic.h (100%) rename ion/src/device/{ => shared}/regs/otg.h (100%) rename ion/src/device/{ => shared}/regs/pwr.h (100%) rename ion/src/device/{ => shared}/regs/quadspi.h (100%) rename ion/src/device/{ => shared}/regs/rcc.h (100%) rename ion/src/device/{ => shared}/regs/register.h (100%) rename ion/src/device/{ => shared}/regs/regs.h (100%) rename ion/src/device/{ => shared}/regs/rng.h (100%) rename ion/src/device/{ => shared}/regs/sdio.h (100%) rename ion/src/device/{ => shared}/regs/spi.h (100%) rename ion/src/device/{ => shared}/regs/syscfg.h (100%) rename ion/src/device/{ => shared}/regs/tim.h (100%) rename ion/src/device/{ => shared}/regs/usart.h (100%) create mode 100644 ion/src/device/shared/usb/Makefile rename ion/src/device/{ => shared}/usb/boot.cpp (100%) rename ion/src/device/{ => shared}/usb/calculator.cpp (86%) rename ion/src/device/{ => shared}/usb/calculator.h (98%) rename ion/src/device/{ => shared}/usb/dfu.ld (100%) rename ion/src/device/{ => shared}/usb/dfu_interface.cpp (90%) rename ion/src/device/{ => shared}/usb/dfu_interface.h (98%) rename ion/src/device/{ => shared}/usb/dfu_relocated.cpp (96%) rename ion/src/device/{ => shared}/usb/dfu_xip.cpp (50%) rename ion/src/device/{ => shared}/usb/flasher.cpp (100%) rename ion/src/device/{ => shared}/usb/flasher.ld (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/bos_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/bos_descriptor.h (87%) rename ion/src/device/{ => shared}/usb/stack/descriptor/configuration_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/configuration_descriptor.h (89%) rename ion/src/device/{ => shared}/usb/stack/descriptor/descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/descriptor.h (84%) rename ion/src/device/{ => shared}/usb/stack/descriptor/device_capability_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/device_capability_descriptor.h (79%) rename ion/src/device/{ => shared}/usb/stack/descriptor/device_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/device_descriptor.h (92%) rename ion/src/device/{ => shared}/usb/stack/descriptor/dfu_functional_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/dfu_functional_descriptor.h (84%) rename ion/src/device/{ => shared}/usb/stack/descriptor/extended_compat_id_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/extended_compat_id_descriptor.h (91%) rename ion/src/device/{ => shared}/usb/stack/descriptor/interface_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/interface_descriptor.h (91%) rename ion/src/device/{ => shared}/usb/stack/descriptor/language_id_string_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/language_id_string_descriptor.h (74%) rename ion/src/device/{ => shared}/usb/stack/descriptor/microsoft_os_string_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/microsoft_os_string_descriptor.h (77%) rename ion/src/device/{ => shared}/usb/stack/descriptor/platform_device_capability_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/platform_device_capability_descriptor.h (88%) rename ion/src/device/{ => shared}/usb/stack/descriptor/string_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/string_descriptor.h (77%) rename ion/src/device/{ => shared}/usb/stack/descriptor/url_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/url_descriptor.h (83%) rename ion/src/device/{ => shared}/usb/stack/descriptor/webusb_platform_descriptor.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/descriptor/webusb_platform_descriptor.h (87%) rename ion/src/device/{ => shared}/usb/stack/device.cpp (99%) rename ion/src/device/{ => shared}/usb/stack/device.h (95%) rename ion/src/device/{ => shared}/usb/stack/endpoint0.cpp (99%) rename ion/src/device/{ => shared}/usb/stack/endpoint0.h (95%) rename ion/src/device/{ => shared}/usb/stack/interface.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/interface.h (93%) rename ion/src/device/{ => shared}/usb/stack/request_recipient.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/request_recipient.h (88%) rename ion/src/device/{ => shared}/usb/stack/setup_packet.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/setup_packet.h (92%) rename ion/src/device/{ => shared}/usb/stack/streamable.cpp (100%) rename ion/src/device/{ => shared}/usb/stack/streamable.h (90%) delete mode 100644 ion/src/device/timing.cpp delete mode 100644 ion/src/device/timing.h delete mode 100644 ion/src/device/usb.h delete mode 100644 ion/src/device/usb/Makefile delete mode 100644 ion/src/device/wakeup.cpp diff --git a/build/platform.device.mak b/build/platform.device.mak index d435f6a06..528a97c9e 100644 --- a/build/platform.device.mak +++ b/build/platform.device.mak @@ -1,4 +1,4 @@ -TOOLCHAIN ?= arm-gcc-m4f +MODEL ?= n0100 USE_LIBA = 1 EXE = elf EPSILON_BOOT_PROMPT = update @@ -7,3 +7,5 @@ EPSILON_DEVICE_BENCH ?= 1 SFLAGS += -DEPSILON_DEVICE_BENCH=$(EPSILON_DEVICE_BENCH) python/port/port.o: CXXFLAGS += -DMP_PORT_USE_STACK_SYMBOLS=1 + +include build/platform.device.$(MODEL).mak diff --git a/build/platform.device.n0100.mak b/build/platform.device.n0100.mak new file mode 100644 index 000000000..9c36368ed --- /dev/null +++ b/build/platform.device.n0100.mak @@ -0,0 +1 @@ +TOOLCHAIN ?= arm-gcc-m4f diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 31a060382..272676c39 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -1,6 +1,6 @@ -include ion/src/device/boot/Makefile -include ion/src/device/bench/Makefile -include ion/src/device/usb/Makefile +# Makefile belows should augment $(ion_device_objs) +include ion/src/device/shared/Makefile +include ion/src/device/$(MODEL)/Makefile ion/src/shared/platform_info.o: SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" @@ -12,27 +12,10 @@ objs += $(addprefix ion/src/shared/, \ # If you need to profile execution, you can replace events_keyboard with # events_replay.o and dummy/events_modifier.o +ION_DEVICE_SFLAGS = -Iion/src/device/$(MODEL) -Iion/src/device/shared -objs += $(addprefix ion/src/device/, \ - backlight.o \ - battery.o\ - base64.o\ - console.o \ - device.o\ - display.o\ - events.o\ - external_flash.o\ - flash.o\ - keyboard.o\ - led.o\ - power.o\ - sd_card.o\ - stack.o\ - swd.o \ - timing.o \ - usb.o \ - wakeup.o \ -) +$(ion_device_objs): SFLAGS += $(ION_DEVICE_SFLAGS) +objs += $(ion_device_objs) # When using the register.h C++ file in production mode, we expect the compiler # to completely inline all bit manipulations. For some reason, if we build using diff --git a/ion/src/device/boot/Makefile b/ion/src/device/boot/Makefile deleted file mode 100644 index ded572acd..000000000 --- a/ion/src/device/boot/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -objs += $(addprefix ion/src/device/boot/, isr.o rt0.o) -LDSCRIPT = ion/src/device/boot/flash.ld diff --git a/ion/src/device/device.h b/ion/src/device/device.h deleted file mode 100644 index 2bdccea28..000000000 --- a/ion/src/device/device.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef ION_DEVICE_H -#define ION_DEVICE_H - -namespace Ion { -namespace Device { - -void init(); -void shutdown(); - -void initFPU(); -#if 0 -void initMPU(); -#endif - -void initSysTick(); -void shutdownSysTick(); - -void coreReset(); -void jumpReset(); - -void initPeripherals(); -void shutdownPeripherals(bool keepLEDAwake = false); -void initClocks(); -void shutdownClocks(bool keepLEDAwake = false); - -/* The serial number is 96 bits long. That's equal to 16 digits in base 64. We - * expose a convenient "copySerialNumber" routine which can be called without - * using a static variable (and therefore without a .bss section). This is used - * in the RAM'ed DFU bootloader. */ -constexpr static int SerialNumberLength = 16; -void copySerialNumber(char * buffer); - -} -} - -#endif diff --git a/ion/src/device/n0100/Makefile b/ion/src/device/n0100/Makefile new file mode 100644 index 000000000..b0bd9c3b1 --- /dev/null +++ b/ion/src/device/n0100/Makefile @@ -0,0 +1,5 @@ +ion_device_objs += $(addprefix ion/src/device/n0100/drivers/, \ + board.o \ +) + +LDSCRIPT ?= ion/src/device/n0100/flash.ld diff --git a/ion/src/device/device.cpp b/ion/src/device/n0100/drivers/board.cpp similarity index 51% rename from ion/src/device/device.cpp rename to ion/src/device/n0100/drivers/board.cpp index d01941f32..f44b5587e 100644 --- a/ion/src/device/device.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -1,75 +1,9 @@ -#include "device.h" -#include "regs/regs.h" -extern "C" { -#include -} +#include +#include #include -#include "led.h" -#include "display.h" -#include "keyboard.h" -#include "battery.h" -#include "sd_card.h" -#include "backlight.h" -#include "console.h" -#include "swd.h" -#include "usb.h" -#include "bench/bench.h" -#include "base64.h" -#include "external_flash.h" - -#define USE_SD_CARD 0 - -extern "C" { - extern const void * _stack_end; -} // Public Ion methods -uint32_t Ion::crc32(const uint32_t * data, size_t length) { - bool initialCRCEngineState = RCC.AHB1ENR()->getCRCEN(); - RCC.AHB1ENR()->setCRCEN(true); - CRC.CR()->setRESET(true); - - const uint32_t * end = data + length; - while (data < end) { - CRC.DR()->set(*data++); - } - - uint32_t result = CRC.DR()->get(); - RCC.AHB1ENR()->setCRCEN(initialCRCEngineState); - return result; -} - -uint32_t Ion::random() { - bool initialRNGEngineState = RCC.AHB2ENR()->getRNGEN(); - RCC.AHB2ENR()->setRNGEN(true); - - RNG.CR()->setRNGEN(true); - - while (RNG.SR()->getDRDY() == 0) { - } - uint32_t result = RNG.DR()->get(); - - RNG.CR()->setRNGEN(false); - RCC.AHB2ENR()->setRNGEN(initialRNGEngineState); - - return result; -} - -void Ion::Device::copySerialNumber(char * buffer) { - const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; - Base64::encode(rawUniqueID, 12, buffer); - buffer[SerialNumberLength] = 0; -} - -const char * Ion::serialNumber() { - static char serialNumber[Device::SerialNumberLength + 1] = {0}; - if (serialNumber[0] == 0) { - Device::copySerialNumber(serialNumber); - } - return serialNumber; -} - const char * Ion::fccId() { return "2ALWP-N0100"; } @@ -78,65 +12,7 @@ const char * Ion::fccId() { namespace Ion { namespace Device { - -void initFPU() { - // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BABDBFBJ.html - CM4.CPACR()->setAccess(10, CM4::CPACR::Access::Full); - CM4.CPACR()->setAccess(11, CM4::CPACR::Access::Full); - // FIXME: The pipeline should be flushed at this point -} - -#if 0 -void initMPU() { - /* Region 0 reprensents the last 128 bytes of the stack: accessing this - * memory means we are really likely to overflow the stack very soon. */ - MPU.RNR()->setREGION(0x00); - MPU.RBAR()->setADDR(&_stack_end); - MPU.RASR()->setSIZE(MPU::RASR::RegionSize::Bytes128); - MPU.RASR()->setENABLE(true); - MPU.RASR()->setAP(0x000); // Forbid access - MPU.CTRL()->setPRIVDEFENA(true); - MPU.CTRL()->setENABLE(true); -} -#endif - -void initSysTick() { - // CPU clock is 96 MHz, and systick clock source is divided by 8 - // To get 1 ms systick overflow we need to reset it to - // 96 000 000 (Hz) / 8 / 1 000 (ms/s) - 1 (because the counter resets *after* counting to 0) - CM4.SYST_RVR()->setRELOAD(11999); - CM4.SYST_CVR()->setCURRENT(0); - CM4.SYST_CSR()->setCLKSOURCE(CM4::SYST_CSR::CLKSOURCE::AHB_DIV8); - CM4.SYST_CSR()->setTICKINT(true); - CM4.SYST_CSR()->setENABLE(true); -} - -void shutdownSysTick() { - CM4.SYST_CSR()->setENABLE(false); - CM4.SYST_CSR()->setTICKINT(false); -} - -void coreReset() { - // Perform a full core reset - CM4.AIRCR()->requestReset(); -} - -void jumpReset() { - uint32_t * stackPointerAddress = reinterpret_cast(0x08000000); - uint32_t * resetHandlerAddress = reinterpret_cast(0x08000004); - - /* Jump to the reset service routine after having reset the stack pointer. - * Both addresses are fetched from the base of the Flash memory, just like a - * real reset would. These operations should be made at once, otherwise the C - * compiler might emit some instructions that modify the stack inbetween. */ - - asm volatile ( - "msr MSP, %[stackPointer] ; bx %[resetHandler]" - : : - [stackPointer] "r" (*stackPointerAddress), - [resetHandler] "r" (*resetHandlerAddress) - ); -} +namespace Board { void init() { initClocks(); @@ -158,7 +34,7 @@ void init() { } #if EPSILON_DEVICE_BENCH - bool consolePeerConnectedOnBoot = Ion::Console::Device::peerConnected(); + bool consolePeerConnectedOnBoot = Console::peerConnected(); #endif initPeripherals(); @@ -170,44 +46,6 @@ void init() { #endif } -void shutdown() { - shutdownPeripherals(); - shutdownClocks(); -} - -void initPeripherals() { - Display::Device::init(); - Backlight::Device::init(); - Keyboard::Device::init(); - LED::Device::init(); - Battery::Device::init(); - USB::Device::init(); -#if USE_SD_CARD - SDCard::Device::init(); -#endif - Console::Device::init(); - SWD::Device::init(); - initSysTick(); - ExternalFlash::Device::init(); -} - -void shutdownPeripherals(bool keepLEDAwake) { - shutdownSysTick(); - SWD::Device::shutdown(); - Console::Device::shutdown(); -#if USE_SD_CARD - SDCard::Device::shutdown(); -#endif - USB::Device::shutdown(); - Battery::Device::shutdown(); - if (!keepLEDAwake) { - LED::Device::shutdown(); - } - Keyboard::Device::shutdown(); - Backlight::Device::shutdown(); - Display::Device::shutdown(); -} - void initClocks() { /* System clock * Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */ @@ -325,3 +163,4 @@ void shutdownClocks(bool keepLEDAwake) { } } +} diff --git a/ion/src/device/backlight.h b/ion/src/device/n0100/drivers/config/backlight.h similarity index 54% rename from ion/src/device/backlight.h rename to ion/src/device/n0100/drivers/config/backlight.h index 15eab4fb0..cd48215e0 100644 --- a/ion/src/device/backlight.h +++ b/ion/src/device/n0100/drivers/config/backlight.h @@ -1,28 +1,23 @@ -#ifndef ION_DEVICE_BACKLIGHT_H -#define ION_DEVICE_BACKLIGHT_H +#ifndef ION_DEVICE_N0100_CONFIG_BACKLIGHT_H +#define ION_DEVICE_N0100_CONFIG_BACKLIGHT_H -#include - -namespace Ion { -namespace Backlight { -namespace Device { +#include /* Pin | Role | Mode | Function * -----+-------------------+-----------------------+---------- * PC6 | Backlight Enable | Output | */ -void init(); -void shutdown(); -void suspend(); -void resume(); -void setLevel(uint8_t level); -uint8_t level(); +namespace Ion { +namespace Device { +namespace Backlight { +namespace Config { -void sendPulses(int n); +constexpr static GPIOPin BacklightPin = GPIOPin(GPIOC, 6); } } } +} #endif diff --git a/ion/src/device/battery.h b/ion/src/device/n0100/drivers/config/battery.h similarity index 62% rename from ion/src/device/battery.h rename to ion/src/device/n0100/drivers/config/battery.h index ef558dc86..1fc5c7600 100644 --- a/ion/src/device/battery.h +++ b/ion/src/device/n0100/drivers/config/battery.h @@ -1,11 +1,7 @@ -#ifndef ION_DEVICE_BATTERY_H -#define ION_DEVICE_BATTERY_H +#ifndef ION_DEVICE_N0100_CONFIG_BATTERY_H +#define ION_DEVICE_N0100_CONFIG_BATTERY_H -#include "regs/regs.h" - -namespace Ion { -namespace Battery { -namespace Device { +#include /* Pin | Role | Mode | Function * -----+-------------------+-----------------------+---------- @@ -13,23 +9,20 @@ namespace Device { * PA1 | VBAT_SNS | Analog | ADC1_1 */ -void init(); -void shutdown(); -void initGPIO(); -void initADC(); +namespace Ion { +namespace Device { +namespace Battery { +namespace Config { -constexpr GPIO ChargingGPIO = GPIOA; -constexpr uint8_t ChargingPin = 0; - -constexpr GPIO ADCGPIO = GPIOA; -constexpr uint8_t ADCPin = 1; +constexpr static GPIOPin ChargingPin = GPIOPin(GPIOA, 0); +constexpr static GPIOPin ADCPin = GPIOPin(GPIOA, 1); constexpr uint8_t ADCChannel = 1; - constexpr float ADCReferenceVoltage = 2.8f; constexpr float ADCDividerBridgeRatio = 2.0f; } } } +} #endif diff --git a/ion/src/device/console.h b/ion/src/device/n0100/drivers/config/console.h similarity index 68% rename from ion/src/device/console.h rename to ion/src/device/n0100/drivers/config/console.h index b1b14545f..fa6eb363e 100644 --- a/ion/src/device/console.h +++ b/ion/src/device/n0100/drivers/config/console.h @@ -1,12 +1,7 @@ -#ifndef ION_DEVICE_CONSOLE_H -#define ION_DEVICE_CONSOLE_H +#ifndef ION_DEVICE_N0100_CONFIG_CONSOLE_H +#define ION_DEVICE_N0100_CONFIG_CONSOLE_H -#include -#include "regs/regs.h" - -namespace Ion { -namespace Console { -namespace Device { +#include /* Pin | Role | Mode * -----+-------------------+-------------------- @@ -14,11 +9,12 @@ namespace Device { * PD8 | UART3 TX | Alternate Function */ -void init(); -void shutdown(); -bool peerConnected(); +namespace Ion { +namespace Device { +namespace Console { +namespace Config { -constexpr USART UARTPort = USART(3); +constexpr static USART UARTPort = USART(3); constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 11); constexpr static GPIOPin TxPin = GPIOPin(GPIOD, 8); constexpr static GPIOPin Pins[] = { RxPin, TxPin }; @@ -26,5 +22,6 @@ constexpr static GPIOPin Pins[] = { RxPin, TxPin }; } } } +} #endif diff --git a/ion/src/device/display.h b/ion/src/device/n0100/drivers/config/display.h similarity index 63% rename from ion/src/device/display.h rename to ion/src/device/n0100/drivers/config/display.h index e9147b90a..d8f31f86f 100644 --- a/ion/src/device/display.h +++ b/ion/src/device/n0100/drivers/config/display.h @@ -1,16 +1,7 @@ -#ifndef ION_DEVICE_DISPLAY_H -#define ION_DEVICE_DISPLAY_H +#ifndef ION_DEVICE_N0100_CONFIG_DISPLAY_H +#define ION_DEVICE_N0100_CONFIG_DISPLAY_H -#include -#include -extern "C" { -#include -} -#include "regs/regs.h" - -namespace Ion { -namespace Display { -namespace Device { +#include /* Pin | Role | Mode | Function | Note * -----+-------------------+-----------------------+----------|------ @@ -38,44 +29,11 @@ namespace Device { * PE15 | LCD D12 | Alternate Function 12 | FSMC_D12 | */ -void init(); -void shutdown(); -void initDMA(); -void initGPIO(); -void shutdownGPIO(); -void initFSMC(); -void shutdownFSMC(); -void initPanel(); -void shutdownPanel(); - -enum class Orientation { - Landscape = 0, - Portrait = 1 -}; - -void setDrawingArea(KDRect r, Orientation o); -void waitForPendingDMAUploadCompletion(); -void pushPixels(const KDColor * pixels, size_t numberOfPixels); -void pushColor(KDColor color, size_t numberOfPixels); -void pullPixels(KDColor * pixels, size_t numberOfPixels); - -enum class Command : uint16_t { - Nop = 0x00, - Reset = 0x01, - SleepIn = 0x10, - SleepOut = 0x11, - DisplayOff = 0x28, - DisplayOn = 0x29, - ColumnAddressSet = 0x2A, - PageAddressSet = 0x2B, - MemoryWrite = 0x2C, - MemoryRead = 0x2E, - TearingEffectLineOn = 0x35, - MemoryAccessControl = 0x36, - PixelFormatSet = 0x3A, - FrameRateControl = 0xC6 -}; +namespace Ion { +namespace Device { +namespace Display { +namespace Config { constexpr static GPIOPin FSMCPins[] = { GPIOPin(GPIOA, 2), GPIOPin(GPIOA, 3), GPIOPin(GPIOA, 4), GPIOPin(GPIOB, 12), @@ -91,18 +49,10 @@ constexpr static GPIOPin ResetPin = GPIOPin(GPIOE, 9); constexpr static GPIOPin ExtendedCommandPin = GPIOPin(GPIOB, 13); constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 10); -constexpr static int FSMCMemoryBank = 1; -constexpr static int FSMCDataCommandAddressBit = 16; - -constexpr static uint32_t FSMCBaseAddress = 0x60000000; -constexpr static uint32_t FSMCBankAddress = FSMCBaseAddress + (FSMCMemoryBank-1)*0x04000000; - constexpr static DMA DMAEngine = DMA2; constexpr static int DMAStream = 0; -static volatile Command * const CommandAddress = (Command *)(FSMCBankAddress); -static volatile uint16_t * const DataAddress = (uint16_t *)(FSMCBankAddress | (1<<(FSMCDataCommandAddressBit+1))); - +} } } } diff --git a/ion/src/device/n0100/drivers/config/external_flash.h b/ion/src/device/n0100/drivers/config/external_flash.h new file mode 100644 index 000000000..c9c656817 --- /dev/null +++ b/ion/src/device/n0100/drivers/config/external_flash.h @@ -0,0 +1,31 @@ +#ifndef ION_DEVICE_N0100_CONFIG_EXTERNAL_FLASH_H +#define ION_DEVICE_N0100_CONFIG_EXTERNAL_FLASH_H + +#include + +/* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS + * PC8 | 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 { + +constexpr static GPIOPin QSPIPins[] = { + GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), + GPIOPin(GPIOC, 8), GPIOPin(GPIOD,13) +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/keyboard.h b/ion/src/device/n0100/drivers/config/keyboard.h similarity index 56% rename from ion/src/device/keyboard.h rename to ion/src/device/n0100/drivers/config/keyboard.h index fa69ab39f..329ac39e6 100644 --- a/ion/src/device/keyboard.h +++ b/ion/src/device/n0100/drivers/config/keyboard.h @@ -1,13 +1,7 @@ -#ifndef ION_DEVICE_KEYBOARD_H -#define ION_DEVICE_KEYBOARD_H +#ifndef ION_DEVICE_N0100_CONFIG_KEYBOARD_H +#define ION_DEVICE_N0100_CONFIG_KEYBOARD_H -#include -#include -#include "regs/regs.h" - -namespace Ion { -namespace Keyboard { -namespace Device { +#include /* Pin | Role | Mode * -----+-------------------+-------------------- @@ -28,8 +22,10 @@ namespace Device { * PE8 | Keyboard row I | Output, open drain */ -void init(); -void shutdown(); +namespace Ion { +namespace Device { +namespace Keyboard { +namespace Config { constexpr GPIO RowGPIO = GPIOE; constexpr uint8_t numberOfRows = 9; @@ -39,30 +35,7 @@ constexpr GPIO ColumnGPIO = GPIOC; constexpr uint8_t numberOfColumns = 6; constexpr uint8_t ColumnPins[numberOfColumns] = {0, 1, 2, 3, 4, 5}; -inline uint8_t rowForKey(Key key) { - return (int)key/numberOfColumns; } -inline uint8_t columnForKey(Key key) { - return (int)key%numberOfColumns; -} - -inline void activateRow(uint8_t row) { - /* In open-drain mode, a 0 in the register drives the pin low, and a 1 lets - * the pin floating (Hi-Z). So we want to set the current row to zero and all - * the others to 1. */ - uint16_t rowState = ~(1<setBitRange(9, 0, rowState); - - // TODO: 100 us seems to work, but wasn't really calculated - Timing::usleep(100); -} - -inline bool columnIsActive(uint8_t column) { - return !(Device::ColumnGPIO.IDR()->getBitRange(column,column)); -} - } } } diff --git a/ion/src/device/led.h b/ion/src/device/n0100/drivers/config/led.h similarity index 53% rename from ion/src/device/led.h rename to ion/src/device/n0100/drivers/config/led.h index fe46994d9..4c1f23e5c 100644 --- a/ion/src/device/led.h +++ b/ion/src/device/n0100/drivers/config/led.h @@ -1,11 +1,7 @@ -#ifndef ION_DEVICE_LED_H -#define ION_DEVICE_LED_H +#ifndef ION_DEVICE_N0100_CONFIG_LED_H +#define ION_DEVICE_N0100_CONFIG_LED_H -#include "regs/regs.h" - -namespace Ion { -namespace LED { -namespace Device { +#include /* Pin | Role | Mode | Function * -----+-------------------+-----------------------+---------- @@ -14,33 +10,16 @@ namespace Device { * PC7 | LED red | Alternate Function 2 | TIM3_CH2 */ -enum class Mode { - PWM, - Blink -}; - -enum class Color { - Red, - Green, - Blue -}; - -void init(); -void shutdown(); -void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period = 0); - -void initGPIO(); -void shutdownGPIO(); -void initTimer(); -void shutdownTimer(); +namespace Ion { +namespace Device { +namespace LED { +namespace Config { constexpr static GPIOPin RGBPins[] = { GPIOPin(GPIOC, 7), GPIOPin(GPIOB, 1), GPIOPin(GPIOB, 0) }; - -constexpr uint16_t PWMPeriod = 40000; - +} } } } diff --git a/ion/src/device/swd.h b/ion/src/device/n0100/drivers/config/swd.h similarity index 77% rename from ion/src/device/swd.h rename to ion/src/device/n0100/drivers/config/swd.h index 49ea414c8..45d7efc28 100644 --- a/ion/src/device/swd.h +++ b/ion/src/device/n0100/drivers/config/swd.h @@ -1,11 +1,7 @@ -#ifndef ION_DEVICE_SWD_H -#define ION_DEVICE_SWD_H +#ifndef ION_DEVICE_N0100_CONFIG_SWD_H +#define ION_DEVICE_N0100_CONFIG_SWD_H -#include "regs/regs.h" - -namespace Ion { -namespace SWD { -namespace Device { +#include /* Pin | Role | Mode * -----+-------------------+--------------------- @@ -14,8 +10,10 @@ namespace Device { * PB3 | SWO | Alternate Function 0 */ -void init(); -void shutdown(); +namespace Ion { +namespace Device { +namespace SWD { +namespace Config { constexpr static GPIOPin Pins[] = { GPIOPin(GPIOA, 13), GPIOPin(GPIOA, 14), GPIOPin(GPIOB, 3) @@ -24,5 +22,6 @@ constexpr static GPIOPin Pins[] = { } } } +} #endif diff --git a/ion/src/device/n0100/drivers/config/timing.h b/ion/src/device/n0100/drivers/config/timing.h new file mode 100644 index 000000000..71d6c3f3d --- /dev/null +++ b/ion/src/device/n0100/drivers/config/timing.h @@ -0,0 +1,24 @@ +#ifndef ION_DEVICE_N0100_CONFIG_TIMING_H +#define ION_DEVICE_N0100_CONFIG_TIMING_H + +#include + +namespace Ion { +namespace Device { +namespace Timing { +namespace Config { + +constexpr static int LoopsPerMillisecond = 8852; +constexpr static int LoopsPerMicrosecond = 9; +// CPU clock is 96 MHz, and systick clock source is divided by 8 +// To get 1 ms systick overflow we need to reset it to +// 96 000 000 (Hz) / 8 / 1 000 (ms/s) - 1 (because the counter resets *after* counting to 0) +constexpr static int SysTickPerMillisecond = 12000; + + +} +} +} +} + +#endif diff --git a/ion/src/device/n0100/drivers/config/usb.h b/ion/src/device/n0100/drivers/config/usb.h new file mode 100644 index 000000000..cc50e2599 --- /dev/null +++ b/ion/src/device/n0100/drivers/config/usb.h @@ -0,0 +1,20 @@ +#ifndef ION_DEVICE_N0100_CONFIG_USB_H +#define ION_DEVICE_N0100_CONFIG_USB_H + +#include + +namespace Ion { +namespace Device { +namespace USB { +namespace Config { + +constexpr static GPIOPin VbusPin = GPIOPin(GPIOA, 9); +constexpr static GPIOPin DmPin = GPIOPin(GPIOA, 11); +constexpr static GPIOPin DpPin = GPIOPin(GPIOA, 12); + +} +} +} +} + +#endif diff --git a/ion/src/device/boot/flash.ld b/ion/src/device/n0100/flash.ld similarity index 100% rename from ion/src/device/boot/flash.ld rename to ion/src/device/n0100/flash.ld diff --git a/ion/src/device/sd_card.cpp b/ion/src/device/sd_card.cpp deleted file mode 100644 index e677c63be..000000000 --- a/ion/src/device/sd_card.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "sd_card.h" -#include "regs/regs.h" -extern "C" { -#include -} - -namespace Ion { -namespace SDCard { -namespace Device { - -void init() { - initGPIO(); - initCard(); -} - -void initGPIO() { - // Configure GPIOs to use AF - GPIOA.MODER()->setMode(8, GPIO::MODER::Mode::AlternateFunction); - GPIOB.MODER()->setMode(4, GPIO::MODER::Mode::AlternateFunction); - GPIOB.MODER()->setMode(5, GPIO::MODER::Mode::AlternateFunction); - GPIOB.MODER()->setMode(15, GPIO::MODER::Mode::AlternateFunction); - GPIOC.MODER()->setMode(10, GPIO::MODER::Mode::AlternateFunction); - GPIOD.MODER()->setMode(2, GPIO::MODER::Mode::AlternateFunction); - - // More precisely, AF12 which correspond to SDIO alternate functions - GPIOA.AFR()->setAlternateFunction(8, GPIO::AFR::AlternateFunction::AF12); - GPIOB.AFR()->setAlternateFunction(4, GPIO::AFR::AlternateFunction::AF12); - GPIOB.AFR()->setAlternateFunction(5, GPIO::AFR::AlternateFunction::AF12); - GPIOB.AFR()->setAlternateFunction(15, GPIO::AFR::AlternateFunction::AF12); - GPIOC.AFR()->setAlternateFunction(10, GPIO::AFR::AlternateFunction::AF12); - GPIOD.AFR()->setAlternateFunction(2, GPIO::AFR::AlternateFunction::AF12); -} - -void initCard() { - - // Power on - SDIO.POWER()->setPWRCTRL(SDIO::POWER::PWRCTRL::On); - while (SDIO.POWER()->getPWRCTRL() != SDIO::POWER::PWRCTRL::On) { - } - - // Clock set - SDIO.CLKCR()->setCLKDIV(254); - SDIO.CLKCR()->setCLKEN(true); - - sendCommand(0, 0); - // CMD8 : 0b0001 = 2.7 - 3.6V - // 0xB7 = Pattern to see back in response - sendCommand(8, 0x1B7); - - assert(SDIO.RESP(1)->get() == 0x1B7); -} - -void sendCommand(uint32_t cmd, uint32_t arg) { - class SDIO::ICR icr(0); - icr.setCCRCFAILC(true); - icr.setCTIMEOUTC(true); - icr.setCMDRENDC(true); - icr.setCMDSENTC(true); - SDIO.ICR()->set(icr); - - SDIO.ARG()->set(arg); - - SDIO::CMD::WAITRESP responseType = SDIO::CMD::WAITRESP::Short; - switch (cmd) { - case 0: - responseType = SDIO::CMD::WAITRESP::None; - break; - case 2: - case 9: - case 10: - responseType = SDIO::CMD::WAITRESP::Long; - default: - break; - } - - class SDIO::CMD command(0); - command.setCMDINDEX(cmd); - command.setCPSMEN(true); - command.setWAITRESP(responseType); - SDIO.CMD()->set(command); - - if (responseType == SDIO::CMD::WAITRESP::None) { - // Wait for timeout or command sent - while (!SDIO.STA()->getCTIMEOUT() && !SDIO.STA()->getCMDSENT()) { - } - } else { - while (!SDIO.STA()->getCTIMEOUT() && !SDIO.STA()->getCMDREND() && !SDIO.STA()->getCCRCFAIL()) { - } - } - -} - -} -} -} diff --git a/ion/src/device/sd_card.h b/ion/src/device/sd_card.h deleted file mode 100644 index 1a2babfb7..000000000 --- a/ion/src/device/sd_card.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ION_DEVICE_SD_CARD_H -#define ION_DEVICE_SD_CARD_H - -extern "C" { -#include -} - -namespace Ion { -namespace SDCard { -namespace Device { - -/* Pin | Role | Mode | Function - * -----+-------------------+-----------------------+---------- - * PA8 | SDIO D1 | Alternate Function 12 | SDIO_D1 - * PB4 | SDIO D0 | Alternate Function 12 | SDIO_D0 - * PB5 | SDIO D3 | Alternate Function 12 | SDIO_D3 - * PB15 | SDIO CLK | Alternate Function 12 | SDIO_CK - * PC10 | SDIO D2 | Alternate Function 12 | SDIO_D2 - * PD2 | SDIO CMD | Alternate Function 12 | SDIO_CMD - */ - -void init(); -void initGPIO(); - -void initCard(); - -void sendCommand(uint32_t cmd, uint32_t arg); - -} -} -} - -#endif diff --git a/ion/src/device/shared/Makefile b/ion/src/device/shared/Makefile new file mode 100644 index 000000000..f0f076d04 --- /dev/null +++ b/ion/src/device/shared/Makefile @@ -0,0 +1,3 @@ +include ion/src/device/shared/boot/Makefile +include ion/src/device/shared/usb/Makefile +include ion/src/device/shared/drivers/Makefile diff --git a/ion/src/device/shared/boot/Makefile b/ion/src/device/shared/boot/Makefile new file mode 100644 index 000000000..6dbc4b08c --- /dev/null +++ b/ion/src/device/shared/boot/Makefile @@ -0,0 +1,4 @@ +objs += $(addprefix ion/src/device/shared/boot/, \ + isr.o \ + rt0.o \ +) diff --git a/ion/src/device/boot/isr.c b/ion/src/device/shared/boot/isr.c similarity index 100% rename from ion/src/device/boot/isr.c rename to ion/src/device/shared/boot/isr.c diff --git a/ion/src/device/boot/isr.h b/ion/src/device/shared/boot/isr.h similarity index 100% rename from ion/src/device/boot/isr.h rename to ion/src/device/shared/boot/isr.h diff --git a/ion/src/device/boot/rt0.cpp b/ion/src/device/shared/boot/rt0.cpp similarity index 92% rename from ion/src/device/boot/rt0.cpp rename to ion/src/device/shared/boot/rt0.cpp index e4ea05f0f..2e3dfacfc 100644 --- a/ion/src/device/boot/rt0.cpp +++ b/ion/src/device/shared/boot/rt0.cpp @@ -2,9 +2,9 @@ #include #include #include -#include "../device.h" -#include "../timing.h" -#include "../console.h" +#include "../drivers/board.h" +#include "../drivers/reset.h" +#include "../drivers/timing.h" typedef void (*cxx_constructor)(); @@ -23,7 +23,7 @@ void abort() { while (1) { } #else - Ion::Device::coreReset(); + Ion::Device::Reset::core(); #endif } @@ -59,7 +59,8 @@ void start() { /* Initialize the FPU as early as possible. * For example, static C++ objects are very likely to manipulate float values */ - Ion::Device::initFPU(); + // FIXME//TODO//Ion::Device::initFPU(); +#warning BOO #if 0 Ion::Device::initMPU(); @@ -85,7 +86,7 @@ void start() { } #endif - Ion::Device::init(); + Ion::Device::Board::init(); non_inlined_ion_main(); @@ -93,5 +94,7 @@ void start() { } void __attribute__((interrupt)) isr_systick() { - Ion::Timing::Device::MillisElapsed++; +#warning BOO + // FIXME: TODO// + //Ion::Timing::Device::MillisElapsed++; } diff --git a/ion/src/device/shared/drivers/Makefile b/ion/src/device/shared/drivers/Makefile new file mode 100644 index 000000000..4cf8c19e0 --- /dev/null +++ b/ion/src/device/shared/drivers/Makefile @@ -0,0 +1,22 @@ +ion_device_objs += $(addprefix ion/src/device/shared/drivers/, \ + backlight.o \ + battery.o \ + base64.o \ + board.o \ + console.o \ + crc32.o \ + display.o \ + events.o \ + external_flash.o \ + flash.o \ + keyboard.o \ + led.o \ + power.o\ + random.o\ + reset.o \ + serial_number.o \ + swd.o \ + timing.o \ + usb.o \ + wakeup.o \ +) diff --git a/ion/src/device/backlight.cpp b/ion/src/device/shared/drivers/backlight.cpp similarity index 61% rename from ion/src/device/backlight.cpp rename to ion/src/device/shared/drivers/backlight.cpp index efcd49b9d..e60617598 100644 --- a/ion/src/device/backlight.cpp +++ b/ion/src/device/shared/drivers/backlight.cpp @@ -1,6 +1,7 @@ -#include -#include "regs/regs.h" #include "backlight.h" +#include +#include +#include /* This driver controls the RT9365 LED driver. * This chip allows the brightness to be set to 16 different values. It starts @@ -13,12 +14,14 @@ namespace Ion { namespace Backlight { +using namespace Ion::Device::Backlight; + void setBrightness(uint8_t b) { - Device::setLevel(b >> 4); + setLevel(b >> 4); } uint8_t brightness() { - return Device::level() << 4; + return level() << 4; } } @@ -27,29 +30,29 @@ uint8_t brightness() { // Private Ion::Backlight::Device methods namespace Ion { -namespace Backlight { namespace Device { +namespace Backlight { static uint8_t sLevel; void init() { - GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Output); + Config::BacklightPin.group().MODER()->setMode(Config::BacklightPin.pin(), GPIO::MODER::Mode::Output); sLevel = 0xF; resume(); } void shutdown() { - GPIOC.MODER()->setMode(6, GPIO::MODER::Mode::Analog); - GPIOC.PUPDR()->setPull(6, GPIO::PUPDR::Pull::None); + Config::BacklightPin.group().MODER()->setMode(Config::BacklightPin.pin(), GPIO::MODER::Mode::Analog); + Config::BacklightPin.group().PUPDR()->setPull(Config::BacklightPin.pin(), GPIO::PUPDR::Pull::None); } void suspend() { - GPIOC.ODR()->set(6, false); + Config::BacklightPin.group().ODR()->set(Config::BacklightPin.pin(), false); Timing::msleep(3); // Might not need to be blocking } void resume() { - GPIOC.ODR()->set(6, true); + Config::BacklightPin.group().ODR()->set(Config::BacklightPin.pin(), true); Timing::usleep(50); uint8_t level = sLevel; sLevel = 0xF; @@ -73,9 +76,9 @@ uint8_t level() { void sendPulses(int n) { for (int i=0; iset(6, false); + Config::BacklightPin.group().ODR()->set(Config::BacklightPin.pin(), false); Timing::usleep(20); - GPIOC.ODR()->set(6, true); + Config::BacklightPin.group().ODR()->set(Config::BacklightPin.pin(), true); Timing::usleep(20); } } diff --git a/ion/src/device/shared/drivers/backlight.h b/ion/src/device/shared/drivers/backlight.h new file mode 100644 index 000000000..439ed2413 --- /dev/null +++ b/ion/src/device/shared/drivers/backlight.h @@ -0,0 +1,24 @@ +#ifndef ION_DEVICE_SHARED_BACKLIGHT_H +#define ION_DEVICE_SHARED_BACKLIGHT_H + +#include + +namespace Ion { +namespace Device { +namespace Backlight { + +void init(); +void shutdown(); +void suspend(); +void resume(); + +uint8_t level(); +void setLevel(uint8_t level); + +void sendPulses(int n); + +} +} +} + +#endif diff --git a/ion/src/device/base64.cpp b/ion/src/device/shared/drivers/base64.cpp similarity index 100% rename from ion/src/device/base64.cpp rename to ion/src/device/shared/drivers/base64.cpp diff --git a/ion/src/device/base64.h b/ion/src/device/shared/drivers/base64.h similarity index 100% rename from ion/src/device/base64.h rename to ion/src/device/shared/drivers/base64.h diff --git a/ion/src/device/battery.cpp b/ion/src/device/shared/drivers/battery.cpp similarity index 68% rename from ion/src/device/battery.cpp rename to ion/src/device/shared/drivers/battery.cpp index f3ed16364..1ef34b76f 100644 --- a/ion/src/device/battery.cpp +++ b/ion/src/device/shared/drivers/battery.cpp @@ -1,6 +1,6 @@ -#include #include "battery.h" -#include "regs/regs.h" +#include +#include /* To measure the battery voltage, we're using the internal ADC. The ADC works * by comparing the input voltage to a reference voltage. The only fixed voltage @@ -10,11 +10,14 @@ * To avoid draining the battery, we're using an high-impedence voltage divider, * so we need to be careful when sampling the ADC. See AN2834 for more info. */ + namespace Ion { namespace Battery { +using namespace Ion::Device::Battery; + bool isCharging() { - return !Device::ChargingGPIO.IDR()->get(Device::ChargingPin); + return !Config::ChargingPin.group().IDR()->get(Config::ChargingPin.pin()); } Charge level() { @@ -37,22 +40,22 @@ float voltage() { uint16_t value = ADC.DR()->get(); // The ADC is 12 bits by default - return Device::ADCDividerBridgeRatio*(Device::ADCReferenceVoltage * value)/0xFFF; + return Config::ADCDividerBridgeRatio*(Config::ADCReferenceVoltage * value)/0xFFF; } } } namespace Ion { -namespace Battery { namespace Device { +namespace Battery { void init() { initGPIO(); /* The BAT_SNS pin is connected to Vbat through a divider bridge. It therefore * has a voltage of Vbat/2. We'll measure this using ADC channel 0. */ - ADCGPIO.MODER()->setMode(ADCPin, GPIO::MODER::Mode::Analog); + Config::ADCPin.group().MODER()->setMode(Config::ADCPin.pin(), GPIO::MODER::Mode::Analog); // Step 2 - Enable the ADC RCC.APB2ENR()->setADC1EN(true); @@ -60,8 +63,8 @@ void init() { // Configure the ADC channel ADC.SQR1()->setL(0); // Always sample the same channel - ADC.SQR3()->setSQ1(ADCChannel); - ADC.SMPR()->setSamplingTime(ADCChannel, ADC::SMPR::SamplingTime::Cycles480); // Use the max sampling time + ADC.SQR3()->setSQ1(Config::ADCChannel); + ADC.SMPR()->setSamplingTime(Config::ADCChannel, ADC::SMPR::SamplingTime::Cycles480); // Use the max sampling time } void initGPIO() { @@ -70,13 +73,13 @@ void initGPIO() { * open-drain output. Open-drain output are either connected to ground or left * floating. To interact with such an output, our input must therefore be * pulled up. */ - ChargingGPIO.MODER()->setMode(ChargingPin, GPIO::MODER::Mode::Input); - ChargingGPIO.PUPDR()->setPull(ChargingPin, GPIO::PUPDR::Pull::Up); + Config::ChargingPin.group().MODER()->setMode(Config::ChargingPin.pin(), GPIO::MODER::Mode::Input); + Config::ChargingPin.group().PUPDR()->setPull(Config::ChargingPin.pin(), GPIO::PUPDR::Pull::Up); } void shutdown() { - ChargingGPIO.MODER()->setMode(ChargingPin, GPIO::MODER::Mode::Analog); - ChargingGPIO.PUPDR()->setPull(ChargingPin, GPIO::PUPDR::Pull::None); + Config::ChargingPin.group().MODER()->setMode(Config::ChargingPin.pin(), GPIO::MODER::Mode::Analog); + Config::ChargingPin.group().PUPDR()->setPull(Config::ChargingPin.pin(), GPIO::PUPDR::Pull::None); // Disable the ADC ADC.CR2()->setADON(false); diff --git a/ion/src/device/shared/drivers/battery.h b/ion/src/device/shared/drivers/battery.h new file mode 100644 index 000000000..cf3d1607a --- /dev/null +++ b/ion/src/device/shared/drivers/battery.h @@ -0,0 +1,17 @@ +#ifndef ION_DEVICE_SHARED_BATTERY_H +#define ION_DEVICE_SHARED_BATTERY_H + +namespace Ion { +namespace Device { +namespace Battery { + +void init(); +void shutdown(); +void initGPIO(); +void initADC(); + +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp new file mode 100644 index 000000000..da578cc65 --- /dev/null +++ b/ion/src/device/shared/drivers/board.cpp @@ -0,0 +1,59 @@ +#include "board.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ion { +namespace Device { +namespace Board { + +void shutdown() { + shutdownPeripherals(); + shutdownClocks(); +} + +void initFPU() { +// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BABDBFBJ.html + CM4.CPACR()->setAccess(10, CM4::CPACR::Access::Full); + CM4.CPACR()->setAccess(11, CM4::CPACR::Access::Full); + // FIXME: The pipeline should be flushed at this point +} + +void initPeripherals() { + Display::init(); + Backlight::init(); + Keyboard::init(); + LED::init(); + Battery::init(); + USB::init(); + Console::init(); + SWD::init(); + Timing::init(); + ExternalFlash::init(); +} + +void shutdownPeripherals(bool keepLEDAwake) { + Timing::shutdown(); + SWD::shutdown(); + Console::shutdown(); + USB::shutdown(); + Battery::shutdown(); + if (!keepLEDAwake) { + LED::shutdown(); + } + Keyboard::shutdown(); + Backlight::shutdown(); + Display::shutdown(); +} + +} +} +} diff --git a/ion/src/device/shared/drivers/board.h b/ion/src/device/shared/drivers/board.h new file mode 100644 index 000000000..aa0b055d6 --- /dev/null +++ b/ion/src/device/shared/drivers/board.h @@ -0,0 +1,21 @@ +#ifndef ION_DEVICE_SHARED_DRIVERS_BOARD_H +#define ION_DEVICE_SHARED_DRIVERS_BOARD_H + +namespace Ion { +namespace Device { +namespace Board { + +void init(); +void shutdown(); + +void initClocks(); +void shutdownClocks(bool keepLEDAwake = false); + +void initPeripherals(); +void shutdownPeripherals(bool keepLEDAwake = false); + +} +} +} + +#endif diff --git a/ion/src/device/console.cpp b/ion/src/device/shared/drivers/console.cpp similarity index 53% rename from ion/src/device/console.cpp rename to ion/src/device/shared/drivers/console.cpp index 15ba0b275..3b55443d9 100644 --- a/ion/src/device/console.cpp +++ b/ion/src/device/shared/drivers/console.cpp @@ -1,5 +1,7 @@ -#include #include "console.h" +#include +#include +#include /* This file implements a serial console. * We use a 115200 8N1 serial port */ @@ -7,36 +9,38 @@ namespace Ion { namespace Console { +using namespace Ion::Device::Console; + char readChar() { - while (Device::UARTPort.SR()->getRXNE() == 0) { + while (Config::UARTPort.SR()->getRXNE() == 0) { } - return (char)Device::UARTPort.DR()->get(); + return (char)Config::UARTPort.DR()->get(); } void writeChar(char c) { - while (Device::UARTPort.SR()->getTXE() == 0) { + while (Config::UARTPort.SR()->getTXE() == 0) { } - Device::UARTPort.DR()->set(c); + Config::UARTPort.DR()->set(c); } } } namespace Ion { -namespace Console { namespace Device { +namespace Console { void init() { RCC.APB1ENR()->setUSART3EN(true); - for(const GPIOPin & g : Pins) { + for(const GPIOPin & g : Config::Pins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF7); } - UARTPort.CR1()->setUE(true); - UARTPort.CR1()->setTE(true); - UARTPort.CR1()->setRE(true); + Config::UARTPort.CR1()->setUE(true); + Config::UARTPort.CR1()->setTE(true); + Config::UARTPort.CR1()->setRE(true); /* We need to set the baud rate of the UART port. * This is set relative to the APB1 clock, which runs at 48 MHz. @@ -49,28 +53,27 @@ void init() { * DIV_MANTISSA = 26 * DIV_FRAC = 16*0.0416667 = 1 */ - UARTPort.BRR()->setDIV_MANTISSA(26); - UARTPort.BRR()->setDIV_FRAC(1); + Config::UARTPort.BRR()->setDIV_MANTISSA(26); + Config::UARTPort.BRR()->setDIV_FRAC(1); } void shutdown() { - for(const GPIOPin & g : Pins) { + for(const GPIOPin & g : Config::Pins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); } } bool peerConnected() { - RxPin.group().PUPDR()->setPull(RxPin.pin(), GPIO::PUPDR::Pull::Down); - RxPin.group().MODER()->setMode(RxPin.pin(), GPIO::MODER::Mode::Input); + Config::RxPin.group().PUPDR()->setPull(Config::RxPin.pin(), GPIO::PUPDR::Pull::Down); + Config::RxPin.group().MODER()->setMode(Config::RxPin.pin(), GPIO::MODER::Mode::Input); Timing::msleep(1); - bool result = RxPin.group().IDR()->get(RxPin.pin()); - RxPin.group().PUPDR()->setPull(RxPin.pin(), GPIO::PUPDR::Pull::None); - RxPin.group().MODER()->setMode(RxPin.pin(), GPIO::MODER::Mode::AlternateFunction); + bool result = Config::RxPin.group().IDR()->get(Config::RxPin.pin()); + Config::RxPin.group().PUPDR()->setPull(Config::RxPin.pin(), GPIO::PUPDR::Pull::None); + Config::RxPin.group().MODER()->setMode(Config::RxPin.pin(), GPIO::MODER::Mode::AlternateFunction); return result; } - } } } diff --git a/ion/src/device/shared/drivers/console.h b/ion/src/device/shared/drivers/console.h new file mode 100644 index 000000000..15f8004cf --- /dev/null +++ b/ion/src/device/shared/drivers/console.h @@ -0,0 +1,16 @@ +#ifndef ION_DEVICE_SHARED_CONSOLE_H +#define ION_DEVICE_SHARED_CONSOLE_H + +namespace Ion { +namespace Device { +namespace Console { + +void init(); +void shutdown(); +bool peerConnected(); + +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/crc32.cpp b/ion/src/device/shared/drivers/crc32.cpp new file mode 100644 index 000000000..fc2b418a7 --- /dev/null +++ b/ion/src/device/shared/drivers/crc32.cpp @@ -0,0 +1,17 @@ +#include +#include + +uint32_t Ion::crc32(const uint32_t * data, size_t length) { + bool initialCRCEngineState = RCC.AHB1ENR()->getCRCEN(); + RCC.AHB1ENR()->setCRCEN(true); + CRC.CR()->setRESET(true); + + const uint32_t * end = data + length; + while (data < end) { + CRC.DR()->set(*data++); + } + + uint32_t result = CRC.DR()->get(); + RCC.AHB1ENR()->setCRCEN(initialCRCEngineState); + return result; +} diff --git a/ion/src/device/display.cpp b/ion/src/device/shared/drivers/display.cpp similarity index 74% rename from ion/src/device/display.cpp rename to ion/src/device/shared/drivers/display.cpp index 1804a7b19..a187f6f8e 100644 --- a/ion/src/device/display.cpp +++ b/ion/src/device/shared/drivers/display.cpp @@ -1,9 +1,8 @@ -#include #include "display.h" -#include "regs/regs.h" -extern "C" { +#include +#include +#include #include -} /* This driver interfaces with the ST7789V LCD controller. * This chip keeps a whole frame in SRAM memory and feeds it to the LCD panel as @@ -16,42 +15,42 @@ extern "C" { #define USE_DMA (USE_DMA_FOR_PUSH_PIXELS|USE_DMA_FOR_PUSH_COLOR) -// Public Ion::Display methods - namespace Ion { namespace Display { +using namespace Ion::Device::Display; + void pushRect(KDRect r, const KDColor * pixels) { #if USE_DMA - Device::waitForPendingDMAUploadCompletion(); + waitForPendingDMAUploadCompletion(); #endif - Device::setDrawingArea(r, Device::Orientation::Landscape); - Device::pushPixels(pixels, r.width()*r.height()); + setDrawingArea(r, Orientation::Landscape); + pushPixels(pixels, r.width()*r.height()); } void pushRectUniform(KDRect r, KDColor c) { #if USE_DMA - Device::waitForPendingDMAUploadCompletion(); + waitForPendingDMAUploadCompletion(); #endif - Device::setDrawingArea(r, Device::Orientation::Portrait); - Device::pushColor(c, r.width()*r.height()); + setDrawingArea(r, Orientation::Portrait); + pushColor(c, r.width()*r.height()); } void pullRect(KDRect r, KDColor * pixels) { #if USE_DMA - Device::waitForPendingDMAUploadCompletion(); + waitForPendingDMAUploadCompletion(); #endif - Device::setDrawingArea(r, Device::Orientation::Landscape); - Device::pullPixels(pixels, r.width()*r.height()); + setDrawingArea(r, Orientation::Landscape); + pullPixels(pixels, r.width()*r.height()); } void waitForVBlank() { // We want to return as soon as the TE line is transitionning from "DOWN" to "UP" - while (Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) { + while (Config::TearingEffectPin.group().IDR()->get(Config::TearingEffectPin.pin())) { // Loop while high, exit when low // Wait for zero } - while (!Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) { + while (!Config::TearingEffectPin.group().IDR()->get(Config::TearingEffectPin.pin())) { // Loop while low, exit when high } } @@ -59,11 +58,9 @@ void waitForVBlank() { } } -// Private Ion::Display::Device methods - namespace Ion { -namespace Display { namespace Device { +namespace Display { static inline void send_data(uint16_t d) { *DataAddress = d; @@ -107,82 +104,81 @@ void shutdown() { #if USE_DMA void initDMA() { // Only DMA2 can perform memory-to-memory transfers - //assert(DMAEngine == DMA2); + //assert(Config::DMAEngine == DMA2); /* In memory-to-memory transfers, the "peripheral" is the source and the * "memory" is the destination. In other words, memory is copied from address * DMA_SxPAR to address DMA_SxM0AR. */ - DMAEngine.SCR(DMAStream)->setDIR(DMA::SCR::Direction::MemoryToMemory); - DMAEngine.SM0AR(DMAStream)->set((uint32_t)DataAddress); - DMAEngine.SCR(DMAStream)->setMSIZE(DMA::SCR::DataSize::HalfWord); - DMAEngine.SCR(DMAStream)->setPSIZE(DMA::SCR::DataSize::HalfWord); - DMAEngine.SCR(DMAStream)->setMBURST(DMA::SCR::Burst::Incremental4); - DMAEngine.SCR(DMAStream)->setPBURST(DMA::SCR::Burst::Incremental4); - DMAEngine.SCR(DMAStream)->setMINC(false); + Config::DMAEngine.SCR(Config::DMAStream)->setDIR(DMA::SCR::Direction::MemoryToMemory); + Config::DMAEngine.SM0AR(Config::DMAStream)->set((uint32_t)DataAddress); + Config::DMAEngine.SCR(Config::DMAStream)->setMSIZE(DMA::SCR::DataSize::HalfWord); + Config::DMAEngine.SCR(Config::DMAStream)->setPSIZE(DMA::SCR::DataSize::HalfWord); + Config::DMAEngine.SCR(Config::DMAStream)->setMBURST(DMA::SCR::Burst::Incremental4); + Config::DMAEngine.SCR(Config::DMAStream)->setPBURST(DMA::SCR::Burst::Incremental4); + Config::DMAEngine.SCR(Config::DMAStream)->setMINC(false); } void waitForPendingDMAUploadCompletion() { // Loop until DMA engine available - while (DMAEngine.SCR(DMAStream)->getEN()) { + while (Config::DMAEngine.SCR(Config::DMAStream)->getEN()) { } } static inline void startDMAUpload(const KDColor * src, bool incrementSrc, uint16_t length) { // Reset interruption markers - DMAEngine.LIFCR()->set(0xF7D0F7D); + Config::DMAEngine.LIFCR()->set(0xF7D0F7D); - DMAEngine.SNDTR(DMAStream)->set(length); - DMAEngine.SPAR(DMAStream)->set((uint32_t)src); - DMAEngine.SCR(DMAStream)->setPINC(incrementSrc); - DMAEngine.SCR(DMAStream)->setEN(true); + Config::DMAEngine.SNDTR(Config::DMAStream)->set(length); + Config::DMAEngine.SPAR(Config::DMAStream)->set((uint32_t)src); + Config::DMAEngine.SCR(Config::DMAStream)->setPINC(incrementSrc); + Config::DMAEngine.SCR(Config::DMAStream)->setEN(true); } #endif void initGPIO() { // All the FSMC GPIO pins use the alternate function number 12 - for(const GPIOPin & g : FSMCPins) { + for(const GPIOPin & g : Config::FSMCPins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF12); } // Turn on the power - PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Output); - PowerPin.group().ODR()->set(PowerPin.pin(), true); + Config::PowerPin.group().MODER()->setMode(Config::PowerPin.pin(), GPIO::MODER::Mode::Output); + Config::PowerPin.group().ODR()->set(Config::PowerPin.pin(), true); // Turn on the reset pin - ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Output); - ResetPin.group().ODR()->set(ResetPin.pin(), true); + Config::ResetPin.group().MODER()->setMode(Config::ResetPin.pin(), GPIO::MODER::Mode::Output); + Config::ResetPin.group().ODR()->set(Config::ResetPin.pin(), true); // Turn on the extended command pin - ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Output); - ExtendedCommandPin.group().ODR()->set(ExtendedCommandPin.pin(), true); + Config::ExtendedCommandPin.group().MODER()->setMode(Config::ExtendedCommandPin.pin(), GPIO::MODER::Mode::Output); + Config::ExtendedCommandPin.group().ODR()->set(Config::ExtendedCommandPin.pin(), true); // Turn on the Tearing Effect pin - TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Input); - TearingEffectPin.group().PUPDR()->setPull(TearingEffectPin.pin(), GPIO::PUPDR::Pull::None); + Config::TearingEffectPin.group().MODER()->setMode(Config::TearingEffectPin.pin(), GPIO::MODER::Mode::Input); + Config::TearingEffectPin.group().PUPDR()->setPull(Config::TearingEffectPin.pin(), GPIO::PUPDR::Pull::None); Timing::msleep(120); } - void shutdownGPIO() { // All the FSMC GPIO pins use the alternate function number 12 - for(const GPIOPin & g : FSMCPins) { + for(const GPIOPin & g : Config::FSMCPins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); } - ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Analog); - ResetPin.group().PUPDR()->setPull(ResetPin.pin(), GPIO::PUPDR::Pull::None); + Config::ResetPin.group().MODER()->setMode(Config::ResetPin.pin(), GPIO::MODER::Mode::Analog); + Config::ResetPin.group().PUPDR()->setPull(Config::ResetPin.pin(), GPIO::PUPDR::Pull::None); - PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Analog); - PowerPin.group().PUPDR()->setPull(PowerPin.pin(), GPIO::PUPDR::Pull::None); + Config::PowerPin.group().MODER()->setMode(Config::PowerPin.pin(), GPIO::MODER::Mode::Analog); + Config::PowerPin.group().PUPDR()->setPull(Config::PowerPin.pin(), GPIO::PUPDR::Pull::None); - ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Analog); - ExtendedCommandPin.group().PUPDR()->setPull(ExtendedCommandPin.pin(), GPIO::PUPDR::Pull::None); + Config::ExtendedCommandPin.group().MODER()->setMode(Config::ExtendedCommandPin.pin(), GPIO::MODER::Mode::Analog); + Config::ExtendedCommandPin.group().PUPDR()->setPull(Config::ExtendedCommandPin.pin(), GPIO::PUPDR::Pull::None); - TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Analog); + Config::TearingEffectPin.group().MODER()->setMode(Config::TearingEffectPin.pin(), GPIO::MODER::Mode::Analog); } void initFSMC() { diff --git a/ion/src/device/shared/drivers/display.h b/ion/src/device/shared/drivers/display.h new file mode 100644 index 000000000..de4a972c5 --- /dev/null +++ b/ion/src/device/shared/drivers/display.h @@ -0,0 +1,67 @@ +#ifndef ION_DEVICE_SHARED_DISPLAY_H +#define ION_DEVICE_SHARED_DISPLAY_H + +#include +#include +extern "C" { +#include +} + +namespace Ion { +namespace Device { +namespace Display { + +void init(); +void shutdown(); + +void initDMA(); +void initGPIO(); +void shutdownGPIO(); +void initFSMC(); +void shutdownFSMC(); +void initPanel(); +void shutdownPanel(); + +enum class Orientation { + Landscape = 0, + Portrait = 1 +}; + +void setDrawingArea(KDRect r, Orientation o); +void waitForPendingDMAUploadCompletion(); +void pushPixels(const KDColor * pixels, size_t numberOfPixels); +void pushColor(KDColor color, size_t numberOfPixels); +void pullPixels(KDColor * pixels, size_t numberOfPixels); + +enum class Command : uint16_t { + Nop = 0x00, + Reset = 0x01, + SleepIn = 0x10, + SleepOut = 0x11, + DisplayOff = 0x28, + DisplayOn = 0x29, + ColumnAddressSet = 0x2A, + PageAddressSet = 0x2B, + MemoryWrite = 0x2C, + MemoryRead = 0x2E, + TearingEffectLineOn = 0x35, + MemoryAccessControl = 0x36, + PixelFormatSet = 0x3A, + FrameRateControl = 0xC6 +}; + +constexpr static int FSMCMemoryBank = 1; +constexpr static int FSMCDataCommandAddressBit = 16; + +constexpr static uint32_t FSMCBaseAddress = 0x60000000; +constexpr static uint32_t FSMCBankAddress = FSMCBaseAddress + (FSMCMemoryBank-1)*0x04000000; + + +static volatile Command * const CommandAddress = (Command *)(FSMCBankAddress); +static volatile uint16_t * const DataAddress = (uint16_t *)(FSMCBankAddress | (1<<(FSMCDataCommandAddressBit+1))); + +} +} +} + +#endif diff --git a/ion/src/device/events.cpp b/ion/src/device/shared/drivers/events.cpp similarity index 100% rename from ion/src/device/events.cpp rename to ion/src/device/shared/drivers/events.cpp diff --git a/ion/src/device/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp similarity index 99% rename from ion/src/device/external_flash.cpp rename to ion/src/device/shared/drivers/external_flash.cpp index 5de68b64c..deaaf6a78 100644 --- a/ion/src/device/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -1,8 +1,9 @@ #include "external_flash.h" +#include namespace Ion { -namespace ExternalFlash { namespace Device { +namespace ExternalFlash { /* The external flash and the Quad-SPI peripheral support * several operating modes, corresponding to different numbers of signals @@ -206,7 +207,7 @@ void init() { } void initGPIO() { - for(const GPIOPin & g : QSPIPins) { + for(const GPIOPin & g : Config::QSPIPins) { g.group().OSPEEDR()->setOutputSpeed(g.pin(), GPIO::OSPEEDR::OutputSpeed::High); g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), (g.pin() == 6 ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9)); diff --git a/ion/src/device/external_flash.h b/ion/src/device/shared/drivers/external_flash.h similarity index 89% rename from ion/src/device/external_flash.h rename to ion/src/device/shared/drivers/external_flash.h index bde2678f8..38214db3a 100644 --- a/ion/src/device/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -1,9 +1,8 @@ -#ifndef ION_DEVICE_EXTERNAL_FLASH_H -#define ION_DEVICE_EXTERNAL_FLASH_H +#ifndef ION_DEVICE_SHARED_EXTERNAL_FLASH_H +#define ION_DEVICE_SHARED_EXTERNAL_FLASH_H -#include #include -#include "regs/regs.h" +#include // Quad-SPI on STM32 microcontroller // https://www.st.com/resource/en/application_note/dm00227538.pdf @@ -19,8 +18,8 @@ * 2^7 * 2 * 2^3 * 2^4 256B pages 0x....00 - 0x....FF */ namespace Ion { -namespace ExternalFlash { namespace Device { +namespace ExternalFlash { /* Pin | Role | Mode | Function * -----+----------------------+-----------------------+----------------- @@ -69,11 +68,6 @@ constexpr static uint8_t NumberOfAddressBitsInChip = 23; constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; -constexpr static GPIOPin QSPIPins[] = { - GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), - GPIOPin(GPIOC, 8), GPIOPin(GPIOD,13) -}; - } } } diff --git a/ion/src/device/flash.cpp b/ion/src/device/shared/drivers/flash.cpp similarity index 100% rename from ion/src/device/flash.cpp rename to ion/src/device/shared/drivers/flash.cpp index b5b0bae8e..bd071d1b4 100644 --- a/ion/src/device/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -2,8 +2,8 @@ #include namespace Ion { -namespace Flash { namespace Device { +namespace Flash { static inline void wait() { // Wait for pending Flash operations to complete diff --git a/ion/src/device/flash.h b/ion/src/device/shared/drivers/flash.h similarity index 84% rename from ion/src/device/flash.h rename to ion/src/device/shared/drivers/flash.h index 6d8c9b657..cc01d96a4 100644 --- a/ion/src/device/flash.h +++ b/ion/src/device/shared/drivers/flash.h @@ -1,12 +1,12 @@ -#ifndef ION_DEVICE_FLASH_H -#define ION_DEVICE_FLASH_H +#ifndef ION_DEVICE_SHARED_FLASH_H +#define ION_DEVICE_SHARED_FLASH_H #include -#include "regs/flash.h" +#include namespace Ion { -namespace Flash { namespace Device { +namespace Flash { void MassErase(); diff --git a/ion/src/device/keyboard.cpp b/ion/src/device/shared/drivers/keyboard.cpp similarity index 71% rename from ion/src/device/keyboard.cpp rename to ion/src/device/shared/drivers/keyboard.cpp index 91c634dae..692bf25ed 100644 --- a/ion/src/device/keyboard.cpp +++ b/ion/src/device/shared/drivers/keyboard.cpp @@ -42,19 +42,19 @@ #include "keyboard.h" -// Public Ion::Keyboard methods - namespace Ion { namespace Keyboard { +using namespace Ion::Device::Keyboard; + State scan() { uint64_t state = 0; - for (uint8_t i=0; igetBitRange(5,0); + uint8_t columns = Config::ColumnGPIO.IDR()->getBitRange(5,0); /* The key is down if the input is brought low by the output. In other * words, we want to return true if the input is low (false). So we need to @@ -73,37 +73,35 @@ State scan() { } } -// Private Ion::Keyboard::Device methods - namespace Ion { -namespace Keyboard { namespace Device { +namespace Keyboard { void init() { - for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Output); - RowGPIO.OTYPER()->setType(pin, GPIO::OTYPER::Type::OpenDrain); + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Output); + Config::RowGPIO.OTYPER()->setType(pin, GPIO::OTYPER::Type::OpenDrain); } - for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Input); - ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::Up); + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Input); + Config::ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::Up); } } void shutdown() { - for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Analog); - RowGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None); + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Analog); + Config::RowGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None); } - for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Analog); - ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None); + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Analog); + Config::ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None); } } diff --git a/ion/src/device/shared/drivers/keyboard.h b/ion/src/device/shared/drivers/keyboard.h new file mode 100644 index 000000000..33466a389 --- /dev/null +++ b/ion/src/device/shared/drivers/keyboard.h @@ -0,0 +1,45 @@ +#ifndef ION_DEVICE_SHARED_KEYBOARD_H +#define ION_DEVICE_SHARED_KEYBOARD_H + +#include +#include +#include + +namespace Ion { +namespace Device { +namespace Keyboard { + +using namespace Ion::Keyboard; + +void init(); +void shutdown(); + +inline uint8_t rowForKey(Key key) { + return (int)key/Config::numberOfColumns; +} +inline uint8_t columnForKey(Key key) { + return (int)key%Config::numberOfColumns; +} + +inline void activateRow(uint8_t row) { + /* In open-drain mode, a 0 in the register drives the pin low, and a 1 lets + * the pin floating (Hi-Z). So we want to set the current row to zero and all + * the others to 1. */ + uint16_t rowState = ~(1<setBitRange(9, 0, rowState); + + // TODO: 100 us seems to work, but wasn't really calculated + Timing::usleep(100); +} + +inline bool columnIsActive(uint8_t column) { + return !(Config::ColumnGPIO.IDR()->getBitRange(column,column)); +} + +} +} +} + +#endif diff --git a/ion/src/device/led.cpp b/ion/src/device/shared/drivers/led.cpp similarity index 87% rename from ion/src/device/led.cpp rename to ion/src/device/shared/drivers/led.cpp index 028e8e846..1302ecb95 100644 --- a/ion/src/device/led.cpp +++ b/ion/src/device/shared/drivers/led.cpp @@ -1,18 +1,19 @@ -#include -#include -#include "device.h" #include "led.h" -#include "regs/regs.h" - -// Public Ion::LED methods +#include +#include static KDColor sLedColor = KDColorBlack; -KDColor Ion::LED::getColor() { +namespace Ion { +namespace LED { + +using namespace Ion::Device::LED; + +KDColor getColor() { return sLedColor; } -void Ion::LED::setColor(KDColor c) { +void setColor(KDColor c) { sLedColor = c; /* Active all RGB colors */ @@ -22,10 +23,10 @@ void Ion::LED::setColor(KDColor c) { /* Set the PWM duty cycles to display the right color */ constexpr float maxColorValue = (float)((1 << 8) -1); - Device::setPeriodAndDutyCycles(Device::Mode::PWM, c.red()/maxColorValue, c.green()/maxColorValue, c.blue()/maxColorValue); + setPeriodAndDutyCycles(Mode::PWM, c.red()/maxColorValue, c.green()/maxColorValue, c.blue()/maxColorValue); } -void Ion::LED::setBlinking(uint16_t period, float dutyCycle) { +void setBlinking(uint16_t period, float dutyCycle) { /* We want to use the PWM at a slow rate to display a seeable blink. * Consequently, we do not use PWM to display the right color anymore but to * blink. We cannot use the PWM to display the exact color so we 'project the @@ -34,14 +35,15 @@ void Ion::LED::setBlinking(uint16_t period, float dutyCycle) { TIM3.CCMR()->setOC4M(sLedColor.green() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); TIM3.CCMR()->setOC3M(sLedColor.blue() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); - Device::setPeriodAndDutyCycles(Device::Mode::Blink, dutyCycle, dutyCycle, dutyCycle, period); + setPeriodAndDutyCycles(Mode::Blink, dutyCycle, dutyCycle, dutyCycle, period); } -// Private Ion::Device::LED methods +} +} namespace Ion { -namespace LED { namespace Device { +namespace LED { void init() { initGPIO(); @@ -57,14 +59,14 @@ void initGPIO() { /* RED_LED(PC7), GREEN_LED(PB1), and BLUE_LED(PB0) are driven using a timer, * which is an alternate function. More precisely, we will use AF2, which maps * PB0 to TIM3_CH2, PB1 to TIM3_CH4, and PC7 to TIM3_CH2. */ - for(const GPIOPin & g : RGBPins) { + for(const GPIOPin & g : Config::RGBPins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF2); } } void shutdownGPIO() { - for(const GPIOPin & g : RGBPins) { + for(const GPIOPin & g : Config::RGBPins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); } diff --git a/ion/src/device/shared/drivers/led.h b/ion/src/device/shared/drivers/led.h new file mode 100644 index 000000000..13f4b6e6e --- /dev/null +++ b/ion/src/device/shared/drivers/led.h @@ -0,0 +1,36 @@ +#ifndef ION_DEVICE_SHARED_LED_H +#define ION_DEVICE_SHARED_LED_H + +#include + +namespace Ion { +namespace Device { +namespace LED { + +enum class Mode { + PWM, + Blink +}; + +enum class Color { + Red, + Green, + Blue +}; + +void init(); +void shutdown(); +void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period = 0); + +void initGPIO(); +void shutdownGPIO(); +void initTimer(); +void shutdownTimer(); + +constexpr uint16_t PWMPeriod = 40000; + +} +} +} + +#endif diff --git a/ion/src/device/power.cpp b/ion/src/device/shared/drivers/power.cpp similarity index 71% rename from ion/src/device/power.cpp rename to ion/src/device/shared/drivers/power.cpp index 021d75cc6..3e3aa29ef 100644 --- a/ion/src/device/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -1,14 +1,15 @@ -#include -#include "battery.h" -#include "device.h" -#include "display.h" -#include "keyboard.h" -#include "led.h" -#include "usb.h" -#include "wakeup.h" -#include "regs/regs.h" +#include +#include +#include +#include +#include +#include +#include -void Ion::Power::suspend(bool checkIfPowerKeyReleased) { +namespace Ion { +namespace Power { + +void suspend(bool checkIfPowerKeyReleased) { bool isLEDActive = Ion::LED::getColor() != KDColorBlack; if (checkIfPowerKeyReleased) { /* Wait until power is released to avoid restarting just after suspending */ @@ -18,7 +19,7 @@ void Ion::Power::suspend(bool checkIfPowerKeyReleased) { isPowerDown = scan.keyDown(Keyboard::Key::B2); } } - Device::shutdownPeripherals(isLEDActive); + Device::Board::shutdownPeripherals(isLEDActive); PWR.CR()->setLPDS(true); // Turn the regulator off. Takes longer to wake up. PWR.CR()->setFPDS(true); // Put the flash to sleep. Takes longer to wake up. @@ -33,13 +34,13 @@ void Ion::Power::suspend(bool checkIfPowerKeyReleased) { //Ion::LED::setCharging(Ion::USB::isPlugged(), Ion::Battery::isCharging()); #endif - WakeUp::Device::onPowerKeyDown(); - WakeUp::Device::onUSBPlugging(); + Device::WakeUp::onPowerKeyDown(); + Device::WakeUp::onUSBPlugging(); #if EPSILON_LED_WHILE_CHARGING - WakeUp::Device::onChargingEvent(); + Device::WakeUp::onChargingEvent(); #endif - Device::shutdownClocks(isLEDActive); + Device::Board::shutdownClocks(isLEDActive); /* To enter sleep, we need to issue a WFE instruction, which waits for the * event flag to be set and then clears it. However, the event flag might @@ -51,11 +52,11 @@ void Ion::Power::suspend(bool checkIfPowerKeyReleased) { asm("nop"); asm("wfe"); - Device::initClocks(); + Device::Board::initClocks(); - Keyboard::Device::init(); + Device::Keyboard::init(); Keyboard::State scan = Keyboard::scan(); - Keyboard::Device::shutdown(); + Device::Keyboard::shutdown(); Ion::Keyboard::State OnlyPowerKeyDown = Keyboard::State(Keyboard::Key::B2); if (scan == OnlyPowerKeyDown || USB::isPlugged()) { @@ -63,7 +64,10 @@ void Ion::Power::suspend(bool checkIfPowerKeyReleased) { break; } } - Device::initClocks(); + Device::Board::initClocks(); - Device::initPeripherals(); + Device::Board::initPeripherals(); +} + +} } diff --git a/ion/src/device/shared/drivers/random.cpp b/ion/src/device/shared/drivers/random.cpp new file mode 100644 index 000000000..6a218aa99 --- /dev/null +++ b/ion/src/device/shared/drivers/random.cpp @@ -0,0 +1,18 @@ +#include +#include + +uint32_t Ion::random() { + bool initialRNGEngineState = RCC.AHB2ENR()->getRNGEN(); + RCC.AHB2ENR()->setRNGEN(true); + + RNG.CR()->setRNGEN(true); + + while (RNG.SR()->getDRDY() == 0) { + } + uint32_t result = RNG.DR()->get(); + + RNG.CR()->setRNGEN(false); + RCC.AHB2ENR()->setRNGEN(initialRNGEngineState); + + return result; +} diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp new file mode 100644 index 000000000..6e64183f5 --- /dev/null +++ b/ion/src/device/shared/drivers/reset.cpp @@ -0,0 +1,32 @@ +#include "reset.h" +#include + +namespace Ion { +namespace Device { +namespace Reset { + +void core() { + // Perform a full core reset + CM4.AIRCR()->requestReset(); +} + +void jump() { + uint32_t * stackPointerAddress = reinterpret_cast(0x08000000); + uint32_t * resetHandlerAddress = reinterpret_cast(0x08000004); + + /* Jump to the reset service routine after having reset the stack pointer. + * Both addresses are fetched from the base of the Flash memory, just like a + * real reset would. These operations should be made at once, otherwise the C + * compiler might emit some instructions that modify the stack inbetween. */ + + asm volatile ( + "msr MSP, %[stackPointer] ; bx %[resetHandler]" + : : + [stackPointer] "r" (*stackPointerAddress), + [resetHandler] "r" (*resetHandlerAddress) + ); +} + +} +} +} diff --git a/ion/src/device/shared/drivers/reset.h b/ion/src/device/shared/drivers/reset.h new file mode 100644 index 000000000..552af934b --- /dev/null +++ b/ion/src/device/shared/drivers/reset.h @@ -0,0 +1,15 @@ +#ifndef ION_DEVICE_SHARED_RESET_H +#define ION_DEVICE_SHARED_RESET_H + +namespace Ion { +namespace Device { +namespace Reset { + +void core(); +void jump(); + +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/serial_number.cpp b/ion/src/device/shared/drivers/serial_number.cpp new file mode 100644 index 000000000..ea1d354fb --- /dev/null +++ b/ion/src/device/shared/drivers/serial_number.cpp @@ -0,0 +1,30 @@ +#include "serial_number.h" +#include "base64.h" + +namespace Ion { + +using namespace Ion::Device::SerialNumber; + +const char * serialNumber() { + static char serialNumber[Length + 1] = {0}; + if (serialNumber[0] == 0) { + copy(serialNumber); + } + return serialNumber; +} + +} + +namespace Ion { +namespace Device { +namespace SerialNumber { + +void copy(char * buffer) { + const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; + Base64::encode(rawUniqueID, 12, buffer); + buffer[Length] = 0; +} + +} +} +} diff --git a/ion/src/device/shared/drivers/serial_number.h b/ion/src/device/shared/drivers/serial_number.h new file mode 100644 index 000000000..a27ae12b5 --- /dev/null +++ b/ion/src/device/shared/drivers/serial_number.h @@ -0,0 +1,19 @@ +#ifndef ION_DEVICE_SHARED_SERIAL_NUMBER_H +#define ION_DEVICE_SHARED_SERIAL_NUMBER_H + +namespace Ion { +namespace Device { +namespace SerialNumber { + +/* The serial number is 96 bits long. That's equal to 16 digits in base 64. We + * expose a convenient "copySerialNumber" routine which can be called without + * using a static variable (and therefore without a .bss section). This is used + * in the RAM'ed DFU bootloader. */ +constexpr static int Length = 16; +void copy(char * buffer); + +} +} +} + +#endif diff --git a/ion/src/device/swd.cpp b/ion/src/device/shared/drivers/swd.cpp similarity index 78% rename from ion/src/device/swd.cpp rename to ion/src/device/shared/drivers/swd.cpp index 008dc8110..bedc55bc6 100644 --- a/ion/src/device/swd.cpp +++ b/ion/src/device/shared/drivers/swd.cpp @@ -1,19 +1,19 @@ #include "swd.h" -#include "regs/regs.h" +#include namespace Ion { -namespace SWD { namespace Device { +namespace SWD { void init() { - for(const GPIOPin & g : Pins) { + for(const GPIOPin & g : Config::Pins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF0); } } void shutdown() { - for(const GPIOPin & g : Pins) { + for(const GPIOPin & g : Config::Pins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); } diff --git a/ion/src/device/shared/drivers/swd.h b/ion/src/device/shared/drivers/swd.h new file mode 100644 index 000000000..d63b6185d --- /dev/null +++ b/ion/src/device/shared/drivers/swd.h @@ -0,0 +1,15 @@ +#ifndef ION_DEVICE_SHARED_SWD_H +#define ION_DEVICE_SHARED_SWD_H + +namespace Ion { +namespace Device { +namespace SWD { + +void init(); +void shutdown(); + +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/timing.cpp b/ion/src/device/shared/drivers/timing.cpp new file mode 100644 index 000000000..cdb911f22 --- /dev/null +++ b/ion/src/device/shared/drivers/timing.cpp @@ -0,0 +1,53 @@ +#include "timing.h" +#include +#include + +namespace Ion { +namespace Timing { + +using namespace Ion::Device::Timing; + +/* TODO: The delay methods 'msleep' and 'usleep' are currently dependent on the + * optimizations chosen by the compiler. To prevent that and to gain in + * precision, we could use the controller cycle counter (Systick). */ + +void msleep(uint32_t ms) { + for (volatile uint32_t i=0; isetRELOAD(Config::SysTickPerMillisecond - 1); // Remove 1 because the counter resets *after* counting to 0 + CM4.SYST_CVR()->setCURRENT(0); + CM4.SYST_CSR()->setCLKSOURCE(CM4::SYST_CSR::CLKSOURCE::AHB_DIV8); + CM4.SYST_CSR()->setTICKINT(true); + CM4.SYST_CSR()->setENABLE(true); +} + +void shutdown() { + CM4.SYST_CSR()->setENABLE(false); + CM4.SYST_CSR()->setTICKINT(false); +} + +} +} +} diff --git a/ion/src/device/shared/drivers/timing.h b/ion/src/device/shared/drivers/timing.h new file mode 100644 index 000000000..6013df519 --- /dev/null +++ b/ion/src/device/shared/drivers/timing.h @@ -0,0 +1,19 @@ +#ifndef ION_DEVICE_SHARED_TIMING_H +#define ION_DEVICE_SHARED_TIMING_H + +#include + +namespace Ion { +namespace Device { +namespace Timing { + +void init(); +void shutdown(); + +extern volatile uint64_t MillisElapsed; + +} +} +} + +#endif diff --git a/ion/src/device/usb.cpp b/ion/src/device/shared/drivers/usb.cpp similarity index 80% rename from ion/src/device/usb.cpp rename to ion/src/device/shared/drivers/usb.cpp index 1a76cc4e1..7cd581fa7 100644 --- a/ion/src/device/usb.cpp +++ b/ion/src/device/shared/drivers/usb.cpp @@ -1,16 +1,14 @@ -#include #include "usb.h" -#include -#include "device.h" -#include "display.h" -#include "regs/regs.h" -#include +#include +#include namespace Ion { namespace USB { +using namespace Ion::Device::USB; + bool isPlugged() { - return Device::VbusPin.group().IDR()->get(Device::VbusPin.pin()); + return Config::VbusPin.group().IDR()->get(Config::VbusPin.pin()); } bool isEnumerated() { @@ -38,8 +36,8 @@ void disable() { } namespace Ion { -namespace USB { namespace Device { +namespace USB { void init() { initGPIO(); @@ -69,22 +67,22 @@ void initGPIO() { * plugged, the pin must be pulled down. */ // FIXME: Understand how the Vbus pin really works! #if 0 - VbusPin.group().MODER()->setMode(VbusPin.pin(), GPIO::MODER::Mode::Input); - VbusPin.group().PUPDR()->setPull(VbusPin.pin(), GPIO::PUPDR::Pull::Down); + Config::VbusPin.group().MODER()->setMode(Config::VbusPin.pin(), GPIO::MODER::Mode::Input); + Config::VbusPin.group().PUPDR()->setPull(Config::VbusPin.pin(), GPIO::PUPDR::Pull::Down); #else - VbusPin.group().MODER()->setMode(VbusPin.pin(), GPIO::MODER::Mode::AlternateFunction); - VbusPin.group().AFR()->setAlternateFunction(VbusPin.pin(), GPIO::AFR::AlternateFunction::AF10); + Config::VbusPin.group().MODER()->setMode(Config::VbusPin.pin(), GPIO::MODER::Mode::AlternateFunction); + Config::VbusPin.group().AFR()->setAlternateFunction(Config::VbusPin.pin(), GPIO::AFR::AlternateFunction::AF10); #endif - DmPin.group().MODER()->setMode(DmPin.pin(), GPIO::MODER::Mode::AlternateFunction); - DmPin.group().AFR()->setAlternateFunction(DmPin.pin(), GPIO::AFR::AlternateFunction::AF10); + Config::DmPin.group().MODER()->setMode(Config::DmPin.pin(), GPIO::MODER::Mode::AlternateFunction); + Config::DmPin.group().AFR()->setAlternateFunction(Config::DmPin.pin(), GPIO::AFR::AlternateFunction::AF10); - DpPin.group().MODER()->setMode(DpPin.pin(), GPIO::MODER::Mode::AlternateFunction); - DpPin.group().AFR()->setAlternateFunction(DpPin.pin(), GPIO::AFR::AlternateFunction::AF10); + Config::DpPin.group().MODER()->setMode(Config::DpPin.pin(), GPIO::MODER::Mode::AlternateFunction); + Config::DpPin.group().AFR()->setAlternateFunction(Config::DpPin.pin(), GPIO::AFR::AlternateFunction::AF10); } void shutdownGPIO() { - constexpr static GPIOPin USBPins[] = {DpPin, DmPin, VbusPin}; + constexpr static GPIOPin USBPins[] = {Config::DpPin, Config::DmPin, Config::VbusPin}; for (const GPIOPin & g : USBPins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); diff --git a/ion/src/device/shared/drivers/usb.h b/ion/src/device/shared/drivers/usb.h new file mode 100644 index 000000000..a53beb8a0 --- /dev/null +++ b/ion/src/device/shared/drivers/usb.h @@ -0,0 +1,19 @@ +#ifndef ION_DEVICE_SHARED_USB_H +#define ION_DEVICE_SHARED_USB_H + +namespace Ion { +namespace Device { +namespace USB { + +void init(); +void shutdown(); +void initGPIO(); +void shutdownGPIO(); +void initOTG(); +void shutdownOTG(); + +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/wakeup.cpp b/ion/src/device/shared/drivers/wakeup.cpp new file mode 100644 index 000000000..d0bfb12f8 --- /dev/null +++ b/ion/src/device/shared/drivers/wakeup.cpp @@ -0,0 +1,72 @@ +#include "wakeup.h" +#include +#include +#include +#include +#include +#include +#include + +namespace Ion { +namespace Device { +namespace WakeUp { + +void onChargingEvent() { + Battery::initGPIO(); + + /* Warning: pins with the same number in different groups cannot be set as + * source input for EXTI at the same time. Here, EXTICR1 register is filled + * between position 0-3 (charging pin = 0) with + * 0000 (ChargingGPIO = group A). */ + SYSCFG.EXTICR1()->setEXTI(Battery::Config::ChargingPin.pin(), Battery::Config::ChargingPin.group()); + + EXTI.EMR()->set(Battery::Config::ChargingPin.pin(), true); + + /* We need to detect when the battery stops charging. We set the + * wake up event on the rising edge. */ + EXTI.RTSR()->set(Battery::Config::ChargingPin.pin(), true); +} + +void onUSBPlugging() { + USB::initGPIO(); + /* Here, EXTICR3 register is filled between position 4-7 (Vbus pin = 9) with + * 0000 (Vbus GPIO = group A). */ + SYSCFG.EXTICR3()->setEXTI(USB::Config::VbusPin.pin(), USB::Config::VbusPin.group()); + + EXTI.EMR()->set(USB::Config::VbusPin.pin(), true); +#if EPSILON_LED_WHILE_CHARGING + EXTI.FTSR()->set(USB::Config::VbusPin.pin(), true); +#endif + EXTI.RTSR()->set(USB::Config::VbusPin.pin(), true); +} + + +void onPowerKeyDown() { + Keyboard::Key key = Keyboard::Key::B2; + uint8_t rowPin = Keyboard::Config::RowPins[Keyboard::rowForKey(key)]; + Keyboard::Config::RowGPIO.MODER()->setMode(rowPin, GPIO::MODER::Mode::Output); + Keyboard::Config::RowGPIO.OTYPER()->setType(rowPin, GPIO::OTYPER::Type::OpenDrain); + Keyboard::Config::RowGPIO.ODR()->set(rowPin, 0); + + uint8_t column = Keyboard::columnForKey(key); + uint8_t columnPin = Keyboard::Config::ColumnPins[column]; + + Keyboard::Config::ColumnGPIO.MODER()->setMode(columnPin, GPIO::MODER::Mode::Input); + Keyboard::Config::ColumnGPIO.PUPDR()->setPull(columnPin, GPIO::PUPDR::Pull::Up); + + /* Here, EXTICR1 register is filled between position 4-7 (column pin = 1) with + * 0010 (ColumnGPIO = group C). */ + + SYSCFG.EXTICR1()->setEXTI(columnPin, Keyboard::Config::ColumnGPIO); + + EXTI.EMR()->set(columnPin, true); + + /* When the key is pressed, it will go from 1 (because it's pulled up) to + * zero (because it's connected to the open-drain output. In other words, + * we're waiting for a falling edge. */ + EXTI.FTSR()->set(columnPin, true); +} + +} +} +} diff --git a/ion/src/device/wakeup.h b/ion/src/device/shared/drivers/wakeup.h similarity index 100% rename from ion/src/device/wakeup.h rename to ion/src/device/shared/drivers/wakeup.h index 0c7d641b9..7415fddc2 100644 --- a/ion/src/device/wakeup.h +++ b/ion/src/device/shared/drivers/wakeup.h @@ -4,8 +4,8 @@ #include "regs/regs.h" namespace Ion { -namespace WakeUp { namespace Device { +namespace WakeUp { /* All wakeup functions can be called together without overwriting the same * register. All togethed, they will set SYSCFG and EXTi registers as follow: diff --git a/ion/src/device/regs/adc.h b/ion/src/device/shared/regs/adc.h similarity index 100% rename from ion/src/device/regs/adc.h rename to ion/src/device/shared/regs/adc.h diff --git a/ion/src/device/regs/cm4.h b/ion/src/device/shared/regs/cm4.h similarity index 100% rename from ion/src/device/regs/cm4.h rename to ion/src/device/shared/regs/cm4.h diff --git a/ion/src/device/regs/crc.h b/ion/src/device/shared/regs/crc.h similarity index 100% rename from ion/src/device/regs/crc.h rename to ion/src/device/shared/regs/crc.h diff --git a/ion/src/device/regs/dma.h b/ion/src/device/shared/regs/dma.h similarity index 100% rename from ion/src/device/regs/dma.h rename to ion/src/device/shared/regs/dma.h diff --git a/ion/src/device/regs/exti.h b/ion/src/device/shared/regs/exti.h similarity index 100% rename from ion/src/device/regs/exti.h rename to ion/src/device/shared/regs/exti.h diff --git a/ion/src/device/regs/flash.h b/ion/src/device/shared/regs/flash.h similarity index 100% rename from ion/src/device/regs/flash.h rename to ion/src/device/shared/regs/flash.h diff --git a/ion/src/device/regs/fsmc.h b/ion/src/device/shared/regs/fsmc.h similarity index 100% rename from ion/src/device/regs/fsmc.h rename to ion/src/device/shared/regs/fsmc.h diff --git a/ion/src/device/regs/gpio.h b/ion/src/device/shared/regs/gpio.h similarity index 100% rename from ion/src/device/regs/gpio.h rename to ion/src/device/shared/regs/gpio.h diff --git a/ion/src/device/regs/itm.h b/ion/src/device/shared/regs/itm.h similarity index 100% rename from ion/src/device/regs/itm.h rename to ion/src/device/shared/regs/itm.h diff --git a/ion/src/device/regs/mpu.h b/ion/src/device/shared/regs/mpu.h similarity index 100% rename from ion/src/device/regs/mpu.h rename to ion/src/device/shared/regs/mpu.h diff --git a/ion/src/device/regs/nvic.h b/ion/src/device/shared/regs/nvic.h similarity index 100% rename from ion/src/device/regs/nvic.h rename to ion/src/device/shared/regs/nvic.h diff --git a/ion/src/device/regs/otg.h b/ion/src/device/shared/regs/otg.h similarity index 100% rename from ion/src/device/regs/otg.h rename to ion/src/device/shared/regs/otg.h diff --git a/ion/src/device/regs/pwr.h b/ion/src/device/shared/regs/pwr.h similarity index 100% rename from ion/src/device/regs/pwr.h rename to ion/src/device/shared/regs/pwr.h diff --git a/ion/src/device/regs/quadspi.h b/ion/src/device/shared/regs/quadspi.h similarity index 100% rename from ion/src/device/regs/quadspi.h rename to ion/src/device/shared/regs/quadspi.h diff --git a/ion/src/device/regs/rcc.h b/ion/src/device/shared/regs/rcc.h similarity index 100% rename from ion/src/device/regs/rcc.h rename to ion/src/device/shared/regs/rcc.h diff --git a/ion/src/device/regs/register.h b/ion/src/device/shared/regs/register.h similarity index 100% rename from ion/src/device/regs/register.h rename to ion/src/device/shared/regs/register.h diff --git a/ion/src/device/regs/regs.h b/ion/src/device/shared/regs/regs.h similarity index 100% rename from ion/src/device/regs/regs.h rename to ion/src/device/shared/regs/regs.h diff --git a/ion/src/device/regs/rng.h b/ion/src/device/shared/regs/rng.h similarity index 100% rename from ion/src/device/regs/rng.h rename to ion/src/device/shared/regs/rng.h diff --git a/ion/src/device/regs/sdio.h b/ion/src/device/shared/regs/sdio.h similarity index 100% rename from ion/src/device/regs/sdio.h rename to ion/src/device/shared/regs/sdio.h diff --git a/ion/src/device/regs/spi.h b/ion/src/device/shared/regs/spi.h similarity index 100% rename from ion/src/device/regs/spi.h rename to ion/src/device/shared/regs/spi.h diff --git a/ion/src/device/regs/syscfg.h b/ion/src/device/shared/regs/syscfg.h similarity index 100% rename from ion/src/device/regs/syscfg.h rename to ion/src/device/shared/regs/syscfg.h diff --git a/ion/src/device/regs/tim.h b/ion/src/device/shared/regs/tim.h similarity index 100% rename from ion/src/device/regs/tim.h rename to ion/src/device/shared/regs/tim.h diff --git a/ion/src/device/regs/usart.h b/ion/src/device/shared/regs/usart.h similarity index 100% rename from ion/src/device/regs/usart.h rename to ion/src/device/shared/regs/usart.h diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile new file mode 100644 index 000000000..67f0ab958 --- /dev/null +++ b/ion/src/device/shared/usb/Makefile @@ -0,0 +1,73 @@ +usb_objs += $(addprefix ion/src/device/shared/usb/, \ + calculator.o \ + dfu_interface.o\ +) + +usb_objs += $(addprefix ion/src/device/shared/usb/stack/, \ + device.o\ + endpoint0.o \ + interface.o\ + request_recipient.o\ + setup_packet.o\ + streamable.o\ +) + +usb_objs += $(addprefix ion/src/device/shared/usb/stack/descriptor/, \ + bos_descriptor.o\ + configuration_descriptor.o \ + descriptor.o\ + device_descriptor.o\ + device_capability_descriptor.o\ + dfu_functional_descriptor.o\ + extended_compat_id_descriptor.o \ + interface_descriptor.o\ + language_id_string_descriptor.o \ + microsoft_os_string_descriptor.o\ + platform_device_capability_descriptor.o\ + string_descriptor.o\ + url_descriptor.o\ + webusb_platform_descriptor.o\ +) + +$(usb_objs): SFLAGS += $(ION_DEVICE_SFLAGS) + +EPSILON_USB_DFU_XIP ?= 0 + +ifeq ($(EPSILON_USB_DFU_XIP),1) + +ion_device_objs += ion/src/device/shared/usb/dfu_xip.o +ion_device_objs += $(usb_objs) + +else + +dfu_objs += liba/src/assert.o +dfu_objs += liba/src/strlen.o +dfu_objs += liba/src/strlcpy.o +dfu_objs += liba/src/memset.o +dfu_objs += liba/src/memcpy.o +dfu_objs += libaxx/src/cxxabi/pure_virtual.o +dfu_objs += ion/src/device/shared/usb/boot.o +dfu_objs += $(addprefix ion/src/device/shared/drivers/, \ + base64.o \ + external_flash.o \ + flash.o \ + keyboard.o \ + reset.o \ + serial_number.o \ + timing.o \ + usb.o \ +) + +ion/src/device/shared/usb/dfu.elf: LDSCRIPT = ion/src/device/shared/usb/dfu.ld +ion/src/device/shared/usb/dfu.elf: $(usb_objs) $(dfu_objs) + +ion/src/device/shared/usb/dfu.o: ion/src/device/shared/usb/dfu.bin + @echo "OBJCOPY $@" + $(Q) $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata --redefine-sym _binary_ion_src_device_shared_usb_dfu_bin_start=_dfu_bootloader_flash_start --redefine-sym _binary_ion_src_device_shared_usb_dfu_bin_end=_dfu_bootloader_flash_end $< $@ + +ion_device_objs += ion/src/device/shared/usb/dfu.o +ion_device_objs += ion/src/device/shared/usb/dfu_relocated.o + +products += $(usb_objs) $(addprefix ion/src/device/shared/usb/dfu, .elf .bin) + +endif diff --git a/ion/src/device/usb/boot.cpp b/ion/src/device/shared/usb/boot.cpp similarity index 100% rename from ion/src/device/usb/boot.cpp rename to ion/src/device/shared/usb/boot.cpp diff --git a/ion/src/device/usb/calculator.cpp b/ion/src/device/shared/usb/calculator.cpp similarity index 86% rename from ion/src/device/usb/calculator.cpp rename to ion/src/device/shared/usb/calculator.cpp index fc23c1512..0ad5a285c 100644 --- a/ion/src/device/usb/calculator.cpp +++ b/ion/src/device/shared/usb/calculator.cpp @@ -1,27 +1,27 @@ #include "calculator.h" #include -#include -#include -#include +#include +#include +#include namespace Ion { -namespace USB { namespace Device { +namespace USB { void Calculator::PollAndReset(bool exitWithKeyboard) { - char serialNumber[Ion::Device::SerialNumberLength+1]; - Ion::Device::copySerialNumber(serialNumber); + char serialNumber[Ion::Device::SerialNumber::Length+1]; + Ion::Device::SerialNumber::copy(serialNumber); Calculator c(serialNumber); /* Leave DFU mode if the Back key is pressed, the calculator unplugged or the * USB core soft-disconnected. */ Ion::Keyboard::Key exitKey = Ion::Keyboard::Key::A6; - uint8_t exitKeyRow = Ion::Keyboard::Device::rowForKey(exitKey); - uint8_t exitKeyColumn = Ion::Keyboard::Device::columnForKey(exitKey); + uint8_t exitKeyRow = Ion::Device::Keyboard::rowForKey(exitKey); + uint8_t exitKeyColumn = Ion::Device::Keyboard::columnForKey(exitKey); - Ion::Keyboard::Device::activateRow(exitKeyRow); + Ion::Device::Keyboard::activateRow(exitKeyRow); - while (!(exitWithKeyboard && Ion::Keyboard::Device::columnIsActive(exitKeyColumn)) && + while (!(exitWithKeyboard && Ion::Device::Keyboard::columnIsActive(exitKeyColumn)) && Ion::USB::isPlugged() && !c.isSoftDisconnected()) { c.poll(); @@ -35,7 +35,7 @@ void Calculator::PollAndReset(bool exitWithKeyboard) { * thing to do but would therefore result in the device entering the ROMed * DFU bootloader, which we want to avoid. By performing a jump-reset, we * will enter the newly flashed firmware. */ - Ion::Device::jumpReset(); + Ion::Device::Reset::jump(); } } diff --git a/ion/src/device/usb/calculator.h b/ion/src/device/shared/usb/calculator.h similarity index 98% rename from ion/src/device/usb/calculator.h rename to ion/src/device/shared/usb/calculator.h index 81b8c9b2a..bb7c4f43c 100644 --- a/ion/src/device/usb/calculator.h +++ b/ion/src/device/shared/usb/calculator.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_USB_CALCULATOR_H -#define ION_DEVICE_USB_CALCULATOR_H +#ifndef ION_DEVICE_SHARED_USB_CALCULATOR_H +#define ION_DEVICE_SHARED_USB_CALCULATOR_H #include #include @@ -19,8 +19,8 @@ #include "stack/descriptor/webusb_platform_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class Calculator : public Device { public: diff --git a/ion/src/device/usb/dfu.ld b/ion/src/device/shared/usb/dfu.ld similarity index 100% rename from ion/src/device/usb/dfu.ld rename to ion/src/device/shared/usb/dfu.ld diff --git a/ion/src/device/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp similarity index 90% rename from ion/src/device/usb/dfu_interface.cpp rename to ion/src/device/shared/usb/dfu_interface.cpp index 8b30e7ea2..3e414c6b7 100644 --- a/ion/src/device/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -1,11 +1,11 @@ #include "dfu_interface.h" #include -#include -#include +#include "../drivers/flash.h" +#include "../drivers/external_flash.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { static inline uint32_t min(uint32_t x, uint32_t y) { return (x= k_flashStartAddress && eraseAddress <= k_flashEndAddress) { - m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); + m_erasePage = Flash::SectorAtAddress(eraseAddress); } else if (eraseAddress >= k_externalFlashStartAddress && eraseAddress <= k_externalFlashEndAddress) { - m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::SectorAtAddress(eraseAddress - k_externalFlashStartAddress); + m_erasePage = Flash::NumberOfSectors + ExternalFlash::SectorAtAddress(eraseAddress - k_externalFlashStartAddress); } else { // Unrecognized sector m_state = State::dfuERROR; @@ -204,13 +204,13 @@ void DFUInterface::eraseMemoryIfNeeded() { return; } - if (m_erasePage == Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors) { - Flash::Device::MassErase(); - ExternalFlash::Device::MassErase(); - } else if (m_erasePage < Flash::Device::NumberOfSectors) { - Flash::Device::EraseSector(m_erasePage); + if (m_erasePage == Flash::NumberOfSectors + ExternalFlash::NumberOfSectors) { + Flash::MassErase(); + ExternalFlash::MassErase(); + } else if (m_erasePage < Flash::NumberOfSectors) { + Flash::EraseSector(m_erasePage); } else { - ExternalFlash::Device::EraseSector(m_erasePage - Flash::Device::NumberOfSectors); + ExternalFlash::EraseSector(m_erasePage - Flash::NumberOfSectors); } /* Put an out of range value in m_erasePage to indicate that no erase is @@ -223,13 +223,13 @@ void DFUInterface::eraseMemoryIfNeeded() { void DFUInterface::writeOnMemory() { if (m_writeAddress >= k_flashStartAddress && m_writeAddress <= k_flashEndAddress) { // Write to the Flash memory - Flash::Device::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress), m_largeBufferLength); + Flash::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress), m_largeBufferLength); } else if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) { // Write on SRAM // FIXME We should check that we are not overriding the current instructions. memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); } else if (m_writeAddress >= k_externalFlashStartAddress && m_writeAddress <= k_externalFlashEndAddress) { - ExternalFlash::Device::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress) - k_externalFlashStartAddress, m_largeBufferLength); + ExternalFlash::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress) - k_externalFlashStartAddress, m_largeBufferLength); } else { // Invalid write address m_largeBufferLength = 0; diff --git a/ion/src/device/usb/dfu_interface.h b/ion/src/device/shared/usb/dfu_interface.h similarity index 98% rename from ion/src/device/usb/dfu_interface.h rename to ion/src/device/shared/usb/dfu_interface.h index 7fce4bf40..849fa9d98 100644 --- a/ion/src/device/usb/dfu_interface.h +++ b/ion/src/device/shared/usb/dfu_interface.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_USB_DFU_INTERFACE_H -#define ION_DEVICE_USB_DFU_INTERFACE_H +#ifndef ION_DEVICE_SHARED_USB_DFU_INTERFACE_H +#define ION_DEVICE_SHARED_USB_DFU_INTERFACE_H #include #include @@ -10,8 +10,8 @@ #include "stack/streamable.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class DFUInterface : public Interface { diff --git a/ion/src/device/usb/dfu_relocated.cpp b/ion/src/device/shared/usb/dfu_relocated.cpp similarity index 96% rename from ion/src/device/usb/dfu_relocated.cpp rename to ion/src/device/shared/usb/dfu_relocated.cpp index 41566e2a0..a190e6786 100644 --- a/ion/src/device/usb/dfu_relocated.cpp +++ b/ion/src/device/shared/usb/dfu_relocated.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include "../drivers/timing.h" extern char _stack_end; extern char _dfu_bootloader_flash_start; @@ -51,7 +51,7 @@ void DFU() { /* 4- Disable all interrupts * The interrupt service routines live in the Flash and could be overwritten * by garbage during a firmware upgrade opration, so we disable them. */ - Device::shutdownSysTick(); + Device::Timing::shutdown(); /* 5- Jump to DFU bootloader code. We made sure in the linker script that the * first function we want to call is at the beginning of the DFU code. */ @@ -70,7 +70,7 @@ void DFU() { dfu_bootloader_entry(true); /* 5- Restore interrupts */ - Device::initSysTick(); + Device::Timing::init(); /* 6- That's all. The DFU bootloader on the stack is now dead code that will * be overwritten when the stack grows. */ diff --git a/ion/src/device/usb/dfu_xip.cpp b/ion/src/device/shared/usb/dfu_xip.cpp similarity index 50% rename from ion/src/device/usb/dfu_xip.cpp rename to ion/src/device/shared/usb/dfu_xip.cpp index ad2ef68bb..5eedcd6b6 100644 --- a/ion/src/device/usb/dfu_xip.cpp +++ b/ion/src/device/shared/usb/dfu_xip.cpp @@ -1,11 +1,10 @@ #include "calculator.h" -#include "../device.h" namespace Ion { namespace USB { void DFU() { - Ion::USB::Device::Calculator::PollAndReset(true); + Ion::Device::USB::Calculator::PollAndReset(true); } } diff --git a/ion/src/device/usb/flasher.cpp b/ion/src/device/shared/usb/flasher.cpp similarity index 100% rename from ion/src/device/usb/flasher.cpp rename to ion/src/device/shared/usb/flasher.cpp diff --git a/ion/src/device/usb/flasher.ld b/ion/src/device/shared/usb/flasher.ld similarity index 100% rename from ion/src/device/usb/flasher.ld rename to ion/src/device/shared/usb/flasher.ld diff --git a/ion/src/device/usb/stack/descriptor/bos_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/bos_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/bos_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/bos_descriptor.cpp index 069e6d56e..2099df6b4 100644 --- a/ion/src/device/usb/stack/descriptor/bos_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/bos_descriptor.cpp @@ -1,8 +1,8 @@ #include "bos_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { void BOSDescriptor::push(Channel * c) const { Descriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/bos_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/bos_descriptor.h similarity index 87% rename from ion/src/device/usb/stack/descriptor/bos_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/bos_descriptor.h index ce2626099..4c68bb327 100644 --- a/ion/src/device/usb/stack/descriptor/bos_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/bos_descriptor.h @@ -1,12 +1,12 @@ -#ifndef ION_DEVICE_USB_STACK_BOS_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_BOS_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_BOS_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_BOS_DESCRIPTOR_H #include "descriptor.h" #include "device_capability_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class BOSDescriptor : public Descriptor { public: diff --git a/ion/src/device/usb/stack/descriptor/configuration_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/configuration_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/configuration_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/configuration_descriptor.cpp index 3981837b5..c4d97a368 100644 --- a/ion/src/device/usb/stack/descriptor/configuration_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/configuration_descriptor.cpp @@ -1,8 +1,8 @@ #include "configuration_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { void ConfigurationDescriptor::push(Channel * c) const { Descriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/configuration_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/configuration_descriptor.h similarity index 89% rename from ion/src/device/usb/stack/descriptor/configuration_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/configuration_descriptor.h index fc43d59fb..aea863c43 100644 --- a/ion/src/device/usb/stack/descriptor/configuration_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/configuration_descriptor.h @@ -1,12 +1,12 @@ -#ifndef ION_DEVICE_USB_STACK_CONFIGURATION_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_CONFIGURATION_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_CONFIGURATION_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_CONFIGURATION_DESCRIPTOR_H #include "descriptor.h" #include "interface_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class ConfigurationDescriptor : public Descriptor { public: diff --git a/ion/src/device/usb/stack/descriptor/descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/descriptor.cpp index cda9e7a77..373388cb3 100644 --- a/ion/src/device/usb/stack/descriptor/descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/descriptor.cpp @@ -2,8 +2,8 @@ #include namespace Ion { -namespace USB { namespace Device { +namespace USB { void Descriptor::push(Channel * c) const { c->push(bLength()); diff --git a/ion/src/device/usb/stack/descriptor/descriptor.h b/ion/src/device/shared/usb/stack/descriptor/descriptor.h similarity index 84% rename from ion/src/device/usb/stack/descriptor/descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/descriptor.h index 9af940211..b413b3fe4 100644 --- a/ion/src/device/usb/stack/descriptor/descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DESCRIPTOR_H #include "../streamable.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class InterfaceDescriptor; diff --git a/ion/src/device/usb/stack/descriptor/device_capability_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/device_capability_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.cpp index e3ab162d4..8f5d6abb8 100644 --- a/ion/src/device/usb/stack/descriptor/device_capability_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.cpp @@ -1,8 +1,8 @@ #include "device_capability_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { void DeviceCapabilityDescriptor::push(Channel * c) const { Descriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h similarity index 79% rename from ion/src/device/usb/stack/descriptor/device_capability_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h index cf600c9f1..435358a65 100644 --- a/ion/src/device/usb/stack/descriptor/device_capability_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/device_capability_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H #include "descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class BOSDescriptor; diff --git a/ion/src/device/usb/stack/descriptor/device_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/device_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/device_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/device_descriptor.cpp index 1ce33847a..424e891e8 100644 --- a/ion/src/device/usb/stack/descriptor/device_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/device_descriptor.cpp @@ -1,8 +1,8 @@ #include "device_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { void DeviceDescriptor::push(Channel * c) const { Descriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/device_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/device_descriptor.h similarity index 92% rename from ion/src/device/usb/stack/descriptor/device_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/device_descriptor.h index d41241348..840c01da6 100644 --- a/ion/src/device/usb/stack/descriptor/device_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/device_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_DEVICE_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_DEVICE_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_DEVICE_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DEVICE_DESCRIPTOR_H #include "descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class DeviceDescriptor : public Descriptor { public: diff --git a/ion/src/device/usb/stack/descriptor/dfu_functional_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/dfu_functional_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/dfu_functional_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/dfu_functional_descriptor.cpp index 0d531965a..414421d9b 100644 --- a/ion/src/device/usb/stack/descriptor/dfu_functional_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/dfu_functional_descriptor.cpp @@ -1,8 +1,8 @@ #include "dfu_functional_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { void DFUFunctionalDescriptor::push(Channel * c) const { Descriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/dfu_functional_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/dfu_functional_descriptor.h similarity index 84% rename from ion/src/device/usb/stack/descriptor/dfu_functional_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/dfu_functional_descriptor.h index 5d51caa5a..fb04a824a 100644 --- a/ion/src/device/usb/stack/descriptor/dfu_functional_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/dfu_functional_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H #include "descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class DFUFunctionalDescriptor : public Descriptor { public: diff --git a/ion/src/device/usb/stack/descriptor/extended_compat_id_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/extended_compat_id_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/extended_compat_id_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/extended_compat_id_descriptor.cpp index 287ac8dd7..024ac1552 100644 --- a/ion/src/device/usb/stack/descriptor/extended_compat_id_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/extended_compat_id_descriptor.cpp @@ -2,8 +2,8 @@ #include namespace Ion { -namespace USB { namespace Device { +namespace USB { ExtendedCompatIDDescriptor::ExtendedCompatIDDescriptor(const char * compatibleID) : m_dwLength(sizeof(uint32_t) diff --git a/ion/src/device/usb/stack/descriptor/extended_compat_id_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/extended_compat_id_descriptor.h similarity index 91% rename from ion/src/device/usb/stack/descriptor/extended_compat_id_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/extended_compat_id_descriptor.h index 199c39640..016beab97 100644 --- a/ion/src/device/usb/stack/descriptor/extended_compat_id_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/extended_compat_id_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H #include "../streamable.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { /* We use this descriptor to tell the Windows OS that the device should be * treated as a WinUSB device. The Extended Compat ID Descriptor can set diff --git a/ion/src/device/usb/stack/descriptor/interface_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/interface_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/interface_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/interface_descriptor.cpp index f4bb7d739..75f193aba 100644 --- a/ion/src/device/usb/stack/descriptor/interface_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/interface_descriptor.cpp @@ -1,8 +1,8 @@ #include "interface_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { void InterfaceDescriptor::push(Channel * c) const { Descriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/interface_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/interface_descriptor.h similarity index 91% rename from ion/src/device/usb/stack/descriptor/interface_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/interface_descriptor.h index 2d1418c0d..ef8481289 100644 --- a/ion/src/device/usb/stack/descriptor/interface_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/interface_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_INTERFACE_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_INTERFACE_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_INTERFACE_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_INTERFACE_DESCRIPTOR_H #include "descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class ConfigurationDescriptor; diff --git a/ion/src/device/usb/stack/descriptor/language_id_string_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/language_id_string_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/language_id_string_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/language_id_string_descriptor.cpp index 49dec8e10..8027fb75c 100644 --- a/ion/src/device/usb/stack/descriptor/language_id_string_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/language_id_string_descriptor.cpp @@ -2,8 +2,8 @@ #include namespace Ion { -namespace USB { namespace Device { +namespace USB { void LanguageIDStringDescriptor::push(Channel * c) const { Descriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/language_id_string_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/language_id_string_descriptor.h similarity index 74% rename from ion/src/device/usb/stack/descriptor/language_id_string_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/language_id_string_descriptor.h index 69533bdf5..198befac8 100644 --- a/ion/src/device/usb/stack/descriptor/language_id_string_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/language_id_string_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H #include "descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { // For now this LanguageIDStringDescriptor only ever returns American English diff --git a/ion/src/device/usb/stack/descriptor/microsoft_os_string_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/microsoft_os_string_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/microsoft_os_string_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/microsoft_os_string_descriptor.cpp index 2d119dc09..b125b8efc 100644 --- a/ion/src/device/usb/stack/descriptor/microsoft_os_string_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/microsoft_os_string_descriptor.cpp @@ -1,8 +1,8 @@ #include "microsoft_os_string_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { void MicrosoftOSStringDescriptor::push(Channel * c) const { StringDescriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/microsoft_os_string_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/microsoft_os_string_descriptor.h similarity index 77% rename from ion/src/device/usb/stack/descriptor/microsoft_os_string_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/microsoft_os_string_descriptor.h index 9bf2295b6..69ce1a33d 100644 --- a/ion/src/device/usb/stack/descriptor/microsoft_os_string_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/microsoft_os_string_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H #include "string_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class MicrosoftOSStringDescriptor : public StringDescriptor { public: diff --git a/ion/src/device/usb/stack/descriptor/platform_device_capability_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/platform_device_capability_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.cpp index b3739ec6e..d5c7756ba 100644 --- a/ion/src/device/usb/stack/descriptor/platform_device_capability_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.cpp @@ -1,8 +1,8 @@ #include "platform_device_capability_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { void PlatformDeviceCapabilityDescriptor::push(Channel * c) const { DeviceCapabilityDescriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h similarity index 88% rename from ion/src/device/usb/stack/descriptor/platform_device_capability_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h index 76087b565..3c35dc11d 100644 --- a/ion/src/device/usb/stack/descriptor/platform_device_capability_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/platform_device_capability_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H #include "device_capability_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class PlatformDeviceCapabilityDescriptor : public DeviceCapabilityDescriptor { public: diff --git a/ion/src/device/usb/stack/descriptor/string_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/string_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/string_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/string_descriptor.cpp index c4ee5068e..044e54fb0 100644 --- a/ion/src/device/usb/stack/descriptor/string_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/string_descriptor.cpp @@ -2,8 +2,8 @@ #include namespace Ion { -namespace USB { namespace Device { +namespace USB { void StringDescriptor::push(Channel * c) const { Descriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/string_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/string_descriptor.h similarity index 77% rename from ion/src/device/usb/stack/descriptor/string_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/string_descriptor.h index 296ac79ca..e2f5ea37c 100644 --- a/ion/src/device/usb/stack/descriptor/string_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/string_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_STRING_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_STRING_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_STRING_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_STRING_DESCRIPTOR_H #include "descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class StringDescriptor : public Descriptor { public: diff --git a/ion/src/device/usb/stack/descriptor/url_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/url_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/url_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/url_descriptor.cpp index 3001d8143..2b4580b8a 100644 --- a/ion/src/device/usb/stack/descriptor/url_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/url_descriptor.cpp @@ -2,8 +2,8 @@ #include namespace Ion { -namespace USB { namespace Device { +namespace USB { void URLDescriptor::push(Channel * c) const { Descriptor::push(c); diff --git a/ion/src/device/usb/stack/descriptor/url_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/url_descriptor.h similarity index 83% rename from ion/src/device/usb/stack/descriptor/url_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/url_descriptor.h index d9e39af29..3552201a2 100644 --- a/ion/src/device/usb/stack/descriptor/url_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/url_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_URL_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_URL_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_URL_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_URL_DESCRIPTOR_H #include "descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class URLDescriptor : public Descriptor { public: diff --git a/ion/src/device/usb/stack/descriptor/webusb_platform_descriptor.cpp b/ion/src/device/shared/usb/stack/descriptor/webusb_platform_descriptor.cpp similarity index 100% rename from ion/src/device/usb/stack/descriptor/webusb_platform_descriptor.cpp rename to ion/src/device/shared/usb/stack/descriptor/webusb_platform_descriptor.cpp index e4a4ff548..d859f5927 100644 --- a/ion/src/device/usb/stack/descriptor/webusb_platform_descriptor.cpp +++ b/ion/src/device/shared/usb/stack/descriptor/webusb_platform_descriptor.cpp @@ -1,8 +1,8 @@ #include "webusb_platform_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { constexpr uint8_t WebUSBPlatformDescriptor::k_webUSBUUID[]; diff --git a/ion/src/device/usb/stack/descriptor/webusb_platform_descriptor.h b/ion/src/device/shared/usb/stack/descriptor/webusb_platform_descriptor.h similarity index 87% rename from ion/src/device/usb/stack/descriptor/webusb_platform_descriptor.h rename to ion/src/device/shared/usb/stack/descriptor/webusb_platform_descriptor.h index 89d39c712..a1b5bcb4b 100644 --- a/ion/src/device/usb/stack/descriptor/webusb_platform_descriptor.h +++ b/ion/src/device/shared/usb/stack/descriptor/webusb_platform_descriptor.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H +#ifndef ION_DEVICE_SHARED_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H +#define ION_DEVICE_SHARED_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H #include "platform_device_capability_descriptor.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class WebUSBPlatformDescriptor : public PlatformDeviceCapabilityDescriptor { public: diff --git a/ion/src/device/usb/stack/device.cpp b/ion/src/device/shared/usb/stack/device.cpp similarity index 99% rename from ion/src/device/usb/stack/device.cpp rename to ion/src/device/shared/usb/stack/device.cpp index 27bf6d5de..631170248 100644 --- a/ion/src/device/usb/stack/device.cpp +++ b/ion/src/device/shared/usb/stack/device.cpp @@ -1,9 +1,9 @@ #include "device.h" -#include +#include namespace Ion { -namespace USB { namespace Device { +namespace USB { static inline uint16_t min(uint16_t x, uint16_t y) { return (x -#include +#include #include "device.h" #include "interface.h" #include "request_recipient.h" @@ -8,8 +8,8 @@ #define MIN(a, b) ((a) < (b) ? (a) : (b)) namespace Ion { -namespace USB { namespace Device { +namespace USB { void Endpoint0::setup() { // Setup the IN direction diff --git a/ion/src/device/usb/stack/endpoint0.h b/ion/src/device/shared/usb/stack/endpoint0.h similarity index 95% rename from ion/src/device/usb/stack/endpoint0.h rename to ion/src/device/shared/usb/stack/endpoint0.h index 2088aaadd..ca6809bfb 100644 --- a/ion/src/device/usb/stack/endpoint0.h +++ b/ion/src/device/shared/usb/stack/endpoint0.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_ENDPOINT0_H -#define ION_DEVICE_USB_ENDPOINT0_H +#ifndef ION_DEVICE_SHARED_USB_ENDPOINT0_H +#define ION_DEVICE_SHARED_USB_ENDPOINT0_H #include "setup_packet.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class RequestRecipient; diff --git a/ion/src/device/usb/stack/interface.cpp b/ion/src/device/shared/usb/stack/interface.cpp similarity index 100% rename from ion/src/device/usb/stack/interface.cpp rename to ion/src/device/shared/usb/stack/interface.cpp index 3fa95947e..9fe460041 100644 --- a/ion/src/device/usb/stack/interface.cpp +++ b/ion/src/device/shared/usb/stack/interface.cpp @@ -1,8 +1,8 @@ #include "interface.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { static inline uint16_t min(uint16_t x, uint16_t y) { return (xfollowingTransaction() == SetupPacket::TransactionType::InTransaction) { diff --git a/ion/src/device/usb/stack/request_recipient.h b/ion/src/device/shared/usb/stack/request_recipient.h similarity index 88% rename from ion/src/device/usb/stack/request_recipient.h rename to ion/src/device/shared/usb/stack/request_recipient.h index 7f0fc8819..8973fba8c 100644 --- a/ion/src/device/usb/stack/request_recipient.h +++ b/ion/src/device/shared/usb/stack/request_recipient.h @@ -1,12 +1,12 @@ -#ifndef ION_DEVICE_USB_REQUEST_RECIPIENT_H -#define ION_DEVICE_USB_REQUEST_RECIPIENT_H +#ifndef ION_DEVICE_SHARED_USB_REQUEST_RECIPIENT_H +#define ION_DEVICE_SHARED_USB_REQUEST_RECIPIENT_H #include "endpoint0.h" #include "setup_packet.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { class RequestRecipient { public: diff --git a/ion/src/device/usb/stack/setup_packet.cpp b/ion/src/device/shared/usb/stack/setup_packet.cpp similarity index 100% rename from ion/src/device/usb/stack/setup_packet.cpp rename to ion/src/device/shared/usb/stack/setup_packet.cpp index d879b59e9..77882d6a5 100644 --- a/ion/src/device/usb/stack/setup_packet.cpp +++ b/ion/src/device/shared/usb/stack/setup_packet.cpp @@ -2,8 +2,8 @@ #include namespace Ion { -namespace USB { namespace Device { +namespace USB { SetupPacket::SetupPacket(void * buffer) { memcpy(this, buffer, sizeof(SetupPacket)); diff --git a/ion/src/device/usb/stack/setup_packet.h b/ion/src/device/shared/usb/stack/setup_packet.h similarity index 92% rename from ion/src/device/usb/stack/setup_packet.h rename to ion/src/device/shared/usb/stack/setup_packet.h index 054ff81e9..d68ec32fb 100644 --- a/ion/src/device/usb/stack/setup_packet.h +++ b/ion/src/device/shared/usb/stack/setup_packet.h @@ -1,11 +1,11 @@ -#ifndef ION_DEVICE_USB_SETUP_PACKET_H -#define ION_DEVICE_USB_SETUP_PACKET_H +#ifndef ION_DEVICE_SHARED_USB_SETUP_PACKET_H +#define ION_DEVICE_SHARED_USB_SETUP_PACKET_H #include namespace Ion { -namespace USB { namespace Device { +namespace USB { class SetupPacket { public: diff --git a/ion/src/device/usb/stack/streamable.cpp b/ion/src/device/shared/usb/stack/streamable.cpp similarity index 100% rename from ion/src/device/usb/stack/streamable.cpp rename to ion/src/device/shared/usb/stack/streamable.cpp index 3f4677df8..f680439ce 100644 --- a/ion/src/device/usb/stack/streamable.cpp +++ b/ion/src/device/shared/usb/stack/streamable.cpp @@ -1,8 +1,8 @@ #include "streamable.h" namespace Ion { -namespace USB { namespace Device { +namespace USB { uint16_t Streamable::copy(void * target, size_t maxSize) const { Channel c(target, maxSize); diff --git a/ion/src/device/usb/stack/streamable.h b/ion/src/device/shared/usb/stack/streamable.h similarity index 90% rename from ion/src/device/usb/stack/streamable.h rename to ion/src/device/shared/usb/stack/streamable.h index 3a653ef1e..c6a79ad62 100644 --- a/ion/src/device/usb/stack/streamable.h +++ b/ion/src/device/shared/usb/stack/streamable.h @@ -1,12 +1,12 @@ -#ifndef ION_DEVICE_USB_STREAMABLE_H -#define ION_DEVICE_USB_STREAMABLE_H +#ifndef ION_DEVICE_SHARED_USB_STREAMABLE_H +#define ION_DEVICE_SHARED_USB_STREAMABLE_H #include #include namespace Ion { -namespace USB { namespace Device { +namespace USB { class Streamable { public: diff --git a/ion/src/device/timing.cpp b/ion/src/device/timing.cpp deleted file mode 100644 index c22d425f7..000000000 --- a/ion/src/device/timing.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "timing.h" - -namespace Ion { -namespace Timing { - -/* TODO: The delay methods 'msleep' and 'usleep' are currently dependent on the - * optimizations chosen by the compiler. To prevent that and to gain in - * precision, we could use the controller cycle counter (Systick). */ - -void msleep(uint32_t ms) { - for (volatile uint32_t i=0; i<8852*ms; i++) { - __asm volatile("nop"); - } -} -void usleep(uint32_t us) { - for (volatile uint32_t i=0; i<9*us; i++) { - __asm volatile("nop"); - } -} - -uint64_t millis() { - return Ion::Timing::Device::MillisElapsed; -} - -} -} - -namespace Ion { -namespace Timing { -namespace Device { - -volatile uint64_t MillisElapsed = 0; - -} -} -} diff --git a/ion/src/device/timing.h b/ion/src/device/timing.h deleted file mode 100644 index 017bbd31a..000000000 --- a/ion/src/device/timing.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef ION_DEVICE_TIMING_H -#define ION_DEVICE_TIMING_H - -#include - -namespace Ion { -namespace Timing { -namespace Device { - -extern volatile uint64_t MillisElapsed; - -} -} -} - -#endif diff --git a/ion/src/device/usb.h b/ion/src/device/usb.h deleted file mode 100644 index 7c462cd47..000000000 --- a/ion/src/device/usb.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef ION_DEVICE_USB_H -#define ION_DEVICE_USB_H - -#include "regs/regs.h" -#include "ion.h" -#include "usb/calculator.h" - -namespace Ion { -namespace USB { -namespace Device { - -/* Pin | Role | Mode | Function - * -----+-------------------+-----------------------+---------- - * PA9 | VBUS | Input, pulled down//TODO | Low = unplugged, high = plugged - * PA11 | USB D- | Alternate Function 10 | - * PA12 | USB D+ | Alternate Function 10 | - */ - -constexpr static GPIOPin VbusPin = GPIOPin(GPIOA, 9); -constexpr static GPIOPin DmPin = GPIOPin(GPIOA, 11); -constexpr static GPIOPin DpPin = GPIOPin(GPIOA, 12); - -void init(); -void shutdown(); -void initGPIO(); -void shutdownGPIO(); -void initOTG(); -void shutdownOTG(); - -} -} -} - -#endif diff --git a/ion/src/device/usb/Makefile b/ion/src/device/usb/Makefile deleted file mode 100644 index 6a859fcc9..000000000 --- a/ion/src/device/usb/Makefile +++ /dev/null @@ -1,68 +0,0 @@ -usb_objs += $(addprefix ion/src/device/usb/, \ - calculator.o \ - dfu_interface.o\ -) - -usb_objs += $(addprefix ion/src/device/usb/stack/, \ - device.o\ - endpoint0.o \ - interface.o\ - request_recipient.o\ - setup_packet.o\ - streamable.o\ -) - -usb_objs += $(addprefix ion/src/device/usb/stack/descriptor/, \ - bos_descriptor.o\ - configuration_descriptor.o \ - descriptor.o\ - device_descriptor.o\ - device_capability_descriptor.o\ - dfu_functional_descriptor.o\ - extended_compat_id_descriptor.o \ - interface_descriptor.o\ - language_id_string_descriptor.o \ - microsoft_os_string_descriptor.o\ - platform_device_capability_descriptor.o\ - string_descriptor.o\ - url_descriptor.o\ - webusb_platform_descriptor.o\ -) - -EPSILON_USB_DFU_XIP ?= 0 - -ifeq ($(EPSILON_USB_DFU_XIP),1) - -objs += ion/src/device/usb/dfu_xip.o -objs += $(usb_objs) - -else - -dfu_objs += liba/src/assert.o -dfu_objs += liba/src/strlen.o -dfu_objs += liba/src/strlcpy.o -dfu_objs += liba/src/memset.o -dfu_objs += liba/src/memcpy.o -dfu_objs += libaxx/src/cxxabi/pure_virtual.o -dfu_objs += ion/src/device/usb/boot.o -dfu_objs += ion/src/device/keyboard.o -dfu_objs += ion/src/device/device.o -dfu_objs += ion/src/device/usb.o -dfu_objs += ion/src/device/base64.o -dfu_objs += ion/src/device/flash.o -dfu_objs += ion/src/device/external_flash.o -dfu_objs += ion/src/device/timing.o - -ion/src/device/usb/dfu.elf: LDSCRIPT = ion/src/device/usb/dfu.ld -ion/src/device/usb/dfu.elf: $(usb_objs) $(dfu_objs) - -ion/src/device/usb/dfu.o: ion/src/device/usb/dfu.bin - @echo "OBJCOPY $@" - $(Q) $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata --redefine-sym _binary_ion_src_device_usb_dfu_bin_start=_dfu_bootloader_flash_start --redefine-sym _binary_ion_src_device_usb_dfu_bin_end=_dfu_bootloader_flash_end $< $@ - -objs += ion/src/device/usb/dfu.o -objs += ion/src/device/usb/dfu_relocated.o - -products += $(usb_objs) $(addprefix ion/src/device/usb/dfu, .elf .bin) - -endif diff --git a/ion/src/device/wakeup.cpp b/ion/src/device/wakeup.cpp deleted file mode 100644 index 3582d6ad3..000000000 --- a/ion/src/device/wakeup.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "wakeup.h" -#include "regs/regs.h" -#include "battery.h" -#include "usb.h" -#include "keyboard.h" - -namespace Ion { -namespace WakeUp { -namespace Device { - -void onChargingEvent() { - Battery::Device::initGPIO(); - - /* Warning: pins with the same number in different groups cannot be set as - * source input for EXTI at the same time. Here, EXTICR1 register is filled - * between position 0-3 (charging pin = 0) with - * 0000 (ChargingGPIO = group A). */ - SYSCFG.EXTICR1()->setEXTI(Battery::Device::ChargingPin, Battery::Device::ChargingGPIO); - - EXTI.EMR()->set(Battery::Device::ChargingPin, true); - - /* We need to detect when the battery stops charging. We set the - * wake up event on the rising edge. */ - EXTI.RTSR()->set(Battery::Device::ChargingPin, true); -} - -void onUSBPlugging() { - USB::Device::initGPIO(); - /* Here, EXTICR3 register is filled between position 4-7 (Vbus pin = 9) with - * 0000 (Vbus GPIO = group A). */ - SYSCFG.EXTICR3()->setEXTI(USB::Device::VbusPin.pin(), USB::Device::VbusPin.group()); - - EXTI.EMR()->set(USB::Device::VbusPin.pin(), true); -#if EPSILON_LED_WHILE_CHARGING - EXTI.FTSR()->set(USB::Device::VbusPin.pin(), true); -#endif - EXTI.RTSR()->set(USB::Device::VbusPin.pin(), true); -} - - -void onPowerKeyDown() { - Keyboard::Key key = Keyboard::Key::B2; - uint8_t rowPin = Keyboard::Device::RowPins[Keyboard::Device::rowForKey(key)]; - Keyboard::Device::RowGPIO.MODER()->setMode(rowPin, GPIO::MODER::Mode::Output); - Keyboard::Device::RowGPIO.OTYPER()->setType(rowPin, GPIO::OTYPER::Type::OpenDrain); - Keyboard::Device::RowGPIO.ODR()->set(rowPin, 0); - - uint8_t column = Keyboard::Device::columnForKey(key); - uint8_t columnPin = Keyboard::Device::ColumnPins[column]; - - Keyboard::Device::ColumnGPIO.MODER()->setMode(columnPin, GPIO::MODER::Mode::Input); - Keyboard::Device::ColumnGPIO.PUPDR()->setPull(columnPin, GPIO::PUPDR::Pull::Up); - - /* Here, EXTICR1 register is filled between position 4-7 (column pin = 1) with - * 0010 (ColumnGPIO = group C). */ - - SYSCFG.EXTICR1()->setEXTI(columnPin, Keyboard::Device::ColumnGPIO); - - EXTI.EMR()->set(columnPin, true); - - /* When the key is pressed, it will go from 1 (because it's pulled up) to - * zero (because it's connected to the open-drain output. In other words, - * we're waiting for a falling edge. */ - EXTI.FTSR()->set(columnPin, true); -} - -} -} -} From 7640805093a4d3a08b10013a184f167b24ac4988 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 5 Feb 2019 12:18:01 +0100 Subject: [PATCH 0131/1750] [ion] Use the AFGPIOPin class --- ion/src/device/n0100/drivers/config/console.h | 10 ++----- .../n0100/drivers/config/external_flash.h | 10 ++++--- ion/src/device/n0100/drivers/config/led.h | 13 +++------- ion/src/device/n0100/drivers/config/swd.h | 13 +++------- ion/src/device/n0100/drivers/config/usb.h | 6 ++--- ion/src/device/shared/drivers/console.cpp | 26 ++++++++++--------- .../device/shared/drivers/external_flash.cpp | 6 ++--- ion/src/device/shared/drivers/led.cpp | 10 +++---- ion/src/device/shared/drivers/swd.cpp | 10 +++---- ion/src/device/shared/drivers/usb.cpp | 18 +++++-------- ion/src/device/shared/regs/gpio.h | 23 ++++++++++++++++ 11 files changed, 73 insertions(+), 72 deletions(-) diff --git a/ion/src/device/n0100/drivers/config/console.h b/ion/src/device/n0100/drivers/config/console.h index fa6eb363e..5ec489512 100644 --- a/ion/src/device/n0100/drivers/config/console.h +++ b/ion/src/device/n0100/drivers/config/console.h @@ -3,21 +3,15 @@ #include -/* Pin | Role | Mode - * -----+-------------------+-------------------- - * PC11 | UART3 RX | Alternate Function - * PD8 | UART3 TX | Alternate Function - */ - namespace Ion { namespace Device { namespace Console { namespace Config { -constexpr static USART UARTPort = USART(3); +constexpr static USART Port = USART(3); constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 11); constexpr static GPIOPin TxPin = GPIOPin(GPIOD, 8); -constexpr static GPIOPin Pins[] = { RxPin, TxPin }; +constexpr static GPIO::AFR::AlternateFunction AlternateFunction = GPIO::AFR::AlternateFunction::AF8; } } diff --git a/ion/src/device/n0100/drivers/config/external_flash.h b/ion/src/device/n0100/drivers/config/external_flash.h index c9c656817..257942071 100644 --- a/ion/src/device/n0100/drivers/config/external_flash.h +++ b/ion/src/device/n0100/drivers/config/external_flash.h @@ -18,9 +18,13 @@ namespace Device { namespace ExternalFlash { namespace Config { -constexpr static GPIOPin QSPIPins[] = { - GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), - GPIOPin(GPIOC, 8), GPIOPin(GPIOD,13) +constexpr static AFGPIOPin Pins[] = { + AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOC, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOD, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOD, 13, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOE, 2, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), }; } diff --git a/ion/src/device/n0100/drivers/config/led.h b/ion/src/device/n0100/drivers/config/led.h index 4c1f23e5c..328158927 100644 --- a/ion/src/device/n0100/drivers/config/led.h +++ b/ion/src/device/n0100/drivers/config/led.h @@ -3,20 +3,15 @@ #include -/* Pin | Role | Mode | Function - * -----+-------------------+-----------------------+---------- - * PB0 | LED blue | Alternate Function 2 | TIM3_CH3 - * PB1 | LED green | Alternate Function 2 | TIM3_CH4 - * PC7 | LED red | Alternate Function 2 | TIM3_CH2 - */ - namespace Ion { namespace Device { namespace LED { namespace Config { -constexpr static GPIOPin RGBPins[] = { - GPIOPin(GPIOC, 7), GPIOPin(GPIOB, 1), GPIOPin(GPIOB, 0) +constexpr static AFGPIOPin RGBPins[] = { + AFGPIOPin(GPIOB, 0, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOB, 1, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOC, 7, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), }; } diff --git a/ion/src/device/n0100/drivers/config/swd.h b/ion/src/device/n0100/drivers/config/swd.h index 45d7efc28..744d7f018 100644 --- a/ion/src/device/n0100/drivers/config/swd.h +++ b/ion/src/device/n0100/drivers/config/swd.h @@ -3,20 +3,15 @@ #include -/* Pin | Role | Mode - * -----+-------------------+--------------------- - * PA13 | SWDIO | Alternate Function 0 - * PA14 | SWCLK | Alternate Function 0 - * PB3 | SWO | Alternate Function 0 - */ - namespace Ion { namespace Device { namespace SWD { namespace Config { -constexpr static GPIOPin Pins[] = { - GPIOPin(GPIOA, 13), GPIOPin(GPIOA, 14), GPIOPin(GPIOB, 3) +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), }; } diff --git a/ion/src/device/n0100/drivers/config/usb.h b/ion/src/device/n0100/drivers/config/usb.h index cc50e2599..c42119c67 100644 --- a/ion/src/device/n0100/drivers/config/usb.h +++ b/ion/src/device/n0100/drivers/config/usb.h @@ -8,9 +8,9 @@ namespace Device { namespace USB { namespace Config { -constexpr static GPIOPin VbusPin = GPIOPin(GPIOA, 9); -constexpr static GPIOPin DmPin = GPIOPin(GPIOA, 11); -constexpr static GPIOPin DpPin = GPIOPin(GPIOA, 12); +constexpr static AFGPIOPin VbusPin(GPIOA, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); +constexpr static AFGPIOPin DmPin(GPIOA, 11, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); +constexpr static AFGPIOPin DpPin(GPIOA, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); } } diff --git a/ion/src/device/shared/drivers/console.cpp b/ion/src/device/shared/drivers/console.cpp index 3b55443d9..61d2d86dd 100644 --- a/ion/src/device/shared/drivers/console.cpp +++ b/ion/src/device/shared/drivers/console.cpp @@ -12,15 +12,15 @@ namespace Console { using namespace Ion::Device::Console; char readChar() { - while (Config::UARTPort.SR()->getRXNE() == 0) { + while (Config::Port.SR()->getRXNE() == 0) { } - return (char)Config::UARTPort.DR()->get(); + return (char)Config::Port.DR()->get(); } void writeChar(char c) { - while (Config::UARTPort.SR()->getTXE() == 0) { + while (Config::Port.SR()->getTXE() == 0) { } - Config::UARTPort.DR()->set(c); + Config::Port.DR()->set(c); } } @@ -30,17 +30,19 @@ namespace Ion { namespace Device { namespace Console { +constexpr static GPIOPin Pins[] = { Config::RxPin, Config::TxPin }; + void init() { RCC.APB1ENR()->setUSART3EN(true); - for(const GPIOPin & g : Config::Pins) { + for(const GPIOPin & g : Pins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF7); + g.group().AFR()->setAlternateFunction(g.pin(), Config::AlternateFunction); } - Config::UARTPort.CR1()->setUE(true); - Config::UARTPort.CR1()->setTE(true); - Config::UARTPort.CR1()->setRE(true); + Config::Port.CR1()->setUE(true); + Config::Port.CR1()->setTE(true); + Config::Port.CR1()->setRE(true); /* We need to set the baud rate of the UART port. * This is set relative to the APB1 clock, which runs at 48 MHz. @@ -53,12 +55,12 @@ void init() { * DIV_MANTISSA = 26 * DIV_FRAC = 16*0.0416667 = 1 */ - Config::UARTPort.BRR()->setDIV_MANTISSA(26); - Config::UARTPort.BRR()->setDIV_FRAC(1); + Config::Port.BRR()->setDIV_MANTISSA(26); + Config::Port.BRR()->setDIV_FRAC(1); } void shutdown() { - for(const GPIOPin & g : Config::Pins) { + for(const GPIOPin & g : Pins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); } diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index deaaf6a78..c46451150 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -207,10 +207,8 @@ void init() { } void initGPIO() { - for(const GPIOPin & g : Config::QSPIPins) { - g.group().OSPEEDR()->setOutputSpeed(g.pin(), GPIO::OSPEEDR::OutputSpeed::High); - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), (g.pin() == 6 ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9)); + for(const AFGPIOPin & p : Config::Pins) { + p.init(); } } diff --git a/ion/src/device/shared/drivers/led.cpp b/ion/src/device/shared/drivers/led.cpp index 1302ecb95..6d7af8ef6 100644 --- a/ion/src/device/shared/drivers/led.cpp +++ b/ion/src/device/shared/drivers/led.cpp @@ -59,16 +59,14 @@ void initGPIO() { /* RED_LED(PC7), GREEN_LED(PB1), and BLUE_LED(PB0) are driven using a timer, * which is an alternate function. More precisely, we will use AF2, which maps * PB0 to TIM3_CH2, PB1 to TIM3_CH4, and PC7 to TIM3_CH2. */ - for(const GPIOPin & g : Config::RGBPins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF2); + for(const AFGPIOPin & p : Config::RGBPins) { + p.init(); } } void shutdownGPIO() { - for(const GPIOPin & g : Config::RGBPins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); - g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + for(const AFGPIOPin & p : Config::RGBPins) { + p.shutdown(); } } diff --git a/ion/src/device/shared/drivers/swd.cpp b/ion/src/device/shared/drivers/swd.cpp index bedc55bc6..d64ec521b 100644 --- a/ion/src/device/shared/drivers/swd.cpp +++ b/ion/src/device/shared/drivers/swd.cpp @@ -6,16 +6,14 @@ namespace Device { namespace SWD { void init() { - for(const GPIOPin & g : Config::Pins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF0); + for(const AFGPIOPin & p : Config::Pins) { + p.init(); } } void shutdown() { - for(const GPIOPin & g : Config::Pins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); - g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + for(const AFGPIOPin & p : Config::Pins) { + p.shutdown(); } } diff --git a/ion/src/device/shared/drivers/usb.cpp b/ion/src/device/shared/drivers/usb.cpp index 7cd581fa7..1b81b390f 100644 --- a/ion/src/device/shared/drivers/usb.cpp +++ b/ion/src/device/shared/drivers/usb.cpp @@ -70,22 +70,16 @@ void initGPIO() { Config::VbusPin.group().MODER()->setMode(Config::VbusPin.pin(), GPIO::MODER::Mode::Input); Config::VbusPin.group().PUPDR()->setPull(Config::VbusPin.pin(), GPIO::PUPDR::Pull::Down); #else - Config::VbusPin.group().MODER()->setMode(Config::VbusPin.pin(), GPIO::MODER::Mode::AlternateFunction); - Config::VbusPin.group().AFR()->setAlternateFunction(Config::VbusPin.pin(), GPIO::AFR::AlternateFunction::AF10); + Config::VbusPin.init(); #endif - - Config::DmPin.group().MODER()->setMode(Config::DmPin.pin(), GPIO::MODER::Mode::AlternateFunction); - Config::DmPin.group().AFR()->setAlternateFunction(Config::DmPin.pin(), GPIO::AFR::AlternateFunction::AF10); - - Config::DpPin.group().MODER()->setMode(Config::DpPin.pin(), GPIO::MODER::Mode::AlternateFunction); - Config::DpPin.group().AFR()->setAlternateFunction(Config::DpPin.pin(), GPIO::AFR::AlternateFunction::AF10); + Config::DmPin.init(); + Config::DpPin.init(); } void shutdownGPIO() { - constexpr static GPIOPin USBPins[] = {Config::DpPin, Config::DmPin, Config::VbusPin}; - for (const GPIOPin & g : USBPins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); - g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); + constexpr static AFGPIOPin Pins[] = {Config::DpPin, Config::DmPin, Config::VbusPin}; + for (const AFGPIOPin & p : Pins) { + p.shutdown(); } } diff --git a/ion/src/device/shared/regs/gpio.h b/ion/src/device/shared/regs/gpio.h index bf7300295..98021f26f 100644 --- a/ion/src/device/shared/regs/gpio.h +++ b/ion/src/device/shared/regs/gpio.h @@ -115,4 +115,27 @@ private: uint8_t m_data; }; +class AFGPIOPin { +public: + constexpr AFGPIOPin(GPIO group, uint8_t pin, GPIO::AFR::AlternateFunction af, GPIO::PUPDR::Pull pull, GPIO::OSPEEDR::OutputSpeed speed) : + m_data( ((static_cast(speed)&0x3)<<14) | ((static_cast(pull)&0x3)<<12) | ((static_cast(af)&0xF)<<8) | (group&0xF)<<4 | (pin&0xF) ) {} + GPIO group() const { return GPIO((m_data>>4)&0xF); } + uint8_t pin() const { return m_data&0xF; } + GPIO::AFR::AlternateFunction alternateFunction() const { return static_cast((m_data>>8)&0xF); } + GPIO::PUPDR::Pull pull() const { return static_cast((m_data>>12)&0x3); } + GPIO::OSPEEDR::OutputSpeed outputSpeed() const { return static_cast((m_data>>14)&0x3); } + void init() const { + group().OSPEEDR()->setOutputSpeed(pin(), outputSpeed()); + group().MODER()->setMode(pin(), GPIO::MODER::Mode::AlternateFunction); + group().AFR()->setAlternateFunction(pin(), alternateFunction()); + // TODO: Configure pulls + } + void shutdown() const { + group().MODER()->setMode(pin(), GPIO::MODER::Mode::Analog); + group().PUPDR()->setPull(pin(), GPIO::PUPDR::Pull::None); + } +private: + uint16_t m_data; +}; + #endif From 6dcdbede5633fd37dd4ba518083550f0a03a97dd Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 5 Feb 2019 17:07:42 +0100 Subject: [PATCH 0132/1750] [ion/device] Get N0101 working --- build/platform.device.n0101.mak | 1 + ion/src/device/n0100/regs/config/flash.h | 6 + ion/src/device/n0101/Makefile | 5 + ion/src/device/n0101/drivers/board.cpp | 179 ++++++++++++++++++ .../{f730 => device/n0101/drivers}/cache.h | 2 + .../device/n0101/drivers/config/backlight.h | 23 +++ ion/src/device/n0101/drivers/config/battery.h | 28 +++ ion/src/device/n0101/drivers/config/console.h | 21 ++ ion/src/device/n0101/drivers/config/display.h | 32 ++++ .../n0101/drivers/config/external_flash.h | 35 ++++ .../device/n0101/drivers/config/keyboard.h | 43 +++++ ion/src/device/n0101/drivers/config/led.h | 22 +++ ion/src/device/n0101/drivers/config/swd.h | 22 +++ ion/src/device/n0101/drivers/config/timing.h | 24 +++ ion/src/device/n0101/drivers/config/usb.h | 20 ++ ion/src/{f730/boot => device/n0101}/flash.ld | 0 ion/src/device/n0101/regs/config/flash.h | 6 + ion/src/device/shared/boot/rt0.cpp | 7 +- ion/src/device/shared/drivers/board.h | 1 + ion/src/device/shared/drivers/flash.cpp | 9 + ion/src/device/shared/regs/cm4.h | 34 ++++ ion/src/device/shared/regs/flash.h | 6 + ion/src/device/shared/regs/mpu.h | 20 +- ion/src/device/shared/regs/rcc.h | 1 + 24 files changed, 536 insertions(+), 11 deletions(-) create mode 100644 build/platform.device.n0101.mak create mode 100644 ion/src/device/n0100/regs/config/flash.h create mode 100644 ion/src/device/n0101/Makefile create mode 100644 ion/src/device/n0101/drivers/board.cpp rename ion/src/{f730 => device/n0101/drivers}/cache.h (97%) create mode 100644 ion/src/device/n0101/drivers/config/backlight.h create mode 100644 ion/src/device/n0101/drivers/config/battery.h create mode 100644 ion/src/device/n0101/drivers/config/console.h create mode 100644 ion/src/device/n0101/drivers/config/display.h create mode 100644 ion/src/device/n0101/drivers/config/external_flash.h create mode 100644 ion/src/device/n0101/drivers/config/keyboard.h create mode 100644 ion/src/device/n0101/drivers/config/led.h create mode 100644 ion/src/device/n0101/drivers/config/swd.h create mode 100644 ion/src/device/n0101/drivers/config/timing.h create mode 100644 ion/src/device/n0101/drivers/config/usb.h rename ion/src/{f730/boot => device/n0101}/flash.ld (100%) create mode 100644 ion/src/device/n0101/regs/config/flash.h diff --git a/build/platform.device.n0101.mak b/build/platform.device.n0101.mak new file mode 100644 index 000000000..b9b31d0f1 --- /dev/null +++ b/build/platform.device.n0101.mak @@ -0,0 +1 @@ +TOOLCHAIN ?= arm-gcc-m7f diff --git a/ion/src/device/n0100/regs/config/flash.h b/ion/src/device/n0100/regs/config/flash.h new file mode 100644 index 000000000..1bb0e23bd --- /dev/null +++ b/ion/src/device/n0100/regs/config/flash.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0100_REGS_CONFIG_FLASH_H +#define ION_DEVICE_N0100_REGS_CONFIG_FLASH_H + +#define REGS_FLASH_CONFIG_ART 0 + +#endif diff --git a/ion/src/device/n0101/Makefile b/ion/src/device/n0101/Makefile new file mode 100644 index 000000000..7bbdb9308 --- /dev/null +++ b/ion/src/device/n0101/Makefile @@ -0,0 +1,5 @@ +ion_device_objs += $(addprefix ion/src/device/n0101/drivers/, \ + board.o \ +) + +LDSCRIPT ?= ion/src/device/n0101/flash.ld diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp new file mode 100644 index 000000000..7a38c84ca --- /dev/null +++ b/ion/src/device/n0101/drivers/board.cpp @@ -0,0 +1,179 @@ +#include +#include +#include +#include + +// Public Ion methods + +const char * Ion::fccId() { + return "2ALWP-N0101"; +} + +// Private Ion::Device methods + +namespace Ion { +namespace Device { +namespace Board { + +void initL1Cache() { + Cache::enableICache(); + Cache::enableDCache(); +} + +void initMPU() { + // Configure MPU settings for the FMC memory area + // This is needed for interfacing with the LCD + MPU.RNR()->setREGION(0x00); + MPU.RBAR()->setADDR(0x60000000); + MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32MB); + 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.CTRL()->setPRIVDEFENA(true); + MPU.CTRL()->setENABLE(true); +} + +void init() { + initFPU(); + initL1Cache(); + initMPU(); + initClocks(); + + // Ensure right location of interrupt vectors + // The bootloader leaves its own after flashing + SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory); + CM4.VTOR()->setVTOR((void*) 0); + + // Put all inputs as Analog Input, No pull-up nor pull-down + // Except for the SWD port (PB3, PA13, PA14) + GPIOA.MODER()->set(0xEBFFFFFF); + GPIOA.PUPDR()->set(0x24000000); + GPIOB.MODER()->set(0xFFFFFFBF); + GPIOB.PUPDR()->set(0x00000000); + for (int g=2; g<5; g++) { + GPIO(g).MODER()->set(0xFFFFFFFF); // All to "Analog" + GPIO(g).PUPDR()->set(0x00000000); // All to "None" + } + + initPeripherals(); +} + +void initClocks() { + /* System clock + * Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */ + + /* 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); + + /* 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 HSE and wait for it to be ready + RCC.CR()->setHSEON(true); + while(!RCC.CR()->getHSERDY()) { + } + + /* Given the crystal used on our device, the HSE will oscillate at 25 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(25); + RCC.PLLCFGR()->setPLLN(384); + RCC.PLLCFGR()->setPLLQ(8); + RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); + // 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz + RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy4); + // 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz + RCC.CFGR()->setPPRE2(RCC::CFGR::APBPrescaler::AHBDividedBy2); + + /* If you want to considerably slow down the whole machine uniformely, which + * can be very useful to diagnose performance issues, just uncomment the line + * below. Note that even booting takes a few seconds, so don't be surprised + * if the screen is black for a short while upon booting. */ + // RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy128); + + // Enable the PLL and wait for it to be ready + RCC.CR()->setPLLON(true); + 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); + + // APB2 bus + class RCC::APB2ENR apb2enr(0); // Reset value + apb2enr.setADC1EN(true); + apb2enr.setSYSCFGEN(true); + RCC.APB2ENR()->set(apb2enr); +} + +void shutdownClocks(bool keepLEDAwake) { + // APB2 bus + RCC.APB2ENR()->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); + + RCC.AHB3ENR()->setFSMCEN(false); +} + +} +} +} diff --git a/ion/src/f730/cache.h b/ion/src/device/n0101/drivers/cache.h similarity index 97% rename from ion/src/f730/cache.h rename to ion/src/device/n0101/drivers/cache.h index 5dfd4f587..0a3d7fe29 100644 --- a/ion/src/f730/cache.h +++ b/ion/src/device/n0101/drivers/cache.h @@ -1,6 +1,8 @@ #ifndef ION_DEVICE_CACHE_H #define ION_DEVICE_CACHE_H +#include + namespace Ion { namespace Device { namespace Cache { diff --git a/ion/src/device/n0101/drivers/config/backlight.h b/ion/src/device/n0101/drivers/config/backlight.h new file mode 100644 index 000000000..80442917a --- /dev/null +++ b/ion/src/device/n0101/drivers/config/backlight.h @@ -0,0 +1,23 @@ +#ifndef ION_DEVICE_N0101_CONFIG_BACKLIGHT_H +#define ION_DEVICE_N0101_CONFIG_BACKLIGHT_H + +#include + +/* Pin | Role | Mode | Function + * -----+-------------------+-----------------------+---------- + * PE0 | Backlight Enable | Output | + */ + +namespace Ion { +namespace Device { +namespace Backlight { +namespace Config { + +constexpr static GPIOPin BacklightPin = GPIOPin(GPIOE, 0); + +} +} +} +} + +#endif diff --git a/ion/src/device/n0101/drivers/config/battery.h b/ion/src/device/n0101/drivers/config/battery.h new file mode 100644 index 000000000..60ce12903 --- /dev/null +++ b/ion/src/device/n0101/drivers/config/battery.h @@ -0,0 +1,28 @@ +#ifndef ION_DEVICE_N0101_CONFIG_BATTERY_H +#define ION_DEVICE_N0101_CONFIG_BATTERY_H + +#include + +/* 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 { + +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 diff --git a/ion/src/device/n0101/drivers/config/console.h b/ion/src/device/n0101/drivers/config/console.h new file mode 100644 index 000000000..adaf19bc5 --- /dev/null +++ b/ion/src/device/n0101/drivers/config/console.h @@ -0,0 +1,21 @@ +#ifndef ION_DEVICE_N0101_CONFIG_CONSOLE_H +#define ION_DEVICE_N0101_CONFIG_CONSOLE_H + +#include + +namespace Ion { +namespace Device { +namespace Console { +namespace Config { + +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; + +} +} +} +} + +#endif diff --git a/ion/src/device/n0101/drivers/config/display.h b/ion/src/device/n0101/drivers/config/display.h new file mode 100644 index 000000000..4d0d1e9d0 --- /dev/null +++ b/ion/src/device/n0101/drivers/config/display.h @@ -0,0 +1,32 @@ +#ifndef ION_DEVICE_N0101_CONFIG_DISPLAY_H +#define ION_DEVICE_N0101_CONFIG_DISPLAY_H + +#include + +namespace Ion { +namespace Device { +namespace Display { +namespace Config { + +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; + +} +} +} +} + +#endif diff --git a/ion/src/device/n0101/drivers/config/external_flash.h b/ion/src/device/n0101/drivers/config/external_flash.h new file mode 100644 index 000000000..54e4c7e29 --- /dev/null +++ b/ion/src/device/n0101/drivers/config/external_flash.h @@ -0,0 +1,35 @@ +#ifndef ION_DEVICE_N0101_CONFIG_EXTERNAL_FLASH_H +#define ION_DEVICE_N0101_CONFIG_EXTERNAL_FLASH_H + +#include + +/* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS + * PC8 | 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 { + +constexpr static AFGPIOPin Pins[] = { + AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOC, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOD, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOD, 13, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOE, 2, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/n0101/drivers/config/keyboard.h b/ion/src/device/n0101/drivers/config/keyboard.h new file mode 100644 index 000000000..22a356287 --- /dev/null +++ b/ion/src/device/n0101/drivers/config/keyboard.h @@ -0,0 +1,43 @@ +#ifndef ION_DEVICE_N0101_CONFIG_KEYBOARD_H +#define ION_DEVICE_N0101_CONFIG_KEYBOARD_H + +#include + +/* 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 + * PE0 | Keyboard row A | Output, open drain + * PE1 | Keyboard row B | Output, open drain + * PE2 | Keyboard row C | Output, open drain + * PE3 | Keyboard row D | Output, open drain + * PE4 | Keyboard row E | Output, open drain + * PE5 | Keyboard row F | Output, open drain + * PE6 | Keyboard row G | Output, open drain + * PE7 | Keyboard row H | Output, open drain + * PE8 | Keyboard row I | Output, open drain + */ + +namespace Ion { +namespace Device { +namespace Keyboard { +namespace Config { + +constexpr GPIO RowGPIO = GPIOA; +constexpr uint8_t numberOfRows = 9; +constexpr uint8_t RowPins[numberOfRows] = {0, 1, 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}; + +} +} +} +} + +#endif diff --git a/ion/src/device/n0101/drivers/config/led.h b/ion/src/device/n0101/drivers/config/led.h new file mode 100644 index 000000000..6d0d06877 --- /dev/null +++ b/ion/src/device/n0101/drivers/config/led.h @@ -0,0 +1,22 @@ +#ifndef ION_DEVICE_N0101_CONFIG_LED_H +#define ION_DEVICE_N0101_CONFIG_LED_H + +#include + +namespace Ion { +namespace Device { +namespace LED { +namespace Config { + +constexpr static AFGPIOPin RGBPins[] = { + AFGPIOPin(GPIOB, 4, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOB, 5, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOB, 0, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/n0101/drivers/config/swd.h b/ion/src/device/n0101/drivers/config/swd.h new file mode 100644 index 000000000..31aa6825f --- /dev/null +++ b/ion/src/device/n0101/drivers/config/swd.h @@ -0,0 +1,22 @@ +#ifndef ION_DEVICE_N0101_CONFIG_SWD_H +#define ION_DEVICE_N0101_CONFIG_SWD_H + +#include + +namespace Ion { +namespace Device { +namespace SWD { +namespace Config { + +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 diff --git a/ion/src/device/n0101/drivers/config/timing.h b/ion/src/device/n0101/drivers/config/timing.h new file mode 100644 index 000000000..71d6c3f3d --- /dev/null +++ b/ion/src/device/n0101/drivers/config/timing.h @@ -0,0 +1,24 @@ +#ifndef ION_DEVICE_N0100_CONFIG_TIMING_H +#define ION_DEVICE_N0100_CONFIG_TIMING_H + +#include + +namespace Ion { +namespace Device { +namespace Timing { +namespace Config { + +constexpr static int LoopsPerMillisecond = 8852; +constexpr static int LoopsPerMicrosecond = 9; +// CPU clock is 96 MHz, and systick clock source is divided by 8 +// To get 1 ms systick overflow we need to reset it to +// 96 000 000 (Hz) / 8 / 1 000 (ms/s) - 1 (because the counter resets *after* counting to 0) +constexpr static int SysTickPerMillisecond = 12000; + + +} +} +} +} + +#endif diff --git a/ion/src/device/n0101/drivers/config/usb.h b/ion/src/device/n0101/drivers/config/usb.h new file mode 100644 index 000000000..864ed5d42 --- /dev/null +++ b/ion/src/device/n0101/drivers/config/usb.h @@ -0,0 +1,20 @@ +#ifndef ION_DEVICE_N0101_CONFIG_USB_H +#define ION_DEVICE_N0101_CONFIG_USB_H + +#include + +namespace Ion { +namespace Device { +namespace USB { +namespace Config { + +constexpr static AFGPIOPin VbusPin = AFGPIOPin(GPIOA, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); +constexpr static AFGPIOPin DmPin = AFGPIOPin(GPIOA, 11, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); +constexpr static AFGPIOPin DpPin = AFGPIOPin(GPIOA, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); + +} +} +} +} + +#endif diff --git a/ion/src/f730/boot/flash.ld b/ion/src/device/n0101/flash.ld similarity index 100% rename from ion/src/f730/boot/flash.ld rename to ion/src/device/n0101/flash.ld diff --git a/ion/src/device/n0101/regs/config/flash.h b/ion/src/device/n0101/regs/config/flash.h new file mode 100644 index 000000000..b0e018fa8 --- /dev/null +++ b/ion/src/device/n0101/regs/config/flash.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0101_REGS_CONFIG_FLASH_H +#define ION_DEVICE_N0101_REGS_CONFIG_FLASH_H + +#define REGS_FLASH_CONFIG_ART 1 + +#endif diff --git a/ion/src/device/shared/boot/rt0.cpp b/ion/src/device/shared/boot/rt0.cpp index 2e3dfacfc..5e4b10f13 100644 --- a/ion/src/device/shared/boot/rt0.cpp +++ b/ion/src/device/shared/boot/rt0.cpp @@ -59,12 +59,7 @@ void start() { /* Initialize the FPU as early as possible. * For example, static C++ objects are very likely to manipulate float values */ - // FIXME//TODO//Ion::Device::initFPU(); -#warning BOO - -#if 0 - Ion::Device::initMPU(); -#endif + Ion::Device::Board::initFPU(); /* Call static C++ object constructors * The C++ compiler creates an initialization function for each static object. diff --git a/ion/src/device/shared/drivers/board.h b/ion/src/device/shared/drivers/board.h index aa0b055d6..0e314c10f 100644 --- a/ion/src/device/shared/drivers/board.h +++ b/ion/src/device/shared/drivers/board.h @@ -8,6 +8,7 @@ namespace Board { void init(); void shutdown(); +void initFPU(); void initClocks(); void shutdownClocks(bool keepLEDAwake = false); diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index bd071d1b4..dcac93c07 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -31,6 +31,14 @@ static void close() { FLASH.CR()->setLOCK(true); // Purge Data and instruction cache +#if REGS_FLASH_CONFIG_ART + if (FLASH.ACR()->getARTEN()) { + FLASH.ACR()->setARTEN(false); + FLASH.ACR()->setARTRST(true); + FLASH.ACR()->setARTRST(false); + FLASH.ACR()->setARTEN(true); + } +#else if (FLASH.ACR()->getDCEN()) { FLASH.ACR()->setDCEN(false); FLASH.ACR()->setDCRST(true); @@ -43,6 +51,7 @@ static void close() { FLASH.ACR()->setICRST(false); FLASH.ACR()->setICEN(true); } +#endif } // Compile-time log2 diff --git a/ion/src/device/shared/regs/cm4.h b/ion/src/device/shared/regs/cm4.h index 58c5af1cb..d3b43d502 100644 --- a/ion/src/device/shared/regs/cm4.h +++ b/ion/src/device/shared/regs/cm4.h @@ -40,6 +40,12 @@ public: REGS_BOOL_FIELD(SLEEPDEEP, 2); }; + class CCR : public Register32 { + public: + REGS_BOOL_FIELD(IC, 17); + REGS_BOOL_FIELD(DC, 16); + }; + class SYST_CSR : public Register32 { public: enum class CLKSOURCE : uint8_t { @@ -62,6 +68,29 @@ public: REGS_FIELD(CURRENT, uint32_t, 23, 0); }; + class ICIALLU : public Register32 { + public: + using Register32::Register32; + }; + + class CSSELR : public Register32 { + public: + REGS_BOOL_FIELD(IND, 0); + }; + + class CCSIDR : public Register32 { + public: + REGS_FIELD(ASSOCIATIVITY, uint16_t, 12, 3); + REGS_FIELD(NUMSETS, uint16_t, 27, 13); + }; + + class DCISW : public Register32 { + public: + DCISW() : Register32(0) {} + REGS_FIELD(SET, uint16_t, 13, 5); + REGS_FIELD(WAY, uint8_t, 31, 30); + }; + constexpr CM4() {}; REGS_REGISTER_AT(SYST_CSR, 0x10); REGS_REGISTER_AT(SYST_RVR, 0x14); @@ -69,7 +98,12 @@ public: REGS_REGISTER_AT(VTOR, 0xD08); REGS_REGISTER_AT(AIRCR, 0xD0C); REGS_REGISTER_AT(SCR, 0xD10); + REGS_REGISTER_AT(CCR, 0xD14); + REGS_REGISTER_AT(CCSIDR, 0xD80); + REGS_REGISTER_AT(CSSELR, 0xD84); REGS_REGISTER_AT(CPACR, 0xD88); + REGS_REGISTER_AT(ICIALLU, 0xF50); + REGS_REGISTER_AT(DCISW, 0xF60); private: constexpr uint32_t Base() const { return 0xE000E000; diff --git a/ion/src/device/shared/regs/flash.h b/ion/src/device/shared/regs/flash.h index 218b17e4b..bec15d24a 100644 --- a/ion/src/device/shared/regs/flash.h +++ b/ion/src/device/shared/regs/flash.h @@ -2,6 +2,7 @@ #define REGS_FLASH_H #include "register.h" +#include class FLASH { public: @@ -9,10 +10,15 @@ public: public: REGS_FIELD(LATENCY, uint8_t, 3, 0); REGS_BOOL_FIELD(PRFTEN, 8); +#if REGS_FLASH_CONFIG_ART + REGS_BOOL_FIELD(ARTEN, 9); + REGS_BOOL_FIELD(ARTRST, 9); +#else REGS_BOOL_FIELD(ICEN, 9); REGS_BOOL_FIELD(DCEN, 10); REGS_BOOL_FIELD(ICRST, 11); REGS_BOOL_FIELD(DCRST, 12); +#endif }; class KEYR : public Register32 { diff --git a/ion/src/device/shared/regs/mpu.h b/ion/src/device/shared/regs/mpu.h index f9c2049f4..2d82a5e10 100644 --- a/ion/src/device/shared/regs/mpu.h +++ b/ion/src/device/shared/regs/mpu.h @@ -26,7 +26,7 @@ public: class RBAR : Register32 { public: - void setADDR(void * address) volatile { assert(((uint32_t)address & 0b11111) == 0); setBitRange(31, 5, (uint32_t)address >> 5); } + void setADDR(uint32_t address) volatile { /* assert((address & 0b11111) == 0);*/ setBitRange(31, 5, address >> 5); } REGS_BOOL_FIELD(VALID, 4); REGS_FIELD(REGION, uint8_t, 3, 0); }; @@ -34,11 +34,19 @@ public: class RASR : Register32 { public: REGS_BOOL_FIELD(XN, 28); - REGS_FIELD(AP, uint8_t, 26, 24); + enum class AccessPermission : uint8_t { + NoAccess = 0, + PrivilegedRO = 5, + PrivilegedRW = 1, + PrivilegedRWUnprivilegedRO = 2, + RO = 6, + RW = 3 + }; + REGS_FIELD(AP, AccessPermission, 26, 24); REGS_FIELD(TEX, uint8_t, 21, 19); - REGS_BOOL_FIELD(S, 18); - REGS_BOOL_FIELD(C, 17); - REGS_BOOL_FIELD(B, 16); + REGS_BOOL_FIELD(S, 18); // Shareable + REGS_BOOL_FIELD(C, 17); // Cacheable + REGS_BOOL_FIELD(B, 16); // Buffereable REGS_FIELD(SRD, uint8_t, 15, 8); enum class RegionSize : uint8_t { Bytes32 = 0b00100, @@ -46,6 +54,8 @@ public: Bytes128 = 0b00110, KyloBytes1 = 0b01001, MegaBytes1 = 0b10011, + _1MB = 19, + _32MB = 24, GigaBytes1 = 0b11101, GigaBytes4 = 0b11111 }; diff --git a/ion/src/device/shared/regs/rcc.h b/ion/src/device/shared/regs/rcc.h index 0536441d4..daa043a35 100644 --- a/ion/src/device/shared/regs/rcc.h +++ b/ion/src/device/shared/regs/rcc.h @@ -57,6 +57,7 @@ public: AHBDividedBy16 = 7 }; void setPPRE1(APBPrescaler r) volatile { setBitRange(12, 10, (uint32_t)r); } + void setPPRE2(APBPrescaler r) volatile { setBitRange(15, 13, (uint32_t)r); } }; class AHB1ENR : public Register32 { From 4145d9b5ba2cef41e0f660daf3d328d63197e51f Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 5 Feb 2019 17:13:18 +0100 Subject: [PATCH 0133/1750] [ion] Fix openocd scripts --- build/device/gdb_script.gdb | 14 ++++---------- build/device/{openocd.cfg => openocd.n0100.cfg} | 0 .../{f730/openocd.cfg => device/openocd.n0101.cfg} | 0 build/platform.f730.mak | 2 -- build/targets.device.mak | 2 +- build/targets.f730.mak | 1 - 6 files changed, 5 insertions(+), 14 deletions(-) rename build/device/{openocd.cfg => openocd.n0100.cfg} (100%) rename build/{f730/openocd.cfg => device/openocd.n0101.cfg} (100%) delete mode 100644 build/platform.f730.mak delete mode 100644 build/targets.f730.mak diff --git a/build/device/gdb_script.gdb b/build/device/gdb_script.gdb index 04d744db2..64447d4a4 100644 --- a/build/device/gdb_script.gdb +++ b/build/device/gdb_script.gdb @@ -3,7 +3,6 @@ define armex printf "SCB_CFSR 0x%x\n", *0xE000ED28 printf "SCB_HFSR 0x%x\n", *0xE000ED2C - printf "SCB_HFSR 0x%x\n", *0xE000ED2C printf "SCB_MMAR 0x%x\n", *0xE000ED34 printf "SCB_BFAR 0x%x\n", *0xE000ED38 printf "xPSR 0x%x\n", *(int *)($msp+28) @@ -33,13 +32,8 @@ set pagination off load # Tell OpenOCD to reset and halt -monitor itm ports on -monitor tpiu config internal swo.log.bin uart off 16000000 -monitor reset halt +# monitor itm ports on +# monitor tpiu config internal swo.log.bin uart off 16000000 +# monitor reset halt -break init -break abort -break __assert -watch *(int *)(&_stack_end) - -continue +# continue diff --git a/build/device/openocd.cfg b/build/device/openocd.n0100.cfg similarity index 100% rename from build/device/openocd.cfg rename to build/device/openocd.n0100.cfg diff --git a/build/f730/openocd.cfg b/build/device/openocd.n0101.cfg similarity index 100% rename from build/f730/openocd.cfg rename to build/device/openocd.n0101.cfg diff --git a/build/platform.f730.mak b/build/platform.f730.mak deleted file mode 100644 index 76511cbcd..000000000 --- a/build/platform.f730.mak +++ /dev/null @@ -1,2 +0,0 @@ -TOOLCHAIN ?= arm-gcc-m7f -include build/platform.device.mak diff --git a/build/targets.device.mak b/build/targets.device.mak index 6ff86d987..78e1ec730 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -48,7 +48,7 @@ products += $(patsubst %.$(EXE),%.map,$(filter %.$(EXE),$(products))) .PHONY: openocd openocd: - openocd -f build/$(PLATFORM)/openocd.cfg + openocd -f build/$(PLATFORM)/openocd.$(MODEL).cfg ifeq ($(EPSILON_USB_DFU_XIP)$(EPSILON_DEVICE_BENCH),10) flasher.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/usb/flasher.ld diff --git a/build/targets.f730.mak b/build/targets.f730.mak deleted file mode 100644 index 11effd8c7..000000000 --- a/build/targets.f730.mak +++ /dev/null @@ -1 +0,0 @@ -include build/targets.device.mak From f859573db746bfd566cd39c52ebab98838ded502 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 5 Feb 2019 17:30:49 +0100 Subject: [PATCH 0134/1750] [ion/device] Configure the display from HCLK frequency --- ion/src/device/n0100/drivers/config/display.h | 2 ++ ion/src/device/n0101/drivers/config/display.h | 2 ++ ion/src/device/shared/drivers/display.cpp | 16 ++++++++++------ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/ion/src/device/n0100/drivers/config/display.h b/ion/src/device/n0100/drivers/config/display.h index d8f31f86f..e70f75301 100644 --- a/ion/src/device/n0100/drivers/config/display.h +++ b/ion/src/device/n0100/drivers/config/display.h @@ -52,6 +52,8 @@ constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 10); constexpr static DMA DMAEngine = DMA2; constexpr static int DMAStream = 0; +constexpr static int HCLKFrequencyInMHz = 96; + } } } diff --git a/ion/src/device/n0101/drivers/config/display.h b/ion/src/device/n0101/drivers/config/display.h index 4d0d1e9d0..636416d4e 100644 --- a/ion/src/device/n0101/drivers/config/display.h +++ b/ion/src/device/n0101/drivers/config/display.h @@ -24,6 +24,8 @@ constexpr static GPIOPin TearingEffectPin = GPIOPin(GPIOB, 11); constexpr static DMA DMAEngine = DMA2; constexpr static int DMAStream = 0; +constexpr static int HCLKFrequencyInMHz = 192; + } } } diff --git a/ion/src/device/shared/drivers/display.cpp b/ion/src/device/shared/drivers/display.cpp index a187f6f8e..6ad254189 100644 --- a/ion/src/device/shared/drivers/display.cpp +++ b/ion/src/device/shared/drivers/display.cpp @@ -181,6 +181,10 @@ void shutdownGPIO() { Config::TearingEffectPin.group().MODER()->setMode(Config::TearingEffectPin.pin(), GPIO::MODER::Mode::Analog); } +static inline int nsToCycles(int nanoseconds) { + return (nanoseconds*Config::HCLKFrequencyInMHz)/1000 + 1; +} + void initFSMC() { /* Set up the FSMC control registers. * We address the LCD panel as if it were an SRAM module, using a 16bits wide @@ -224,17 +228,17 @@ void initFSMC() { */ // Read timing from the LCD - FSMC.BTR(FSMCMemoryBank)->setADDSET(2); + FSMC.BTR(FSMCMemoryBank)->setADDSET(nsToCycles(15)); FSMC.BTR(FSMCMemoryBank)->setADDHLD(0); - FSMC.BTR(FSMCMemoryBank)->setDATAST(36); - FSMC.BTR(FSMCMemoryBank)->setBUSTURN(10); + FSMC.BTR(FSMCMemoryBank)->setDATAST(nsToCycles(355+15)); + FSMC.BTR(FSMCMemoryBank)->setBUSTURN(nsToCycles(90+15)); FSMC.BTR(FSMCMemoryBank)->setACCMOD(FSMC::BTR::ACCMOD::A); // Write timings for the LCD - FSMC.BWTR(FSMCMemoryBank)->setADDSET(2); + FSMC.BWTR(FSMCMemoryBank)->setADDSET(nsToCycles(15)); FSMC.BWTR(FSMCMemoryBank)->setADDHLD(0); - FSMC.BWTR(FSMCMemoryBank)->setDATAST(3); - FSMC.BWTR(FSMCMemoryBank)->setBUSTURN(3); + FSMC.BWTR(FSMCMemoryBank)->setDATAST(nsToCycles(15+15)); + FSMC.BWTR(FSMCMemoryBank)->setBUSTURN(nsToCycles(15+15)); FSMC.BWTR(FSMCMemoryBank)->setACCMOD(FSMC::BWTR::ACCMOD::A); } From a95b7df09148845e7e5e8ba8c283e43b2e7a88c4 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 8 Feb 2019 16:55:02 +0100 Subject: [PATCH 0135/1750] [ion] Update the flasher's target --- build/targets.device.mak | 7 +++- ion/src/device/n0100/ram.ld | 77 +++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 ion/src/device/n0100/ram.ld diff --git a/build/targets.device.mak b/build/targets.device.mak index 78e1ec730..ec225b87a 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -50,9 +50,12 @@ products += $(patsubst %.$(EXE),%.map,$(filter %.$(EXE),$(products))) openocd: openocd -f build/$(PLATFORM)/openocd.$(MODEL).cfg +# The flasher target is defined here because otherwise $(objs) has not been +# fully filled ifeq ($(EPSILON_USB_DFU_XIP)$(EPSILON_DEVICE_BENCH),10) -flasher.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/usb/flasher.ld -flasher.$(EXE): $(objs) $(usb_objs) ion/src/$(PLATFORM)/usb/flasher.o +ion/src/$(PLATFORM)/shared/usb/flasher.o: SFLAGS += $(ION_DEVICE_SFLAGS) +flasher.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/$(MODEL)/ram.ld +flasher.$(EXE): $(objs) ion/src/$(PLATFORM)/shared/usb/flasher.o else flasher.$(EXE): @echo "Error: flasher.elf requires EPSILON_DEVICE_BENCH=0 EPSILON_USB_DFU_XIP=1" diff --git a/ion/src/device/n0100/ram.ld b/ion/src/device/n0100/ram.ld new file mode 100644 index 000000000..5621192b3 --- /dev/null +++ b/ion/src/device/n0100/ram.ld @@ -0,0 +1,77 @@ +/* Create a USB DFU firmware that runs from RAM. + * Flashing using ST's ROMed DFU bootloader is reliable but very slow. To make + * flashing faster, we can leverage ST's bootloader to copy a small "flasher" in + * RAM, and run it from there. + * Caution: ST's bootloader uses some RAM, so we want to stay off of that memory + * region. Per AN2606, section 47, it's using 0x20003000 - 0x2003FFFF; We'll try + * to play safe and avoid the first 32KB of RAM. */ + +MEMORY { + RAM_BUFFER (rw) : ORIGIN = 0x20000000 + 32K, LENGTH = 256K - 32K +} + +/* The stack is quite large, and should really be equal to Epsilon's. Indeed, + * we're making the Calculator object live on the stack, and it's quite large + * (about 4K just for this single object). Using a stack too small will result + * in some memory being overwritten (for instance, vtables that live in the + * .rodata section). */ + +STACK_SIZE = 32K; + +SECTIONS { + .isr_vector_table ORIGIN(RAM_BUFFER) : { + KEEP(*(.isr_vector_table)) + } >RAM_BUFFER + + .text : { + . = ALIGN(4); + *(.text) + *(.text.*) + } >RAM_BUFFER + + .init_array : { + . = ALIGN(4); + _init_array_start = .; + KEEP (*(.init_array*)) + _init_array_end = .; + } >RAM_BUFFER + + .rodata : { + . = ALIGN(4); + *(.rodata) + *(.rodata.*) + } >RAM_BUFFER + + .data : { + . = ALIGN(4); + *(.data) + *(.data.*) + } >RAM_BUFFER + + .bss : { + . = ALIGN(4); + _bss_section_start_ram = .; + *(.bss) + *(.bss.*) + *(COMMON) + _bss_section_end_ram = .; + } >RAM_BUFFER + + .stack : { + . = ALIGN(8); + _stack_end = .; + . += (STACK_SIZE - 8); + . = ALIGN(8); + _stack_start = .; + } >RAM_BUFFER + + .phony : { + /* We won't do dynamic memory allocation */ + _heap_start = .; + _heap_end = .; + /* Effectively bypass copying .data to RAM */ + _data_section_start_flash = .; + _data_section_start_ram = .; + _data_section_end_ram = .; + } >RAM_BUFFER +} From 4dbcf1619cbe0bccfe5fac9648e5daaf49296399 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 8 Feb 2019 16:56:29 +0100 Subject: [PATCH 0136/1750] [ion/flasher] Fix includes --- ion/src/device/shared/usb/flasher.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/shared/usb/flasher.cpp b/ion/src/device/shared/usb/flasher.cpp index 71cebf261..2412529a3 100644 --- a/ion/src/device/shared/usb/flasher.cpp +++ b/ion/src/device/shared/usb/flasher.cpp @@ -1,4 +1,4 @@ -#include "../regs/regs.h" +#include #include "../usb/calculator.h" #include @@ -8,6 +8,6 @@ void ion_main(int argc, char * argv[]) { Ion::USB::enable(); while (!Ion::USB::isEnumerated()) { } - Ion::USB::Device::Calculator::PollAndReset(false); + Ion::Device::USB::Calculator::PollAndReset(false); } } From 825425775087700bd53dd4fb18bf36ecd7b4941c Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 8 Feb 2019 20:50:38 +0100 Subject: [PATCH 0137/1750] [build] Cleanup --- build/f730/gdb_script.gdb | 36 ------------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 build/f730/gdb_script.gdb diff --git a/build/f730/gdb_script.gdb b/build/f730/gdb_script.gdb deleted file mode 100644 index 515e20021..000000000 --- a/build/f730/gdb_script.gdb +++ /dev/null @@ -1,36 +0,0 @@ -# Add a routine to debug ARM exceptions - -define armex - printf "SCB_CFSR 0x%x\n", *0xE000ED28 - printf "SCB_HFSR 0x%x\n", *0xE000ED2C - printf "SCB_HFSR 0x%x\n", *0xE000ED2C - printf "SCB_MMAR 0x%x\n", *0xE000ED34 - printf "SCB_BFAR 0x%x\n", *0xE000ED38 - printf "xPSR 0x%x\n", *(int *)($msp+28) - printf "ReturnAddress 0x%x\n", *(int *)($msp+24) - printf "LR (R14) 0x%x\n", *(int *)($msp+20) - printf "R12 0x%x\n", *(int *)($msp+16) - printf "R3 0x%x\n", *(int *)($msp+12) - printf "R2 0x%x\n", *(int *)($msp+8) - printf "R1 0x%x\n", *(int *)($msp+4) - printf "R0 0x%x\n", *(int *)($msp) - printf "Return instruction:\n" - x/i *(int *)($msp+24) -end - -document armex -ARMv7 Exception entry behavior. -xPSR, ReturnAddress, LR (R14), R12, R3, R2, R1, and R0 -end - -# Let's connect to OpenOCD -target remote localhost:3333 - -# GDB pagniation is annoying -set pagination off - -# Load our executable -load - -# Tell OpenOCD to reset and halt -monitor reset halt From d96d53e1600b5fd1f1e7c04e6b91a02eafad83f7 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 8 Feb 2019 22:12:54 +0100 Subject: [PATCH 0138/1750] [build/device] Generate DfuSe files --- build/device/elf2dfu.py | 66 ++++++++++++++++++++++++++++++++++++++++ build/targets.device.mak | 4 +++ 2 files changed, 70 insertions(+) create mode 100644 build/device/elf2dfu.py diff --git a/build/device/elf2dfu.py b/build/device/elf2dfu.py new file mode 100644 index 000000000..418065dae --- /dev/null +++ b/build/device/elf2dfu.py @@ -0,0 +1,66 @@ +#coding=utf-8 +# Translate ELF file into DfuSe file + +import sys +import struct +import zlib # for CRC-32 calculation +import subprocess +import re +import argparse + +# arm-none-eabi-objcopy -h -w file.elf +# arm-none-eabi-objcopy -O binary -j .data file.elf file.bin + +def loadable_sections(elf_file): + objdump_section_headers_pattern = re.compile("^\s+\d+\s+(\.[\w\.]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+).*LOAD", flags=re.MULTILINE) + objdump_output = subprocess.check_output(["arm-none-eabi-objdump", "-h", "-w", elf_file]) + sections = [] + for (name, size, vma, lma, offset) in re.findall(objdump_section_headers_pattern, objdump_output): + int_size = int(size,16) + if (int_size > 0): + sections.append({'name': name, 'size': int_size, 'vma': int(vma,16), 'lma': int(lma,16), 'offset':int(offset,16)}) + return sections + +def generate_dfu_file(targets, usb_vid_pid, dfu_file): + data = b'' + for t, target in enumerate(targets): + target_data = b'' + for image in target: + # Pad the image to 8 bytes, this seems to be needed + pad = (8 - len(image['data']) % 8 ) % 8 + image['data'] = image['data'] + bytes(bytearray(8)[0:pad]) + target_data += struct.pack('<2I', image['address'], len(image['data'])) + image['data'] + target_data = struct.pack('<6sBI255s2I', b'Target', 0, 1, b'ST...', len(target_data), len(target)) + target_data + data += target_data + data = struct.pack('<5sBIB', b'DfuSe', 1, len(data) + 11, len(targets)) + data + v, d = map(lambda x: int(x,0) & 0xFFFF, usb_vid_pid.split(':')) + data += struct.pack('<4H3sB', 0, d, v, 0x011a, b'UFD', 16) + crc = (0xFFFFFFFF & -zlib.crc32(data) - 1) + data += struct.pack(' Date: Mon, 11 Feb 2019 12:29:03 +0100 Subject: [PATCH 0139/1750] [ion] Fix Serial Number adresses on different models --- .../device/n0100/drivers/config/serial_number.h | 14 ++++++++++++++ .../device/n0101/drivers/config/serial_number.h | 15 +++++++++++++++ ion/src/device/shared/drivers/serial_number.cpp | 4 ++-- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 ion/src/device/n0100/drivers/config/serial_number.h create mode 100644 ion/src/device/n0101/drivers/config/serial_number.h diff --git a/ion/src/device/n0100/drivers/config/serial_number.h b/ion/src/device/n0100/drivers/config/serial_number.h new file mode 100644 index 000000000..475ac02b3 --- /dev/null +++ b/ion/src/device/n0100/drivers/config/serial_number.h @@ -0,0 +1,14 @@ +#ifndef ION_DEVICE_N0100_SERIAL_NUMBER_H +#define ION_DEVICE_N0100_SERIAL_NUMBER_H + +namespace Ion { +namespace Device { +namespace SerialNumber { + +constexpr static int RawUniqueIDAddress = 0x1FFF7A10; + +} +} +} + +#endif diff --git a/ion/src/device/n0101/drivers/config/serial_number.h b/ion/src/device/n0101/drivers/config/serial_number.h new file mode 100644 index 000000000..87982ea9e --- /dev/null +++ b/ion/src/device/n0101/drivers/config/serial_number.h @@ -0,0 +1,15 @@ +#ifndef ION_DEVICE_N0101_SERIAL_NUMBER_H +#define ION_DEVICE_N0101_SERIAL_NUMBER_H + +namespace Ion { +namespace Device { +namespace SerialNumber { + +constexpr static int RawUniqueIDAddress = 0x1FF07A10; + + +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/serial_number.cpp b/ion/src/device/shared/drivers/serial_number.cpp index ea1d354fb..bb21c8ca9 100644 --- a/ion/src/device/shared/drivers/serial_number.cpp +++ b/ion/src/device/shared/drivers/serial_number.cpp @@ -1,4 +1,5 @@ #include "serial_number.h" +#include #include "base64.h" namespace Ion { @@ -20,8 +21,7 @@ namespace Device { namespace SerialNumber { void copy(char * buffer) { - const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; - Base64::encode(rawUniqueID, 12, buffer); + Base64::encode((const unsigned char *)RawUniqueIDAddress, 12, buffer); buffer[Length] = 0; } From 91da98cd92eec8c98752d2a6d59c0a06f37b456a Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 12 Feb 2019 10:22:02 +0100 Subject: [PATCH 0140/1750] Revert "[ion] Fix Serial Number adresses on different models" This reverts commit c7334c1551e9dbc27a50e1c270435992a34c39a3. --- .../device/n0100/drivers/config/serial_number.h | 14 -------------- .../device/n0101/drivers/config/serial_number.h | 15 --------------- ion/src/device/shared/drivers/serial_number.cpp | 4 ++-- 3 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 ion/src/device/n0100/drivers/config/serial_number.h delete mode 100644 ion/src/device/n0101/drivers/config/serial_number.h diff --git a/ion/src/device/n0100/drivers/config/serial_number.h b/ion/src/device/n0100/drivers/config/serial_number.h deleted file mode 100644 index 475ac02b3..000000000 --- a/ion/src/device/n0100/drivers/config/serial_number.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef ION_DEVICE_N0100_SERIAL_NUMBER_H -#define ION_DEVICE_N0100_SERIAL_NUMBER_H - -namespace Ion { -namespace Device { -namespace SerialNumber { - -constexpr static int RawUniqueIDAddress = 0x1FFF7A10; - -} -} -} - -#endif diff --git a/ion/src/device/n0101/drivers/config/serial_number.h b/ion/src/device/n0101/drivers/config/serial_number.h deleted file mode 100644 index 87982ea9e..000000000 --- a/ion/src/device/n0101/drivers/config/serial_number.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ION_DEVICE_N0101_SERIAL_NUMBER_H -#define ION_DEVICE_N0101_SERIAL_NUMBER_H - -namespace Ion { -namespace Device { -namespace SerialNumber { - -constexpr static int RawUniqueIDAddress = 0x1FF07A10; - - -} -} -} - -#endif diff --git a/ion/src/device/shared/drivers/serial_number.cpp b/ion/src/device/shared/drivers/serial_number.cpp index bb21c8ca9..ea1d354fb 100644 --- a/ion/src/device/shared/drivers/serial_number.cpp +++ b/ion/src/device/shared/drivers/serial_number.cpp @@ -1,5 +1,4 @@ #include "serial_number.h" -#include #include "base64.h" namespace Ion { @@ -21,7 +20,8 @@ namespace Device { namespace SerialNumber { void copy(char * buffer) { - Base64::encode((const unsigned char *)RawUniqueIDAddress, 12, buffer); + const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; + Base64::encode(rawUniqueID, 12, buffer); buffer[Length] = 0; } From 403c81a02c8998999d6f072383a13b40097c439c Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 8 Feb 2019 18:13:23 +0100 Subject: [PATCH 0141/1750] [ion/device] Use a configurable UniqueDeviceIDAddress --- .../n0100/drivers/config/serial_number.h | 18 ++++++++++++++++++ .../n0101/drivers/config/serial_number.h | 18 ++++++++++++++++++ .../device/shared/drivers/serial_number.cpp | 3 ++- 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 ion/src/device/n0100/drivers/config/serial_number.h create mode 100644 ion/src/device/n0101/drivers/config/serial_number.h diff --git a/ion/src/device/n0100/drivers/config/serial_number.h b/ion/src/device/n0100/drivers/config/serial_number.h new file mode 100644 index 000000000..c0050d890 --- /dev/null +++ b/ion/src/device/n0100/drivers/config/serial_number.h @@ -0,0 +1,18 @@ +#ifndef ION_DEVICE_N0100_CONFIG_SERIAL_NUMBER_H +#define ION_DEVICE_N0100_CONFIG_SERIAL_NUMBER_H + +#include + +namespace Ion { +namespace Device { +namespace SerialNumber { +namespace Config { + +constexpr uint32_t UniqueDeviceIDAddress = 0x1FFF7A10; + +} +} +} +} + +#endif diff --git a/ion/src/device/n0101/drivers/config/serial_number.h b/ion/src/device/n0101/drivers/config/serial_number.h new file mode 100644 index 000000000..7798366de --- /dev/null +++ b/ion/src/device/n0101/drivers/config/serial_number.h @@ -0,0 +1,18 @@ +#ifndef ION_DEVICE_N0101_CONFIG_SERIAL_NUMBER_H +#define ION_DEVICE_N0101_CONFIG_SERIAL_NUMBER_H + +#include + +namespace Ion { +namespace Device { +namespace SerialNumber { +namespace Config { + +constexpr uint32_t UniqueDeviceIDAddress = 0x1FF07A10; + +} +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/serial_number.cpp b/ion/src/device/shared/drivers/serial_number.cpp index ea1d354fb..9a95f8bc7 100644 --- a/ion/src/device/shared/drivers/serial_number.cpp +++ b/ion/src/device/shared/drivers/serial_number.cpp @@ -1,4 +1,5 @@ #include "serial_number.h" +#include #include "base64.h" namespace Ion { @@ -20,7 +21,7 @@ namespace Device { namespace SerialNumber { void copy(char * buffer) { - const unsigned char * rawUniqueID = (const unsigned char *)0x1FFF7A10; + const unsigned char * rawUniqueID = reinterpret_cast(Config::UniqueDeviceIDAddress); Base64::encode(rawUniqueID, 12, buffer); buffer[Length] = 0; } From 4c5636e7386e0ad55db7611ebcc2e6e07d68109d Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 8 Feb 2019 18:13:47 +0100 Subject: [PATCH 0142/1750] [ion/device] Fix a small typo --- ion/src/device/n0101/drivers/config/timing.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/n0101/drivers/config/timing.h b/ion/src/device/n0101/drivers/config/timing.h index 71d6c3f3d..0c0e48a4b 100644 --- a/ion/src/device/n0101/drivers/config/timing.h +++ b/ion/src/device/n0101/drivers/config/timing.h @@ -1,5 +1,5 @@ -#ifndef ION_DEVICE_N0100_CONFIG_TIMING_H -#define ION_DEVICE_N0100_CONFIG_TIMING_H +#ifndef ION_DEVICE_N0101_CONFIG_TIMING_H +#define ION_DEVICE_N0101_CONFIG_TIMING_H #include From 4eeffbd98cee29c5a5aa87656d8e01f6ab9450eb Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 8 Feb 2019 18:17:30 +0100 Subject: [PATCH 0143/1750] [ion/device] Share a RAM linker script --- build/targets.device.mak | 2 +- ion/src/device/{n0100 => shared}/ram.ld | 0 ion/src/device/shared/usb/flasher.ld | 77 ------------------------- 3 files changed, 1 insertion(+), 78 deletions(-) rename ion/src/device/{n0100 => shared}/ram.ld (100%) delete mode 100644 ion/src/device/shared/usb/flasher.ld diff --git a/build/targets.device.mak b/build/targets.device.mak index 18238e070..9f49be554 100644 --- a/build/targets.device.mak +++ b/build/targets.device.mak @@ -58,7 +58,7 @@ openocd: # fully filled ifeq ($(EPSILON_USB_DFU_XIP)$(EPSILON_DEVICE_BENCH),10) ion/src/$(PLATFORM)/shared/usb/flasher.o: SFLAGS += $(ION_DEVICE_SFLAGS) -flasher.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/$(MODEL)/ram.ld +flasher.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld flasher.$(EXE): $(objs) ion/src/$(PLATFORM)/shared/usb/flasher.o else flasher.$(EXE): diff --git a/ion/src/device/n0100/ram.ld b/ion/src/device/shared/ram.ld similarity index 100% rename from ion/src/device/n0100/ram.ld rename to ion/src/device/shared/ram.ld diff --git a/ion/src/device/shared/usb/flasher.ld b/ion/src/device/shared/usb/flasher.ld deleted file mode 100644 index 5621192b3..000000000 --- a/ion/src/device/shared/usb/flasher.ld +++ /dev/null @@ -1,77 +0,0 @@ -/* Create a USB DFU firmware that runs from RAM. - * Flashing using ST's ROMed DFU bootloader is reliable but very slow. To make - * flashing faster, we can leverage ST's bootloader to copy a small "flasher" in - * RAM, and run it from there. - * Caution: ST's bootloader uses some RAM, so we want to stay off of that memory - * region. Per AN2606, section 47, it's using 0x20003000 - 0x2003FFFF; We'll try - * to play safe and avoid the first 32KB of RAM. */ - -MEMORY { - RAM_BUFFER (rw) : ORIGIN = 0x20000000 + 32K, LENGTH = 256K - 32K -} - -/* The stack is quite large, and should really be equal to Epsilon's. Indeed, - * we're making the Calculator object live on the stack, and it's quite large - * (about 4K just for this single object). Using a stack too small will result - * in some memory being overwritten (for instance, vtables that live in the - * .rodata section). */ - -STACK_SIZE = 32K; - -SECTIONS { - .isr_vector_table ORIGIN(RAM_BUFFER) : { - KEEP(*(.isr_vector_table)) - } >RAM_BUFFER - - .text : { - . = ALIGN(4); - *(.text) - *(.text.*) - } >RAM_BUFFER - - .init_array : { - . = ALIGN(4); - _init_array_start = .; - KEEP (*(.init_array*)) - _init_array_end = .; - } >RAM_BUFFER - - .rodata : { - . = ALIGN(4); - *(.rodata) - *(.rodata.*) - } >RAM_BUFFER - - .data : { - . = ALIGN(4); - *(.data) - *(.data.*) - } >RAM_BUFFER - - .bss : { - . = ALIGN(4); - _bss_section_start_ram = .; - *(.bss) - *(.bss.*) - *(COMMON) - _bss_section_end_ram = .; - } >RAM_BUFFER - - .stack : { - . = ALIGN(8); - _stack_end = .; - . += (STACK_SIZE - 8); - . = ALIGN(8); - _stack_start = .; - } >RAM_BUFFER - - .phony : { - /* We won't do dynamic memory allocation */ - _heap_start = .; - _heap_end = .; - /* Effectively bypass copying .data to RAM */ - _data_section_start_flash = .; - _data_section_start_ram = .; - _data_section_end_ram = .; - } >RAM_BUFFER -} From 476dd1130908ddcd317d66095b6a10dcc0f4b7ce Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 12 Feb 2019 10:48:57 +0100 Subject: [PATCH 0144/1750] [ion/device] Fix a typo in a register definition --- ion/src/device/shared/regs/flash.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/regs/flash.h b/ion/src/device/shared/regs/flash.h index bec15d24a..40582da0c 100644 --- a/ion/src/device/shared/regs/flash.h +++ b/ion/src/device/shared/regs/flash.h @@ -12,7 +12,7 @@ public: REGS_BOOL_FIELD(PRFTEN, 8); #if REGS_FLASH_CONFIG_ART REGS_BOOL_FIELD(ARTEN, 9); - REGS_BOOL_FIELD(ARTRST, 9); + REGS_BOOL_FIELD(ARTRST, 11); #else REGS_BOOL_FIELD(ICEN, 9); REGS_BOOL_FIELD(DCEN, 10); From 8f48f7ed1488ca2d5b973fc7032a4d2dc0a7ae11 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Tue, 12 Feb 2019 14:06:36 +0100 Subject: [PATCH 0145/1750] [ion/device] Fix ExternalFlash GPIO config --- ion/src/device/n0101/drivers/config/external_flash.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ion/src/device/n0101/drivers/config/external_flash.h b/ion/src/device/n0101/drivers/config/external_flash.h index 54e4c7e29..ca0388fe0 100644 --- a/ion/src/device/n0101/drivers/config/external_flash.h +++ b/ion/src/device/n0101/drivers/config/external_flash.h @@ -19,12 +19,12 @@ namespace ExternalFlash { namespace Config { constexpr static AFGPIOPin Pins[] = { - AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOC, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOD, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOD, 13, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOE, 2, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOC, 9, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOD, 12, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOD, 13, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOE, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), }; } From d601451ceb6c57eab80527427e73bc309724354d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 6 Feb 2019 15:25:31 +0100 Subject: [PATCH 0146/1750] [ion/bench] Fix compiling --- ion/src/device/Makefile | 1 + ion/src/device/bench/command/adc.cpp | 2 +- ion/src/device/bench/command/backlight.cpp | 6 +++--- ion/src/device/bench/command/display.cpp | 6 +++--- ion/src/device/bench/command/led.cpp | 6 +++--- ion/src/device/bench/command/mcu_serial.cpp | 6 +++--- ion/src/device/bench/command/print.cpp | 2 +- ion/src/device/bench/command/vblank.cpp | 2 +- 8 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index 272676c39..bd55cada8 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -1,5 +1,6 @@ # Makefile belows should augment $(ion_device_objs) include ion/src/device/shared/Makefile +include ion/src/device/bench/Makefile include ion/src/device/$(MODEL)/Makefile ion/src/shared/platform_info.o: SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" diff --git a/ion/src/device/bench/command/adc.cpp b/ion/src/device/bench/command/adc.cpp index 8c3e677a4..9ef903677 100644 --- a/ion/src/device/bench/command/adc.cpp +++ b/ion/src/device/bench/command/adc.cpp @@ -1,7 +1,7 @@ #include "command.h" #include #include -#include +#include namespace Ion { namespace Device { diff --git a/ion/src/device/bench/command/backlight.cpp b/ion/src/device/bench/command/backlight.cpp index b4a7f2d9f..34b9132c5 100644 --- a/ion/src/device/bench/command/backlight.cpp +++ b/ion/src/device/bench/command/backlight.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include namespace Ion { namespace Device { @@ -10,12 +10,12 @@ namespace Command { void Backlight(const char * input) { // Input must be of the form "0xAA" or "ON" or "OFF" if (strcmp(input, sON) == 0) { - Ion::Backlight::Device::init(); + Ion::Device::Backlight::init(); reply(sOK); return; } if (strcmp(input, sOFF) == 0) { - Ion::Backlight::Device::shutdown(); + Ion::Device::Backlight::shutdown(); reply(sOK); return; } diff --git a/ion/src/device/bench/command/display.cpp b/ion/src/device/bench/command/display.cpp index 553e6e54a..43e723222 100644 --- a/ion/src/device/bench/command/display.cpp +++ b/ion/src/device/bench/command/display.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include #include namespace Ion { @@ -12,12 +12,12 @@ namespace Command { void Display(const char * input) { if (strcmp(input, sON) == 0) { - Ion::Display::Device::init(); + Ion::Device::Display::init(); reply(sOK); return; } if (strcmp(input, sOFF) == 0) { - Ion::Display::Device::shutdown(); + Ion::Device::Display::shutdown(); reply(sOK); return; } diff --git a/ion/src/device/bench/command/led.cpp b/ion/src/device/bench/command/led.cpp index 43f7fcac1..39157faeb 100644 --- a/ion/src/device/bench/command/led.cpp +++ b/ion/src/device/bench/command/led.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include namespace Ion { namespace Device { @@ -10,12 +10,12 @@ namespace Command { // Input must be of the form "0xAABBCC" or "ON" or "OFF" void LED(const char * input) { if (strcmp(input, sON) == 0) { - Ion::LED::Device::init(); + Ion::Device::LED::init(); Ion::Console::writeLine(sOK); return; } if (strcmp(input, sOFF) == 0) { - Ion::LED::Device::shutdown(); + Ion::Device::LED::shutdown(); Ion::Console::writeLine(sOK); return; } diff --git a/ion/src/device/bench/command/mcu_serial.cpp b/ion/src/device/bench/command/mcu_serial.cpp index b6703085b..b6d4730ba 100644 --- a/ion/src/device/bench/command/mcu_serial.cpp +++ b/ion/src/device/bench/command/mcu_serial.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include "../../device.h" +#include namespace Ion { namespace Device { @@ -12,8 +12,8 @@ void MCUSerial(const char * input) { reply(sSyntaxError); return; } - char response[11 + Ion::Device::SerialNumberLength + 1] = {'M', 'C', 'U', '_', 'S', 'E', 'R', 'I', 'A', 'L', '=', 0}; - Ion::Device::copySerialNumber(response + 11); + char response[11 + Ion::Device::SerialNumber::Length + 1] = {'M', 'C', 'U', '_', 'S', 'E', 'R', 'I', 'A', 'L', '=', 0}; + Ion::Device::SerialNumber::copy(response + 11); reply(response); } diff --git a/ion/src/device/bench/command/print.cpp b/ion/src/device/bench/command/print.cpp index 8b5eb3bfc..825ae2025 100644 --- a/ion/src/device/bench/command/print.cpp +++ b/ion/src/device/bench/command/print.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include #include namespace Ion { diff --git a/ion/src/device/bench/command/vblank.cpp b/ion/src/device/bench/command/vblank.cpp index 3c6971061..fb7da4d87 100644 --- a/ion/src/device/bench/command/vblank.cpp +++ b/ion/src/device/bench/command/vblank.cpp @@ -1,6 +1,6 @@ #include "command.h" #include -#include +#include namespace Ion { namespace Device { From 6550d14050e709fd954ac81344841b305b467530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 12 Feb 2019 12:36:59 +0100 Subject: [PATCH 0147/1750] [ion] Init USART clock in initClocks --- ion/src/device/n0100/drivers/board.cpp | 1 + ion/src/device/n0101/drivers/board.cpp | 1 + ion/src/device/shared/drivers/console.cpp | 2 -- ion/src/device/shared/regs/rcc.h | 1 + 4 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index f44b5587e..0bd19fb34 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -131,6 +131,7 @@ void initClocks() { // We're using TIM3 for the LEDs RCC.APB1ENR()->setTIM3EN(true); RCC.APB1ENR()->setPWREN(true); + RCC.APB1ENR()->setUSART3EN(true); // APB2 bus class RCC::APB2ENR apb2enr(0x00008000); // Reset value diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 7a38c84ca..ef293aaf1 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -153,6 +153,7 @@ void initClocks() { class RCC::APB2ENR apb2enr(0); // Reset value apb2enr.setADC1EN(true); apb2enr.setSYSCFGEN(true); + apb2enr.setUSART6EN(true); RCC.APB2ENR()->set(apb2enr); } diff --git a/ion/src/device/shared/drivers/console.cpp b/ion/src/device/shared/drivers/console.cpp index 61d2d86dd..8310af2dc 100644 --- a/ion/src/device/shared/drivers/console.cpp +++ b/ion/src/device/shared/drivers/console.cpp @@ -33,8 +33,6 @@ namespace Console { constexpr static GPIOPin Pins[] = { Config::RxPin, Config::TxPin }; void init() { - RCC.APB1ENR()->setUSART3EN(true); - for(const GPIOPin & g : Pins) { g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); g.group().AFR()->setAlternateFunction(g.pin(), Config::AlternateFunction); diff --git a/ion/src/device/shared/regs/rcc.h b/ion/src/device/shared/regs/rcc.h index daa043a35..b2a3da9c9 100644 --- a/ion/src/device/shared/regs/rcc.h +++ b/ion/src/device/shared/regs/rcc.h @@ -102,6 +102,7 @@ public: using Register32::Register32; REGS_BOOL_FIELD(TIM1EN, 0); REGS_BOOL_FIELD(USART1EN, 4); + REGS_BOOL_FIELD(USART6EN, 5); REGS_BOOL_FIELD(ADC1EN, 8); REGS_BOOL_FIELD(SDIOEN, 11); REGS_BOOL_FIELD(SPI1EN, 12); From 4bd4c692c7c6ae18d81c3aad61304e8f5c3ed269 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 12 Feb 2019 14:11:56 +0100 Subject: [PATCH 0148/1750] [ion] Fix USART registers for n0101 --- ion/src/device/n0100/regs/config/usart.h | 21 +++++++++++++++++++++ ion/src/device/n0101/regs/config/usart.h | 21 +++++++++++++++++++++ ion/src/device/shared/drivers/console.cpp | 4 ++-- ion/src/device/shared/regs/usart.h | 22 +++++++++++++++------- 4 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 ion/src/device/n0100/regs/config/usart.h create mode 100644 ion/src/device/n0101/regs/config/usart.h diff --git a/ion/src/device/n0100/regs/config/usart.h b/ion/src/device/n0100/regs/config/usart.h new file mode 100644 index 000000000..5171cc4c3 --- /dev/null +++ b/ion/src/device/n0100/regs/config/usart.h @@ -0,0 +1,21 @@ +#ifndef ION_DEVICE_N0100_REGS_CONFIG_USART_H +#define ION_DEVICE_N0100_REGS_CONFIG_USART_H + +namespace Ion { +namespace Device { +namespace USART { +namespace Config { + +constexpr static int SROffset = 0x00; +constexpr static int RDROffset = 0x04; +constexpr static int TDROffset = 0x04; +constexpr static int BRROffset = 0x08; +constexpr static int CR1Offset = 0x0C; +constexpr static int UEOffset = 13; + +} +} +} +} + +#endif diff --git a/ion/src/device/n0101/regs/config/usart.h b/ion/src/device/n0101/regs/config/usart.h new file mode 100644 index 000000000..058a744a6 --- /dev/null +++ b/ion/src/device/n0101/regs/config/usart.h @@ -0,0 +1,21 @@ +#ifndef ION_DEVICE_N0101_REGS_CONFIG_USART_H +#define ION_DEVICE_N0101_REGS_CONFIG_USART_H + +namespace Ion { +namespace Device { +namespace USART { +namespace Config { + +constexpr static int SROffset = 0x1C; +constexpr static int RDROffset = 0x24; +constexpr static int TDROffset = 0x28; +constexpr static int BRROffset = 0x0C; +constexpr static int CR1Offset = 0x00; +constexpr static int UEOffset = 0; + +} +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/console.cpp b/ion/src/device/shared/drivers/console.cpp index 8310af2dc..1dbae3d1f 100644 --- a/ion/src/device/shared/drivers/console.cpp +++ b/ion/src/device/shared/drivers/console.cpp @@ -14,13 +14,13 @@ using namespace Ion::Device::Console; char readChar() { while (Config::Port.SR()->getRXNE() == 0) { } - return (char)Config::Port.DR()->get(); + return (char)Config::Port.RDR()->get(); } void writeChar(char c) { while (Config::Port.SR()->getTXE() == 0) { } - Config::Port.DR()->set(c); + Config::Port.TDR()->set(c); } } diff --git a/ion/src/device/shared/regs/usart.h b/ion/src/device/shared/regs/usart.h index cb454c2d6..032b7d4ec 100644 --- a/ion/src/device/shared/regs/usart.h +++ b/ion/src/device/shared/regs/usart.h @@ -2,6 +2,7 @@ #define REGS_USART_H #include "register.h" +#include class USART { public: @@ -11,11 +12,16 @@ public: REGS_BOOL_FIELD(TXE, 7); }; - class DR : Register32 { + // RDR and TDR are the same DR register on some models. + class RDR : Register32 { public: uint16_t get() volatile { return (uint16_t)getBitRange(8, 0); } + }; + + class TDR : Register32 { + public: void set(uint16_t v) volatile { setBitRange(8, 0, v); } @@ -23,26 +29,28 @@ public: class BRR : Register32 { public: + // TODO one value only on 0-15 REGS_FIELD(DIV_FRAC, uint8_t, 3, 0); REGS_FIELD(DIV_MANTISSA, uint16_t, 15, 4); }; class CR1 : Register32 { public: - REGS_BOOL_FIELD(UE, 13); + REGS_BOOL_FIELD(UE, Ion::Device::USART::Config::UEOffset); REGS_BOOL_FIELD(TE, 3); REGS_BOOL_FIELD(RE, 2); }; constexpr USART(int i) : m_index(i) {} constexpr operator int() const { return m_index; } - REGS_REGISTER_AT(SR, 0x00); - REGS_REGISTER_AT(DR, 0x04); - REGS_REGISTER_AT(BRR, 0x08); - REGS_REGISTER_AT(CR1, 0x0C); + REGS_REGISTER_AT(SR, Ion::Device::USART::Config::SROffset); + REGS_REGISTER_AT(RDR, Ion::Device::USART::Config::RDROffset); + REGS_REGISTER_AT(TDR, Ion::Device::USART::Config::TDROffset); + REGS_REGISTER_AT(BRR, Ion::Device::USART::Config::BRROffset); + REGS_REGISTER_AT(CR1, Ion::Device::USART::Config::CR1Offset); private: constexpr uint32_t Base() const { - return ((uint32_t []){0x40011000, 0x40004400, 0x40004800})[m_index-1]; + return ((uint32_t []){0x40011000, 0x40004400, 0x40004800, 0x40004C00, 0x40005000, 0x40011400})[m_index-1]; }; int m_index; }; From 425042d9571ad6300d09620ab878350170d2b9b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 12 Feb 2019 14:15:43 +0100 Subject: [PATCH 0149/1750] [ion/console] Tune the value of the USART baud rate --- ion/src/device/n0100/drivers/config/console.h | 9 +++++++++ ion/src/device/n0101/drivers/config/console.h | 9 +++++++++ ion/src/device/shared/drivers/console.cpp | 15 +-------------- ion/src/device/shared/regs/usart.h | 6 +++--- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/ion/src/device/n0100/drivers/config/console.h b/ion/src/device/n0100/drivers/config/console.h index 5ec489512..92db525ec 100644 --- a/ion/src/device/n0100/drivers/config/console.h +++ b/ion/src/device/n0100/drivers/config/console.h @@ -13,6 +13,15 @@ constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 11); constexpr static GPIOPin TxPin = GPIOPin(GPIOD, 8); 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 = fAPB1 = 48 MHz, so USARTDIV = 416.666 */ +constexpr static int USARTDIVValue = 417; + + } } } diff --git a/ion/src/device/n0101/drivers/config/console.h b/ion/src/device/n0101/drivers/config/console.h index adaf19bc5..75ee95cc1 100644 --- a/ion/src/device/n0101/drivers/config/console.h +++ b/ion/src/device/n0101/drivers/config/console.h @@ -13,6 +13,15 @@ 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; + + } } } diff --git a/ion/src/device/shared/drivers/console.cpp b/ion/src/device/shared/drivers/console.cpp index 1dbae3d1f..9de97539c 100644 --- a/ion/src/device/shared/drivers/console.cpp +++ b/ion/src/device/shared/drivers/console.cpp @@ -41,20 +41,7 @@ void init() { Config::Port.CR1()->setUE(true); Config::Port.CR1()->setTE(true); Config::Port.CR1()->setRE(true); - - /* We need to set the baud rate of the UART port. - * This is set relative to the APB1 clock, which runs at 48 MHz. - * - * The baud rate is set by the following equation: - * BaudRate = fAPB1/(16*USARTDIV), where USARTDIV is a divider. - * In other words, USARDFIV = fAPB1/(16*BaudRate). All frequencies in Hz. - * - * In our case, fAPB1 = 48 MHz, so USARTDIV = 26.0416667 - * DIV_MANTISSA = 26 - * DIV_FRAC = 16*0.0416667 = 1 - */ - Config::Port.BRR()->setDIV_MANTISSA(26); - Config::Port.BRR()->setDIV_FRAC(1); + Config::Port.BRR()->set(Config::USARTDIVValue); } void shutdown() { diff --git a/ion/src/device/shared/regs/usart.h b/ion/src/device/shared/regs/usart.h index 032b7d4ec..ede46e2d4 100644 --- a/ion/src/device/shared/regs/usart.h +++ b/ion/src/device/shared/regs/usart.h @@ -29,9 +29,9 @@ public: class BRR : Register32 { public: - // TODO one value only on 0-15 - REGS_FIELD(DIV_FRAC, uint8_t, 3, 0); - REGS_FIELD(DIV_MANTISSA, uint16_t, 15, 4); + void set(uint16_t v) volatile { + setBitRange(15, 0, v); + } }; class CR1 : Register32 { From 37cc0c862b9c7270f4a472cab242ddeeafc34d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 12 Feb 2019 14:47:56 +0100 Subject: [PATCH 0150/1750] [ion] Proper namespacing --- ion/src/device/n0100/drivers/config/console.h | 2 +- ion/src/device/n0101/drivers/config/console.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/n0100/drivers/config/console.h b/ion/src/device/n0100/drivers/config/console.h index 92db525ec..1bc9098b2 100644 --- a/ion/src/device/n0100/drivers/config/console.h +++ b/ion/src/device/n0100/drivers/config/console.h @@ -8,7 +8,7 @@ namespace Device { namespace Console { namespace Config { -constexpr static USART Port = USART(3); +constexpr static ::USART Port = ::USART(3); constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 11); constexpr static GPIOPin TxPin = GPIOPin(GPIOD, 8); constexpr static GPIO::AFR::AlternateFunction AlternateFunction = GPIO::AFR::AlternateFunction::AF8; diff --git a/ion/src/device/n0101/drivers/config/console.h b/ion/src/device/n0101/drivers/config/console.h index 75ee95cc1..5f421518a 100644 --- a/ion/src/device/n0101/drivers/config/console.h +++ b/ion/src/device/n0101/drivers/config/console.h @@ -8,7 +8,7 @@ namespace Device { namespace Console { namespace Config { -constexpr static USART Port = USART(6); +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; From 92e82aab9548bac6e68ad8330b4633b7a6245ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 12 Feb 2019 15:12:44 +0100 Subject: [PATCH 0151/1750] [ion] Fix build on n0100 --- ion/src/device/n0100/drivers/board.cpp | 4 ++++ ion/src/device/n0101/drivers/board.cpp | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index 0bd19fb34..c960d87be 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -1,6 +1,8 @@ #include +#include #include #include +#include // Public Ion methods @@ -131,7 +133,9 @@ void initClocks() { // We're using TIM3 for the LEDs RCC.APB1ENR()->setTIM3EN(true); RCC.APB1ENR()->setPWREN(true); +#if EPSILON_DEVICE_BENCH RCC.APB1ENR()->setUSART3EN(true); +#endif // APB2 bus class RCC::APB2ENR apb2enr(0x00008000); // Reset value diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index ef293aaf1..eefe4075f 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -61,6 +61,8 @@ void init() { } initPeripherals(); + + // TODO if EPSILON_DEVICE_BENCH, run bench? See n0100 } void initClocks() { @@ -153,7 +155,7 @@ void initClocks() { class RCC::APB2ENR apb2enr(0); // Reset value apb2enr.setADC1EN(true); apb2enr.setSYSCFGEN(true); - apb2enr.setUSART6EN(true); + apb2enr.setUSART6EN(true); // TODO if EPSILON_DEVICE_BENCH? RCC.APB2ENR()->set(apb2enr); } From 51161b63a9fb3a9d1e0e37fef819f009296a9829 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 13 Feb 2019 15:08:10 +0100 Subject: [PATCH 0152/1750] [ion] Move regs in a namespace --- ion/src/device/n0100/drivers/board.cpp | 2 ++ ion/src/device/n0100/drivers/config/backlight.h | 2 ++ ion/src/device/n0100/drivers/config/battery.h | 2 ++ ion/src/device/n0100/drivers/config/console.h | 2 ++ ion/src/device/n0100/drivers/config/display.h | 2 ++ ion/src/device/n0100/drivers/config/external_flash.h | 2 ++ ion/src/device/n0100/drivers/config/keyboard.h | 2 ++ ion/src/device/n0100/drivers/config/led.h | 2 ++ ion/src/device/n0100/drivers/config/swd.h | 2 ++ ion/src/device/n0100/drivers/config/usb.h | 2 ++ ion/src/device/n0101/drivers/board.cpp | 2 ++ ion/src/device/n0101/drivers/cache.h | 2 ++ ion/src/device/n0101/drivers/config/backlight.h | 2 ++ ion/src/device/n0101/drivers/config/battery.h | 2 ++ ion/src/device/n0101/drivers/config/console.h | 2 ++ ion/src/device/n0101/drivers/config/display.h | 2 ++ ion/src/device/n0101/drivers/config/external_flash.h | 2 ++ ion/src/device/n0101/drivers/config/keyboard.h | 2 ++ ion/src/device/n0101/drivers/config/led.h | 2 ++ ion/src/device/n0101/drivers/config/swd.h | 2 ++ ion/src/device/n0101/drivers/config/usb.h | 2 ++ ion/src/device/shared/drivers/backlight.cpp | 2 ++ ion/src/device/shared/drivers/battery.cpp | 5 ++++- ion/src/device/shared/drivers/board.cpp | 2 ++ ion/src/device/shared/drivers/console.cpp | 4 +++- ion/src/device/shared/drivers/crc32.cpp | 8 +++++++- ion/src/device/shared/drivers/display.cpp | 4 +++- ion/src/device/shared/drivers/external_flash.cpp | 2 ++ ion/src/device/shared/drivers/flash.cpp | 2 ++ ion/src/device/shared/drivers/flash.h | 2 +- ion/src/device/shared/drivers/keyboard.cpp | 2 ++ ion/src/device/shared/drivers/led.cpp | 5 ++++- ion/src/device/shared/drivers/power.cpp | 2 ++ ion/src/device/shared/drivers/random.cpp | 8 +++++++- ion/src/device/shared/drivers/reset.cpp | 2 ++ ion/src/device/shared/drivers/serial_number.cpp | 2 +- ion/src/device/shared/drivers/swd.cpp | 2 ++ ion/src/device/shared/drivers/timing.cpp | 4 +++- ion/src/device/shared/drivers/usb.cpp | 5 ++++- ion/src/device/shared/drivers/wakeup.cpp | 2 ++ ion/src/device/shared/regs/adc.h | 8 ++++++++ ion/src/device/shared/regs/cm4.h | 8 ++++++++ ion/src/device/shared/regs/crc.h | 8 ++++++++ ion/src/device/shared/regs/dma.h | 8 ++++++++ ion/src/device/shared/regs/exti.h | 8 ++++++++ ion/src/device/shared/regs/flash.h | 8 ++++++++ ion/src/device/shared/regs/fsmc.h | 8 ++++++++ ion/src/device/shared/regs/gpio.h | 8 ++++++++ ion/src/device/shared/regs/itm.h | 8 ++++++++ ion/src/device/shared/regs/mpu.h | 8 ++++++++ ion/src/device/shared/regs/nvic.h | 8 ++++++++ ion/src/device/shared/regs/otg.h | 8 ++++++++ ion/src/device/shared/regs/pwr.h | 8 ++++++++ ion/src/device/shared/regs/quadspi.h | 8 ++++++++ ion/src/device/shared/regs/rcc.h | 8 ++++++++ ion/src/device/shared/regs/register.h | 8 ++++++++ ion/src/device/shared/regs/rng.h | 8 ++++++++ ion/src/device/shared/regs/sdio.h | 8 ++++++++ ion/src/device/shared/regs/spi.h | 8 ++++++++ ion/src/device/shared/regs/syscfg.h | 8 ++++++++ ion/src/device/shared/regs/tim.h | 8 ++++++++ ion/src/device/shared/regs/usart.h | 8 ++++++++ ion/src/device/shared/usb/dfu_interface.cpp | 2 ++ ion/src/device/shared/usb/stack/device.cpp | 2 ++ ion/src/device/shared/usb/stack/endpoint0.cpp | 2 ++ 65 files changed, 279 insertions(+), 10 deletions(-) diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index f44b5587e..058b9b456 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -14,6 +14,8 @@ namespace Ion { namespace Device { namespace Board { +using namespace Regs; + void init() { initClocks(); diff --git a/ion/src/device/n0100/drivers/config/backlight.h b/ion/src/device/n0100/drivers/config/backlight.h index cd48215e0..edfaf1103 100644 --- a/ion/src/device/n0100/drivers/config/backlight.h +++ b/ion/src/device/n0100/drivers/config/backlight.h @@ -13,6 +13,8 @@ namespace Device { namespace Backlight { namespace Config { +using namespace Regs; + constexpr static GPIOPin BacklightPin = GPIOPin(GPIOC, 6); } diff --git a/ion/src/device/n0100/drivers/config/battery.h b/ion/src/device/n0100/drivers/config/battery.h index 1fc5c7600..2e07bda3e 100644 --- a/ion/src/device/n0100/drivers/config/battery.h +++ b/ion/src/device/n0100/drivers/config/battery.h @@ -14,6 +14,8 @@ namespace Device { namespace Battery { namespace Config { +using namespace Regs; + constexpr static GPIOPin ChargingPin = GPIOPin(GPIOA, 0); constexpr static GPIOPin ADCPin = GPIOPin(GPIOA, 1); constexpr uint8_t ADCChannel = 1; diff --git a/ion/src/device/n0100/drivers/config/console.h b/ion/src/device/n0100/drivers/config/console.h index 5ec489512..357c40042 100644 --- a/ion/src/device/n0100/drivers/config/console.h +++ b/ion/src/device/n0100/drivers/config/console.h @@ -8,6 +8,8 @@ namespace Device { namespace Console { namespace Config { +using namespace Regs; + constexpr static USART Port = USART(3); constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 11); constexpr static GPIOPin TxPin = GPIOPin(GPIOD, 8); diff --git a/ion/src/device/n0100/drivers/config/display.h b/ion/src/device/n0100/drivers/config/display.h index e70f75301..42b15187b 100644 --- a/ion/src/device/n0100/drivers/config/display.h +++ b/ion/src/device/n0100/drivers/config/display.h @@ -35,6 +35,8 @@ namespace Device { namespace Display { namespace Config { +using namespace Regs; + constexpr static GPIOPin FSMCPins[] = { GPIOPin(GPIOA, 2), GPIOPin(GPIOA, 3), GPIOPin(GPIOA, 4), GPIOPin(GPIOB, 12), GPIOPin(GPIOB, 12), GPIOPin(GPIOD, 0), GPIOPin(GPIOD, 1), GPIOPin(GPIOD, 4), diff --git a/ion/src/device/n0100/drivers/config/external_flash.h b/ion/src/device/n0100/drivers/config/external_flash.h index 257942071..704586fdc 100644 --- a/ion/src/device/n0100/drivers/config/external_flash.h +++ b/ion/src/device/n0100/drivers/config/external_flash.h @@ -18,6 +18,8 @@ namespace Device { namespace ExternalFlash { namespace Config { +using namespace Regs; + constexpr static AFGPIOPin Pins[] = { AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), diff --git a/ion/src/device/n0100/drivers/config/keyboard.h b/ion/src/device/n0100/drivers/config/keyboard.h index 329ac39e6..8a4028d14 100644 --- a/ion/src/device/n0100/drivers/config/keyboard.h +++ b/ion/src/device/n0100/drivers/config/keyboard.h @@ -27,6 +27,8 @@ namespace Device { namespace Keyboard { namespace Config { +using namespace Regs; + constexpr GPIO RowGPIO = GPIOE; constexpr uint8_t numberOfRows = 9; constexpr uint8_t RowPins[numberOfRows] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; diff --git a/ion/src/device/n0100/drivers/config/led.h b/ion/src/device/n0100/drivers/config/led.h index 328158927..cb584a4a3 100644 --- a/ion/src/device/n0100/drivers/config/led.h +++ b/ion/src/device/n0100/drivers/config/led.h @@ -8,6 +8,8 @@ namespace Device { namespace LED { namespace Config { +using namespace Regs; + constexpr static AFGPIOPin RGBPins[] = { AFGPIOPin(GPIOB, 0, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), AFGPIOPin(GPIOB, 1, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), diff --git a/ion/src/device/n0100/drivers/config/swd.h b/ion/src/device/n0100/drivers/config/swd.h index 744d7f018..f7b7fe877 100644 --- a/ion/src/device/n0100/drivers/config/swd.h +++ b/ion/src/device/n0100/drivers/config/swd.h @@ -8,6 +8,8 @@ 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), diff --git a/ion/src/device/n0100/drivers/config/usb.h b/ion/src/device/n0100/drivers/config/usb.h index c42119c67..9c340f7ea 100644 --- a/ion/src/device/n0100/drivers/config/usb.h +++ b/ion/src/device/n0100/drivers/config/usb.h @@ -8,6 +8,8 @@ namespace Device { namespace USB { namespace Config { +using namespace Regs; + constexpr static AFGPIOPin VbusPin(GPIOA, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); constexpr static AFGPIOPin DmPin(GPIOA, 11, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); constexpr static AFGPIOPin DpPin(GPIOA, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 7a38c84ca..eff9e85ae 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -15,6 +15,8 @@ namespace Ion { namespace Device { namespace Board { +using namespace Regs; + void initL1Cache() { Cache::enableICache(); Cache::enableDCache(); diff --git a/ion/src/device/n0101/drivers/cache.h b/ion/src/device/n0101/drivers/cache.h index 0a3d7fe29..8640cd534 100644 --- a/ion/src/device/n0101/drivers/cache.h +++ b/ion/src/device/n0101/drivers/cache.h @@ -7,6 +7,8 @@ namespace Ion { namespace Device { namespace Cache { +using namespace Regs; + inline void dsb() { asm volatile("dsb 0xF":::"memory"); } diff --git a/ion/src/device/n0101/drivers/config/backlight.h b/ion/src/device/n0101/drivers/config/backlight.h index 80442917a..d01ee7cc2 100644 --- a/ion/src/device/n0101/drivers/config/backlight.h +++ b/ion/src/device/n0101/drivers/config/backlight.h @@ -13,6 +13,8 @@ namespace Device { namespace Backlight { namespace Config { +using namespace Regs; + constexpr static GPIOPin BacklightPin = GPIOPin(GPIOE, 0); } diff --git a/ion/src/device/n0101/drivers/config/battery.h b/ion/src/device/n0101/drivers/config/battery.h index 60ce12903..ed0db6b5e 100644 --- a/ion/src/device/n0101/drivers/config/battery.h +++ b/ion/src/device/n0101/drivers/config/battery.h @@ -14,6 +14,8 @@ 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; diff --git a/ion/src/device/n0101/drivers/config/console.h b/ion/src/device/n0101/drivers/config/console.h index adaf19bc5..763e37cfb 100644 --- a/ion/src/device/n0101/drivers/config/console.h +++ b/ion/src/device/n0101/drivers/config/console.h @@ -8,6 +8,8 @@ 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); diff --git a/ion/src/device/n0101/drivers/config/display.h b/ion/src/device/n0101/drivers/config/display.h index 636416d4e..dc4d7b1dc 100644 --- a/ion/src/device/n0101/drivers/config/display.h +++ b/ion/src/device/n0101/drivers/config/display.h @@ -8,6 +8,8 @@ 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), diff --git a/ion/src/device/n0101/drivers/config/external_flash.h b/ion/src/device/n0101/drivers/config/external_flash.h index ca0388fe0..633bbe1ff 100644 --- a/ion/src/device/n0101/drivers/config/external_flash.h +++ b/ion/src/device/n0101/drivers/config/external_flash.h @@ -18,6 +18,8 @@ namespace Device { namespace ExternalFlash { namespace Config { +using namespace Regs; + constexpr static AFGPIOPin Pins[] = { AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), diff --git a/ion/src/device/n0101/drivers/config/keyboard.h b/ion/src/device/n0101/drivers/config/keyboard.h index 22a356287..fb441b6fe 100644 --- a/ion/src/device/n0101/drivers/config/keyboard.h +++ b/ion/src/device/n0101/drivers/config/keyboard.h @@ -27,6 +27,8 @@ namespace Device { namespace Keyboard { namespace Config { +using namespace Regs; + constexpr GPIO RowGPIO = GPIOA; constexpr uint8_t numberOfRows = 9; constexpr uint8_t RowPins[numberOfRows] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; diff --git a/ion/src/device/n0101/drivers/config/led.h b/ion/src/device/n0101/drivers/config/led.h index 6d0d06877..e7a887f40 100644 --- a/ion/src/device/n0101/drivers/config/led.h +++ b/ion/src/device/n0101/drivers/config/led.h @@ -8,6 +8,8 @@ namespace Device { namespace LED { namespace Config { +using namespace Regs; + constexpr static AFGPIOPin RGBPins[] = { AFGPIOPin(GPIOB, 4, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), AFGPIOPin(GPIOB, 5, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), diff --git a/ion/src/device/n0101/drivers/config/swd.h b/ion/src/device/n0101/drivers/config/swd.h index 31aa6825f..aa89b512d 100644 --- a/ion/src/device/n0101/drivers/config/swd.h +++ b/ion/src/device/n0101/drivers/config/swd.h @@ -8,6 +8,8 @@ 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), diff --git a/ion/src/device/n0101/drivers/config/usb.h b/ion/src/device/n0101/drivers/config/usb.h index 864ed5d42..b2a59b9cf 100644 --- a/ion/src/device/n0101/drivers/config/usb.h +++ b/ion/src/device/n0101/drivers/config/usb.h @@ -8,6 +8,8 @@ namespace Device { namespace USB { namespace Config { +using namespace Regs; + constexpr static AFGPIOPin VbusPin = AFGPIOPin(GPIOA, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); constexpr static AFGPIOPin DmPin = AFGPIOPin(GPIOA, 11, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); constexpr static AFGPIOPin DpPin = AFGPIOPin(GPIOA, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High); diff --git a/ion/src/device/shared/drivers/backlight.cpp b/ion/src/device/shared/drivers/backlight.cpp index e60617598..a751ce22b 100644 --- a/ion/src/device/shared/drivers/backlight.cpp +++ b/ion/src/device/shared/drivers/backlight.cpp @@ -33,6 +33,8 @@ namespace Ion { namespace Device { namespace Backlight { +using namespace Regs; + static uint8_t sLevel; void init() { diff --git a/ion/src/device/shared/drivers/battery.cpp b/ion/src/device/shared/drivers/battery.cpp index 1ef34b76f..02cb62c94 100644 --- a/ion/src/device/shared/drivers/battery.cpp +++ b/ion/src/device/shared/drivers/battery.cpp @@ -14,7 +14,8 @@ namespace Ion { namespace Battery { -using namespace Ion::Device::Battery; +using namespace Device::Battery; +using namespace Device::Regs; bool isCharging() { return !Config::ChargingPin.group().IDR()->get(Config::ChargingPin.pin()); @@ -50,6 +51,8 @@ namespace Ion { namespace Device { namespace Battery { +using namespace Regs; + void init() { initGPIO(); diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index da578cc65..4fdfd4ae3 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -15,6 +15,8 @@ namespace Ion { namespace Device { namespace Board { +using namespace Regs; + void shutdown() { shutdownPeripherals(); shutdownClocks(); diff --git a/ion/src/device/shared/drivers/console.cpp b/ion/src/device/shared/drivers/console.cpp index 61d2d86dd..2fe1d5fda 100644 --- a/ion/src/device/shared/drivers/console.cpp +++ b/ion/src/device/shared/drivers/console.cpp @@ -9,7 +9,7 @@ namespace Ion { namespace Console { -using namespace Ion::Device::Console; +using namespace Device::Console; char readChar() { while (Config::Port.SR()->getRXNE() == 0) { @@ -30,6 +30,8 @@ namespace Ion { namespace Device { namespace Console { +using namespace Regs; + constexpr static GPIOPin Pins[] = { Config::RxPin, Config::TxPin }; void init() { diff --git a/ion/src/device/shared/drivers/crc32.cpp b/ion/src/device/shared/drivers/crc32.cpp index fc2b418a7..41fe5ab25 100644 --- a/ion/src/device/shared/drivers/crc32.cpp +++ b/ion/src/device/shared/drivers/crc32.cpp @@ -1,7 +1,11 @@ #include #include -uint32_t Ion::crc32(const uint32_t * data, size_t length) { +namespace Ion { + +using namespace Device::Regs; + +uint32_t crc32(const uint32_t * data, size_t length) { bool initialCRCEngineState = RCC.AHB1ENR()->getCRCEN(); RCC.AHB1ENR()->setCRCEN(true); CRC.CR()->setRESET(true); @@ -15,3 +19,5 @@ uint32_t Ion::crc32(const uint32_t * data, size_t length) { RCC.AHB1ENR()->setCRCEN(initialCRCEngineState); return result; } + +} diff --git a/ion/src/device/shared/drivers/display.cpp b/ion/src/device/shared/drivers/display.cpp index 6ad254189..17bbcfea8 100644 --- a/ion/src/device/shared/drivers/display.cpp +++ b/ion/src/device/shared/drivers/display.cpp @@ -18,7 +18,7 @@ namespace Ion { namespace Display { -using namespace Ion::Device::Display; +using namespace Device::Display; void pushRect(KDRect r, const KDColor * pixels) { #if USE_DMA @@ -62,6 +62,8 @@ namespace Ion { namespace Device { namespace Display { +using namespace Regs; + static inline void send_data(uint16_t d) { *DataAddress = d; } diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index c46451150..d40ffa1b4 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -5,6 +5,8 @@ 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. diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index dcac93c07..8ef655e6e 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -5,6 +5,8 @@ namespace Ion { namespace Device { namespace Flash { +using namespace Regs; + static inline void wait() { // Wait for pending Flash operations to complete while (FLASH.SR()->getBSY()) { diff --git a/ion/src/device/shared/drivers/flash.h b/ion/src/device/shared/drivers/flash.h index cc01d96a4..5a48a88e6 100644 --- a/ion/src/device/shared/drivers/flash.h +++ b/ion/src/device/shared/drivers/flash.h @@ -18,7 +18,7 @@ void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); /* The Device is powered by a 2.8V LDO. This allows us to perform writes to the * Flash 32 bits at once. */ -constexpr FLASH::CR::PSIZE MemoryAccessWidth = FLASH::CR::PSIZE::X32; +constexpr Regs::FLASH::CR::PSIZE MemoryAccessWidth = Regs::FLASH::CR::PSIZE::X32; typedef uint32_t MemoryAccessType; } diff --git a/ion/src/device/shared/drivers/keyboard.cpp b/ion/src/device/shared/drivers/keyboard.cpp index 692bf25ed..6e27a8cc8 100644 --- a/ion/src/device/shared/drivers/keyboard.cpp +++ b/ion/src/device/shared/drivers/keyboard.cpp @@ -77,6 +77,8 @@ namespace Ion { namespace Device { namespace Keyboard { +using namespace Regs; + void init() { for (uint8_t i=0; i #include -uint32_t Ion::random() { +namespace Ion { + +using namespace Device::Regs; + +uint32_t random() { bool initialRNGEngineState = RCC.AHB2ENR()->getRNGEN(); RCC.AHB2ENR()->setRNGEN(true); @@ -16,3 +20,5 @@ uint32_t Ion::random() { return result; } + +} diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index 6e64183f5..eeaf4b156 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -5,6 +5,8 @@ namespace Ion { namespace Device { namespace Reset { +using namespace Regs; + void core() { // Perform a full core reset CM4.AIRCR()->requestReset(); diff --git a/ion/src/device/shared/drivers/serial_number.cpp b/ion/src/device/shared/drivers/serial_number.cpp index 9a95f8bc7..4d61a6b4c 100644 --- a/ion/src/device/shared/drivers/serial_number.cpp +++ b/ion/src/device/shared/drivers/serial_number.cpp @@ -4,7 +4,7 @@ namespace Ion { -using namespace Ion::Device::SerialNumber; +using namespace Device::SerialNumber; const char * serialNumber() { static char serialNumber[Length + 1] = {0}; diff --git a/ion/src/device/shared/drivers/swd.cpp b/ion/src/device/shared/drivers/swd.cpp index d64ec521b..53cf12653 100644 --- a/ion/src/device/shared/drivers/swd.cpp +++ b/ion/src/device/shared/drivers/swd.cpp @@ -5,6 +5,8 @@ namespace Ion { namespace Device { namespace SWD { +using namespace Regs; + void init() { for(const AFGPIOPin & p : Config::Pins) { p.init(); diff --git a/ion/src/device/shared/drivers/timing.cpp b/ion/src/device/shared/drivers/timing.cpp index cdb911f22..24be59ca9 100644 --- a/ion/src/device/shared/drivers/timing.cpp +++ b/ion/src/device/shared/drivers/timing.cpp @@ -5,7 +5,7 @@ namespace Ion { namespace Timing { -using namespace Ion::Device::Timing; +using namespace Device::Timing; /* TODO: The delay methods 'msleep' and 'usleep' are currently dependent on the * optimizations chosen by the compiler. To prevent that and to gain in @@ -33,6 +33,8 @@ namespace Ion { namespace Device { namespace Timing { +using namespace Regs; + volatile uint64_t MillisElapsed = 0; void init() { diff --git a/ion/src/device/shared/drivers/usb.cpp b/ion/src/device/shared/drivers/usb.cpp index 1b81b390f..ec79da42a 100644 --- a/ion/src/device/shared/drivers/usb.cpp +++ b/ion/src/device/shared/drivers/usb.cpp @@ -5,7 +5,8 @@ namespace Ion { namespace USB { -using namespace Ion::Device::USB; +using namespace Device::USB; +using namespace Device::Regs; bool isPlugged() { return Config::VbusPin.group().IDR()->get(Config::VbusPin.pin()); @@ -39,6 +40,8 @@ namespace Ion { namespace Device { namespace USB { +using namespace Regs; + void init() { initGPIO(); initOTG(); diff --git a/ion/src/device/shared/drivers/wakeup.cpp b/ion/src/device/shared/drivers/wakeup.cpp index d0bfb12f8..c22804ae3 100644 --- a/ion/src/device/shared/drivers/wakeup.cpp +++ b/ion/src/device/shared/drivers/wakeup.cpp @@ -11,6 +11,8 @@ namespace Ion { namespace Device { namespace WakeUp { +using namespace Regs; + void onChargingEvent() { Battery::initGPIO(); diff --git a/ion/src/device/shared/regs/adc.h b/ion/src/device/shared/regs/adc.h index 2881cf23b..f44cb251e 100644 --- a/ion/src/device/shared/regs/adc.h +++ b/ion/src/device/shared/regs/adc.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class ADC { public: class SR : Register32 { @@ -70,4 +74,8 @@ private: constexpr ADC ADC; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/cm4.h b/ion/src/device/shared/regs/cm4.h index d3b43d502..a5cd133b3 100644 --- a/ion/src/device/shared/regs/cm4.h +++ b/ion/src/device/shared/regs/cm4.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class CM4 { public: // Vector table offset register @@ -112,4 +116,8 @@ private: constexpr CM4 CM4; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/crc.h b/ion/src/device/shared/regs/crc.h index 6ae4a2c10..b761cf342 100644 --- a/ion/src/device/shared/regs/crc.h +++ b/ion/src/device/shared/regs/crc.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class CRC { public: class DR : public Register32 { @@ -24,4 +28,8 @@ private: constexpr CRC CRC; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/dma.h b/ion/src/device/shared/regs/dma.h index de2acc702..194c3fb27 100644 --- a/ion/src/device/shared/regs/dma.h +++ b/ion/src/device/shared/regs/dma.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class DMA { public: class LIFCR : public Register32 { @@ -60,4 +64,8 @@ private: constexpr DMA DMA1(0); constexpr DMA DMA2(1); +} +} +} + #endif diff --git a/ion/src/device/shared/regs/exti.h b/ion/src/device/shared/regs/exti.h index 46ee8550b..7ce6ac0b6 100644 --- a/ion/src/device/shared/regs/exti.h +++ b/ion/src/device/shared/regs/exti.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class EXTI { public: class MaskRegister : Register32 { @@ -31,4 +35,8 @@ private: constexpr EXTI EXTI; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/flash.h b/ion/src/device/shared/regs/flash.h index 40582da0c..711c370a7 100644 --- a/ion/src/device/shared/regs/flash.h +++ b/ion/src/device/shared/regs/flash.h @@ -4,6 +4,10 @@ #include "register.h" #include +namespace Ion { +namespace Device { +namespace Regs { + class FLASH { public: class ACR : public Register32 { @@ -59,4 +63,8 @@ private: constexpr FLASH FLASH; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/fsmc.h b/ion/src/device/shared/regs/fsmc.h index 52bfcbdac..8cfe797c6 100644 --- a/ion/src/device/shared/regs/fsmc.h +++ b/ion/src/device/shared/regs/fsmc.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class FSMC { public: class BCR : Register32 { @@ -76,4 +80,8 @@ private: constexpr FSMC FSMC; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/gpio.h b/ion/src/device/shared/regs/gpio.h index 98021f26f..d35c21855 100644 --- a/ion/src/device/shared/regs/gpio.h +++ b/ion/src/device/shared/regs/gpio.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class GPIO { public: class MODER : public Register32 { @@ -138,4 +142,8 @@ private: uint16_t m_data; }; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/itm.h b/ion/src/device/shared/regs/itm.h index 196737ca3..c987cef9e 100644 --- a/ion/src/device/shared/regs/itm.h +++ b/ion/src/device/shared/regs/itm.h @@ -4,6 +4,10 @@ #include "register.h" // See ARM Cortex M4 TRM +// +namespace Ion { +namespace Device { +namespace Regs { class ITM { public: @@ -29,4 +33,8 @@ private: constexpr ITM ITM; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/mpu.h b/ion/src/device/shared/regs/mpu.h index 2d82a5e10..e89120891 100644 --- a/ion/src/device/shared/regs/mpu.h +++ b/ion/src/device/shared/regs/mpu.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class MPU { public: class TYPER : Register32 { @@ -78,4 +82,8 @@ private: constexpr MPU MPU; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/nvic.h b/ion/src/device/shared/regs/nvic.h index dd72ea0d5..c5766a355 100644 --- a/ion/src/device/shared/regs/nvic.h +++ b/ion/src/device/shared/regs/nvic.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + // http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf class NVIC { public: @@ -34,4 +38,8 @@ private: constexpr NVIC NVIC; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/otg.h b/ion/src/device/shared/regs/otg.h index 5c4f0c89e..e0ea12c9f 100644 --- a/ion/src/device/shared/regs/otg.h +++ b/ion/src/device/shared/regs/otg.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class OTG { public: class GAHBCFG : public Register32 { @@ -192,4 +196,8 @@ private: constexpr OTG OTG; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/pwr.h b/ion/src/device/shared/regs/pwr.h index 089d72e18..7532dd286 100644 --- a/ion/src/device/shared/regs/pwr.h +++ b/ion/src/device/shared/regs/pwr.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class PWR { public: class CR : Register32 { @@ -22,4 +26,8 @@ private: constexpr PWR PWR; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/quadspi.h b/ion/src/device/shared/regs/quadspi.h index 18d6c81f0..c2b924eea 100644 --- a/ion/src/device/shared/regs/quadspi.h +++ b/ion/src/device/shared/regs/quadspi.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + // Quad-SPI register map on STM32 microcontroller // See section 12 of the STM32F412 reference manual @@ -123,4 +127,8 @@ private: constexpr QUADSPI QUADSPI; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/rcc.h b/ion/src/device/shared/regs/rcc.h index daa043a35..5bd59b87f 100644 --- a/ion/src/device/shared/regs/rcc.h +++ b/ion/src/device/shared/regs/rcc.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class RCC { public: class CR : public Register32 { @@ -147,4 +151,8 @@ private: constexpr RCC RCC; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/register.h b/ion/src/device/shared/regs/register.h index 5ed38d779..cac002a6b 100644 --- a/ion/src/device/shared/regs/register.h +++ b/ion/src/device/shared/regs/register.h @@ -4,6 +4,10 @@ #include #include +namespace Ion { +namespace Device { +namespace Regs { + template class Register { public: @@ -49,6 +53,10 @@ typedef Register Register16; typedef Register Register32; typedef Register Register64; +} +} +} + #define REGS_FIELD_R(name,type,high,low) type get##name() volatile { return (type)getBitRange(high,low); }; #define REGS_FIELD_W(name,type,high,low) void set##name(type v) volatile { static_assert(sizeof(type) <= 4, "Invalid size"); setBitRange(high, low, static_cast(v)); }; #define REGS_FIELD(name,type,high,low) REGS_FIELD_R(name,type,high,low); REGS_FIELD_W(name,type,high,low); diff --git a/ion/src/device/shared/regs/rng.h b/ion/src/device/shared/regs/rng.h index 6aa2d6505..447bb2897 100644 --- a/ion/src/device/shared/regs/rng.h +++ b/ion/src/device/shared/regs/rng.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class RNG { public: class CR : Register32 { @@ -30,4 +34,8 @@ private: constexpr RNG RNG; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/sdio.h b/ion/src/device/shared/regs/sdio.h index bb8149175..4f70367c1 100644 --- a/ion/src/device/shared/regs/sdio.h +++ b/ion/src/device/shared/regs/sdio.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class SDIO { public: class POWER : Register32 { @@ -109,4 +113,8 @@ private: constexpr SDIO SDIO; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/spi.h b/ion/src/device/shared/regs/spi.h index abe050b97..7df52a6f7 100644 --- a/ion/src/device/shared/regs/spi.h +++ b/ion/src/device/shared/regs/spi.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class SPI { public: class CR1 : Register16 { @@ -42,4 +46,8 @@ private: constexpr SPI SPI1(1); +} +} +} + #endif diff --git a/ion/src/device/shared/regs/syscfg.h b/ion/src/device/shared/regs/syscfg.h index 0690b9430..469654dd6 100644 --- a/ion/src/device/shared/regs/syscfg.h +++ b/ion/src/device/shared/regs/syscfg.h @@ -5,6 +5,10 @@ #include "gpio.h" +namespace Ion { +namespace Device { +namespace Regs { + class SYSCFG { public: class MEMRMP : Register32 { @@ -41,4 +45,8 @@ private: constexpr SYSCFG SYSCFG; +} +} +} + #endif diff --git a/ion/src/device/shared/regs/tim.h b/ion/src/device/shared/regs/tim.h index d76f8e293..8f0c9b28f 100644 --- a/ion/src/device/shared/regs/tim.h +++ b/ion/src/device/shared/regs/tim.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + template class TIM { public: @@ -97,4 +101,8 @@ private: constexpr TIM TIM3(3); +} +} +} + #endif diff --git a/ion/src/device/shared/regs/usart.h b/ion/src/device/shared/regs/usart.h index cb454c2d6..81a6eb69b 100644 --- a/ion/src/device/shared/regs/usart.h +++ b/ion/src/device/shared/regs/usart.h @@ -3,6 +3,10 @@ #include "register.h" +namespace Ion { +namespace Device { +namespace Regs { + class USART { public: class SR : Register32 { @@ -47,4 +51,8 @@ private: int m_index; }; +} +} +} + #endif diff --git a/ion/src/device/shared/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index 3e414c6b7..1d79cad96 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -7,6 +7,8 @@ namespace Ion { namespace Device { namespace USB { +using namespace Ion::Device::Regs; + static inline uint32_t min(uint32_t x, uint32_t y) { return (x Date: Wed, 13 Feb 2019 15:34:48 +0100 Subject: [PATCH 0153/1750] [ion/device/keyboard] Use actual Gpio pins --- ion/src/device/n0101/drivers/config/keyboard.h | 2 +- ion/src/device/shared/drivers/keyboard.h | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ion/src/device/n0101/drivers/config/keyboard.h b/ion/src/device/n0101/drivers/config/keyboard.h index fb441b6fe..41b57c43f 100644 --- a/ion/src/device/n0101/drivers/config/keyboard.h +++ b/ion/src/device/n0101/drivers/config/keyboard.h @@ -31,7 +31,7 @@ using namespace Regs; constexpr GPIO RowGPIO = GPIOA; constexpr uint8_t numberOfRows = 9; -constexpr uint8_t RowPins[numberOfRows] = {0, 1, 2, 3, 4, 5, 6, 7, 8}; +constexpr uint8_t RowPins[numberOfRows] = {1, 0, 2, 3, 4, 5, 6, 7, 8}; constexpr GPIO ColumnGPIO = GPIOC; constexpr uint8_t numberOfColumns = 6; diff --git a/ion/src/device/shared/drivers/keyboard.h b/ion/src/device/shared/drivers/keyboard.h index 33466a389..924abccac 100644 --- a/ion/src/device/shared/drivers/keyboard.h +++ b/ion/src/device/shared/drivers/keyboard.h @@ -25,7 +25,7 @@ inline void activateRow(uint8_t row) { /* In open-drain mode, a 0 in the register drives the pin low, and a 1 lets * the pin floating (Hi-Z). So we want to set the current row to zero and all * the others to 1. */ - uint16_t rowState = ~(1<(Config::RowPins[row])); // TODO: Assert pin numbers are sequentials and dynamically find 9 and 0 Config::RowGPIO.ODR()->setBitRange(9, 0, rowState); @@ -35,7 +35,8 @@ inline void activateRow(uint8_t row) { } inline bool columnIsActive(uint8_t column) { - return !(Config::ColumnGPIO.IDR()->getBitRange(column,column)); + uint8_t pin = Config::ColumnPins[column]; + return !(Config::ColumnGPIO.IDR()->getBitRange(pin,pin)); } } From 990ff9fcbfac250b78cbb60a7e5cbafea9efa505 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 13 Feb 2019 15:35:22 +0100 Subject: [PATCH 0154/1750] [ion/device/display] Add inversion commands --- ion/src/device/shared/drivers/display.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/device/shared/drivers/display.h b/ion/src/device/shared/drivers/display.h index de4a972c5..bb06cf2db 100644 --- a/ion/src/device/shared/drivers/display.h +++ b/ion/src/device/shared/drivers/display.h @@ -38,6 +38,8 @@ enum class Command : uint16_t { Reset = 0x01, SleepIn = 0x10, SleepOut = 0x11, + DisplayInversionOff = 0x20, + DisplayInversionOn = 0x21, DisplayOff = 0x28, DisplayOn = 0x29, ColumnAddressSet = 0x2A, From ec3a60063c25c8d3647c51b9a937ceba30760a6f Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 13 Feb 2019 15:35:45 +0100 Subject: [PATCH 0155/1750] [ion/device/n0101] Update fccid --- ion/src/device/n0100/drivers/board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index 058b9b456..6d8e37419 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -5,7 +5,7 @@ // Public Ion methods const char * Ion::fccId() { - return "2ALWP-N0100"; + return "2ALWP-N0101"; } // Private Ion::Device methods From 1d2468de37d782fd5e236963e452049e0868d0d8 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 13 Feb 2019 15:39:42 +0100 Subject: [PATCH 0156/1750] [ion/device/n0101] Use a linker script for external flash --- build/toolchain.arm-gcc.mak | 7 +++- ion/src/device/n0101/flash.ld | 60 +++++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/build/toolchain.arm-gcc.mak b/build/toolchain.arm-gcc.mak index 09e057b50..e8a1961b2 100644 --- a/build/toolchain.arm-gcc.mak +++ b/build/toolchain.arm-gcc.mak @@ -8,6 +8,9 @@ SIZE = arm-none-eabi-size # Always generate debug information SFLAGS += -ggdb3 +# Put data/code symbols in their own subsection +# This allows the linker script to precisely place a given symbol +SFLAGS += -fdata-sections -ffunction-sections # LTO ?= NOT(DEBUG) ifeq ($(DEBUG),1) @@ -21,8 +24,10 @@ ifeq ($(LTO),1) SFLAGS += -flto else # Otherwise, just get rid of unused symbols -SFLAGS += -fdata-sections -ffunction-sections LDFLAGS += -Wl,--gc-sections endif LDFLAGS += $(SFLAGS) -lgcc -Wl,-T,$(LDSCRIPT) + +# To debug linker scripts, add the following line +# LDFLAGS += -Wl,-M diff --git a/ion/src/device/n0101/flash.ld b/ion/src/device/n0101/flash.ld index a6179f9f4..c396ac067 100644 --- a/ion/src/device/n0101/flash.ld +++ b/ion/src/device/n0101/flash.ld @@ -6,17 +6,25 @@ * 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 + * This will let us use shortcuts such as ">INTERNAL_FLASH" to ask for a given section to * be stored in Flash. */ + MEMORY { - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K - SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K + INTERNAL_FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K + SRAM (rw) : ORIGIN = 0x20000000, LENGTH = 256K + EXTERNAL_FLASH (rx) : ORIGIN = 0x90000000, LENGTH = 8M + /* + 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; SECTIONS { - .isr_vector_table ORIGIN(FLASH) : { + .isr_vector_table ORIGIN(INTERNAL_FLASH) : { /* 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 @@ -32,30 +40,50 @@ SECTIONS { * routine for each interrupt. */ KEEP(*(.isr_vector_table)) - } >FLASH + } >INTERNAL_FLASH .header : { KEEP(*(.header)) - } >FLASH + } >INTERNAL_FLASH - .text : { + /* Internal flash memory */ + /* Use boot routine and required dependencies */ + /* We're relying on symbols being in their own sub-section. On GCC, this is + * done with -fdata-sections -ffunction-sections */ + .text.internal : { + . = ALIGN(4); + *(.text.start) + *(.text.abort) + *(.text.isr_systick) + *(.text.memcpy) + *(.text.memset) + *(.text._ZN3Ion*) + *(.text._ZNV3Ion*) + *(.text._ZNK3Ion*) + } >INTERNAL_FLASH + + .rodata.internal : { + . = ALIGN(4); + *(.rodata._ZN3Ion*) + } >INTERNAL_FLASH + + .text.external : { . = ALIGN(4); *(.text) *(.text.*) - } >FLASH + } >EXTERNAL_FLASH + + .rodata.external : { + *(.rodata) + *(.rodata.*) + } >EXTERNAL_FLASH .init_array : { . = ALIGN(4); _init_array_start = .; KEEP (*(.init_array*)) _init_array_end = .; - } >FLASH - - .rodata : { - . = ALIGN(4); - *(.rodata) - *(.rodata.*) - } >FLASH + } >INTERNAL_FLASH .data : { /* The data section is written to Flash but linked as if it were in RAM. @@ -76,7 +104,7 @@ SECTIONS { *(.data) *(.data.*) _data_section_end_ram = .; - } >SRAM AT> FLASH + } >SRAM AT> INTERNAL_FLASH .bss : { /* The bss section contains data for all uninitialized variables From 0deb8715bb1c7dea05ec0de3386a8367f58ccf79 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 13 Feb 2019 15:40:07 +0100 Subject: [PATCH 0157/1750] [build] Fix a typo --- build/device/elf2dfu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/device/elf2dfu.py b/build/device/elf2dfu.py index 418065dae..1a2326d76 100644 --- a/build/device/elf2dfu.py +++ b/build/device/elf2dfu.py @@ -8,7 +8,7 @@ import subprocess import re import argparse -# arm-none-eabi-objcopy -h -w file.elf +# arm-none-eabi-objdump -h -w file.elf # arm-none-eabi-objcopy -O binary -j .data file.elf file.bin def loadable_sections(elf_file): From a6b681acd0ca1120eadddc5f2e7804fda79c1d54 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 13 Feb 2019 15:57:23 +0100 Subject: [PATCH 0158/1750] [ion/device/regs] Use macros to configure the registers --- ion/src/device/n0100/regs/config/usart.h | 21 ++++++--------------- ion/src/device/n0101/regs/config/usart.h | 21 ++++++--------------- ion/src/device/shared/regs/usart.h | 12 ++++++------ 3 files changed, 18 insertions(+), 36 deletions(-) diff --git a/ion/src/device/n0100/regs/config/usart.h b/ion/src/device/n0100/regs/config/usart.h index 5171cc4c3..ecaac4d12 100644 --- a/ion/src/device/n0100/regs/config/usart.h +++ b/ion/src/device/n0100/regs/config/usart.h @@ -1,21 +1,12 @@ #ifndef ION_DEVICE_N0100_REGS_CONFIG_USART_H #define ION_DEVICE_N0100_REGS_CONFIG_USART_H -namespace Ion { -namespace Device { -namespace USART { -namespace Config { +#define REGS_USART_SR_OFFSET 0x00 +#define REGS_USART_RDR_OFFSET 0x04 +#define REGS_USART_TDR_OFFSET 0x04 +#define REGS_USART_BRR_OFFSET 0x08 +#define REGS_USART_CR1_OFFSET 0x0C -constexpr static int SROffset = 0x00; -constexpr static int RDROffset = 0x04; -constexpr static int TDROffset = 0x04; -constexpr static int BRROffset = 0x08; -constexpr static int CR1Offset = 0x0C; -constexpr static int UEOffset = 13; - -} -} -} -} +#define REGS_USART_CR1_UE_BIT 13 #endif diff --git a/ion/src/device/n0101/regs/config/usart.h b/ion/src/device/n0101/regs/config/usart.h index 058a744a6..a7a7442b1 100644 --- a/ion/src/device/n0101/regs/config/usart.h +++ b/ion/src/device/n0101/regs/config/usart.h @@ -1,21 +1,12 @@ #ifndef ION_DEVICE_N0101_REGS_CONFIG_USART_H #define ION_DEVICE_N0101_REGS_CONFIG_USART_H -namespace Ion { -namespace Device { -namespace USART { -namespace Config { +#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 -constexpr static int SROffset = 0x1C; -constexpr static int RDROffset = 0x24; -constexpr static int TDROffset = 0x28; -constexpr static int BRROffset = 0x0C; -constexpr static int CR1Offset = 0x00; -constexpr static int UEOffset = 0; - -} -} -} -} +#define REGS_USART_CR1_UE_BIT 0 #endif diff --git a/ion/src/device/shared/regs/usart.h b/ion/src/device/shared/regs/usart.h index 1b4850ac5..58b5a35cc 100644 --- a/ion/src/device/shared/regs/usart.h +++ b/ion/src/device/shared/regs/usart.h @@ -40,18 +40,18 @@ public: class CR1 : Register32 { public: - REGS_BOOL_FIELD(UE, Ion::Device::USART::Config::UEOffset); + REGS_BOOL_FIELD(UE, REGS_USART_CR1_UE_BIT); REGS_BOOL_FIELD(TE, 3); REGS_BOOL_FIELD(RE, 2); }; constexpr USART(int i) : m_index(i) {} constexpr operator int() const { return m_index; } - REGS_REGISTER_AT(SR, Ion::Device::USART::Config::SROffset); - REGS_REGISTER_AT(RDR, Ion::Device::USART::Config::RDROffset); - REGS_REGISTER_AT(TDR, Ion::Device::USART::Config::TDROffset); - REGS_REGISTER_AT(BRR, Ion::Device::USART::Config::BRROffset); - REGS_REGISTER_AT(CR1, Ion::Device::USART::Config::CR1Offset); + REGS_REGISTER_AT(SR, REGS_USART_SR_OFFSET); + REGS_REGISTER_AT(RDR, REGS_USART_RDR_OFFSET); + REGS_REGISTER_AT(TDR, REGS_USART_TDR_OFFSET); + REGS_REGISTER_AT(BRR, REGS_USART_BRR_OFFSET); + REGS_REGISTER_AT(CR1, REGS_USART_CR1_OFFSET); private: constexpr uint32_t Base() const { return ((uint32_t []){0x40011000, 0x40004400, 0x40004800, 0x40004C00, 0x40005000, 0x40011400})[m_index-1]; From b32a676258d74d36e2656dd6c9c2fe8a8c51caf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 12 Feb 2019 17:22:28 +0100 Subject: [PATCH 0159/1750] [ion] PWR registers differ between models --- ion/src/device/bench/bench.cpp | 1 + ion/src/device/n0100/regs/config/pwr.h | 6 ++++++ ion/src/device/n0101/regs/config/pwr.h | 6 ++++++ ion/src/device/shared/drivers/power.cpp | 7 +++++++ ion/src/device/shared/regs/pwr.h | 10 ++++++++++ 5 files changed, 30 insertions(+) create mode 100644 ion/src/device/n0100/regs/config/pwr.h create mode 100644 ion/src/device/n0101/regs/config/pwr.h diff --git a/ion/src/device/bench/bench.cpp b/ion/src/device/bench/bench.cpp index 5458111bc..c48b7c93d 100644 --- a/ion/src/device/bench/bench.cpp +++ b/ion/src/device/bench/bench.cpp @@ -32,6 +32,7 @@ void run() { ctx->fillRect(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColorWhite); ctx->drawString("BENCH", KDPoint((320-50)/2, (240-18)/2)); char command[kMaxCommandLength]; + Ion::LED::setColor(KDColorBlue); while (true) { Ion::Console::readLine(command, kMaxCommandLength); const CommandHandler * ch = sCommandList.dispatch(command); diff --git a/ion/src/device/n0100/regs/config/pwr.h b/ion/src/device/n0100/regs/config/pwr.h new file mode 100644 index 000000000..de0f1671f --- /dev/null +++ b/ion/src/device/n0100/regs/config/pwr.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0100_REGS_CONFIG_PWR_H +#define ION_DEVICE_N0100_REGS_CONFIG_PWR_H + +#define REGS_PWR_CONFIG_ADDITIONAL_FIELDS 0 + +#endif diff --git a/ion/src/device/n0101/regs/config/pwr.h b/ion/src/device/n0101/regs/config/pwr.h new file mode 100644 index 000000000..a904537d9 --- /dev/null +++ b/ion/src/device/n0101/regs/config/pwr.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0101_REGS_CONFIG_PWR_H +#define ION_DEVICE_N0101_REGS_CONFIG_PWR_H + +#define REGS_PWR_CONFIG_ADDITIONAL_FIELDS 1 + +#endif diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index f393f4158..ec25cec1b 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace Ion { namespace Power { @@ -23,8 +24,14 @@ void suspend(bool checkIfPowerKeyReleased) { } Device::Board::shutdownPeripherals(isLEDActive); + // This is done differently on the models PWR.CR()->setLPDS(true); // Turn the regulator off. Takes longer to wake up. PWR.CR()->setFPDS(true); // Put the flash to sleep. Takes longer to wake up. +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS + PWR.CR()->setLPUDS(true); + PWR.CR()->setUDEN(PWR::CR::UnderDrive::Enable); +#endif + CM4.SCR()->setSLEEPDEEP(!isLEDActive); while (1) { diff --git a/ion/src/device/shared/regs/pwr.h b/ion/src/device/shared/regs/pwr.h index 7532dd286..917d3736d 100644 --- a/ion/src/device/shared/regs/pwr.h +++ b/ion/src/device/shared/regs/pwr.h @@ -2,6 +2,7 @@ #define REGS_PWR_H #include "register.h" +#include namespace Ion { namespace Device { @@ -14,6 +15,15 @@ public: REGS_BOOL_FIELD(LPDS, 0); REGS_BOOL_FIELD(PPDS, 1); REGS_BOOL_FIELD(FPDS, 9); +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS + REGS_BOOL_FIELD(LPUDS, 10); + REGS_BOOL_FIELD(MRUDS, 11); + enum class UnderDrive { + Disable = 0, + Enable = 3 + }; + REGS_FIELD_W(UDEN, UnderDrive, 19, 18); +#endif }; constexpr PWR() {}; From 017b74c2713723ee81b1e73b0b9fb6d0cea3550a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 8 Mar 2019 16:01:59 +0100 Subject: [PATCH 0160/1750] [ion] LED timers differ between models --- ion/src/device/n0100/drivers/config/led.h | 10 ++++-- ion/src/device/n0101/drivers/config/led.h | 10 ++++-- ion/src/device/shared/drivers/led.cpp | 44 +++++++++++------------ ion/src/device/shared/regs/tim.h | 44 +++++++++++++++++++++++ 4 files changed, 80 insertions(+), 28 deletions(-) diff --git a/ion/src/device/n0100/drivers/config/led.h b/ion/src/device/n0100/drivers/config/led.h index cb584a4a3..2e91f624d 100644 --- a/ion/src/device/n0100/drivers/config/led.h +++ b/ion/src/device/n0100/drivers/config/led.h @@ -10,10 +10,14 @@ namespace Config { using namespace Regs; +static constexpr int RedChannel = 2; +static constexpr int GreenChannel = 4; +static constexpr int BlueChannel = 3; + constexpr static AFGPIOPin RGBPins[] = { - AFGPIOPin(GPIOB, 0, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOB, 1, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOC, 7, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOC, 7, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), // RED + AFGPIOPin(GPIOB, 1, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), // GREEN + AFGPIOPin(GPIOB, 0, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High) // BLUE }; } diff --git a/ion/src/device/n0101/drivers/config/led.h b/ion/src/device/n0101/drivers/config/led.h index e7a887f40..4c5442030 100644 --- a/ion/src/device/n0101/drivers/config/led.h +++ b/ion/src/device/n0101/drivers/config/led.h @@ -10,10 +10,14 @@ 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::High), - AFGPIOPin(GPIOB, 5, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOB, 0, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), + AFGPIOPin(GPIOB, 4, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), // RED + AFGPIOPin(GPIOB, 5, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), // GREEN + AFGPIOPin(GPIOB, 0, GPIO::AFR::AlternateFunction::AF2, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High) // BLUE }; } diff --git a/ion/src/device/shared/drivers/led.cpp b/ion/src/device/shared/drivers/led.cpp index b7c6b434a..3a72a5dde 100644 --- a/ion/src/device/shared/drivers/led.cpp +++ b/ion/src/device/shared/drivers/led.cpp @@ -18,9 +18,9 @@ void setColor(KDColor c) { sLedColor = c; /* Active all RGB colors */ - TIM3.CCMR()->setOC2M(TIM::CCMR::OCM::PWM1); - TIM3.CCMR()->setOC4M(TIM::CCMR::OCM::PWM1); - TIM3.CCMR()->setOC3M(TIM::CCMR::OCM::PWM1); + TIM3.CCMR()->setOCM(Ion::Device::LED::Config::RedChannel, TIM::CCMR::OCM::PWM1); + TIM3.CCMR()->setOCM(Ion::Device::LED::Config::GreenChannel, TIM::CCMR::OCM::PWM1); + TIM3.CCMR()->setOCM(Ion::Device::LED::Config::BlueChannel, TIM::CCMR::OCM::PWM1); /* Set the PWM duty cycles to display the right color */ constexpr float maxColorValue = (float)((1 << 8) -1); @@ -32,9 +32,9 @@ void setBlinking(uint16_t period, float dutyCycle) { * Consequently, we do not use PWM to display the right color anymore but to * blink. We cannot use the PWM to display the exact color so we 'project the * color on 3 bits' : all colors have 2 states - active or not. */ - TIM3.CCMR()->setOC2M(sLedColor.red() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); - TIM3.CCMR()->setOC4M(sLedColor.green() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); - TIM3.CCMR()->setOC3M(sLedColor.blue() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOCM(Ion::Device::LED::Config::RedChannel, sLedColor.red() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOCM(Ion::Device::LED::Config::GreenChannel, sLedColor.green() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOCM(Ion::Device::LED::Config::BlueChannel, sLedColor.blue() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); setPeriodAndDutyCycles(Mode::Blink, dutyCycle, dutyCycle, dutyCycle, period); } @@ -59,9 +59,9 @@ void shutdown() { } void initGPIO() { - /* RED_LED(PC7), GREEN_LED(PB1), and BLUE_LED(PB0) are driven using a timer, - * which is an alternate function. More precisely, we will use AF2, which maps - * PB0 to TIM3_CH2, PB1 to TIM3_CH4, and PC7 to TIM3_CH2. */ + /* RED_LED, GREEN_LED, and BLUE_LED are driven using a timer, which is an + * alternate function. More precisely, we will use AF2, which maps each GPIO + * to a TIM3_CH. */ for(const AFGPIOPin & p : Config::RGBPins) { p.init(); } @@ -74,18 +74,18 @@ void shutdownGPIO() { } void initTimer() { - // Output preload enable for channels 2-4 - TIM3.CCMR()->setOC2PE(true); - TIM3.CCMR()->setOC3PE(true); - TIM3.CCMR()->setOC4PE(true); + // Output preload enable + TIM3.CCMR()->setOCPE(Ion::Device::LED::Config::RedChannel, true); + TIM3.CCMR()->setOCPE(Ion::Device::LED::Config::GreenChannel, true); + TIM3.CCMR()->setOCPE(Ion::Device::LED::Config::BlueChannel, true); // Auto-reload preload enable TIM3.CR1()->setARPE(true); // Enable Capture/Compare for channel 2 to 4 - TIM3.CCER()->setCC2E(true); - TIM3.CCER()->setCC3E(true); - TIM3.CCER()->setCC4E(true); + TIM3.CCER()->setCCE(Ion::Device::LED::Config::RedChannel, true); + TIM3.CCER()->setCCE(Ion::Device::LED::Config::GreenChannel, true); + TIM3.CCER()->setCCE(Ion::Device::LED::Config::BlueChannel, true); TIM3.BDTR()->setMOE(true); @@ -93,9 +93,9 @@ void initTimer() { } void shutdownTimer() { - TIM3.CCMR()->setOC2M(TIM::CCMR::OCM::ForceInactive); - TIM3.CCMR()->setOC4M(TIM::CCMR::OCM::ForceInactive); - TIM3.CCMR()->setOC3M(TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOCM(Ion::Device::LED::Config::RedChannel, TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOCM(Ion::Device::LED::Config::GreenChannel, TIM::CCMR::OCM::ForceInactive); + TIM3.CCMR()->setOCM(Ion::Device::LED::Config::BlueChannel, TIM::CCMR::OCM::ForceInactive); } /* Pulse width modulation mode allows you to generate a signal with a @@ -126,9 +126,9 @@ void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, break; } - TIM3.CCR2()->set(dutyCycleRed*period); - TIM3.CCR3()->set(dutyCycleBlue*period); - TIM3.CCR4()->set(dutyCycleGreen*period); + TIM3.CCR(Ion::Device::LED::Config::RedChannel)->set(dutyCycleRed*period); + TIM3.CCR(Ion::Device::LED::Config::GreenChannel)->set(dutyCycleBlue*period); + TIM3.CCR(Ion::Device::LED::Config::BlueChannel)->set(dutyCycleGreen*period); } } diff --git a/ion/src/device/shared/regs/tim.h b/ion/src/device/shared/regs/tim.h index 8f0c9b28f..f92d119e6 100644 --- a/ion/src/device/shared/regs/tim.h +++ b/ion/src/device/shared/regs/tim.h @@ -51,6 +51,31 @@ public: REGS_TYPE_FIELD(OC3M, 38, 36); REGS_BOOL_FIELD(OC4PE, 43); REGS_TYPE_FIELD(OC4M, 46, 44); + + void setOCM(int channel, OCM v) volatile { + assert(channel >= 1 && channel <= 4); + if (channel == 1) { + setOC1M(v); + } else if (channel == 2) { + setOC2M(v); + } else if (channel == 3) { + setOC3M(v); + } else { + setOC4M(v); + } + } + void setOCPE(int channel, uint16_t v) volatile { + assert(channel >= 1 && channel <= 4); + if (channel == 1) { + setOC1PE(v); + } else if (channel == 2) { + setOC2PE(v); + } else if (channel == 3) { + setOC3PE(v); + } else { + setOC4PE(v); + } + } }; class CCER : Register16 { @@ -59,6 +84,18 @@ public: REGS_BOOL_FIELD(CC2E, 4); REGS_BOOL_FIELD(CC3E, 8); REGS_BOOL_FIELD(CC4E, 12); + void setCCE(int channel, uint16_t v) volatile { + assert(channel >= 1 && channel <= 4); + if (channel == 1) { + setCC1E(v); + } else if (channel == 2) { + setCC2E(v); + } else if (channel == 3) { + setCC3E(v); + } else { + setCC4E(v); + } + } }; class BDTR : Register16 { @@ -84,6 +121,13 @@ public: REGS_REGISTER_AT(CCR3, 0x3C); REGS_REGISTER_AT(CCR4, 0x40); REGS_REGISTER_AT(BDTR, 0x44); + RegisterWidth * CCR(int channel) const { + return (channel == 1 ? (RegisterWidth *)CCR1() : + (channel == 2 ? (RegisterWidth *)CCR2() : + (channel == 3 ? (RegisterWidth *)CCR3() : + (channel == 4 ? (RegisterWidth *)CCR4() : + nullptr)))); + } private: constexpr uint32_t Base() const { return (m_index == 1 ? 0x40010000 : From 88737f1e297aa2b87087d61364315d9c632d8161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 15 Mar 2019 10:23:00 +0100 Subject: [PATCH 0161/1750] [ion] Dummy implementation of n0100 external flash to avoid initing the QUADSPI peripheral when unused --- ion/src/device/n0100/Makefile | 1 + .../device/n0100/drivers/external_flash.cpp | 31 +++++++++ ion/src/device/n0101/Makefile | 1 + .../drivers/external_flash.cpp | 23 ++++++- ion/src/device/n0101/drivers/external_flash.h | 69 +++++++++++++++++++ ion/src/device/shared/drivers/Makefile | 1 - ion/src/device/shared/drivers/board.cpp | 2 +- .../device/shared/drivers/external_flash.h | 55 +-------------- ion/src/device/shared/usb/Makefile | 2 +- ion/src/device/shared/usb/dfu_interface.cpp | 4 +- 10 files changed, 129 insertions(+), 60 deletions(-) create mode 100644 ion/src/device/n0100/drivers/external_flash.cpp rename ion/src/device/{shared => n0101}/drivers/external_flash.cpp (95%) create mode 100644 ion/src/device/n0101/drivers/external_flash.h diff --git a/ion/src/device/n0100/Makefile b/ion/src/device/n0100/Makefile index 931bd69d0..cbab943f0 100644 --- a/ion/src/device/n0100/Makefile +++ b/ion/src/device/n0100/Makefile @@ -1,5 +1,6 @@ ion_device_src += $(addprefix ion/src/device/n0100/drivers/, \ board.cpp \ + external_flash.cpp \ ) LDSCRIPT ?= ion/src/device/n0100/flash.ld diff --git a/ion/src/device/n0100/drivers/external_flash.cpp b/ion/src/device/n0100/drivers/external_flash.cpp new file mode 100644 index 000000000..03bb0d9bf --- /dev/null +++ b/ion/src/device/n0100/drivers/external_flash.cpp @@ -0,0 +1,31 @@ +#include + +namespace Ion { +namespace Device { +namespace ExternalFlash { + +// Dummy implementation: N0100 has no external flash + +void init() { +} + +void shutdown() { +} + +int NumberOfSectors() { + return 0; +} + +int SectorAtAddress(uint32_t address) { + return 0; +} + +void MassErase() {} + +void EraseSector(int i) {} + +void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) {} + +} +} +} diff --git a/ion/src/device/n0101/Makefile b/ion/src/device/n0101/Makefile index ec4576e4d..4b65cd8fa 100644 --- a/ion/src/device/n0101/Makefile +++ b/ion/src/device/n0101/Makefile @@ -1,5 +1,6 @@ ion_device_src += $(addprefix ion/src/device/n0101/drivers/, \ board.cpp \ + external_flash.cpp \ ) LDSCRIPT ?= ion/src/device/n0101/flash.ld diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/n0101/drivers/external_flash.cpp similarity index 95% rename from ion/src/device/shared/drivers/external_flash.cpp rename to ion/src/device/n0101/drivers/external_flash.cpp index d40ffa1b4..4c9ceefc8 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/n0101/drivers/external_flash.cpp @@ -1,4 +1,5 @@ #include "external_flash.h" +#include #include namespace Ion { @@ -208,12 +209,26 @@ void init() { initChip(); } +void shutdown() { + shutdownChip(); + shutdownQSPI(); + shutdownGPIO(); +} + void initGPIO() { for(const AFGPIOPin & p : Config::Pins) { p.init(); } } +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); + } +} + void initQSPI() { // Enable QUADSPI AHB3 peripheral clock RCC.AHB3ENR()->setQSPIEN(true); @@ -257,9 +272,13 @@ void initChip() { set_as_memory_mapped(); } +int NumberOfSectors() { + return k_numberOfSectors; +} + int SectorAtAddress(uint32_t address) { int i = address >> NumberOfAddressBitsIn64KbyteBlock; - if (i >= NumberOfSectors) { + if (i >= NumberOfSectors()) { return -1; } return i; @@ -275,7 +294,7 @@ void MassErase() { } void EraseSector(int i) { - assert(i >= 0 && i < NumberOfSectors); + assert(i >= 0 && i < NumberOfSectors()); unset_memory_mapped_mode(); send_command(Command::WriteEnable); wait(); diff --git a/ion/src/device/n0101/drivers/external_flash.h b/ion/src/device/n0101/drivers/external_flash.h new file mode 100644 index 000000000..70776a892 --- /dev/null +++ b/ion/src/device/n0101/drivers/external_flash.h @@ -0,0 +1,69 @@ +#ifndef ION_DEVICE_EXTERNAL_FLASH_H +#define ION_DEVICE_EXTERNAL_FLASH_H + +#include +#include + +// Quad-SPI on STM32 microcontroller +// https://www.st.com/resource/en/application_note/dm00227538.pdf + +// Adesto Technologies AT25SF641 +// https://www.adestotech.com/wp-content/uploads/AT25SF641_111.pdf + +/* External Flash Memory map Address space + * 8MiB chip 0x000000 - 0x7FFFFF + * 2^7 64KiB blocks 0x..0000 - 0x..FFFF + * 2^7 * 2 32KiB blocks 0x..0000 - 0x..7FFF or 0x..8000 - 0x..FFFF + * 2^7 * 2 * 2^3 4KiB blocks 0x...000 - 0x...FFF + * 2^7 * 2 * 2^3 * 2^4 256B pages 0x....00 - 0x....FF */ + +namespace Ion { +namespace Device { +namespace ExternalFlash { + +/* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS + * PC8 | 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 + */ + +void initGPIO(); +void shutdownGPIO(); +void initQSPI(); +void shutdownQSPI(); +void initChip(); +void shutdownChip(); + +constexpr int k_numberOfSectors = 128; + +enum class Command : uint8_t { + ReadStatusRegister = 0x05, + WriteStatusRegister2 = 0x31, + WriteEnable = 0x06, + ReadData = 0x03, + FastRead = 0x0B, + FastReadQuadIO = 0xEB, + // Program previously erased memory areas as being "0" + PageProgram = 0x02, + QuadPageProgram = 0x33, + EnableQPI = 0x38, + // Erase the whole chip or a 64-Kbyte block as being "1" + ChipErase = 0xC7, + Erase64KbyteBlock = 0xD8, + SetReadParameters = 0xC0 +}; + +constexpr static uint32_t QSPIBaseAddress = 0x90000000; +constexpr static uint8_t NumberOfAddressBitsInChip = 23; +constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; + +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/Makefile b/ion/src/device/shared/drivers/Makefile index 473a72b96..a1d3d7711 100644 --- a/ion/src/device/shared/drivers/Makefile +++ b/ion/src/device/shared/drivers/Makefile @@ -7,7 +7,6 @@ ion_device_src += $(addprefix ion/src/device/shared/drivers/, \ crc32.cpp \ display.cpp \ events.cpp \ - external_flash.cpp \ flash.cpp \ keyboard.cpp \ led.cpp \ diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index 4fdfd4ae3..a81383371 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -1,10 +1,10 @@ #include "board.h" +#include "external_flash.h" #include #include #include #include #include -#include #include #include #include diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index 38214db3a..709c4162f 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -4,70 +4,19 @@ #include #include -// Quad-SPI on STM32 microcontroller -// https://www.st.com/resource/en/application_note/dm00227538.pdf - -// Adesto Technologies AT25SF641 -// https://www.adestotech.com/wp-content/uploads/AT25SF641_111.pdf - -/* External Flash Memory map Address space - * 8MiB chip 0x000000 - 0x7FFFFF - * 2^7 64KiB blocks 0x..0000 - 0x..FFFF - * 2^7 * 2 32KiB blocks 0x..0000 - 0x..7FFF or 0x..8000 - 0x..FFFF - * 2^7 * 2 * 2^3 4KiB blocks 0x...000 - 0x...FFF - * 2^7 * 2 * 2^3 * 2^4 256B pages 0x....00 - 0x....FF */ - namespace Ion { namespace Device { namespace ExternalFlash { -/* Pin | Role | Mode | Function - * -----+----------------------+-----------------------+----------------- - * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK - * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS - * PC8 | 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 - */ - void init(); void shutdown(); -void initGPIO(); -void initQSPI(); -void initChip(); - -void MassErase(); - -constexpr int NumberOfSectors = 128; +int NumberOfSectors(); int SectorAtAddress(uint32_t address); +void MassErase(); void EraseSector(int i); - void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); -enum class Command : uint8_t { - ReadStatusRegister = 0x05, - WriteStatusRegister2 = 0x31, - WriteEnable = 0x06, - ReadData = 0x03, - FastRead = 0x0B, - FastReadQuadIO = 0xEB, - // Program previously erased memory areas as being "0" - PageProgram = 0x02, - QuadPageProgram = 0x33, - EnableQPI = 0x38, - // Erase the whole chip or a 64-Kbyte block as being "1" - ChipErase = 0xC7, - Erase64KbyteBlock = 0xD8, - SetReadParameters = 0xC0 -}; - -constexpr static uint32_t QSPIBaseAddress = 0x90000000; -constexpr static uint8_t NumberOfAddressBitsInChip = 23; -constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; -constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; - } } } diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index fd26a27a7..0daf6aa82 100644 --- a/ion/src/device/shared/usb/Makefile +++ b/ion/src/device/shared/usb/Makefile @@ -47,9 +47,9 @@ dfu_src += liba/src/memset.c dfu_src += liba/src/memcpy.c dfu_src += libaxx/src/cxxabi/pure_virtual.cpp dfu_src += ion/src/device/shared/usb/boot.cpp +dfu_src += ion/src/device/$(MODEL)/drivers/external_flash.cpp dfu_src += $(addprefix ion/src/device/shared/drivers/, \ base64.cpp \ - external_flash.cpp \ flash.cpp \ keyboard.cpp \ reset.cpp \ diff --git a/ion/src/device/shared/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index 1d79cad96..c3dd280f0 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -176,7 +176,7 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe if (transferBufferLength == 1) { // Mass erase - m_erasePage = Flash::NumberOfSectors + ExternalFlash::NumberOfSectors; + m_erasePage = Flash::NumberOfSectors + ExternalFlash::NumberOfSectors(); return; } @@ -206,7 +206,7 @@ void DFUInterface::eraseMemoryIfNeeded() { return; } - if (m_erasePage == Flash::NumberOfSectors + ExternalFlash::NumberOfSectors) { + if (m_erasePage == Flash::NumberOfSectors + ExternalFlash::NumberOfSectors()) { Flash::MassErase(); ExternalFlash::MassErase(); } else if (m_erasePage < Flash::NumberOfSectors) { From 3ac9749e75d79c2d4ad81b393cb6619fade99ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 15 Mar 2019 16:46:50 +0100 Subject: [PATCH 0162/1750] [ion] WriteMemory arguments order same as memcpy --- ion/src/device/n0100/drivers/external_flash.cpp | 2 +- ion/src/device/n0101/drivers/external_flash.cpp | 2 +- ion/src/device/shared/drivers/external_flash.h | 2 +- ion/src/device/shared/drivers/flash.cpp | 6 +++--- ion/src/device/shared/drivers/flash.h | 2 +- ion/src/device/shared/usb/dfu_interface.cpp | 4 ++-- ion/test/external_flash.cpp | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ion/src/device/n0100/drivers/external_flash.cpp b/ion/src/device/n0100/drivers/external_flash.cpp index 03bb0d9bf..4ed7479b6 100644 --- a/ion/src/device/n0100/drivers/external_flash.cpp +++ b/ion/src/device/n0100/drivers/external_flash.cpp @@ -24,7 +24,7 @@ void MassErase() {} void EraseSector(int i) {} -void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) {} +void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) {} } } diff --git a/ion/src/device/n0101/drivers/external_flash.cpp b/ion/src/device/n0101/drivers/external_flash.cpp index 4c9ceefc8..0a1ad7957 100644 --- a/ion/src/device/n0101/drivers/external_flash.cpp +++ b/ion/src/device/n0101/drivers/external_flash.cpp @@ -303,7 +303,7 @@ void EraseSector(int i) { set_as_memory_mapped(); } -void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { +void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) { 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. diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index 709c4162f..384ba7b49 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -15,7 +15,7 @@ int NumberOfSectors(); int SectorAtAddress(uint32_t address); void MassErase(); void EraseSector(int i); -void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); +void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); } } diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 8ef655e6e..56e87bb53 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -86,7 +86,7 @@ static inline T min(T i, T j) { return (isetPG(true); - flash_memcpy(source, destination, length); + flash_memcpy(destination, source, length); wait(); FLASH.CR()->setPG(false); close(); diff --git a/ion/src/device/shared/drivers/flash.h b/ion/src/device/shared/drivers/flash.h index 5a48a88e6..b2ae1073c 100644 --- a/ion/src/device/shared/drivers/flash.h +++ b/ion/src/device/shared/drivers/flash.h @@ -14,7 +14,7 @@ constexpr int NumberOfSectors = 12; int SectorAtAddress(uint32_t address); void EraseSector(int i); -void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); +void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); /* The Device is powered by a 2.8V LDO. This allows us to perform writes to the * Flash 32 bits at once. */ diff --git a/ion/src/device/shared/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index c3dd280f0..7c0c8ee88 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -225,13 +225,13 @@ void DFUInterface::eraseMemoryIfNeeded() { void DFUInterface::writeOnMemory() { if (m_writeAddress >= k_flashStartAddress && m_writeAddress <= k_flashEndAddress) { // Write to the Flash memory - Flash::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress), m_largeBufferLength); + Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); } else if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) { // Write on SRAM // FIXME We should check that we are not overriding the current instructions. memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); } else if (m_writeAddress >= k_externalFlashStartAddress && m_writeAddress <= k_externalFlashEndAddress) { - ExternalFlash::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress) - k_externalFlashStartAddress, m_largeBufferLength); + ExternalFlash::WriteMemory(reinterpret_cast(m_writeAddress) - k_externalFlashStartAddress, m_largeBuffer, m_largeBufferLength); } else { // Invalid write address m_largeBufferLength = 0; diff --git a/ion/test/external_flash.cpp b/ion/test/external_flash.cpp index 392a058da..0386eeb5c 100644 --- a/ion/test/external_flash.cpp +++ b/ion/test/external_flash.cpp @@ -103,7 +103,7 @@ QUIZ_CASE(ion_ext_flash_program) { for (int byte = 0; byte < 256; byte++) { buffer[byte] = expected_value_at(reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress + page * 256 + byte)); } - Ion::ExternalFlash::Device::WriteMemory(buffer, reinterpret_cast(page * 256), 256); + Ion::ExternalFlash::Device::WriteMemory(reinterpret_cast(page * 256), buffer, 256); } printElapsedTime(startTime); #endif From 25453f32f79ee15c864314855d501001bf921275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 15 Mar 2019 17:27:26 +0100 Subject: [PATCH 0163/1750] Revert "[ion] Dummy implementation of n0100 external flash to avoid initing the" This reverts commit 88737f1e297aa2b87087d61364315d9c632d8161. --- ion/src/device/n0100/Makefile | 1 - .../device/n0100/drivers/external_flash.cpp | 31 --------- ion/src/device/n0101/Makefile | 1 - ion/src/device/n0101/drivers/external_flash.h | 69 ------------------- ion/src/device/shared/drivers/Makefile | 1 + ion/src/device/shared/drivers/board.cpp | 2 +- .../drivers/external_flash.cpp | 23 +------ .../device/shared/drivers/external_flash.h | 54 ++++++++++++++- ion/src/device/shared/usb/Makefile | 2 +- ion/src/device/shared/usb/dfu_interface.cpp | 4 +- 10 files changed, 59 insertions(+), 129 deletions(-) delete mode 100644 ion/src/device/n0100/drivers/external_flash.cpp delete mode 100644 ion/src/device/n0101/drivers/external_flash.h rename ion/src/device/{n0101 => shared}/drivers/external_flash.cpp (95%) diff --git a/ion/src/device/n0100/Makefile b/ion/src/device/n0100/Makefile index cbab943f0..931bd69d0 100644 --- a/ion/src/device/n0100/Makefile +++ b/ion/src/device/n0100/Makefile @@ -1,6 +1,5 @@ ion_device_src += $(addprefix ion/src/device/n0100/drivers/, \ board.cpp \ - external_flash.cpp \ ) LDSCRIPT ?= ion/src/device/n0100/flash.ld diff --git a/ion/src/device/n0100/drivers/external_flash.cpp b/ion/src/device/n0100/drivers/external_flash.cpp deleted file mode 100644 index 4ed7479b6..000000000 --- a/ion/src/device/n0100/drivers/external_flash.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include - -namespace Ion { -namespace Device { -namespace ExternalFlash { - -// Dummy implementation: N0100 has no external flash - -void init() { -} - -void shutdown() { -} - -int NumberOfSectors() { - return 0; -} - -int SectorAtAddress(uint32_t address) { - return 0; -} - -void MassErase() {} - -void EraseSector(int i) {} - -void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) {} - -} -} -} diff --git a/ion/src/device/n0101/Makefile b/ion/src/device/n0101/Makefile index 4b65cd8fa..ec4576e4d 100644 --- a/ion/src/device/n0101/Makefile +++ b/ion/src/device/n0101/Makefile @@ -1,6 +1,5 @@ ion_device_src += $(addprefix ion/src/device/n0101/drivers/, \ board.cpp \ - external_flash.cpp \ ) LDSCRIPT ?= ion/src/device/n0101/flash.ld diff --git a/ion/src/device/n0101/drivers/external_flash.h b/ion/src/device/n0101/drivers/external_flash.h deleted file mode 100644 index 70776a892..000000000 --- a/ion/src/device/n0101/drivers/external_flash.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef ION_DEVICE_EXTERNAL_FLASH_H -#define ION_DEVICE_EXTERNAL_FLASH_H - -#include -#include - -// Quad-SPI on STM32 microcontroller -// https://www.st.com/resource/en/application_note/dm00227538.pdf - -// Adesto Technologies AT25SF641 -// https://www.adestotech.com/wp-content/uploads/AT25SF641_111.pdf - -/* External Flash Memory map Address space - * 8MiB chip 0x000000 - 0x7FFFFF - * 2^7 64KiB blocks 0x..0000 - 0x..FFFF - * 2^7 * 2 32KiB blocks 0x..0000 - 0x..7FFF or 0x..8000 - 0x..FFFF - * 2^7 * 2 * 2^3 4KiB blocks 0x...000 - 0x...FFF - * 2^7 * 2 * 2^3 * 2^4 256B pages 0x....00 - 0x....FF */ - -namespace Ion { -namespace Device { -namespace ExternalFlash { - -/* Pin | Role | Mode | Function - * -----+----------------------+-----------------------+----------------- - * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK - * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS - * PC8 | 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 - */ - -void initGPIO(); -void shutdownGPIO(); -void initQSPI(); -void shutdownQSPI(); -void initChip(); -void shutdownChip(); - -constexpr int k_numberOfSectors = 128; - -enum class Command : uint8_t { - ReadStatusRegister = 0x05, - WriteStatusRegister2 = 0x31, - WriteEnable = 0x06, - ReadData = 0x03, - FastRead = 0x0B, - FastReadQuadIO = 0xEB, - // Program previously erased memory areas as being "0" - PageProgram = 0x02, - QuadPageProgram = 0x33, - EnableQPI = 0x38, - // Erase the whole chip or a 64-Kbyte block as being "1" - ChipErase = 0xC7, - Erase64KbyteBlock = 0xD8, - SetReadParameters = 0xC0 -}; - -constexpr static uint32_t QSPIBaseAddress = 0x90000000; -constexpr static uint8_t NumberOfAddressBitsInChip = 23; -constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; -constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; - -} -} -} - -#endif diff --git a/ion/src/device/shared/drivers/Makefile b/ion/src/device/shared/drivers/Makefile index a1d3d7711..473a72b96 100644 --- a/ion/src/device/shared/drivers/Makefile +++ b/ion/src/device/shared/drivers/Makefile @@ -7,6 +7,7 @@ ion_device_src += $(addprefix ion/src/device/shared/drivers/, \ crc32.cpp \ display.cpp \ events.cpp \ + external_flash.cpp \ flash.cpp \ keyboard.cpp \ led.cpp \ diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index a81383371..4fdfd4ae3 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -1,10 +1,10 @@ #include "board.h" -#include "external_flash.h" #include #include #include #include #include +#include #include #include #include diff --git a/ion/src/device/n0101/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp similarity index 95% rename from ion/src/device/n0101/drivers/external_flash.cpp rename to ion/src/device/shared/drivers/external_flash.cpp index 0a1ad7957..533d6e40f 100644 --- a/ion/src/device/n0101/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -1,5 +1,4 @@ #include "external_flash.h" -#include #include namespace Ion { @@ -209,26 +208,12 @@ void init() { initChip(); } -void shutdown() { - shutdownChip(); - shutdownQSPI(); - shutdownGPIO(); -} - void initGPIO() { for(const AFGPIOPin & p : Config::Pins) { p.init(); } } -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); - } -} - void initQSPI() { // Enable QUADSPI AHB3 peripheral clock RCC.AHB3ENR()->setQSPIEN(true); @@ -272,13 +257,9 @@ void initChip() { set_as_memory_mapped(); } -int NumberOfSectors() { - return k_numberOfSectors; -} - int SectorAtAddress(uint32_t address) { int i = address >> NumberOfAddressBitsIn64KbyteBlock; - if (i >= NumberOfSectors()) { + if (i >= NumberOfSectors) { return -1; } return i; @@ -294,7 +275,7 @@ void MassErase() { } void EraseSector(int i) { - assert(i >= 0 && i < NumberOfSectors()); + assert(i >= 0 && i < NumberOfSectors); unset_memory_mapped_mode(); send_command(Command::WriteEnable); wait(); diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index 384ba7b49..cce593b81 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -4,19 +4,69 @@ #include #include +// Quad-SPI on STM32 microcontroller +// https://www.st.com/resource/en/application_note/dm00227538.pdf + +// Adesto Technologies AT25SF641 +// https://www.adestotech.com/wp-content/uploads/AT25SF641_111.pdf + +/* External Flash Memory map Address space + * 8MiB chip 0x000000 - 0x7FFFFF + * 2^7 64KiB blocks 0x..0000 - 0x..FFFF + * 2^7 * 2 32KiB blocks 0x..0000 - 0x..7FFF or 0x..8000 - 0x..FFFF + * 2^7 * 2 * 2^3 4KiB blocks 0x...000 - 0x...FFF + * 2^7 * 2 * 2^3 * 2^4 256B pages 0x....00 - 0x....FF */ + namespace Ion { namespace Device { namespace ExternalFlash { +/* Pin | Role | Mode | Function + * -----+----------------------+-----------------------+----------------- + * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK + * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS + * PC8 | 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 + */ + void init(); void shutdown(); -int NumberOfSectors(); -int SectorAtAddress(uint32_t address); +void initGPIO(); +void initQSPI(); +void initChip(); + void MassErase(); + +constexpr int NumberOfSectors = 128; +int SectorAtAddress(uint32_t address); void EraseSector(int i); void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); +enum class Command : uint8_t { + ReadStatusRegister = 0x05, + WriteStatusRegister2 = 0x31, + WriteEnable = 0x06, + ReadData = 0x03, + FastRead = 0x0B, + FastReadQuadIO = 0xEB, + // Program previously erased memory areas as being "0" + PageProgram = 0x02, + QuadPageProgram = 0x33, + EnableQPI = 0x38, + // Erase the whole chip or a 64-Kbyte block as being "1" + ChipErase = 0xC7, + Erase64KbyteBlock = 0xD8, + SetReadParameters = 0xC0 +}; + +constexpr static uint32_t QSPIBaseAddress = 0x90000000; +constexpr static uint8_t NumberOfAddressBitsInChip = 23; +constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; + } } } diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index 0daf6aa82..fd26a27a7 100644 --- a/ion/src/device/shared/usb/Makefile +++ b/ion/src/device/shared/usb/Makefile @@ -47,9 +47,9 @@ dfu_src += liba/src/memset.c dfu_src += liba/src/memcpy.c dfu_src += libaxx/src/cxxabi/pure_virtual.cpp dfu_src += ion/src/device/shared/usb/boot.cpp -dfu_src += ion/src/device/$(MODEL)/drivers/external_flash.cpp dfu_src += $(addprefix ion/src/device/shared/drivers/, \ base64.cpp \ + external_flash.cpp \ flash.cpp \ keyboard.cpp \ reset.cpp \ diff --git a/ion/src/device/shared/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index 7c0c8ee88..161af279d 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -176,7 +176,7 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe if (transferBufferLength == 1) { // Mass erase - m_erasePage = Flash::NumberOfSectors + ExternalFlash::NumberOfSectors(); + m_erasePage = Flash::NumberOfSectors + ExternalFlash::NumberOfSectors; return; } @@ -206,7 +206,7 @@ void DFUInterface::eraseMemoryIfNeeded() { return; } - if (m_erasePage == Flash::NumberOfSectors + ExternalFlash::NumberOfSectors()) { + if (m_erasePage == Flash::NumberOfSectors + ExternalFlash::NumberOfSectors) { Flash::MassErase(); ExternalFlash::MassErase(); } else if (m_erasePage < Flash::NumberOfSectors) { From 1e536c187ea6a600c75c60f34fcb502061593296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 15 Mar 2019 17:54:43 +0100 Subject: [PATCH 0164/1750] [ion] External flash: avoid initing QUADSPI peripheral if the model does not have any external flash --- .../n0100/drivers/config/external_flash.h | 11 ++-- .../n0101/drivers/config/external_flash.h | 2 + .../device/shared/drivers/external_flash.cpp | 54 +++++++++++++++---- .../device/shared/drivers/external_flash.h | 28 ---------- ion/src/device/shared/usb/dfu_interface.cpp | 5 +- 5 files changed, 51 insertions(+), 49 deletions(-) diff --git a/ion/src/device/n0100/drivers/config/external_flash.h b/ion/src/device/n0100/drivers/config/external_flash.h index 704586fdc..f239ddb72 100644 --- a/ion/src/device/n0100/drivers/config/external_flash.h +++ b/ion/src/device/n0100/drivers/config/external_flash.h @@ -20,14 +20,9 @@ namespace Config { using namespace Regs; -constexpr static AFGPIOPin Pins[] = { - AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOC, 9, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOD, 12, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOD, 13, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), - AFGPIOPin(GPIOE, 2, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), -}; +constexpr static uint32_t QSPIBaseAddress = 0; +constexpr static int NumberOfSectors = 0; +constexpr static AFGPIOPin Pins[] = {}; } } diff --git a/ion/src/device/n0101/drivers/config/external_flash.h b/ion/src/device/n0101/drivers/config/external_flash.h index 633bbe1ff..d16753a00 100644 --- a/ion/src/device/n0101/drivers/config/external_flash.h +++ b/ion/src/device/n0101/drivers/config/external_flash.h @@ -20,6 +20,8 @@ namespace Config { using namespace Regs; +constexpr static uint32_t QSPIBaseAddress = 0x90000000; +constexpr static int NumberOfSectors = 128; constexpr static AFGPIOPin Pins[] = { AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), AFGPIOPin(GPIOB, 6, GPIO::AFR::AlternateFunction::AF10, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 533d6e40f..c9dffcdd4 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -52,6 +52,29 @@ using namespace Regs; * 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 { + ReadStatusRegister = 0x05, + WriteStatusRegister2 = 0x31, + WriteEnable = 0x06, + ReadData = 0x03, + FastRead = 0x0B, + FastReadQuadIO = 0xEB, + // Program previously erased memory areas as being "0" + PageProgram = 0x02, + QuadPageProgram = 0x33, + EnableQPI = 0x38, + // Erase the whole chip or a 64-Kbyte block as being "1" + ChipErase = 0xC7, + Erase64KbyteBlock = 0xD8, + SetReadParameters = 0xC0, + DeepPowerDown = 0xB9, + ReleaseDeepPowerDown = 0xAB +}; + +static constexpr uint8_t NumberOfAddressBitsInChip = 23; +static constexpr uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; +static constexpr uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; + class ExternalFlashStatusRegister { public: class StatusRegister1 : Register8 { @@ -151,7 +174,7 @@ static void unset_memory_mapped_mode() { ); } -void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { +static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { class QUADSPI::CCR ccr(0); ccr.setFMODE(functionalMode); if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { @@ -202,19 +225,13 @@ void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR } } -void init() { - initGPIO(); - initQSPI(); - initChip(); -} - -void initGPIO() { +static void initGPIO() { for(const AFGPIOPin & p : Config::Pins) { p.init(); } } -void initQSPI() { +static void initQSPI() { // Enable QUADSPI AHB3 peripheral clock RCC.AHB3ENR()->setQSPIEN(true); // Configure controller for target device @@ -229,7 +246,7 @@ void initQSPI() { QUADSPI.CR()->set(cr); } -void initChip() { +static void initChip() { /* The chip initially expects commands in SPI mode. We need to use SPI to tell * it to switch to QPI. */ if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { @@ -257,15 +274,27 @@ void initChip() { set_as_memory_mapped(); } +void init() { + if (Config::NumberOfSectors == 0) { + return; + } + initGPIO(); + initQSPI(); + initChip(); +} + int SectorAtAddress(uint32_t address) { int i = address >> NumberOfAddressBitsIn64KbyteBlock; - if (i >= NumberOfSectors) { + if (i >= Config::NumberOfSectors) { return -1; } return i; } void MassErase() { + if (Config::NumberOfSectors == 0) { + return; + } unset_memory_mapped_mode(); send_command(Command::WriteEnable); wait(); @@ -285,6 +314,9 @@ void EraseSector(int i) { } void WriteMemory(uint8_t * destination, 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. diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index cce593b81..52f4dd11c 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -34,39 +34,11 @@ namespace ExternalFlash { void init(); void shutdown(); -void initGPIO(); -void initQSPI(); -void initChip(); - void MassErase(); - -constexpr int NumberOfSectors = 128; int SectorAtAddress(uint32_t address); void EraseSector(int i); void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); -enum class Command : uint8_t { - ReadStatusRegister = 0x05, - WriteStatusRegister2 = 0x31, - WriteEnable = 0x06, - ReadData = 0x03, - FastRead = 0x0B, - FastReadQuadIO = 0xEB, - // Program previously erased memory areas as being "0" - PageProgram = 0x02, - QuadPageProgram = 0x33, - EnableQPI = 0x38, - // Erase the whole chip or a 64-Kbyte block as being "1" - ChipErase = 0xC7, - Erase64KbyteBlock = 0xD8, - SetReadParameters = 0xC0 -}; - -constexpr static uint32_t QSPIBaseAddress = 0x90000000; -constexpr static uint8_t NumberOfAddressBitsInChip = 23; -constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; -constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; - } } } diff --git a/ion/src/device/shared/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index 161af279d..5d4f81b01 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -1,4 +1,5 @@ #include "dfu_interface.h" +#include #include #include "../drivers/flash.h" #include "../drivers/external_flash.h" @@ -176,7 +177,7 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe if (transferBufferLength == 1) { // Mass erase - m_erasePage = Flash::NumberOfSectors + ExternalFlash::NumberOfSectors; + m_erasePage = Flash::NumberOfSectors + ExternalFlash::Config::NumberOfSectors; return; } @@ -206,7 +207,7 @@ void DFUInterface::eraseMemoryIfNeeded() { return; } - if (m_erasePage == Flash::NumberOfSectors + ExternalFlash::NumberOfSectors) { + if (m_erasePage == Flash::NumberOfSectors + ExternalFlash::Config::NumberOfSectors) { Flash::MassErase(); ExternalFlash::MassErase(); } else if (m_erasePage < Flash::NumberOfSectors) { From 5ec4fb54869c7a4dc1c83f15106e06a9e5fcc054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 15 Mar 2019 17:55:44 +0100 Subject: [PATCH 0165/1750] [ion] ExternalFlash: implement ExternalFlash::shutdown --- ion/src/device/shared/drivers/board.cpp | 1 + .../device/shared/drivers/external_flash.cpp | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index 4fdfd4ae3..a93a5145c 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -43,6 +43,7 @@ void initPeripherals() { } void shutdownPeripherals(bool keepLEDAwake) { + ExternalFlash::shutdown(); Timing::shutdown(); SWD::shutdown(); Console::shutdown(); diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index c9dffcdd4..1486a3b05 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -1,5 +1,6 @@ #include "external_flash.h" #include +#include namespace Ion { namespace Device { @@ -283,6 +284,33 @@ void init() { 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(); + send_command(Command::DeepPowerDown); + Timing::usleep(100); // TODO should be 3us when usleep adjusted +} + +static void shutdownQSPI() { + RCC.AHB3ENR()->setQSPIEN(false); // TODO: move in Device::shutdownClocks +} + +void shutdown() { + if (Config::NumberOfSectors == 0) { + return; + } + shutdownChip(); + shutdownQSPI(); + shutdownGPIO(); +} + int SectorAtAddress(uint32_t address) { int i = address >> NumberOfAddressBitsIn64KbyteBlock; if (i >= Config::NumberOfSectors) { From 1aef86cedb1d3b245a50ecedae0da5e680da98d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 18 Mar 2019 11:20:05 +0100 Subject: [PATCH 0166/1750] [ion] Timing: decrease AHB clock frequency in msleep to save power y modified: scripts/device/openocd.n0100.cfg --- ion/src/device/n0100/drivers/config/timing.h | 2 ++ ion/src/device/n0101/drivers/config/timing.h | 1 + ion/src/device/shared/drivers/board.cpp | 15 +++++++++++++++ ion/src/device/shared/drivers/board.h | 7 +++++++ ion/src/device/shared/drivers/timing.cpp | 4 ++++ 5 files changed, 29 insertions(+) diff --git a/ion/src/device/n0100/drivers/config/timing.h b/ion/src/device/n0100/drivers/config/timing.h index 71d6c3f3d..27757c153 100644 --- a/ion/src/device/n0100/drivers/config/timing.h +++ b/ion/src/device/n0100/drivers/config/timing.h @@ -8,6 +8,8 @@ namespace Device { namespace Timing { namespace Config { +#warning N100-calibration +//TODO: calibrate LoopsPerMillisecond now that we decrease AHB clock while msleeping constexpr static int LoopsPerMillisecond = 8852; constexpr static int LoopsPerMicrosecond = 9; // CPU clock is 96 MHz, and systick clock source is divided by 8 diff --git a/ion/src/device/n0101/drivers/config/timing.h b/ion/src/device/n0101/drivers/config/timing.h index 0c0e48a4b..747611b5c 100644 --- a/ion/src/device/n0101/drivers/config/timing.h +++ b/ion/src/device/n0101/drivers/config/timing.h @@ -8,6 +8,7 @@ namespace Device { namespace Timing { namespace Config { +// TODO: calibrate msleep constexpr static int LoopsPerMillisecond = 8852; constexpr static int LoopsPerMicrosecond = 9; // CPU clock is 96 MHz, and systick clock source is divided by 8 diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index a93a5145c..f37af00e5 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -57,6 +57,21 @@ void shutdownPeripherals(bool keepLEDAwake) { Display::shutdown(); } +void setClockFrequency(Frequency f) { + switch (f) { + case Frequency::High: + RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClk); + return; + default: + assert(f == Frequency::Low); + /* 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.5MHz. */ + RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy8); + return; + } +} + } } } diff --git a/ion/src/device/shared/drivers/board.h b/ion/src/device/shared/drivers/board.h index 0e314c10f..372843077 100644 --- a/ion/src/device/shared/drivers/board.h +++ b/ion/src/device/shared/drivers/board.h @@ -15,6 +15,13 @@ void shutdownClocks(bool keepLEDAwake = false); void initPeripherals(); void shutdownPeripherals(bool keepLEDAwake = false); +enum class Frequency { + Low = 0, + High = 1 +}; + +void setClockFrequency(Frequency f); + } } } diff --git a/ion/src/device/shared/drivers/timing.cpp b/ion/src/device/shared/drivers/timing.cpp index 24be59ca9..e2a30d100 100644 --- a/ion/src/device/shared/drivers/timing.cpp +++ b/ion/src/device/shared/drivers/timing.cpp @@ -1,4 +1,5 @@ #include "timing.h" +#include "board.h" #include #include @@ -12,9 +13,12 @@ using namespace Device::Timing; * precision, we could use the controller cycle counter (Systick). */ void msleep(uint32_t ms) { + // We decrease the AHB clock frequency to save power while sleeping. + Device::Board::setClockFrequency(Device::Board::Frequency::Low); for (volatile uint32_t i=0; i Date: Tue, 19 Mar 2019 09:32:30 +0100 Subject: [PATCH 0167/1750] [ion] Fix build of device/shared/drivers/external_flash.o --- ion/src/device/shared/drivers/external_flash.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 1486a3b05..44067a184 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -332,7 +332,7 @@ void MassErase() { } void EraseSector(int i) { - assert(i >= 0 && i < NumberOfSectors); + assert(i >= 0 && i < Config::NumberOfSectors); unset_memory_mapped_mode(); send_command(Command::WriteEnable); wait(); From 252049a8384a5ff4c5a211427c49ae7420830b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 19 Mar 2019 11:21:31 +0100 Subject: [PATCH 0168/1750] [ion] Flash: clear error flags when closing the Flash --- ion/src/device/shared/drivers/flash.cpp | 10 ++++++++++ ion/src/device/shared/regs/flash.h | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 56e87bb53..faac8663e 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -26,6 +26,16 @@ static void open() { } static void close() { + // Clear error flags + class FLASH::SR sr(0); + // Error flags are cleared by writing 1 + sr.setERSERR(true); + sr.setPGPERR(true); + sr.setPGAERR(true); + sr.setWRPERR(true); + sr.setEOP(true); + FLASH.SR()->set(sr); + // Lock the Flash configuration register assert(!FLASH.CR()->getMER()); assert(!FLASH.CR()->getSER()); diff --git a/ion/src/device/shared/regs/flash.h b/ion/src/device/shared/regs/flash.h index 711c370a7..f17637710 100644 --- a/ion/src/device/shared/regs/flash.h +++ b/ion/src/device/shared/regs/flash.h @@ -47,7 +47,13 @@ public: class SR : public Register32 { public: + using Register32::Register32; REGS_BOOL_FIELD(BSY, 16); + REGS_BOOL_FIELD(ERSERR, 7); + REGS_BOOL_FIELD(PGPERR, 6); + REGS_BOOL_FIELD(PGAERR, 5); + REGS_BOOL_FIELD(WRPERR, 4); + REGS_BOOL_FIELD(EOP, 0); }; constexpr FLASH() {}; From 521eeaa83c3776c56183e290af9c7071687aefea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 19 Mar 2019 11:22:34 +0100 Subject: [PATCH 0169/1750] [ion] Flash: wait for the flash BUSY flag to clear between two flash operations (Otherwise, flashing the internal flash of N0101 fails) --- ion/src/device/shared/drivers/flash.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index faac8663e..03cceac2d 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -161,6 +161,7 @@ static void flash_memcpy(uint8_t * destination, uint8_t * source, size_t length) // Then eventually write the header back into the aligned destination *alignedDestination++ = header; + wait(); } /* Step 2 - Copy the bulk of the data @@ -169,6 +170,7 @@ static void flash_memcpy(uint8_t * destination, uint8_t * source, size_t length) MemoryAccessType * lastAlignedDestination = align(destination + length); while (alignedDestination < lastAlignedDestination) { *alignedDestination++ = eat(&source); + wait(); } /* Step 3 - Copy a footer if needed @@ -201,6 +203,7 @@ static void flash_memcpy(uint8_t * destination, uint8_t * source, size_t length) // Then eventually write the footer back into the aligned destination *alignedDestination = footer; + wait(); } } @@ -244,7 +247,6 @@ void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) { open(); FLASH.CR()->setPG(true); flash_memcpy(destination, source, length); - wait(); FLASH.CR()->setPG(false); close(); } From 1ac616742096b465bb545d17bff84548fcf236ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 19 Mar 2019 11:55:03 +0100 Subject: [PATCH 0170/1750] [ion] Clean drivers/cache: implement dummy dsb for N0100 --- ion/src/device/n0100/drivers/cache.h | 18 +++++++++++ ion/src/device/n0101/Makefile | 1 + ion/src/device/n0101/drivers/cache.cpp | 45 ++++++++++++++++++++++++++ ion/src/device/n0101/drivers/cache.h | 36 ++------------------- 4 files changed, 66 insertions(+), 34 deletions(-) create mode 100644 ion/src/device/n0100/drivers/cache.h create mode 100644 ion/src/device/n0101/drivers/cache.cpp diff --git a/ion/src/device/n0100/drivers/cache.h b/ion/src/device/n0100/drivers/cache.h new file mode 100644 index 000000000..ce6cb2032 --- /dev/null +++ b/ion/src/device/n0100/drivers/cache.h @@ -0,0 +1,18 @@ +#ifndef ION_DEVICE_CACHE_H +#define ION_DEVICE_CACHE_H + +#include + +namespace Ion { +namespace Device { +namespace Cache { + +using namespace Regs; + +inline void dsb() {} // No L1-cache on N0100 + +} +} +} + +#endif diff --git a/ion/src/device/n0101/Makefile b/ion/src/device/n0101/Makefile index ec4576e4d..5f1bf5b13 100644 --- a/ion/src/device/n0101/Makefile +++ b/ion/src/device/n0101/Makefile @@ -1,5 +1,6 @@ ion_device_src += $(addprefix ion/src/device/n0101/drivers/, \ board.cpp \ + cache.cpp \ ) LDSCRIPT ?= ion/src/device/n0101/flash.ld diff --git a/ion/src/device/n0101/drivers/cache.cpp b/ion/src/device/n0101/drivers/cache.cpp new file mode 100644 index 000000000..366dfdf2b --- /dev/null +++ b/ion/src/device/n0101/drivers/cache.cpp @@ -0,0 +1,45 @@ +#include "cache.h" + +namespace Ion { +namespace Device { +namespace Cache { + +using namespace Regs; + +void enableDCache() { + CM4.CSSELR()->set(0); + dsb(); + + //Associativity = 6 + + uint32_t sets = CM4.CCSIDR()->getNUMSETS(); + do { + uint32_t ways = CM4.CCSIDR()->getASSOCIATIVITY(); + do { + class CM4::DCISW dcisw; + dcisw.setSET(sets); + dcisw.setWAY(ways); + CM4.DCISW()->set(dcisw); + } while (ways-- != 0); + } while (sets-- != 0); + + dsb(); + CM4.CCR()->setDC(true); + dsb(); + isb(); +} + +void enableICache() { + dsb(); + isb(); + CM4.ICIALLU()->set(0); // Invalidate I-Cache + dsb(); + isb(); + CM4.CCR()->setIC(true); + dsb(); + isb(); +} + +} +} +} diff --git a/ion/src/device/n0101/drivers/cache.h b/ion/src/device/n0101/drivers/cache.h index 8640cd534..a8b0e8a0c 100644 --- a/ion/src/device/n0101/drivers/cache.h +++ b/ion/src/device/n0101/drivers/cache.h @@ -7,8 +7,6 @@ namespace Ion { namespace Device { namespace Cache { -using namespace Regs; - inline void dsb() { asm volatile("dsb 0xF":::"memory"); } @@ -17,39 +15,9 @@ inline void isb() { asm volatile("isb 0xF":::"memory"); } -void enableDCache() { - CM4.CSSELR()->set(0); - dsb(); +void enableDCache(); - //Associativity = 6 - - uint32_t sets = CM4.CCSIDR()->getNUMSETS(); - do { - uint32_t ways = CM4.CCSIDR()->getASSOCIATIVITY(); - do { - class CM4::DCISW dcisw; - dcisw.setSET(sets); - dcisw.setWAY(ways); - CM4.DCISW()->set(dcisw); - } while (ways-- != 0); - } while (sets-- != 0); - - dsb(); - CM4.CCR()->setDC(true); - dsb(); - isb(); -} - -void enableICache() { - dsb(); - isb(); - CM4.ICIALLU()->set(0); // Invalidate I-Cache - dsb(); - isb(); - CM4.CCR()->setIC(true); - dsb(); - isb(); -} +void enableICache(); } } From 2ba1a053a54bc86a1c89da074d3e971dd2e8c85b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 19 Mar 2019 11:56:11 +0100 Subject: [PATCH 0171/1750] [ion] Flash: between data write operations or accesses to FLASH_CR, issue a DSB instruction to guarantee the completion of the operation --- ion/src/device/shared/drivers/flash.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 03cceac2d..7a3a0db1d 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -1,4 +1,5 @@ #include "flash.h" +#include #include namespace Ion { @@ -8,6 +9,9 @@ namespace Flash { using namespace Regs; static inline void wait() { + /* Issue a DSB instruction to guarantee the completion of a previous access + * to FLASH_CR register or data write operation. (RM0431) */ + Cache::dsb(); // Wait for pending Flash operations to complete while (FLASH.SR()->getBSY()) { } From 86f58a3cf9f359a7c11c8036d3e4e9b07c3de0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 19 Mar 2019 12:31:16 +0100 Subject: [PATCH 0172/1750] [ion] Add configuration for Flash of N0101 and N0100 --- .../n0100/drivers/config/external_flash.h | 3 ++- ion/src/device/n0100/drivers/config/flash.h | 26 +++++++++++++++++++ .../n0101/drivers/config/external_flash.h | 3 ++- ion/src/device/n0101/drivers/config/flash.h | 24 +++++++++++++++++ ion/src/device/shared/drivers/flash.cpp | 9 ++----- ion/src/device/shared/drivers/flash.h | 2 +- ion/src/device/shared/usb/dfu_interface.cpp | 26 ++++++++++--------- ion/src/device/shared/usb/dfu_interface.h | 4 --- ion/test/external_flash.cpp | 12 ++++----- 9 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 ion/src/device/n0100/drivers/config/flash.h create mode 100644 ion/src/device/n0101/drivers/config/flash.h diff --git a/ion/src/device/n0100/drivers/config/external_flash.h b/ion/src/device/n0100/drivers/config/external_flash.h index f239ddb72..79d39f768 100644 --- a/ion/src/device/n0100/drivers/config/external_flash.h +++ b/ion/src/device/n0100/drivers/config/external_flash.h @@ -20,7 +20,8 @@ namespace Config { using namespace Regs; -constexpr static uint32_t QSPIBaseAddress = 0; +constexpr static uint32_t StartAddress = 0xFFFFFFFF; +constexpr static uint32_t EndAddress = 0xFFFFFFFF; constexpr static int NumberOfSectors = 0; constexpr static AFGPIOPin Pins[] = {}; diff --git a/ion/src/device/n0100/drivers/config/flash.h b/ion/src/device/n0100/drivers/config/flash.h new file mode 100644 index 000000000..aa23e3135 --- /dev/null +++ b/ion/src/device/n0100/drivers/config/flash.h @@ -0,0 +1,26 @@ +#ifndef ION_DEVICE_N0100_CONFIG_FLASH_H +#define ION_DEVICE_N0100_CONFIG_FLASH_H + +#include + +namespace Ion { +namespace Device { +namespace Flash { +namespace Config { + +constexpr static uint32_t StartAddress = 0x08000000; +constexpr static uint32_t EndAddress = 0x08100000; +constexpr static int NumberOfSectors = 12; +constexpr static uint32_t SectorAddresses[NumberOfSectors+1] = { + 0x08000000, 0x08004000, 0x08008000, 0x0800C000, + 0x08010000, 0x08020000, 0x08040000, 0x08060000, + 0x08080000, 0x080A0000, 0x080C0000, 0x080E0000, + 0x08100000 +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/n0101/drivers/config/external_flash.h b/ion/src/device/n0101/drivers/config/external_flash.h index d16753a00..ea77ac250 100644 --- a/ion/src/device/n0101/drivers/config/external_flash.h +++ b/ion/src/device/n0101/drivers/config/external_flash.h @@ -20,7 +20,8 @@ namespace Config { using namespace Regs; -constexpr static uint32_t QSPIBaseAddress = 0x90000000; +constexpr static uint32_t StartAddress = 0x90000000; +constexpr static uint32_t EndAddress = 0x90800000; constexpr static int NumberOfSectors = 128; constexpr static AFGPIOPin Pins[] = { AFGPIOPin(GPIOB, 2, GPIO::AFR::AlternateFunction::AF9, GPIO::PUPDR::Pull::None, GPIO::OSPEEDR::OutputSpeed::High), diff --git a/ion/src/device/n0101/drivers/config/flash.h b/ion/src/device/n0101/drivers/config/flash.h new file mode 100644 index 000000000..cfd7bbd90 --- /dev/null +++ b/ion/src/device/n0101/drivers/config/flash.h @@ -0,0 +1,24 @@ +#ifndef ION_DEVICE_N0101_CONFIG_FLASH_H +#define ION_DEVICE_N0101_CONFIG_FLASH_H + +#include + +namespace Ion { +namespace Device { +namespace Flash { +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 +}; + +} +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index 7a3a0db1d..dbd36e71f 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -1,5 +1,6 @@ #include "flash.h" #include +#include #include namespace Ion { @@ -212,14 +213,8 @@ static void flash_memcpy(uint8_t * destination, uint8_t * source, size_t length) } int SectorAtAddress(uint32_t address) { - uint32_t sectorAddresses[NumberOfSectors+1] = { - 0x08000000, 0x08004000, 0x08008000, 0x0800C000, - 0x08010000, 0x08020000, 0x08040000, 0x08060000, - 0x08080000, 0x080A0000, 0x080C0000, 0x080E0000, - 0x08100000 - }; for (int i=0; i= sectorAddresses[i] && address < sectorAddresses[i+1]) { + if (address >= Config::SectorAddresses[i] && address < Config::SectorAddresses[i+1]) { return i; } } diff --git a/ion/src/device/shared/drivers/flash.h b/ion/src/device/shared/drivers/flash.h index b2ae1073c..25935c989 100644 --- a/ion/src/device/shared/drivers/flash.h +++ b/ion/src/device/shared/drivers/flash.h @@ -10,7 +10,7 @@ namespace Flash { void MassErase(); -constexpr int NumberOfSectors = 12; +constexpr int NumberOfSectors = 4; int SectorAtAddress(uint32_t address); void EraseSector(int i); diff --git a/ion/src/device/shared/usb/dfu_interface.cpp b/ion/src/device/shared/usb/dfu_interface.cpp index 5d4f81b01..dc915e71b 100644 --- a/ion/src/device/shared/usb/dfu_interface.cpp +++ b/ion/src/device/shared/usb/dfu_interface.cpp @@ -1,8 +1,10 @@ #include "dfu_interface.h" #include #include -#include "../drivers/flash.h" -#include "../drivers/external_flash.h" +#include +#include +#include +#include namespace Ion { namespace Device { @@ -177,7 +179,7 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe if (transferBufferLength == 1) { // Mass erase - m_erasePage = Flash::NumberOfSectors + ExternalFlash::Config::NumberOfSectors; + m_erasePage = Flash::Config::NumberOfSectors + ExternalFlash::Config::NumberOfSectors; return; } @@ -189,10 +191,10 @@ void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBuffe + (transferBuffer[3] << 16) + (transferBuffer[4] << 24); - if (eraseAddress >= k_flashStartAddress && eraseAddress <= k_flashEndAddress) { + if (eraseAddress >= Flash::Config::StartAddress && eraseAddress <= Flash::Config::EndAddress) { m_erasePage = Flash::SectorAtAddress(eraseAddress); - } else if (eraseAddress >= k_externalFlashStartAddress && eraseAddress <= k_externalFlashEndAddress) { - m_erasePage = Flash::NumberOfSectors + ExternalFlash::SectorAtAddress(eraseAddress - k_externalFlashStartAddress); + } else if (eraseAddress >= ExternalFlash::Config::StartAddress && eraseAddress <= ExternalFlash::Config::EndAddress) { + m_erasePage = Flash::Config::NumberOfSectors + ExternalFlash::SectorAtAddress(eraseAddress - ExternalFlash::Config::StartAddress); } else { // Unrecognized sector m_state = State::dfuERROR; @@ -207,13 +209,13 @@ void DFUInterface::eraseMemoryIfNeeded() { return; } - if (m_erasePage == Flash::NumberOfSectors + ExternalFlash::Config::NumberOfSectors) { + if (m_erasePage == Flash::Config::NumberOfSectors + ExternalFlash::Config::NumberOfSectors) { Flash::MassErase(); ExternalFlash::MassErase(); - } else if (m_erasePage < Flash::NumberOfSectors) { + } else if (m_erasePage < Flash::Config::NumberOfSectors) { Flash::EraseSector(m_erasePage); } else { - ExternalFlash::EraseSector(m_erasePage - Flash::NumberOfSectors); + ExternalFlash::EraseSector(m_erasePage - Flash::Config::NumberOfSectors); } /* Put an out of range value in m_erasePage to indicate that no erase is @@ -224,15 +226,15 @@ void DFUInterface::eraseMemoryIfNeeded() { } void DFUInterface::writeOnMemory() { - if (m_writeAddress >= k_flashStartAddress && m_writeAddress <= k_flashEndAddress) { + if (m_writeAddress >= Flash::Config::StartAddress && m_writeAddress <= Flash::Config::EndAddress) { // Write to the Flash memory Flash::WriteMemory(reinterpret_cast(m_writeAddress), m_largeBuffer, m_largeBufferLength); } else if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) { // Write on SRAM // FIXME We should check that we are not overriding the current instructions. memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); - } else if (m_writeAddress >= k_externalFlashStartAddress && m_writeAddress <= k_externalFlashEndAddress) { - ExternalFlash::WriteMemory(reinterpret_cast(m_writeAddress) - k_externalFlashStartAddress, m_largeBuffer, m_largeBufferLength); + } else if (m_writeAddress >= ExternalFlash::Config::StartAddress && m_writeAddress <= ExternalFlash::Config::EndAddress) { + ExternalFlash::WriteMemory(reinterpret_cast(m_writeAddress) - ExternalFlash::Config::StartAddress, m_largeBuffer, m_largeBufferLength); } else { // Invalid write address m_largeBufferLength = 0; diff --git a/ion/src/device/shared/usb/dfu_interface.h b/ion/src/device/shared/usb/dfu_interface.h index 849fa9d98..fab74834d 100644 --- a/ion/src/device/shared/usb/dfu_interface.h +++ b/ion/src/device/shared/usb/dfu_interface.h @@ -126,12 +126,8 @@ private: /* The Flash and SRAM addresses are in flash.ld. However, dfu_interface is * linked with dfu.ld, so we cannot access the values. */ - constexpr static uint32_t k_flashStartAddress = 0x08000000; - constexpr static uint32_t k_flashEndAddress = 0x08100000; constexpr static uint32_t k_sramStartAddress = 0x20000000; constexpr static uint32_t k_sramEndAddress = 0x20040000; - constexpr static uint32_t k_externalFlashStartAddress = 0x90000000; - constexpr static uint32_t k_externalFlashEndAddress = 0x90800000; // Download and upload bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); diff --git a/ion/test/external_flash.cpp b/ion/test/external_flash.cpp index 0386eeb5c..72caef964 100644 --- a/ion/test/external_flash.cpp +++ b/ion/test/external_flash.cpp @@ -7,7 +7,7 @@ // Choose some not too uniform data to program and test the external flash memory with. static inline uint8_t expected_value_at(uint8_t * ptr) { - uint32_t address = reinterpret_cast(ptr) - Ion::ExternalFlash::Device::QSPIBaseAddress; + uint32_t address = reinterpret_cast(ptr) - Ion::ExternalFlash::Device::StartAddress; return (address / 0x10000) + (address / 0x100) + address; // Example: the value expected at the address 0x123456 is 0x12 + 0x34 + 0x56. } @@ -31,8 +31,8 @@ static inline void check(volatile T * p, int repeat) { template void test(int accessType, int repeat) { - uint8_t * start = reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress); - uint8_t * end = reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress + Ion::ExternalFlash::Device::FlashAddressSpaceSize); + uint8_t * start = reinterpret_cast(Ion::ExternalFlash::Device::StartAddress); + uint8_t * end = reinterpret_cast(Ion::ExternalFlash::Device::StartAddress + Ion::ExternalFlash::Device::FlashAddressSpaceSize); // Forward sequential access if (accessType == 0) { @@ -52,10 +52,10 @@ void test(int accessType, int repeat) { // Random access if (accessType == 2) { - T * endT = reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress + Ion::ExternalFlash::Device::FlashAddressSpaceSize); + T * endT = reinterpret_cast(Ion::ExternalFlash::Device::StartAddress + Ion::ExternalFlash::Device::FlashAddressSpaceSize); for (size_t i=0; i> (32 - Ion::ExternalFlash::Device::NumberOfAddressBitsInChip); - volatile T * q = reinterpret_cast(randomAddr + Ion::ExternalFlash::Device::QSPIBaseAddress); + volatile T * q = reinterpret_cast(randomAddr + Ion::ExternalFlash::Device::StartAddress); if (q <= endT - 1) { check(q, repeat); } @@ -101,7 +101,7 @@ QUIZ_CASE(ion_ext_flash_program) { for (int page = 0; page < (1<<15); page++) { uint8_t buffer[256]; for (int byte = 0; byte < 256; byte++) { - buffer[byte] = expected_value_at(reinterpret_cast(Ion::ExternalFlash::Device::QSPIBaseAddress + page * 256 + byte)); + buffer[byte] = expected_value_at(reinterpret_cast(Ion::ExternalFlash::Device::StartAddress + page * 256 + byte)); } Ion::ExternalFlash::Device::WriteMemory(reinterpret_cast(page * 256), buffer, 256); } From da0c95a8013fa269d7bee570587f18fe36a5d82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 19 Mar 2019 15:59:14 +0100 Subject: [PATCH 0173/1750] [scripts] Change elf2dfu to build a .dfu with 2 elements: internal & external --- scripts/device/elf2dfu.py | 29 ++++++++++++++++++----------- scripts/targets.device.mak | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/scripts/device/elf2dfu.py b/scripts/device/elf2dfu.py index 1a2326d76..2e5fcd31f 100644 --- a/scripts/device/elf2dfu.py +++ b/scripts/device/elf2dfu.py @@ -11,8 +11,8 @@ import argparse # arm-none-eabi-objdump -h -w file.elf # arm-none-eabi-objcopy -O binary -j .data file.elf file.bin -def loadable_sections(elf_file): - objdump_section_headers_pattern = re.compile("^\s+\d+\s+(\.[\w\.]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+).*LOAD", flags=re.MULTILINE) +def loadable_sections(elf_file, section_name_appendix = ""): + objdump_section_headers_pattern = re.compile("^\s+\d+\s+(\.[\w\.]+"+section_name_appendix+")\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+).*LOAD", flags=re.MULTILINE) objdump_output = subprocess.check_output(["arm-none-eabi-objdump", "-h", "-w", elf_file]) sections = [] for (name, size, vma, lma, offset) in re.findall(objdump_section_headers_pattern, objdump_output): @@ -39,23 +39,30 @@ def generate_dfu_file(targets, usb_vid_pid, dfu_file): data += struct.pack(' Date: Wed, 20 Mar 2019 15:43:36 +0100 Subject: [PATCH 0174/1750] [ion] Cache invalidation methods --- ion/src/device/n0100/drivers/cache.h | 2 ++ ion/src/device/n0101/drivers/cache.cpp | 22 +++++++++++++++------- ion/src/device/n0101/drivers/cache.h | 6 ++++++ 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/ion/src/device/n0100/drivers/cache.h b/ion/src/device/n0100/drivers/cache.h index ce6cb2032..570ad0abd 100644 --- a/ion/src/device/n0100/drivers/cache.h +++ b/ion/src/device/n0100/drivers/cache.h @@ -10,6 +10,8 @@ namespace Cache { using namespace Regs; inline void dsb() {} // No L1-cache on N0100 +inline void invalidateDCache() {} +inline void invalidateICache() {} } } diff --git a/ion/src/device/n0101/drivers/cache.cpp b/ion/src/device/n0101/drivers/cache.cpp index 366dfdf2b..26bc274a0 100644 --- a/ion/src/device/n0101/drivers/cache.cpp +++ b/ion/src/device/n0101/drivers/cache.cpp @@ -6,11 +6,11 @@ namespace Cache { using namespace Regs; -void enableDCache() { +void invalidateDCache() { CM4.CSSELR()->set(0); dsb(); - //Associativity = 6 + // Associativity = 6 uint32_t sets = CM4.CCSIDR()->getNUMSETS(); do { @@ -24,17 +24,25 @@ void enableDCache() { } while (sets-- != 0); dsb(); +} + +void enableDCache() { + invalidateDCache(); CM4.CCR()->setDC(true); dsb(); isb(); } +void invalidateICache() { + dsb(); + isb(); + CM4.ICIALLU()->set(0); + dsb(); + isb(); +} + void enableICache() { - dsb(); - isb(); - CM4.ICIALLU()->set(0); // Invalidate I-Cache - dsb(); - isb(); + invalidateICache(); CM4.CCR()->setIC(true); dsb(); isb(); diff --git a/ion/src/device/n0101/drivers/cache.h b/ion/src/device/n0101/drivers/cache.h index a8b0e8a0c..8dcae1d5a 100644 --- a/ion/src/device/n0101/drivers/cache.h +++ b/ion/src/device/n0101/drivers/cache.h @@ -7,16 +7,22 @@ namespace Ion { namespace Device { namespace Cache { +/* 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 invalidateDCache(); void enableDCache(); +void invalidateICache(); void enableICache(); } From ac7c5e8e367671f5f2f4f01632565a3cc410a0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 20 Mar 2019 15:47:46 +0100 Subject: [PATCH 0175/1750] [ion] After a reset, invalidate the data/instruction caches --- ion/src/device/shared/boot/Makefile | 2 +- ion/src/device/shared/boot/rt0.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ion/src/device/shared/boot/Makefile b/ion/src/device/shared/boot/Makefile index 7c702f7ab..2bb8d22d7 100644 --- a/ion/src/device/shared/boot/Makefile +++ b/ion/src/device/shared/boot/Makefile @@ -1,4 +1,4 @@ -src += $(addprefix ion/src/device/shared/boot/, \ +ion_device_src += $(addprefix ion/src/device/shared/boot/, \ isr.c \ rt0.cpp \ ) diff --git a/ion/src/device/shared/boot/rt0.cpp b/ion/src/device/shared/boot/rt0.cpp index 5e4b10f13..79acbdbdc 100644 --- a/ion/src/device/shared/boot/rt0.cpp +++ b/ion/src/device/shared/boot/rt0.cpp @@ -5,6 +5,7 @@ #include "../drivers/board.h" #include "../drivers/reset.h" #include "../drivers/timing.h" +#include typedef void (*cxx_constructor)(); @@ -41,8 +42,13 @@ static void __attribute__((noinline)) non_inlined_ion_main() { } void start() { - // This is where execution starts after reset. - // Many things are not initialized yet so the code here has to pay attention. + /* This is where execution starts after reset. + * Many things are not initialized yet so the code here has to pay attention. */ + + /* First of all, reset the data/instruction caches. Indeed, if we do not, "an + * UNPREDICTIBLE behavior can occur".*/ + Ion::Device::Cache::invalidateDCache(); + Ion::Device::Cache::invalidateICache(); /* Copy data section to RAM * The data section is R/W but its initialization value matters. It's stored From e5c5413afcd57f71a784b8704b2dbea8e14ab135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 20 Mar 2019 17:20:12 +0100 Subject: [PATCH 0176/1750] [ion] Disable cache before a jump --- ion/src/device/n0100/drivers/cache.h | 10 +++- ion/src/device/n0101/drivers/cache.cpp | 75 +++++++++++++++++++------ ion/src/device/n0101/drivers/cache.h | 4 ++ ion/src/device/shared/drivers/reset.cpp | 6 ++ ion/src/device/shared/regs/cm4.h | 16 ++++++ 5 files changed, 93 insertions(+), 18 deletions(-) diff --git a/ion/src/device/n0100/drivers/cache.h b/ion/src/device/n0100/drivers/cache.h index 570ad0abd..dd9a8a3c6 100644 --- a/ion/src/device/n0100/drivers/cache.h +++ b/ion/src/device/n0100/drivers/cache.h @@ -9,9 +9,17 @@ namespace Cache { using namespace Regs; -inline void dsb() {} // No L1-cache on N0100 +// No L1-cache on N0100 + +inline void dsb() {} + inline void invalidateDCache() {} +inline void enableDCache() {} +inline void disableDCache() {} + inline void invalidateICache() {} +inline void enableICache() {} +inline void disableICache() {} } } diff --git a/ion/src/device/n0101/drivers/cache.cpp b/ion/src/device/n0101/drivers/cache.cpp index 26bc274a0..dee071964 100644 --- a/ion/src/device/n0101/drivers/cache.cpp +++ b/ion/src/device/n0101/drivers/cache.cpp @@ -7,23 +7,7 @@ namespace Cache { using namespace Regs; void invalidateDCache() { - CM4.CSSELR()->set(0); - dsb(); - - // Associativity = 6 - - uint32_t sets = CM4.CCSIDR()->getNUMSETS(); - do { - uint32_t ways = CM4.CCSIDR()->getASSOCIATIVITY(); - do { - class CM4::DCISW dcisw; - dcisw.setSET(sets); - dcisw.setWAY(ways); - CM4.DCISW()->set(dcisw); - } while (ways-- != 0); - } while (sets-- != 0); - - dsb(); + privateCleanInvalidateDisableDCache(false, true, false); } void enableDCache() { @@ -33,6 +17,55 @@ void enableDCache() { isb(); } +void disableDCache() { + privateCleanInvalidateDisableDCache(true, true, true); +} + +void cleanDCache() { + privateCleanInvalidateDisableDCache(true, false, false); +} + +void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disable) { + CM4.CSSELR()->set(0); + dsb(); + + // Associativity = 6 + + uint32_t sets = CM4.CCSIDR()->getNUMSETS(); + uint32_t ways = CM4.CCSIDR()->getASSOCIATIVITY(); + + if (disable) { + CM4.CCR()->setDC(false); + } + + do { + uint32_t w = ways; + do { + if (clean) { + if (invalidate) { + class CM4::DCCISW dccisw; + dccisw.setSET(sets); + dccisw.setWAY(w); + CM4.DCCISW()->set(dccisw); + } else { + class CM4::DCCSW dccsw; + dccsw.setSET(sets); + dccsw.setWAY(w); + CM4.DCCSW()->set(dccsw); + } + } else if (invalidate) { + class CM4::DCISW dcisw; + dcisw.setSET(sets); + dcisw.setWAY(w); + CM4.DCISW()->set(dcisw); + } + } while (w-- != 0); + } while (sets-- != 0); + + dsb(); +} + + void invalidateICache() { dsb(); isb(); @@ -48,6 +81,14 @@ void enableICache() { isb(); } +void disableICache() { + dsb(); + isb(); + CM4.CCR()->setIC(false); + invalidateICache(); +} + + } } } diff --git a/ion/src/device/n0101/drivers/cache.h b/ion/src/device/n0101/drivers/cache.h index 8dcae1d5a..13263e047 100644 --- a/ion/src/device/n0101/drivers/cache.h +++ b/ion/src/device/n0101/drivers/cache.h @@ -21,9 +21,13 @@ inline void isb() { void invalidateDCache(); void enableDCache(); +void disableDCache(); void invalidateICache(); void enableICache(); +void disableICache(); + +void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disable); } } diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index eeaf4b156..a7c91d3b6 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -1,5 +1,6 @@ #include "reset.h" #include +#include namespace Ion { namespace Device { @@ -13,6 +14,11 @@ void core() { } void jump() { + /* Disabling all caches finishes ongoing operations, so for instance we make + * sure all memcpy are done before jumpung to a code copied. */ + Ion::Device::Cache::disableDCache(); + Ion::Device::Cache::disableICache(); + uint32_t * stackPointerAddress = reinterpret_cast(0x08000000); uint32_t * resetHandlerAddress = reinterpret_cast(0x08000004); diff --git a/ion/src/device/shared/regs/cm4.h b/ion/src/device/shared/regs/cm4.h index a5cd133b3..d20f5a587 100644 --- a/ion/src/device/shared/regs/cm4.h +++ b/ion/src/device/shared/regs/cm4.h @@ -95,6 +95,20 @@ public: REGS_FIELD(WAY, uint8_t, 31, 30); }; + class DCCSW : public Register32 { + public: + DCCSW() : Register32(0) {} + REGS_FIELD(SET, uint16_t, 13, 5); + REGS_FIELD(WAY, uint8_t, 31, 30); + }; + + class DCCISW : public Register32 { + public: + DCCISW() : Register32(0) {} + REGS_FIELD(SET, uint16_t, 13, 5); + REGS_FIELD(WAY, uint8_t, 31, 30); + }; + constexpr CM4() {}; REGS_REGISTER_AT(SYST_CSR, 0x10); REGS_REGISTER_AT(SYST_RVR, 0x14); @@ -108,6 +122,8 @@ public: REGS_REGISTER_AT(CPACR, 0xD88); REGS_REGISTER_AT(ICIALLU, 0xF50); REGS_REGISTER_AT(DCISW, 0xF60); + REGS_REGISTER_AT(DCCSW, 0xF6C); + REGS_REGISTER_AT(DCCISW, 0xF74); private: constexpr uint32_t Base() const { return 0xE000E000; From e20e8030c235348313363b4c68188b83d89b4000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 21 Mar 2019 10:50:17 +0100 Subject: [PATCH 0177/1750] [ion] Fix comment on Reset::jump --- ion/src/device/shared/drivers/reset.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index a7c91d3b6..1205462bf 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -14,8 +14,7 @@ void core() { } void jump() { - /* Disabling all caches finishes ongoing operations, so for instance we make - * sure all memcpy are done before jumpung to a code copied. */ + // Disable cache before reset Ion::Device::Cache::disableDCache(); Ion::Device::Cache::disableICache(); From 4b437812a5f82b7f0806c44904f34263b73ecf23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 21 Mar 2019 13:25:12 +0100 Subject: [PATCH 0178/1750] [ion] N0101 cache: expose cleanDCache and hide privateCleanInvalidateDisableDCache methods --- ion/src/device/n0101/drivers/cache.cpp | 37 +++++++++++++------------- ion/src/device/n0101/drivers/cache.h | 3 +-- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/ion/src/device/n0101/drivers/cache.cpp b/ion/src/device/n0101/drivers/cache.cpp index dee071964..e8b1b700e 100644 --- a/ion/src/device/n0101/drivers/cache.cpp +++ b/ion/src/device/n0101/drivers/cache.cpp @@ -6,25 +6,6 @@ namespace Cache { using namespace Regs; -void invalidateDCache() { - privateCleanInvalidateDisableDCache(false, true, false); -} - -void enableDCache() { - invalidateDCache(); - CM4.CCR()->setDC(true); - dsb(); - isb(); -} - -void disableDCache() { - privateCleanInvalidateDisableDCache(true, true, true); -} - -void cleanDCache() { - privateCleanInvalidateDisableDCache(true, false, false); -} - void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disable) { CM4.CSSELR()->set(0); dsb(); @@ -65,6 +46,24 @@ void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disab dsb(); } +void invalidateDCache() { + privateCleanInvalidateDisableDCache(false, true, false); +} + +void cleanDCache() { + privateCleanInvalidateDisableDCache(true, false, false); +} + +void enableDCache() { + invalidateDCache(); + CM4.CCR()->setDC(true); + dsb(); + isb(); +} + +void disableDCache() { + privateCleanInvalidateDisableDCache(true, true, true); +} void invalidateICache() { dsb(); diff --git a/ion/src/device/n0101/drivers/cache.h b/ion/src/device/n0101/drivers/cache.h index 13263e047..60d4d4b61 100644 --- a/ion/src/device/n0101/drivers/cache.h +++ b/ion/src/device/n0101/drivers/cache.h @@ -20,6 +20,7 @@ inline void isb() { } void invalidateDCache(); +void cleanDCache(); void enableDCache(); void disableDCache(); @@ -27,8 +28,6 @@ void invalidateICache(); void enableICache(); void disableICache(); -void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disable); - } } } From d8ce253ca299e852c9f7c2b34dcada0cb4c67592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 21 Mar 2019 13:27:00 +0100 Subject: [PATCH 0179/1750] [ion] N0101 cache: add missing dsb, isb --- ion/src/device/n0101/drivers/cache.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/device/n0101/drivers/cache.cpp b/ion/src/device/n0101/drivers/cache.cpp index e8b1b700e..ae3fff882 100644 --- a/ion/src/device/n0101/drivers/cache.cpp +++ b/ion/src/device/n0101/drivers/cache.cpp @@ -17,6 +17,7 @@ void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disab if (disable) { CM4.CCR()->setDC(false); + dsb(); } do { @@ -44,6 +45,7 @@ void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disab } while (sets-- != 0); dsb(); + isb(); } void invalidateDCache() { From 02317a76597577a07845220028e340246877e1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 21 Mar 2019 13:56:06 +0100 Subject: [PATCH 0180/1750] [ion] start: discard useless cache invalidations when booting --- ion/src/device/shared/boot/rt0.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ion/src/device/shared/boot/rt0.cpp b/ion/src/device/shared/boot/rt0.cpp index 79acbdbdc..482cff27f 100644 --- a/ion/src/device/shared/boot/rt0.cpp +++ b/ion/src/device/shared/boot/rt0.cpp @@ -5,7 +5,6 @@ #include "../drivers/board.h" #include "../drivers/reset.h" #include "../drivers/timing.h" -#include typedef void (*cxx_constructor)(); @@ -45,11 +44,6 @@ void start() { /* This is where execution starts after reset. * Many things are not initialized yet so the code here has to pay attention. */ - /* First of all, reset the data/instruction caches. Indeed, if we do not, "an - * UNPREDICTIBLE behavior can occur".*/ - Ion::Device::Cache::invalidateDCache(); - Ion::Device::Cache::invalidateICache(); - /* 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 From d68c568e8de6f010565ebb4ca3ff7cecd728f9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 21 Mar 2019 13:57:51 +0100 Subject: [PATCH 0181/1750] [ion] Add TODO on Reset::jump --- ion/src/device/shared/drivers/reset.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index 1205462bf..924dfd993 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -14,6 +14,7 @@ void core() { } void jump() { + // TODO: shutdown all clocks and peripherial to mimic a hardware reset // Disable cache before reset Ion::Device::Cache::disableDCache(); Ion::Device::Cache::disableICache(); From 47327c39c9379bc5fa71d607ac09e3a7f6d58d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 21 Mar 2019 13:58:27 +0100 Subject: [PATCH 0182/1750] [ion] DFU reallocation: clean DCache after copying the DFU bootloader to the RAM --- ion/src/device/shared/usb/Makefile | 1 + ion/src/device/shared/usb/dfu_relocated.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index fd26a27a7..7c77dff0d 100644 --- a/ion/src/device/shared/usb/Makefile +++ b/ion/src/device/shared/usb/Makefile @@ -47,6 +47,7 @@ dfu_src += liba/src/memset.c dfu_src += liba/src/memcpy.c dfu_src += libaxx/src/cxxabi/pure_virtual.cpp dfu_src += ion/src/device/shared/usb/boot.cpp +dfu_src += ion/src/device/$(MODEL)/drivers/cache.cpp dfu_src += $(addprefix ion/src/device/shared/drivers/, \ base64.cpp \ external_flash.cpp \ diff --git a/ion/src/device/shared/usb/dfu_relocated.cpp b/ion/src/device/shared/usb/dfu_relocated.cpp index a190e6786..f39a1313f 100644 --- a/ion/src/device/shared/usb/dfu_relocated.cpp +++ b/ion/src/device/shared/usb/dfu_relocated.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "../drivers/timing.h" extern char _stack_end; @@ -47,6 +48,12 @@ void DFU() { /* 3- Copy the DFU bootloader from Flash to RAM. */ memcpy(dfu_bootloader_ram_start, &_dfu_bootloader_flash_start, dfu_bootloader_size); + /* The DFU bootloader might have been copied in the DCache. However, when we + * run the instructions from the DFU bootloader, the CPU looks for + * instructions in the ICache and then in the RAM. We thus need to flush the + * DCache to update the RAM. */ + // Flush data cache + Device::Cache::cleanDCache(); /* 4- Disable all interrupts * The interrupt service routines live in the Flash and could be overwritten From c298c17af8b1fef1f1c3220ec38ed4898f57cac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 21 Mar 2019 14:06:54 +0100 Subject: [PATCH 0183/1750] [ion] Remove outdated f730 folder --- ion/src/f730/Makefile | 52 --- ion/src/f730/backlight.cpp | 85 ---- ion/src/f730/backlight.h | 31 -- ion/src/f730/base64.cpp | 48 --- ion/src/f730/base64.h | 5 - ion/src/f730/battery.cpp | 87 ---- ion/src/f730/battery.h | 33 -- ion/src/f730/bench/Makefile | 21 - ion/src/f730/bench/bench.cpp | 46 -- ion/src/f730/bench/bench.h | 14 - ion/src/f730/bench/command/adc.cpp | 27 -- ion/src/f730/bench/command/backlight.cpp | 34 -- ion/src/f730/bench/command/charge.cpp | 24 -- ion/src/f730/bench/command/command.cpp | 47 --- ion/src/f730/bench/command/command.h | 42 -- ion/src/f730/bench/command/display.cpp | 74 ---- ion/src/f730/bench/command/exit.cpp | 19 - ion/src/f730/bench/command/keyboard.cpp | 26 -- ion/src/f730/bench/command/led.cpp | 35 -- ion/src/f730/bench/command/mcu_serial.cpp | 23 - ion/src/f730/bench/command/ping.cpp | 19 - ion/src/f730/bench/command/print.cpp | 30 -- ion/src/f730/bench/command/suspend.cpp | 22 - ion/src/f730/bench/command/vblank.cpp | 26 -- ion/src/f730/bench/command_handler.cpp | 44 -- ion/src/f730/bench/command_handler.h | 27 -- ion/src/f730/bench/command_list.cpp | 22 - ion/src/f730/bench/command_list.h | 22 - ion/src/f730/boot/Makefile | 2 - ion/src/f730/boot/isr.c | 129 ------ ion/src/f730/boot/isr.h | 16 - ion/src/f730/boot/rt0.cpp | 105 ----- ion/src/f730/console.cpp | 76 ---- ion/src/f730/console.h | 30 -- ion/src/f730/device.cpp | 342 --------------- ion/src/f730/device.h | 36 -- ion/src/f730/display.cpp | 392 ------------------ ion/src/f730/display.h | 111 ----- ion/src/f730/events.cpp | 98 ----- ion/src/f730/external_flash.cpp | 343 --------------- ion/src/f730/external_flash.h | 86 ---- ion/src/f730/flash.cpp | 235 ----------- ion/src/f730/flash.h | 28 -- ion/src/f730/keyboard.cpp | 112 ----- ion/src/f730/keyboard.h | 70 ---- ion/src/f730/led.cpp | 133 ------ ion/src/f730/led.h | 48 --- ion/src/f730/log.cpp | 15 - ion/src/f730/power.cpp | 71 ---- ion/src/f730/regs/adc.h | 73 ---- ion/src/f730/regs/cm4.h | 115 ----- ion/src/f730/regs/crc.h | 27 -- ion/src/f730/regs/dma.h | 63 --- ion/src/f730/regs/exti.h | 34 -- ion/src/f730/regs/flash.h | 54 --- ion/src/f730/regs/fmc.h | 79 ---- ion/src/f730/regs/gpio.h | 118 ------ ion/src/f730/regs/itm.h | 32 -- ion/src/f730/regs/mpu.h | 81 ---- ion/src/f730/regs/nvic.h | 37 -- ion/src/f730/regs/otg.h | 195 --------- ion/src/f730/regs/pwr.h | 32 -- ion/src/f730/regs/quadspi.h | 126 ------ ion/src/f730/regs/rcc.h | 150 ------- ion/src/f730/regs/register.h | 61 --- ion/src/f730/regs/regs.h | 26 -- ion/src/f730/regs/rng.h | 33 -- ion/src/f730/regs/sdio.h | 112 ----- ion/src/f730/regs/spi.h | 45 -- ion/src/f730/regs/syscfg.h | 44 -- ion/src/f730/regs/tim.h | 100 ----- ion/src/f730/regs/usart.h | 50 --- ion/src/f730/sd_card.cpp | 95 ----- ion/src/f730/sd_card.h | 33 -- ion/src/f730/stack.cpp | 10 - ion/src/f730/swd.cpp | 24 -- ion/src/f730/swd.h | 28 -- ion/src/f730/timing.cpp | 36 -- ion/src/f730/timing.h | 16 - ion/src/f730/usb.cpp | 187 --------- ion/src/f730/usb.h | 34 -- ion/src/f730/usb/Makefile | 138 ------ ion/src/f730/usb/boot.cpp | 2 - ion/src/f730/usb/calculator.cpp | 94 ----- ion/src/f730/usb/calculator.h | 166 -------- ion/src/f730/usb/dfu.ld | 50 --- ion/src/f730/usb/dfu_interface.cpp | 286 ------------- ion/src/f730/usb/dfu_interface.h | 174 -------- ion/src/f730/usb/dfu_relocated.cpp | 80 ---- ion/src/f730/usb/dfu_xip.cpp | 12 - ion/src/f730/usb/flasher.cpp | 13 - ion/src/f730/usb/flasher.ld | 77 ---- .../usb/stack/descriptor/bos_descriptor.cpp | 22 - .../usb/stack/descriptor/bos_descriptor.h | 36 -- .../descriptor/configuration_descriptor.cpp | 26 -- .../descriptor/configuration_descriptor.h | 48 --- .../f730/usb/stack/descriptor/descriptor.cpp | 15 - .../f730/usb/stack/descriptor/descriptor.h | 32 -- .../device_capability_descriptor.cpp | 18 - .../descriptor/device_capability_descriptor.h | 31 -- .../stack/descriptor/device_descriptor.cpp | 29 -- .../usb/stack/descriptor/device_descriptor.h | 62 --- .../descriptor/dfu_functional_descriptor.cpp | 21 - .../descriptor/dfu_functional_descriptor.h | 38 -- .../extended_compat_id_descriptor.cpp | 61 --- .../extended_compat_id_descriptor.h | 43 -- .../stack/descriptor/interface_descriptor.cpp | 27 -- .../stack/descriptor/interface_descriptor.h | 55 --- .../language_id_string_descriptor.cpp | 19 - .../language_id_string_descriptor.h | 25 -- .../microsoft_os_string_descriptor.cpp | 19 - .../microsoft_os_string_descriptor.h | 30 -- .../platform_device_capability_descriptor.cpp | 21 - .../platform_device_capability_descriptor.h | 47 --- .../stack/descriptor/string_descriptor.cpp | 25 -- .../usb/stack/descriptor/string_descriptor.h | 28 -- .../usb/stack/descriptor/url_descriptor.cpp | 25 -- .../usb/stack/descriptor/url_descriptor.h | 36 -- .../descriptor/webusb_platform_descriptor.cpp | 22 - .../descriptor/webusb_platform_descriptor.h | 37 -- ion/src/f730/usb/stack/device.cpp | 156 ------- ion/src/f730/usb/stack/device.h | 66 --- ion/src/f730/usb/stack/endpoint0.cpp | 344 --------------- ion/src/f730/usb/stack/endpoint0.h | 79 ---- ion/src/f730/usb/stack/interface.cpp | 66 --- ion/src/f730/usb/stack/interface.h | 42 -- ion/src/f730/usb/stack/request_recipient.cpp | 32 -- ion/src/f730/usb/stack/request_recipient.h | 29 -- ion/src/f730/usb/stack/setup_packet.cpp | 38 -- ion/src/f730/usb/stack/setup_packet.h | 65 --- ion/src/f730/usb/stack/streamable.cpp | 15 - ion/src/f730/usb/stack/streamable.h | 44 -- ion/src/f730/wakeup.cpp | 71 ---- ion/src/f730/wakeup.h | 39 -- 134 files changed, 8689 deletions(-) delete mode 100644 ion/src/f730/Makefile delete mode 100644 ion/src/f730/backlight.cpp delete mode 100644 ion/src/f730/backlight.h delete mode 100644 ion/src/f730/base64.cpp delete mode 100644 ion/src/f730/base64.h delete mode 100644 ion/src/f730/battery.cpp delete mode 100644 ion/src/f730/battery.h delete mode 100644 ion/src/f730/bench/Makefile delete mode 100644 ion/src/f730/bench/bench.cpp delete mode 100644 ion/src/f730/bench/bench.h delete mode 100644 ion/src/f730/bench/command/adc.cpp delete mode 100644 ion/src/f730/bench/command/backlight.cpp delete mode 100644 ion/src/f730/bench/command/charge.cpp delete mode 100644 ion/src/f730/bench/command/command.cpp delete mode 100644 ion/src/f730/bench/command/command.h delete mode 100644 ion/src/f730/bench/command/display.cpp delete mode 100644 ion/src/f730/bench/command/exit.cpp delete mode 100644 ion/src/f730/bench/command/keyboard.cpp delete mode 100644 ion/src/f730/bench/command/led.cpp delete mode 100644 ion/src/f730/bench/command/mcu_serial.cpp delete mode 100644 ion/src/f730/bench/command/ping.cpp delete mode 100644 ion/src/f730/bench/command/print.cpp delete mode 100644 ion/src/f730/bench/command/suspend.cpp delete mode 100644 ion/src/f730/bench/command/vblank.cpp delete mode 100644 ion/src/f730/bench/command_handler.cpp delete mode 100644 ion/src/f730/bench/command_handler.h delete mode 100644 ion/src/f730/bench/command_list.cpp delete mode 100644 ion/src/f730/bench/command_list.h delete mode 100644 ion/src/f730/boot/Makefile delete mode 100644 ion/src/f730/boot/isr.c delete mode 100644 ion/src/f730/boot/isr.h delete mode 100644 ion/src/f730/boot/rt0.cpp delete mode 100644 ion/src/f730/console.cpp delete mode 100644 ion/src/f730/console.h delete mode 100644 ion/src/f730/device.cpp delete mode 100644 ion/src/f730/device.h delete mode 100644 ion/src/f730/display.cpp delete mode 100644 ion/src/f730/display.h delete mode 100644 ion/src/f730/events.cpp delete mode 100644 ion/src/f730/external_flash.cpp delete mode 100644 ion/src/f730/external_flash.h delete mode 100644 ion/src/f730/flash.cpp delete mode 100644 ion/src/f730/flash.h delete mode 100644 ion/src/f730/keyboard.cpp delete mode 100644 ion/src/f730/keyboard.h delete mode 100644 ion/src/f730/led.cpp delete mode 100644 ion/src/f730/led.h delete mode 100644 ion/src/f730/log.cpp delete mode 100644 ion/src/f730/power.cpp delete mode 100644 ion/src/f730/regs/adc.h delete mode 100644 ion/src/f730/regs/cm4.h delete mode 100644 ion/src/f730/regs/crc.h delete mode 100644 ion/src/f730/regs/dma.h delete mode 100644 ion/src/f730/regs/exti.h delete mode 100644 ion/src/f730/regs/flash.h delete mode 100644 ion/src/f730/regs/fmc.h delete mode 100644 ion/src/f730/regs/gpio.h delete mode 100644 ion/src/f730/regs/itm.h delete mode 100644 ion/src/f730/regs/mpu.h delete mode 100644 ion/src/f730/regs/nvic.h delete mode 100644 ion/src/f730/regs/otg.h delete mode 100644 ion/src/f730/regs/pwr.h delete mode 100644 ion/src/f730/regs/quadspi.h delete mode 100644 ion/src/f730/regs/rcc.h delete mode 100644 ion/src/f730/regs/register.h delete mode 100644 ion/src/f730/regs/regs.h delete mode 100644 ion/src/f730/regs/rng.h delete mode 100644 ion/src/f730/regs/sdio.h delete mode 100644 ion/src/f730/regs/spi.h delete mode 100644 ion/src/f730/regs/syscfg.h delete mode 100644 ion/src/f730/regs/tim.h delete mode 100644 ion/src/f730/regs/usart.h delete mode 100644 ion/src/f730/sd_card.cpp delete mode 100644 ion/src/f730/sd_card.h delete mode 100644 ion/src/f730/stack.cpp delete mode 100644 ion/src/f730/swd.cpp delete mode 100644 ion/src/f730/swd.h delete mode 100644 ion/src/f730/timing.cpp delete mode 100644 ion/src/f730/timing.h delete mode 100644 ion/src/f730/usb.cpp delete mode 100644 ion/src/f730/usb.h delete mode 100644 ion/src/f730/usb/Makefile delete mode 100644 ion/src/f730/usb/boot.cpp delete mode 100644 ion/src/f730/usb/calculator.cpp delete mode 100644 ion/src/f730/usb/calculator.h delete mode 100644 ion/src/f730/usb/dfu.ld delete mode 100644 ion/src/f730/usb/dfu_interface.cpp delete mode 100644 ion/src/f730/usb/dfu_interface.h delete mode 100644 ion/src/f730/usb/dfu_relocated.cpp delete mode 100644 ion/src/f730/usb/dfu_xip.cpp delete mode 100644 ion/src/f730/usb/flasher.cpp delete mode 100644 ion/src/f730/usb/flasher.ld delete mode 100644 ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/bos_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/configuration_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/device_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/device_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/interface_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/string_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/string_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/url_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/url_descriptor.h delete mode 100644 ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp delete mode 100644 ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h delete mode 100644 ion/src/f730/usb/stack/device.cpp delete mode 100644 ion/src/f730/usb/stack/device.h delete mode 100644 ion/src/f730/usb/stack/endpoint0.cpp delete mode 100644 ion/src/f730/usb/stack/endpoint0.h delete mode 100644 ion/src/f730/usb/stack/interface.cpp delete mode 100644 ion/src/f730/usb/stack/interface.h delete mode 100644 ion/src/f730/usb/stack/request_recipient.cpp delete mode 100644 ion/src/f730/usb/stack/request_recipient.h delete mode 100644 ion/src/f730/usb/stack/setup_packet.cpp delete mode 100644 ion/src/f730/usb/stack/setup_packet.h delete mode 100644 ion/src/f730/usb/stack/streamable.cpp delete mode 100644 ion/src/f730/usb/stack/streamable.h delete mode 100644 ion/src/f730/wakeup.cpp delete mode 100644 ion/src/f730/wakeup.h diff --git a/ion/src/f730/Makefile b/ion/src/f730/Makefile deleted file mode 100644 index 19c5e33da..000000000 --- a/ion/src/f730/Makefile +++ /dev/null @@ -1,52 +0,0 @@ -include ion/src/f730/boot/Makefile -include ion/src/f730/bench/Makefile -include ion/src/f730/usb/Makefile - -ion/src/shared/platform_info.o: SFLAGS += -DHEADER_SECTION="__attribute__((section(\".header\")))" - -objs += $(addprefix ion/src/shared/, \ - console_line.o \ - crc32_padded.o\ - events_modifier.o \ -) - -# If you need to profile execution, you can replace events_keyboard with -# events_replay.o and dummy/events_modifier.o - -objs += $(addprefix ion/src/f730/, \ - backlight.o \ - battery.o\ - base64.o\ - console.o \ - device.o\ - display.o\ - events.o\ - external_flash.o\ - flash.o\ - keyboard.o\ - led.o\ - power.o\ - sd_card.o\ - stack.o\ - swd.o \ - timing.o \ - usb.o \ - wakeup.o \ -) - -# When using the register.h C++ file in production mode, we expect the compiler -# to completely inline all bit manipulations. For some reason, if we build using -# the -Os optimization flag, GCC doesn't inline everything and and ends up -# emitting calls to aeabi_llsl for 64-bits registers. This is very sub-optimal -# so we're enforcing -O3 for this specific file. - -ifneq ($(DEBUG),1) -ifneq ($(COMPILER),llvm) -ion/src/f730/led.o: SFLAGS+=-O3 -ion/src/f730/console.o: SFLAGS+=-O3 -ion/src/f730/display.o: SFLAGS+=-O3 -ion/src/f730/swd.o: SFLAGS+=-O3 -endif -endif - -#objs += $(addprefix ion/src/f730/keyboard/, keyboard.o) diff --git a/ion/src/f730/backlight.cpp b/ion/src/f730/backlight.cpp deleted file mode 100644 index 2b52448b0..000000000 --- a/ion/src/f730/backlight.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include "regs/regs.h" -#include "backlight.h" - -/* This driver controls the RT9365 LED driver. - * This chip allows the brightness to be set to 16 different values. It starts - * at full brightness on power on. Applying a pulse on the EN pin will select - * the next value in decreasing order. Once it reaches the minimal value, the - * next pulse will loop back to full brightness. */ - -// Public Ion::Backlight methods - -namespace Ion { -namespace Backlight { - -void setBrightness(uint8_t b) { - Device::setLevel(b >> 4); -} - -uint8_t brightness() { - return Device::level() << 4; -} - -} -} - -// Private Ion::Backlight::Device methods - -namespace Ion { -namespace Backlight { -namespace Device { - -static uint8_t sLevel; - -void init() { - BacklightPin.group().MODER()->setMode(BacklightPin.pin(), GPIO::MODER::Mode::Output); - sLevel = 0xF; - resume(); -} - -void shutdown() { - BacklightPin.group().MODER()->setMode(BacklightPin.pin(), GPIO::MODER::Mode::Analog); - BacklightPin.group().PUPDR()->setPull(BacklightPin.pin(), GPIO::PUPDR::Pull::None); -} - -void suspend() { - BacklightPin.group().ODR()->set(BacklightPin.pin(), false); - Timing::msleep(3); // Might not need to be blocking -} - -void resume() { - BacklightPin.group().ODR()->set(BacklightPin.pin(), true); - Timing::usleep(50); - uint8_t level = sLevel; - sLevel = 0xF; - setLevel(level); -} - -void setLevel(uint8_t level) { - // From sLevel = 12 to level 7 : 5 pulses - // From sLevel = 5 to level 9 : 12 pulses (5 to go to level 16, and 7 to 9) - if (sLevel < level) { - sendPulses(16 + sLevel - level); - } else { - sendPulses(sLevel - level); - } - sLevel = level; -} - -uint8_t level() { - return sLevel; -} - -void sendPulses(int n) { - for (int i=0; iset(BacklightPin.pin(), false); - Timing::usleep(20); - BacklightPin.group().ODR()->set(BacklightPin.pin(), true); - Timing::usleep(20); - } -} - -} -} -} diff --git a/ion/src/f730/backlight.h b/ion/src/f730/backlight.h deleted file mode 100644 index 527bc1344..000000000 --- a/ion/src/f730/backlight.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef ION_DEVICE_BACKLIGHT_H -#define ION_DEVICE_BACKLIGHT_H - -#include -#include "regs/regs.h" - -namespace Ion { -namespace Backlight { -namespace Device { - -/* Pin | Role | Mode | Function - * -----+-------------------+-----------------------+---------- - * PE0 | Backlight Enable | Output | - */ - -void init(); -void shutdown(); -void suspend(); -void resume(); -void setLevel(uint8_t level); -uint8_t level(); - -void sendPulses(int n); - -constexpr static GPIOPin BacklightPin = GPIOPin(GPIOE, 0); - -} -} -} - -#endif diff --git a/ion/src/f730/base64.cpp b/ion/src/f730/base64.cpp deleted file mode 100644 index 6b4bb950c..000000000 --- a/ion/src/f730/base64.cpp +++ /dev/null @@ -1,48 +0,0 @@ -namespace Base64 { - -static constexpr char encodeTable[] = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '+', '/', -}; - -constexpr char Padding = '='; - -void encode(const unsigned char * input, unsigned int inputLength, char * output) { - unsigned int i, j; - for (i = j = 0; i < inputLength; i++) { - int s = i % 3; /* from 6/gcd(6, 8) */ - - switch (s) { - case 0: - output[j++] = encodeTable[(input[i] >> 2) & 0x3F]; - continue; - case 1: - output[j++] = encodeTable[((input[i-1] & 0x3) << 4) + ((input[i] >> 4) & 0xF)]; - continue; - case 2: - output[j++] = encodeTable[((input[i-1] & 0xF) << 2) + ((input[i] >> 6) & 0x3)]; - output[j++] = encodeTable[input[i] & 0x3F]; - } - } - - /* move back */ - i -= 1; - - /* check the last and add padding */ - if ((i % 3) == 0) { - output[j++] = encodeTable[(input[i] & 0x3) << 4]; - output[j++] = Padding; - output[j++] = Padding; - } else if ((i % 3) == 1) { - output[j++] = encodeTable[(input[i] & 0xF) << 2]; - output[j++] = Padding; - } -} - -} diff --git a/ion/src/f730/base64.h b/ion/src/f730/base64.h deleted file mode 100644 index b9a4f2852..000000000 --- a/ion/src/f730/base64.h +++ /dev/null @@ -1,5 +0,0 @@ -namespace Base64 { - -void encode(const unsigned char * input, unsigned int inputLength, char * output); - -} diff --git a/ion/src/f730/battery.cpp b/ion/src/f730/battery.cpp deleted file mode 100644 index c613f0a27..000000000 --- a/ion/src/f730/battery.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include "battery.h" - -#include "regs/adc.h" - -/* To measure the battery voltage, we're using the internal ADC. The ADC works - * by comparing the input voltage to a reference voltage. The only fixed voltage - * we have around is 2.8V, so that's the one we're using as a refrence. However, - * and ADC can only measure voltage that is lower than the reference voltage. So - * we need to use a voltage divider before sampling Vbat. - * To avoid draining the battery, we're using an high-impedence voltage divider, - * so we need to be careful when sampling the ADC. See AN2834 for more info. */ - -namespace Ion { -namespace Battery { - -bool isCharging() { - return !Device::ChargingPin.group().IDR()->get(Device::ChargingPin.pin()); -} - -Charge level() { - if (voltage() < 3.2f) { - return Charge::EMPTY; - } - if (voltage() < 3.5f) { - return Charge::LOW; - } - if (voltage() < 3.8f) { - return Charge::SOMEWHERE_INBETWEEN; - } - return Charge::FULL; -} - -float voltage() { - ADC.CR2()->setSWSTART(true); - while (ADC.SR()->getEOC() != true) { - } - uint16_t value = ADC.DR()->get(); - - // The ADC is 12 bits by default - return Device::ADCDividerBridgeRatio*(Device::ADCReferenceVoltage * value)/0xFFF; -} - -} -} - -namespace Ion { -namespace Battery { -namespace Device { - -void init() { - initGPIO(); - - /* The BAT_SNS pin is connected to Vbat through a divider bridge. It therefore - * has a voltage of Vbat/2. We'll measure this using ADC channel 0. */ - VoltagePin.group().MODER()->setMode(VoltagePin.pin(), GPIO::MODER::Mode::Analog); - - // Step 2 - Power on the ADC - ADC.CR2()->setADON(true); - - // Configure the ADC channel - ADC.SQR1()->setL(0); // Always sample the same channel - ADC.SQR3()->setSQ1(ADCChannel); - ADC.SMPR()->setSamplingTime(ADCChannel, ADC::SMPR::SamplingTime::Cycles480); // Use the max sampling time -} - -void initGPIO() { - /* Step 1 - Configure the GPIOs - * The BAT_CHRG pin is connected to the Li-Po charging IC. That pin uses an - * open-drain output. Open-drain output are either connected to ground or left - * floating. To interact with such an output, our input must therefore be - * pulled up. */ - ChargingPin.group().MODER()->setMode(ChargingPin.pin(), GPIO::MODER::Mode::Input); - ChargingPin.group().PUPDR()->setPull(ChargingPin.pin(), GPIO::PUPDR::Pull::Up); -} - -void shutdown() { - ChargingPin.group().MODER()->setMode(ChargingPin.pin(), GPIO::MODER::Mode::Analog); - ChargingPin.group().PUPDR()->setPull(ChargingPin.pin(), GPIO::PUPDR::Pull::None); - - // Power down the ADC - ADC.CR2()->setADON(false); -} - -} -} -} diff --git a/ion/src/f730/battery.h b/ion/src/f730/battery.h deleted file mode 100644 index e9c402d44..000000000 --- a/ion/src/f730/battery.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ION_DEVICE_BATTERY_H -#define ION_DEVICE_BATTERY_H - -#include "regs/gpio.h" - -namespace Ion { -namespace Battery { -namespace Device { - -/* Pin | Role | Mode | Function - * -----+-------------------+-----------------------+---------- - * PE3 | BAT_CHRG | Input, pulled up | Low = charging, high = full - * PB1 | VBAT_SNS | Analog | ADC1_IN9 - */ - -void init(); -void shutdown(); -void initGPIO(); -void initADC(); - -constexpr GPIOPin ChargingPin = GPIOPin(GPIOE, 3); - -constexpr GPIOPin VoltagePin = GPIOPin(GPIOB, 1); -constexpr uint8_t ADCChannel = 9; - -constexpr float ADCReferenceVoltage = 2.8f; -constexpr float ADCDividerBridgeRatio = 2.0f; - -} -} -} - -#endif diff --git a/ion/src/f730/bench/Makefile b/ion/src/f730/bench/Makefile deleted file mode 100644 index 28e33f7fa..000000000 --- a/ion/src/f730/bench/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -objs += $(addprefix ion/src/f730/bench/, \ - bench.o \ - command_handler.o \ - command_list.o \ -) - -objs += $(addprefix ion/src/f730/bench/command/, \ - command.o \ - adc.o \ - backlight.o \ - charge.o \ - display.o \ - exit.o \ - keyboard.o \ - led.o \ - mcu_serial.o \ - ping.o \ - print.o \ - suspend.o \ - vblank.o \ -) diff --git a/ion/src/f730/bench/bench.cpp b/ion/src/f730/bench/bench.cpp deleted file mode 100644 index 5458111bc..000000000 --- a/ion/src/f730/bench/bench.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "bench.h" -#include -#include -#include "command_list.h" - -namespace Ion { -namespace Device { -namespace Bench { - -constexpr CommandHandler handles[] = { - CommandHandler("ADC", Command::ADC), - CommandHandler("BACKLIGHT", Command::Backlight), - CommandHandler("CHARGE", Command::Charge), - CommandHandler("DISPLAY", Command::Display), - CommandHandler("EXIT", Command::Exit), - CommandHandler("KEYBOARD", Command::Keyboard), - CommandHandler("LED", Command::LED), - CommandHandler("MCU_SERIAL", Command::MCUSerial), - CommandHandler("PING", Command::Ping), - CommandHandler("PRINT", Command::Print), - CommandHandler("SUSPEND", Command::Suspend), - CommandHandler("VBLANK", Command::VBlank), - CommandHandler(nullptr, nullptr) -}; - -constexpr const CommandList sCommandList = CommandList(handles); - -constexpr int kMaxCommandLength = 255; - -void run() { - KDContext * ctx = KDIonContext::sharedContext(); - ctx->fillRect(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColorWhite); - ctx->drawString("BENCH", KDPoint((320-50)/2, (240-18)/2)); - char command[kMaxCommandLength]; - while (true) { - Ion::Console::readLine(command, kMaxCommandLength); - const CommandHandler * ch = sCommandList.dispatch(command); - if (ch != nullptr && ch->function() == Command::Exit) { - break; - } - } -} - -} -} -} diff --git a/ion/src/f730/bench/bench.h b/ion/src/f730/bench/bench.h deleted file mode 100644 index de999cd37..000000000 --- a/ion/src/f730/bench/bench.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef ION_DEVICE_BENCH_BENCH_H -#define ION_DEVICE_BENCH_BENCH_H - -namespace Ion { -namespace Device { -namespace Bench { - -void run(); - -} -} -} - -#endif diff --git a/ion/src/f730/bench/command/adc.cpp b/ion/src/f730/bench/command/adc.cpp deleted file mode 100644 index 26f86c0a9..000000000 --- a/ion/src/f730/bench/command/adc.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "command.h" -#include -#include -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -void ADC(const char * input) { - if (input != nullptr) { - reply(sSyntaxError); - return; - } - float result = Ion::Battery::voltage(); - constexpr int precision = 8; - constexpr int bufferSize = Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(precision); - char responseBuffer[bufferSize+4] = {'A', 'D', 'C', '='}; // ADC= - Poincare::PrintFloat::convertFloatToText(result, responseBuffer+4, bufferSize, precision, Poincare::Preferences::PrintFloatMode::Decimal); - reply(responseBuffer); -} - -} -} -} -} diff --git a/ion/src/f730/bench/command/backlight.cpp b/ion/src/f730/bench/command/backlight.cpp deleted file mode 100644 index 86e630c55..000000000 --- a/ion/src/f730/bench/command/backlight.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "command.h" -#include -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -void Backlight(const char * input) { - // Input must be of the form "0xAA" or "ON" or "OFF" - if (strcmp(input, sON) == 0) { - Ion::Backlight::Device::init(); - reply(sOK); - return; - } - if (strcmp(input, sOFF) == 0) { - Ion::Backlight::Device::shutdown(); - reply(sOK); - return; - } - if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || input[4] != NULL) { - reply(sSyntaxError); - return; - } - uint32_t brightness = hexNumber(input+2); - Ion::Backlight::setBrightness(brightness); - reply(sOK); -} - -} -} -} -} diff --git a/ion/src/f730/bench/command/charge.cpp b/ion/src/f730/bench/command/charge.cpp deleted file mode 100644 index 8aa14f536..000000000 --- a/ion/src/f730/bench/command/charge.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "command.h" -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -void Charge(const char * input) { - if (input != nullptr) { - reply(sSyntaxError); - return; - } - if (Ion::Battery::isCharging()) { - reply("CHARGE=ON"); - } else { - reply("CHARGE=OFF"); - } -} - -} -} -} -} diff --git a/ion/src/f730/bench/command/command.cpp b/ion/src/f730/bench/command/command.cpp deleted file mode 100644 index 645b224d9..000000000 --- a/ion/src/f730/bench/command/command.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "command.h" -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -const char * const sOK = "OK"; -const char * const sKO = "KO"; -const char * const sSyntaxError = "SYNTAX_ERROR"; -const char * const sON = "ON"; -const char * const sOFF = "OFF"; - -void reply(const char * s) { - Console::writeLine(s); -} - -int8_t hexChar(char c) { - if (c >= '0' && c <= '9') { - return (c - '0'); - } - if (c >= 'A' && c <= 'F') { - return (c - 'A') + 0xA; - } - return -1; -} - -bool isHex(char c) { - return hexChar(c) >= 0; -} - -uint32_t hexNumber(const char * s, int maxLength) { - uint32_t result = 0; - int index = 0; - while ((maxLength < 0 || index < maxLength) && s[index] != NULL) { - result = (result << 4) | hexChar(s[index]); - index++; - } - return result; -} - - -} -} -} -} diff --git a/ion/src/f730/bench/command/command.h b/ion/src/f730/bench/command/command.h deleted file mode 100644 index c749d3728..000000000 --- a/ion/src/f730/bench/command/command.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef ION_DEVICE_BENCH_COMMAND_COMMAND_H -#define ION_DEVICE_BENCH_COMMAND_COMMAND_H - -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -typedef void (*Function)(const char * input); - -void ADC(const char * input); -void Backlight(const char * input); -void Charge(const char * input); -void Display(const char * input); -void Exit(const char * input); -void Keyboard(const char * input); -void LED(const char * input); -void MCUSerial(const char * input); -void Ping(const char * input); -void Print(const char * input); -void Suspend(const char * input); -void VBlank(const char * input); - -extern const char * const sOK; -extern const char * const sKO; -extern const char * const sSyntaxError; -extern const char * const sON; -extern const char * const sOFF; - -void reply(const char * s); -int8_t hexChar(char c); -bool isHex(char c); -uint32_t hexNumber(const char * s, int maxLength = -1); - -} -} -} -} - -#endif diff --git a/ion/src/f730/bench/command/display.cpp b/ion/src/f730/bench/command/display.cpp deleted file mode 100644 index 61b74c322..000000000 --- a/ion/src/f730/bench/command/display.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "command.h" -#include -#include -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -// Input must be of the form "0xAABBCC" or "ON" or "OFF" -void Display(const char * input) { - - if (strcmp(input, sON) == 0) { - Ion::Display::Device::init(); - reply(sOK); - return; - } - if (strcmp(input, sOFF) == 0) { - Ion::Display::Device::shutdown(); - reply(sOK); - return; - } - if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || !isHex(input[4]) || !isHex(input[5]) || !isHex(input[6]) || !isHex(input[7]) || input[8] != NULL) { - reply(sSyntaxError); - return; - } - - /* We fill the screen with a color and return OK if we read that color back everywhere. */ - - KDColor c = KDColor::RGB24(hexNumber(input)); - - constexpr int stampHeight = 10; - constexpr int stampWidth = 10; - static_assert(Ion::Display::Width % stampWidth == 0, "Stamps must tesselate the display"); - static_assert(Ion::Display::Height % stampHeight == 0, "Stamps must tesselate the display"); - static_assert(stampHeight % 2 == 0 || stampWidth % 2 == 0, "Even number of XOR needed."); - - KDColor stamp[stampWidth*stampHeight]; - for (int i=0;i -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -// Input must be of the form "0xAABBCC" or "ON" or "OFF" -void LED(const char * input) { - if (strcmp(input, sON) == 0) { - Ion::LED::Device::init(); - Ion::Console::writeLine(sOK); - return; - } - if (strcmp(input, sOFF) == 0) { - Ion::LED::Device::shutdown(); - Ion::Console::writeLine(sOK); - return; - } - if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || !isHex(input[4]) || !isHex(input[5]) || !isHex(input[6]) || !isHex(input[7]) || input[8] != NULL) { - Ion::Console::writeLine(sSyntaxError); - return; - } - uint32_t hexColor = hexNumber(input+2); - KDColor ledColor = KDColor::RGB24(hexColor); - Ion::LED::setColor(ledColor); - Ion::Console::writeLine(sOK); -} - -} -} -} -} diff --git a/ion/src/f730/bench/command/mcu_serial.cpp b/ion/src/f730/bench/command/mcu_serial.cpp deleted file mode 100644 index b6703085b..000000000 --- a/ion/src/f730/bench/command/mcu_serial.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include "command.h" -#include -#include "../../device.h" - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -void MCUSerial(const char * input) { - if (input != nullptr) { - reply(sSyntaxError); - return; - } - char response[11 + Ion::Device::SerialNumberLength + 1] = {'M', 'C', 'U', '_', 'S', 'E', 'R', 'I', 'A', 'L', '=', 0}; - Ion::Device::copySerialNumber(response + 11); - reply(response); -} - -} -} -} -} diff --git a/ion/src/f730/bench/command/ping.cpp b/ion/src/f730/bench/command/ping.cpp deleted file mode 100644 index 23649a7dc..000000000 --- a/ion/src/f730/bench/command/ping.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "command.h" - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -void Ping(const char * input) { - if (input != nullptr) { - reply(sSyntaxError); - return; - } - reply("PONG"); -} - -} -} -} -} diff --git a/ion/src/f730/bench/command/print.cpp b/ion/src/f730/bench/command/print.cpp deleted file mode 100644 index fa73e6234..000000000 --- a/ion/src/f730/bench/command/print.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "command.h" -#include -#include -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -// Input must be of the form "XX,YY,STRING" -void Print(const char * input) { - if (input == nullptr || !isHex(input[0]) || !isHex(input[1]) || input[2] != ',' || !isHex(input[3]) || !isHex(input[4]) || input[5] != ',') { - reply(sKO); - return; - } - - char x = hexNumber(input, 2); - char y = hexNumber(input+3, 2); - - KDContext * ctx = KDIonContext::sharedContext(); - ctx->drawString(input+6, KDPoint(x, y)); - - reply(sOK); -} - -} -} -} -} diff --git a/ion/src/f730/bench/command/suspend.cpp b/ion/src/f730/bench/command/suspend.cpp deleted file mode 100644 index a494501c0..000000000 --- a/ion/src/f730/bench/command/suspend.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "command.h" -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -void Suspend(const char * input) { - if (input != nullptr) { - reply(sSyntaxError); - return; - } - reply(sOK); - Ion::Timing::msleep(100); - Ion::Power::suspend(); -} - -} -} -} -} diff --git a/ion/src/f730/bench/command/vblank.cpp b/ion/src/f730/bench/command/vblank.cpp deleted file mode 100644 index d292cab26..000000000 --- a/ion/src/f730/bench/command/vblank.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "command.h" -#include -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -void VBlank(const char * input) { - if (input != nullptr) { - reply(sSyntaxError); - return; - } - - for (int i=0; i<6; i++) { - Ion::Display::waitForVBlank(); - } - - reply(sOK); -} - -} -} -} -} diff --git a/ion/src/f730/bench/command_handler.cpp b/ion/src/f730/bench/command_handler.cpp deleted file mode 100644 index 082d229a0..000000000 --- a/ion/src/f730/bench/command_handler.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "command_handler.h" -#include - -namespace Ion { -namespace Device { -namespace Bench { - -bool CommandHandler::valid() const { - return (m_name != nullptr && m_function != nullptr); -} - -bool CommandHandler::handle(const char * command) const { - if (matches(command)) { - size_t nameLength = strlen(m_name); - if (command[nameLength] == '=') { - m_function(command+nameLength+1); // Skip the "Equal character" - } else { - m_function(nullptr); - } - return true; - } - return false; -} - -bool CommandHandler::matches(const char * command) const { - const char * c = command; - const char * n = m_name; - while (true) { - if (*n == NULL) { - if (*c == NULL || *c == '=') { - return true; - } - } - if (*c != *n) { - return false; - } - c++; - n++; - } -} - -} -} -} diff --git a/ion/src/f730/bench/command_handler.h b/ion/src/f730/bench/command_handler.h deleted file mode 100644 index be453e80c..000000000 --- a/ion/src/f730/bench/command_handler.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef ION_DEVICE_BENCH_COMMAND_HANDLER_H -#define ION_DEVICE_BENCH_COMMAND_HANDLER_H - -#include "command/command.h" - -namespace Ion { -namespace Device { -namespace Bench { - -class CommandHandler { -public: - constexpr CommandHandler(const char * name, Command::Function function) : - m_name(name), m_function(function) {} - bool valid() const; - bool handle(const char * command) const; - Command::Function function() const { return m_function; } -private: - bool matches(const char * command) const; - const char * m_name; - Command::Function m_function; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/bench/command_list.cpp b/ion/src/f730/bench/command_list.cpp deleted file mode 100644 index 917f54dfb..000000000 --- a/ion/src/f730/bench/command_list.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "command_list.h" -#include - -namespace Ion { -namespace Device { -namespace Bench { - -const CommandHandler * CommandList::dispatch(const char * command) const { - const CommandHandler * handler = m_handlers; - while (handler->valid()) { - if (handler->handle(command)) { - return handler; - } - handler++; - } - Console::writeLine("NOT_FOUND"); - return nullptr; -} - -} -} -} diff --git a/ion/src/f730/bench/command_list.h b/ion/src/f730/bench/command_list.h deleted file mode 100644 index 2b47b88b3..000000000 --- a/ion/src/f730/bench/command_list.h +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef ION_DEVICE_BENCH_COMMAND_LIST_H -#define ION_DEVICE_BENCH_COMMAND_LIST_H - -#include "command_handler.h" - -namespace Ion { -namespace Device { -namespace Bench { - -class CommandList { -public: - constexpr CommandList(const CommandHandler * handlers) : m_handlers(handlers) {} - const CommandHandler * dispatch(const char * command) const; -private: - const CommandHandler * m_handlers; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/boot/Makefile b/ion/src/f730/boot/Makefile deleted file mode 100644 index 153e0b034..000000000 --- a/ion/src/f730/boot/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -objs += $(addprefix ion/src/f730/boot/, isr.o rt0.o) -LDSCRIPT = ion/src/f730/boot/flash.ld diff --git a/ion/src/f730/boot/isr.c b/ion/src/f730/boot/isr.c deleted file mode 100644 index 5c201aa67..000000000 --- a/ion/src/f730/boot/isr.c +++ /dev/null @@ -1,129 +0,0 @@ -#include "isr.h" -extern const void * _stack_start; - -/* Interrupt Service Routines are void->void functions */ -typedef void(*ISR)(void); - -/* Notice: The Cortex-M4 expects all jumps to be made at an odd address when - * jumping to Thumb code. For example, if you want to execute Thumb code at - * address 0x100, you'll have to jump to 0x101. Luckily, this idiosyncrasy is - * properly handled by the C compiler that will generate proper addresses when - * using function pointers. */ - -#define INITIALISATION_VECTOR_SIZE 0x71 - -ISR InitialisationVector[INITIALISATION_VECTOR_SIZE] - __attribute__((section(".isr_vector_table"))) - __attribute__((used)) - = { - (ISR)&_stack_start, // Stack start - start, // Reset service routine, - 0, // NMI service routine, - abort, // HardFault service routine, - 0, // MemManage service routine, - 0, // BusFault service routine, - 0, // UsageFault service routine, - 0, 0, 0, 0, // Reserved - 0, // SVCall service routine, - 0, // DebugMonitor service routine, - 0, // Reserved - 0, // PendSV service routine, - isr_systick, // SysTick service routine - 0, // WWDG service routine - 0, // PVD service routine - 0, // TampStamp service routine - 0, // RtcWakeup service routine - 0, // Flash service routine - 0, // RCC service routine - 0, // EXTI0 service routine - 0, // EXTI1 service routine - 0, // EXTI2 service routine - 0, // EXTI3 service routine - 0, // EXTI4 service routine - 0, // DMA1Stream0 service routine - 0, // DMA1Stream1 service routine - 0, // DMA1Stream2 service routine - 0, // DMA1Stream3 service routine - 0, // DMA1Stream4 service routine - 0, // DMA1Stream5 service routine - 0, // DMA1Stream6 service routine - 0, // ADC1 global interrupt - 0, // CAN1 TX interrupt - 0, // CAN1 RX0 interrupt - 0, // CAN1 RX1 interrupt - 0, // CAN1 SCE interrupt - 0, // EXTI Line[9:5] interrupts - 0, // TIM1 Break interrupt and TIM9 global interrupt - 0, // TIM1 update interrupt and TIM10 global interrupt - 0, // TIM1 Trigger & Commutation interrupts and TIM11 global interrupt - 0, // TIM1 Capture Compare interrupt - 0, // TIM2 global interrupt - 0, // TIM3 global interrupt - 0, // TIM4 global interrupt - 0, // I2C1 global event interrupt - 0, // I2C1 global error interrupt - 0, // I2C2 global event interrupt - 0, // I2C2 global error interrupt - 0, // SPI1 global interrupt - 0, // SPI2 global interrupt - 0, // USART1 global interrupt - 0, // USART2 global interrupt - 0, // USART3 global interrupt - 0, // EXTI Line[15:10] interrupts - 0, // EXTI Line 17 interrupt RTC Alarms (A and B) through EXTI line interrupt - 0, // EXTI Line 18 interrupt / USB On-The-Go FS Wakeup through EXTI line interrupt - 0, // TIM8 Break interrupt TIM12 global interrupt - 0, // TIM8 Update interrupt TIM13 global interrupt - 0, // TIM8 Trigger & Commutation interrupt TIM14 global interrupt - 0, // TIM8 Cap/Com interrupt - 0, // DMA1 global interrupt Channel 7 - 0, // FSMC global interrupt - 0, // SDIO global interrupt - 0, // TIM5 global interrupt - 0, // SPI3 global interrupt - 0, // ? - 0, // ? - 0, // TIM6 global interrupt - 0, // TIM7 global interrupt - 0, // DMA2 Stream0 global interrupt - 0, // DMA2 Stream1 global interrupt - 0, // DMA2 Stream2 global interrupt - 0, // DMA2 Stream3 global interrupt - 0, // DMA2 Stream4 global interrupt - 0, // SD filter0 global interrupt - 0, // SD filter1 global interrupt - 0, // CAN2 TX interrupt - 0, // BXCAN2 RX0 interrupt - 0, // BXCAN2 RX1 interrupt - 0, // CAN2 SCE interrupt - 0, // USB On The Go FS global interrupt - 0, // DMA2 Stream5 global interrupts - 0, // DMA2 Stream6 global interrupt - 0, // DMA2 Stream7 global interrupt - 0, // USART6 global interrupt - 0, // I2C3 event interrupt - 0, // I2C3 error interrupt - 0, // ? - 0, // ? - 0, // ? - 0, // ? - 0, // ? - 0, // ? - 0, // RNG global interrupt - 0, // FPU global interrupt - 0, // ? - 0, // ? - 0, // SPI4 global interrupt - 0, // SPI5 global interrupt - 0, // ? - 0, // ? - 0, // ? - 0, // ? - 0, // ? - 0, // ? - 0, // Quad-SPI global interrupt - 0, // ? - 0, // ? - 0, // I2CFMP1 event interrupt - 0 // I2CFMP1 error interrupt -}; diff --git a/ion/src/f730/boot/isr.h b/ion/src/f730/boot/isr.h deleted file mode 100644 index ca5becb13..000000000 --- a/ion/src/f730/boot/isr.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef ION_DEVICE_BOOT_ISR_H -#define ION_DEVICE_BOOT_ISR_H - -#ifdef __cplusplus -extern "C" { -#endif - -void start(); -void abort(); -void isr_systick(); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/ion/src/f730/boot/rt0.cpp b/ion/src/f730/boot/rt0.cpp deleted file mode 100644 index 6e10b70ac..000000000 --- a/ion/src/f730/boot/rt0.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include "isr.h" -#include -#include -#include -#include "../device.h" -#include "../timing.h" -#include "../console.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 abort() { -#if DEBUG - while (1) { - } -#else - Ion::Device::coreReset(); -#endif -} - -/* 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. */ -static void __attribute__((noinline)) non_inlined_ion_main() { - return ion_main(0, nullptr); -} - -void 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); - -#define JUMP_TO_EXTERNAL_FLASH 1 -#if JUMP_TO_EXTERNAL_FLASH - /* Initialize the FPU as early as possible. - * For example, static C++ objects are very likely to manipulate float values */ - Ion::Device::initFPU(); -#endif - - /* 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 - -#if JUMP_TO_EXTERNAL_FLASH - Ion::Device::init(); - - typedef void(*ISR)(void); - -// non_inlined_ion_main(); - - ISR externalFlashReset = *(ISR *)(0x90000004); - externalFlashReset(); -#else - non_inlined_ion_main(); -#endif - - abort(); -} - -void __attribute__((interrupt)) isr_systick() { - Ion::Timing::Device::MillisElapsed++; -} diff --git a/ion/src/f730/console.cpp b/ion/src/f730/console.cpp deleted file mode 100644 index 15ba0b275..000000000 --- a/ion/src/f730/console.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include "console.h" - -/* This file implements a serial console. - * We use a 115200 8N1 serial port */ - -namespace Ion { -namespace Console { - -char readChar() { - while (Device::UARTPort.SR()->getRXNE() == 0) { - } - return (char)Device::UARTPort.DR()->get(); -} - -void writeChar(char c) { - while (Device::UARTPort.SR()->getTXE() == 0) { - } - Device::UARTPort.DR()->set(c); -} - -} -} - -namespace Ion { -namespace Console { -namespace Device { - -void init() { - RCC.APB1ENR()->setUSART3EN(true); - - for(const GPIOPin & g : Pins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF7); - } - - UARTPort.CR1()->setUE(true); - UARTPort.CR1()->setTE(true); - UARTPort.CR1()->setRE(true); - - /* We need to set the baud rate of the UART port. - * This is set relative to the APB1 clock, which runs at 48 MHz. - * - * The baud rate is set by the following equation: - * BaudRate = fAPB1/(16*USARTDIV), where USARTDIV is a divider. - * In other words, USARDFIV = fAPB1/(16*BaudRate). All frequencies in Hz. - * - * In our case, fAPB1 = 48 MHz, so USARTDIV = 26.0416667 - * DIV_MANTISSA = 26 - * DIV_FRAC = 16*0.0416667 = 1 - */ - UARTPort.BRR()->setDIV_MANTISSA(26); - UARTPort.BRR()->setDIV_FRAC(1); -} - -void shutdown() { - for(const GPIOPin & g : Pins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); - g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); - } -} - -bool peerConnected() { - RxPin.group().PUPDR()->setPull(RxPin.pin(), GPIO::PUPDR::Pull::Down); - RxPin.group().MODER()->setMode(RxPin.pin(), GPIO::MODER::Mode::Input); - Timing::msleep(1); - bool result = RxPin.group().IDR()->get(RxPin.pin()); - RxPin.group().PUPDR()->setPull(RxPin.pin(), GPIO::PUPDR::Pull::None); - RxPin.group().MODER()->setMode(RxPin.pin(), GPIO::MODER::Mode::AlternateFunction); - return result; -} - - -} -} -} diff --git a/ion/src/f730/console.h b/ion/src/f730/console.h deleted file mode 100644 index b1b14545f..000000000 --- a/ion/src/f730/console.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef ION_DEVICE_CONSOLE_H -#define ION_DEVICE_CONSOLE_H - -#include -#include "regs/regs.h" - -namespace Ion { -namespace Console { -namespace Device { - -/* Pin | Role | Mode - * -----+-------------------+-------------------- - * PC11 | UART3 RX | Alternate Function - * PD8 | UART3 TX | Alternate Function - */ - -void init(); -void shutdown(); -bool peerConnected(); - -constexpr USART UARTPort = USART(3); -constexpr static GPIOPin RxPin = GPIOPin(GPIOC, 11); -constexpr static GPIOPin TxPin = GPIOPin(GPIOD, 8); -constexpr static GPIOPin Pins[] = { RxPin, TxPin }; - -} -} -} - -#endif diff --git a/ion/src/f730/device.cpp b/ion/src/f730/device.cpp deleted file mode 100644 index e0479e8de..000000000 --- a/ion/src/f730/device.cpp +++ /dev/null @@ -1,342 +0,0 @@ -#include "device.h" -#include "regs/regs.h" -extern "C" { -#include -} -#include -#include "led.h" -#include "display.h" -#include "keyboard.h" -#include "battery.h" -#include "sd_card.h" -#include "backlight.h" -#include "console.h" -#include "swd.h" -#include "usb.h" -#include "bench/bench.h" -#include "base64.h" -#include "cache.h" -#include "external_flash.h" - -#define USE_SD_CARD 0 - -extern "C" { - extern const void * _stack_end; -} - -// Public Ion methods - -uint32_t Ion::crc32(const uint32_t * data, size_t length) { - bool initialCRCEngineState = RCC.AHB1ENR()->getCRCEN(); - RCC.AHB1ENR()->setCRCEN(true); - CRC.CR()->setRESET(true); - - const uint32_t * end = data + length; - while (data < end) { - CRC.DR()->set(*data++); - } - - uint32_t result = CRC.DR()->get(); - RCC.AHB1ENR()->setCRCEN(initialCRCEngineState); - return result; -} - -uint32_t Ion::random() { - bool initialRNGEngineState = RCC.AHB2ENR()->getRNGEN(); - RCC.AHB2ENR()->setRNGEN(true); - - RNG.CR()->setRNGEN(true); - - while (RNG.SR()->getDRDY() == 0) { - } - uint32_t result = RNG.DR()->get(); - - RNG.CR()->setRNGEN(false); - RCC.AHB2ENR()->setRNGEN(initialRNGEngineState); - - return result; -} - -void Ion::Device::copySerialNumber(char * buffer) { - const unsigned char * rawUniqueID = (const unsigned char *)0x1FF07A10; - Base64::encode(rawUniqueID, 12, buffer); - buffer[SerialNumberLength] = 0; -} - -const char * Ion::serialNumber() { - static char serialNumber[Device::SerialNumberLength + 1] = {0}; - if (serialNumber[0] == 0) { - Device::copySerialNumber(serialNumber); - } - return serialNumber; -} - -const char * Ion::fccId() { - return "2ALWP-N0100"; -} - -// Private Ion::Device methods - -namespace Ion { -namespace Device { - -void initFPU() { - // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BABDBFBJ.html - CM4.CPACR()->setAccess(10, CM4::CPACR::Access::Full); - CM4.CPACR()->setAccess(11, CM4::CPACR::Access::Full); - // FIXME: The pipeline should be flushed at this point -} - -void initMPU() { - -#if 1 - // Configure MPU settings for the FMC memory area - // This is needed for interfacing with the LCD - MPU.RNR()->setREGION(0x00); - MPU.RBAR()->setADDR(0x60000000); - MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_32MB); - 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.CTRL()->setPRIVDEFENA(true); - MPU.CTRL()->setENABLE(true); -#endif - - -} - -void initSysTick() { - // CPU clock is 96 MHz, and systick clock source is divided by 8 - // To get 1 ms systick overflow we need to reset it to - // 96 000 000 (Hz) / 8 / 1 000 (ms/s) - 1 (because the counter resets *after* counting to 0) - CM4.SYST_RVR()->setRELOAD(11999); - CM4.SYST_CVR()->setCURRENT(0); - CM4.SYST_CSR()->setCLKSOURCE(CM4::SYST_CSR::CLKSOURCE::AHB_DIV8); - CM4.SYST_CSR()->setTICKINT(true); - CM4.SYST_CSR()->setENABLE(true); -} - -void shutdownSysTick() { - CM4.SYST_CSR()->setENABLE(false); - CM4.SYST_CSR()->setTICKINT(false); -} - -void coreReset() { - // Perform a full core reset - CM4.AIRCR()->requestReset(); -} - -void jumpReset() { - uint32_t * stackPointerAddress = reinterpret_cast(0x08000000); - uint32_t * resetHandlerAddress = reinterpret_cast(0x08000004); - - /* Jump to the reset service routine after having reset the stack pointer. - * Both addresses are fetched from the base of the Flash memory, just like a - * real reset would. These operations should be made at once, otherwise the C - * compiler might emit some instructions that modify the stack inbetween. */ - - asm volatile ( - "msr MSP, %[stackPointer] ; bx %[resetHandler]" - : : - [stackPointer] "r" (*stackPointerAddress), - [resetHandler] "r" (*resetHandlerAddress) - ); -} - -void initL1Cache() { - Cache::enableICache(); - Cache::enableDCache(); -} - -void init() { - initL1Cache(); - initMPU(); - initClocks(); - - // Ensure right location of interrupt vectors - // The bootloader leaves its own after flashing - SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory); - CM4.VTOR()->setVTOR((void*) 0); - - // Put all inputs as Analog Input, No pull-up nor pull-down - // Except for the SWD port (PB3, PA13, PA14) - GPIOA.MODER()->set(0xEBFFFFFF); - GPIOA.PUPDR()->set(0x24000000); - GPIOB.MODER()->set(0xFFFFFFBF); - GPIOB.PUPDR()->set(0x00000000); - for (int g=2; g<5; g++) { - GPIO(g).MODER()->set(0xFFFFFFFF); // All to "Analog" - GPIO(g).PUPDR()->set(0x00000000); // All to "None" - } - -#if EPSILON_DEVICE_BENCH - bool consolePeerConnectedOnBoot = Ion::Console::Device::peerConnected(); -#endif - - initPeripherals(); - -#if EPSILON_DEVICE_BENCH - if (consolePeerConnectedOnBoot) { - Ion::Device::Bench::run(); - } -#endif -} - -void shutdown() { - shutdownPeripherals(); - shutdownClocks(); -} - -void initPeripherals() { - Display::Device::init(); - Backlight::Device::init(); - Keyboard::Device::init(); - LED::Device::init(); - Battery::Device::init(); - USB::Device::init(); - ExternalFlash::Device::init(); - return; // FIXME, obviously! -#if USE_SD_CARD - SDCard::Device::init(); -#endif - Console::Device::init(); - SWD::Device::init(); - initSysTick(); -} - -void shutdownPeripherals(bool keepLEDAwake) { - shutdownSysTick(); - SWD::Device::shutdown(); - Console::Device::shutdown(); -#if USE_SD_CARD - SDCard::Device::shutdown(); -#endif - ExternalFlash::Device::shutdown(); - USB::Device::shutdown(); - Battery::Device::shutdown(); - if (!keepLEDAwake) { - LED::Device::shutdown(); - } - Keyboard::Device::shutdown(); - Backlight::Device::shutdown(); - Display::Device::shutdown(); -} - -void initClocks() { - /* System clock - * Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */ - - /* 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); - - /* 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 HSE and wait for it to be ready - RCC.CR()->setHSEON(true); - while(!RCC.CR()->getHSERDY()) { - } - - /* Given the crystal used on our device, the HSE will oscillate at 25 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(25); - RCC.PLLCFGR()->setPLLN(384); - RCC.PLLCFGR()->setPLLQ(8); - RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); - // 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz - RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy4); - // 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz - RCC.CFGR()->setPPRE2(RCC::CFGR::APBPrescaler::AHBDividedBy2); - - /* If you want to considerably slow down the whole machine uniformely, which - * can be very useful to diagnose performance issues, just uncomment the line - * below. Note that even booting takes a few seconds, so don't be surprised - * if the screen is black for a short while upon booting. */ - // RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy128); - - // Enable the PLL and wait for it to be ready - RCC.CR()->setPLLON(true); - 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()->setFMCEN(true); - - // APB1 bus - // We're using TIM3 for the LEDs - RCC.APB1ENR()->setTIM3EN(true); - RCC.APB1ENR()->setPWREN(true); - - // APB2 bus - class RCC::APB2ENR apb2enr(0); // Reset value - apb2enr.setADC1EN(true); - apb2enr.setSYSCFGEN(true); - RCC.APB2ENR()->set(apb2enr); -} - -void shutdownClocks(bool keepLEDAwake) { - // APB2 bus - RCC.APB2ENR()->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); - - RCC.AHB3ENR()->setFMCEN(false); -} - -} -} diff --git a/ion/src/f730/device.h b/ion/src/f730/device.h deleted file mode 100644 index e7e136f61..000000000 --- a/ion/src/f730/device.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef ION_DEVICE_H -#define ION_DEVICE_H - -namespace Ion { -namespace Device { - -void init(); -void shutdown(); - -void initFPU(); -#if 1 -void initMPU(); -#endif - -void initSysTick(); -void shutdownSysTick(); - -void coreReset(); -void jumpReset(); - -void initPeripherals(); -void shutdownPeripherals(bool keepLEDAwake = false); -void initClocks(); -void shutdownClocks(bool keepLEDAwake = false); - -/* The serial number is 96 bits long. That's equal to 16 digits in base 64. We - * expose a convenient "copySerialNumber" routine which can be called without - * using a static variable (and therefore without a .bss section). This is used - * in the RAM'ed DFU bootloader. */ -constexpr static int SerialNumberLength = 16; -void copySerialNumber(char * buffer); - -} -} - -#endif diff --git a/ion/src/f730/display.cpp b/ion/src/f730/display.cpp deleted file mode 100644 index 7cba2e64a..000000000 --- a/ion/src/f730/display.cpp +++ /dev/null @@ -1,392 +0,0 @@ -#include -#include "display.h" -#include "regs/regs.h" -#define MEMORY_BARRIER 0 -#if MEMORY_BARRIER -#include "cache.h" -#endif -extern "C" { -#include -} - -/* This driver interfaces with the ST7789V LCD controller. - * This chip keeps a whole frame in SRAM memory and feeds it to the LCD panel as - * needed. We use the STM32's FMC to drive the bus between the ST7789V. Once - * configured, we only need to write in the address space of the MCU to actually - * send some data to the LCD controller. */ - -#define USE_DMA_FOR_PUSH_PIXELS 0 -#define USE_DMA_FOR_PUSH_COLOR 0 - -#define USE_DMA (USE_DMA_FOR_PUSH_PIXELS|USE_DMA_FOR_PUSH_COLOR) - -// Public Ion::Display methods - -namespace Ion { -namespace Display { - -void pushRect(KDRect r, const KDColor * pixels) { -#if USE_DMA - Device::waitForPendingDMAUploadCompletion(); -#endif - Device::setDrawingArea(r, Device::Orientation::Landscape); - Device::pushPixels(pixels, r.width()*r.height()); -} - -void pushRectUniform(KDRect r, KDColor c) { -#if USE_DMA - Device::waitForPendingDMAUploadCompletion(); -#endif - Device::setDrawingArea(r, Device::Orientation::Portrait); - Device::pushColor(c, r.width()*r.height()); -} - -void pullRect(KDRect r, KDColor * pixels) { -#if USE_DMA - Device::waitForPendingDMAUploadCompletion(); -#endif - Device::setDrawingArea(r, Device::Orientation::Landscape); - Device::pullPixels(pixels, r.width()*r.height()); -} - -void waitForVBlank() { - // We want to return as soon as the TE line is transitionning from "DOWN" to "UP" - while (Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) { - // Loop while high, exit when low - // Wait for zero - } - while (!Device::TearingEffectPin.group().IDR()->get(Device::TearingEffectPin.pin())) { - // Loop while low, exit when high - } -} - -} -} - -// Private Ion::Display::Device methods - -namespace Ion { -namespace Display { -namespace Device { - -static inline void send_data(uint16_t d) { -#if MEMORY_BARRIER - Ion::Device::Cache::dsb(); -#endif - *DataAddress = d; -#if MEMORY_BARRIER - Ion::Device::Cache::dsb(); -#endif -} - -static inline uint16_t receive_data() { - return *DataAddress; -} - -template -static inline void send_data(uint16_t d, Args... other) { - send_data(d); - send_data(other...); -} - -static inline void send_command(Command c) { -#if MEMORY_BARRIER - Ion::Device::Cache::dsb(); -#endif - *CommandAddress = c; -#if MEMORY_BARRIER - Ion::Device::Cache::dsb(); -#endif -} - -template -static inline void send_command(Command c, Args... d) { - send_command(c); - send_data(d...); -} - -void init() { -#if USE_DMA - initDMA(); -#endif - initGPIO(); - initFMC(); - initPanel(); -} - -void shutdown() { - shutdownPanel(); - shutdownFMC(); - shutdownGPIO(); -} - -#if USE_DMA -void initDMA() { - // Only DMA2 can perform memory-to-memory transfers - //assert(DMAEngine == DMA2); - - /* In memory-to-memory transfers, the "peripheral" is the source and the - * "memory" is the destination. In other words, memory is copied from address - * DMA_SxPAR to address DMA_SxM0AR. */ - - DMAEngine.SCR(DMAStream)->setDIR(DMA::SCR::Direction::MemoryToMemory); - DMAEngine.SM0AR(DMAStream)->set((uint32_t)DataAddress); - DMAEngine.SCR(DMAStream)->setMSIZE(DMA::SCR::DataSize::HalfWord); - DMAEngine.SCR(DMAStream)->setPSIZE(DMA::SCR::DataSize::HalfWord); - DMAEngine.SCR(DMAStream)->setMBURST(DMA::SCR::Burst::Incremental4); - DMAEngine.SCR(DMAStream)->setPBURST(DMA::SCR::Burst::Incremental4); - DMAEngine.SCR(DMAStream)->setMINC(false); -} - -void waitForPendingDMAUploadCompletion() { - // Loop until DMA engine available - while (DMAEngine.SCR(DMAStream)->getEN()) { - } -} - -static inline void startDMAUpload(const KDColor * src, bool incrementSrc, uint16_t length) { - // Reset interruption markers - DMAEngine.LIFCR()->set(0xF7D0F7D); - - DMAEngine.SNDTR(DMAStream)->set(length); - DMAEngine.SPAR(DMAStream)->set((uint32_t)src); - DMAEngine.SCR(DMAStream)->setPINC(incrementSrc); - DMAEngine.SCR(DMAStream)->setEN(true); -} -#endif - -void initGPIO() { - // All the FMC GPIO pins use the alternate function number 12 - for(const GPIOPin & g : FMCPins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF12); - } - - // Turn on the power - PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Output); - PowerPin.group().ODR()->set(PowerPin.pin(), true); - - // Turn on the reset pin - ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Output); - ResetPin.group().ODR()->set(ResetPin.pin(), true); - - // Turn on the extended command pin - ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Output); - ExtendedCommandPin.group().ODR()->set(ExtendedCommandPin.pin(), true); - - // Turn on the Tearing Effect pin - TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Input); - TearingEffectPin.group().PUPDR()->setPull(TearingEffectPin.pin(), GPIO::PUPDR::Pull::None); - - Timing::msleep(120); -} - - -void shutdownGPIO() { - // All the FMC GPIO pins use the alternate function number 12 - for(const GPIOPin & g : FMCPins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); - g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); - } - - ResetPin.group().MODER()->setMode(ResetPin.pin(), GPIO::MODER::Mode::Analog); - ResetPin.group().PUPDR()->setPull(ResetPin.pin(), GPIO::PUPDR::Pull::None); - - PowerPin.group().MODER()->setMode(PowerPin.pin(), GPIO::MODER::Mode::Analog); - PowerPin.group().PUPDR()->setPull(PowerPin.pin(), GPIO::PUPDR::Pull::None); - - ExtendedCommandPin.group().MODER()->setMode(ExtendedCommandPin.pin(), GPIO::MODER::Mode::Analog); - ExtendedCommandPin.group().PUPDR()->setPull(ExtendedCommandPin.pin(), GPIO::PUPDR::Pull::None); - - TearingEffectPin.group().MODER()->setMode(TearingEffectPin.pin(), GPIO::MODER::Mode::Analog); -} - -void initFMC() { - /* Set up the FMC control registers. - * We address the LCD panel as if it were an SRAM module, using a 16bits wide - * bus, non-multiplexed. - * The STM32 FMC supports two kinds of memory access modes : - * - Base modes (1 and 2), which use the same timings for reads and writes - * - Extended modes (named A to D), which can be customized further. - * The LCD panel can be written to faster than it can be read from, therefore - * we want to use one of the extended modes. */ - FMC.BCR(FMCMemoryBank)->setEXTMOD(true); - FMC.BCR(FMCMemoryBank)->setWREN(true); - FMC.BCR(FMCMemoryBank)->setMWID(FMC::BCR::MWID::SIXTEEN_BITS); - FMC.BCR(FMCMemoryBank)->setMTYP(FMC::BCR::MTYP::SRAM); - FMC.BCR(FMCMemoryBank)->setMUXEN(false); - FMC.BCR(FMCMemoryBank)->setMBKEN(true); - - /* We now need to set the actual timings. First, the FMC and LCD specs don't - * use the same names. Here's the mapping: - * - * FMC | LCD - * -----+----- - * NOE | RDX - * NWE | WRX - * NE1 | CSX - * A16 | D/CX - * Dn | Dn - * - * We need to set the values of the BTR and BWTR which gives the timings for - * reading and writing. Note that the STM32 datasheet doesn't take into - * account the time needed to actually switch from one logic state to another, - * whereas the ST7789V one does, so we'll add T(R) and T(F) as needed. - * Last but not least, timings on the STM32 have to be expressed in terms of - * HCLK = 1/96MHz = 10.42ns. - * - We'll pick Mode A which corresponds to SRAM with OE toggling - * - ADDSET = T(AST) + T(F) = 0ns + 15ns = 2 HCLK - * - ADDHLD is unused in this mode, set to 0 - * - DATAST(read) = T(RDLFM) + T(R) = 355ns + 15ns = 36 HCLK - * DATAST(write) = T(WRL) + T(R) = 15ns + 15ns = 3 HCLK - * - BUSTURN(read) = T(RDHFM) + T(F) = 90ns + 15ns = 10 HCLK - * BUSTURN(write) = T(RDHFM) + T(F) = 15ns + 15ns = 3 HCLK - */ - - // Read timing from the LCD - FMC.BTR(FMCMemoryBank)->setADDSET(4); - FMC.BTR(FMCMemoryBank)->setADDHLD(0); - FMC.BTR(FMCMemoryBank)->setDATAST(72); - FMC.BTR(FMCMemoryBank)->setBUSTURN(20); - FMC.BTR(FMCMemoryBank)->setACCMOD(FMC::BTR::ACCMOD::A); - - // Write timings for the LCD - FMC.BWTR(FMCMemoryBank)->setADDSET(4); - FMC.BWTR(FMCMemoryBank)->setADDHLD(0); - FMC.BWTR(FMCMemoryBank)->setDATAST(6); - FMC.BWTR(FMCMemoryBank)->setBUSTURN(6); - FMC.BWTR(FMCMemoryBank)->setACCMOD(FMC::BWTR::ACCMOD::A); -} - -void shutdownFMC() { -} - -void initPanel() { - send_command(Command::Reset); - Timing::msleep(5); - - send_command(Command::SleepOut); - Timing::msleep(5); - - send_command(Command::PixelFormatSet, 0x05); - send_command(Command::TearingEffectLineOn, 0x00); - send_command(Command::FrameRateControl, 0x1E); // 40 Hz frame rate - - send_command(Command::DisplayOn); -} - -void shutdownPanel() { - send_command(Command::DisplayOff); - send_command(Command::SleepIn); - Timing::msleep(5); -} - -void setDrawingArea(KDRect r, Orientation o) { - uint16_t x_start, x_end, y_start, y_end; - - if (o == Orientation::Landscape) { - send_command(Command::MemoryAccessControl, 0xA0); - x_start = r.x(); - x_end = r.x() + r.width() - 1; - y_start = r.y(); - y_end = r.y() + r.height() - 1; - } else { - send_command(Command::MemoryAccessControl, 0x00); - x_start = r.y(); - x_end = r.y() + r.height() - 1; - y_start = Ion::Display::Width - (r.x() + r.width()); - y_end = Ion::Display::Width - r.x() - 1; - } - - send_command( - Command::ColumnAddressSet, - (x_start >> 8), - (x_start & 0xFF), - (x_end >> 8), - (x_end & 0xFF) - ); - - send_command( - Command::PageAddressSet, - (y_start >> 8), - (y_start & 0xFF), - (y_end >> 8), - (y_end & 0xFF) - ); -} - -void pushPixels(const KDColor * pixels, size_t numberOfPixels) { - send_command(Command::MemoryWrite); - /* Theoretically, we should not be able to use DMA here. Indeed, we have no - * guarantee that the content at "pixels" will remain valid once we exit this - * function call. In practice, we might be able to use DMA here because most - * of the time we push pixels from static locations. */ -#if USE_DMA_FOR_PUSH_PIXELS - startDMAUpload(pixels, true, numberOfPixels); -#else - while (numberOfPixels > 8) { - send_data(*pixels++); - send_data(*pixels++); - send_data(*pixels++); - send_data(*pixels++); - send_data(*pixels++); - send_data(*pixels++); - send_data(*pixels++); - send_data(*pixels++); - numberOfPixels -= 8; - } - while (numberOfPixels--) { - send_data(*pixels++); - } -#endif -} - -void pushColor(KDColor color, size_t numberOfPixels) { - send_command(Command::MemoryWrite); -#if USE_DMA_FOR_PUSH_COLOR - /* The "color" variable lives on the stack. We cannot take its address because - * it will stop being valid as soon as we return. An easy workaround is to - * duplicate the content in a static variable, whose value is guaranteed to be - * kept until the next pushColor call. */ - static KDColor staticColor; - staticColor = color; - startDMAUpload(&staticColor, false, (numberOfPixels > 64000 ? 64000 : numberOfPixels)); -#else - while (numberOfPixels--) { - send_data(color); - } -#endif -} - -void pullPixels(KDColor * pixels, size_t numberOfPixels) { - if (numberOfPixels == 0) { - return; - } - send_command(Command::PixelFormatSet, 0x06); - send_command(Command::MemoryRead); - - receive_data(); // First read is dummy data, per datasheet - while (true) { - if (numberOfPixels == 0) { - break; - } - uint16_t one = receive_data(); - uint16_t two = receive_data(); - uint16_t firstPixel = (one & 0xF800) | (one & 0xFC) << 3 | (two & 0xF800) >> 11; - *pixels++ = KDColor::RGB16(firstPixel); - numberOfPixels--; - - if (numberOfPixels == 0) { - break; - } - uint16_t three = receive_data(); - uint16_t secondPixel = (two & 0xF8) << 8 | (three & 0xFC00) >> 5 | (three & 0xF8) >> 3; - *pixels++ = KDColor::RGB16(secondPixel); - numberOfPixels--; - } - send_command(Command::PixelFormatSet, 0x05); -} - -} -} -} diff --git a/ion/src/f730/display.h b/ion/src/f730/display.h deleted file mode 100644 index 03d08b492..000000000 --- a/ion/src/f730/display.h +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef ION_DEVICE_DISPLAY_H -#define ION_DEVICE_DISPLAY_H - -#include -#include -extern "C" { -#include -} -#include "regs/regs.h" - -namespace Ion { -namespace Display { -namespace Device { - -/* Pin | Role | Mode | Function | Note - * -----+--------------------+-----------------------+----------|------ - * PB11 | LCD Tearing effect | Input | | - * PC8 | LCD power | Output | | LCD controller is powered directly from GPIO - * PD0 | LCD D2 | Alternate Function 12 | FMC_D2 | - * PD1 | LCD D3 | Alternate Function 12 | FMC_D3 | - * PD4 | LCD read signal | Alternate Function 12 | FMC_NOE | - * PD5 | LCD write signal | Alternate Function 12 | FMC_NWE | - * PD6 | LCD extended cmd | Output | | - * PD7 | LCD chip select | Alternate Function 12 | FMC_NE1 | Memory bank 1 - * PD8 | LCD D13 | Alternate Function 12 | FMC_D13 | - * PD9 | LCD D14 | Alternate Function 12 | FMC_D14 | - * PD10 | LCD D15 | Alternate Function 12 | FMC_D15 | - * PD11 | LCD data/command | Alternate Function 12 | FMC_A16 | Data/Command is address bit 16 - * PD14 | LCD D0 | Alternate Function 12 | FMC_D0 | - * PD15 | LCD D1 | Alternate Function 12 | FMC_D1 | - * PE1 | LCD reset | Output | | - * PE7 | LCD D4 | Alternate Function 12 | FMC_D4 | - * PE8 | LCD D5 | Alternate Function 12 | FMC_D5 | - * PE9 | LCD D6 | Alternate Function 12 | FMC_D6 | - * PE10 | LCD D7 | Alternate Function 12 | FMC_D7 | - * PE11 | LCD D8 | Alternate Function 12 | FMC_D8 | - * PE12 | LCD D9 | Alternate Function 12 | FMC_D9 | - * PE13 | LCD D10 | Alternate Function 12 | FMC_D10 | - * PE14 | LCD D11 | Alternate Function 12 | FMC_D11 | - * PE15 | LCD D12 | Alternate Function 12 | FMC_D12 | - */ - -void init(); -void shutdown(); - -void initDMA(); -void initGPIO(); -void shutdownGPIO(); -void initFMC(); -void shutdownFMC(); -void initPanel(); -void shutdownPanel(); - -enum class Orientation { - Landscape = 0, - Portrait = 1 -}; - -void setDrawingArea(KDRect r, Orientation o); -void waitForPendingDMAUploadCompletion(); -void pushPixels(const KDColor * pixels, size_t numberOfPixels); -void pushColor(KDColor color, size_t numberOfPixels); -void pullPixels(KDColor * pixels, size_t numberOfPixels); - -enum class Command : uint16_t { - Nop = 0x00, - Reset = 0x01, - SleepIn = 0x10, - SleepOut = 0x11, - DisplayOff = 0x28, - DisplayOn = 0x29, - ColumnAddressSet = 0x2A, - PageAddressSet = 0x2B, - MemoryWrite = 0x2C, - MemoryRead = 0x2E, - TearingEffectLineOn = 0x35, - MemoryAccessControl = 0x36, - PixelFormatSet = 0x3A, - FrameRateControl = 0xC6 -}; - -constexpr static GPIOPin FMCPins[] = { - 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 int FMCMemoryBank = 1; -constexpr static int FMCDataCommandAddressBit = 16; - -constexpr static uint32_t FMCBaseAddress = 0x60000000; -constexpr static uint32_t FMCBankAddress = FMCBaseAddress + (FMCMemoryBank-1)*0x04000000; - -constexpr static DMA DMAEngine = DMA2; -constexpr static int DMAStream = 0; - -static volatile Command * const CommandAddress = (Command *)(FMCBankAddress); -static volatile uint16_t * const DataAddress = (uint16_t *)(FMCBankAddress | (1<<(FMCDataCommandAddressBit+1))); - -} -} -} - -#endif diff --git a/ion/src/f730/events.cpp b/ion/src/f730/events.cpp deleted file mode 100644 index fdc9c8ff8..000000000 --- a/ion/src/f730/events.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include "regs/regs.h" -#include - -namespace Ion { -namespace Events { - -static bool sleepWithTimeout(int duration, int * timeout) { - if (*timeout >= duration) { - Timing::usleep(duration*125); - //Timing::msleep(duration/8); - *timeout -= duration; - return false; - } else { - //Timing::msleep(*timeout/8); - Timing::usleep(*timeout*125); - *timeout = 0; - return true; - } -} - -Event sLastEvent = Events::None; -Keyboard::State sLastKeyboardState; -bool sLastUSBPlugged = false; -bool sLastUSBEnumerated = false; -bool sEventIsRepeating = 0; -constexpr int delayBeforeRepeat = 200; -constexpr int delayBetweenRepeat = 50; - -bool canRepeatEvent(Event e) { - return (e == Events::Left || e == Events::Up || e == Events::Down || e == Events::Right || e == Events::Backspace); -} - -Event getEvent(int * timeout) { - assert(*timeout > delayBeforeRepeat); - assert(*timeout > delayBetweenRepeat); - int time = 0; - uint64_t keysSeenUp = 0; - uint64_t keysSeenTransitionningFromUpToDown = 0; - while (true) { - // First, check if the USB plugged status has changed - bool usbPlugged = USB::isPlugged(); - if (usbPlugged != sLastUSBPlugged) { - sLastUSBPlugged = usbPlugged; - return Events::USBPlug; - } - - // Second, check if the USB device has been connected to an USB host - bool usbEnumerated = USB::isEnumerated(); - if (usbEnumerated != sLastUSBEnumerated) { - sLastUSBEnumerated = usbEnumerated; - if (usbEnumerated) { - return Events::USBEnumeration; - } - } - - Keyboard::State state = Keyboard::scan(); - keysSeenUp |= ~state; - keysSeenTransitionningFromUpToDown = keysSeenUp & state; - - if (keysSeenTransitionningFromUpToDown != 0) { - sEventIsRepeating = false; - /* The key that triggered the event corresponds to the first non-zero bit - * in "match". This is a rather simple logic operation for the which many - * processors have an instruction (ARM thumb uses CLZ). - * Unfortunately there's no way to express this in standard C, so we have - * to resort to using a builtin function. */ - Keyboard::Key key = (Keyboard::Key)(63-__builtin_clzll(keysSeenTransitionningFromUpToDown)); - Event event(key, isShiftActive(), isAlphaActive()); - updateModifiersFromEvent(event); - sLastEvent = event; - sLastKeyboardState = state; - return event; - } - - RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy8); - if (sleepWithTimeout(10, timeout)) { - // Timeout occured - return Events::None; - } - RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClk); - time += 10; - - // At this point, we know that keysSeenTransitionningFromUpToDown has *always* been zero - // In other words, no new key has been pressed - if (canRepeatEvent(sLastEvent) && (state == sLastKeyboardState)) { - int delay = (sEventIsRepeating ? delayBetweenRepeat : delayBeforeRepeat); - if (time >= delay) { - sEventIsRepeating = true; - sLastKeyboardState = state; - return sLastEvent; - } - } - } -} - -} -} diff --git a/ion/src/f730/external_flash.cpp b/ion/src/f730/external_flash.cpp deleted file mode 100644 index 46a3c99b1..000000000 --- a/ion/src/f730/external_flash.cpp +++ /dev/null @@ -1,343 +0,0 @@ -#include "external_flash.h" -#include "timing.h" - -namespace Ion { -namespace ExternalFlash { -namespace Device { - -/* 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 --> | regsiter --> 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. */ - -class ExternalFlashStatusRegister { -public: - class StatusRegister1 : Register8 { - public: - using Register8::Register8; - REGS_BOOL_FIELD_R(BUSY, 0); - }; - class StatusRegister2 : Register8 { - public: - using Register8::Register8; - REGS_BOOL_FIELD_W(QE, 1); - }; -}; - -static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; -static constexpr int ClockFrequencyDivisor = 256;// TODO: optimize me (value forSTM32F412: 2); -static constexpr int ChipSelectHighTime = (ClockFrequencyDivisor == 1) ? 3 : (ClockFrequencyDivisor == 2) ? 2 : 1; - -static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, 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, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { - send_command_full( - QUADSPI::CCR::FunctionalMode::IndirectWrite, - operatingMode, - c, - reinterpret_cast(FlashAddressSpaceSize), - 0, 0, - 0, - nullptr, 0 - ); -} - -static inline void send_write_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { - send_command_full( - QUADSPI::CCR::FunctionalMode::IndirectWrite, - operatingMode, - c, - address, - 0, 0, - 0, - data, dataLength - ); -} - -static inline void send_read_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { - send_command_full( - QUADSPI::CCR::FunctionalMode::IndirectRead, - operatingMode, - c, - address, - 0, 0, - 0, - data, dataLength - ); -} - -static inline void wait(QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { - ExternalFlashStatusRegister::StatusRegister1 statusRegister1(0); - do { - send_read_command(Command::ReadStatusRegister, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister1), sizeof(statusRegister1), operatingMode); - } 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.) */ - constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : (ClockFrequencyDivisor > 1) ? 4 : 6; - send_command_full( - QUADSPI::CCR::FunctionalMode::MemoryMapped, - DefaultOperatingMode, - Command::FastReadQuadIO, - reinterpret_cast(FlashAddressSpaceSize), - 0xA0, 1, - 2, //FIXME - 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, - DefaultOperatingMode, - Command::FastReadQuadIO, - 0, - ~(0xA0), 1, - 2, //FIXME - &dummyData, 1 - ); -} - -void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength) { - class QUADSPI::CCR ccr(0); - ccr.setFMODE(functionalMode); - if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { - ccr.setDMODE(operatingMode); - } - if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { - QUADSPI.DLR()->set((dataLength > 0) ? dataLength-1 : 0); - } - ccr.setDCYC(dummyCycles); - if (numberOfAltBytes > 0) { - ccr.setABMODE(operatingMode); - ccr.setABSIZE(static_cast(numberOfAltBytes - 1)); - QUADSPI.ABR()->set(altBytes); - } - if (address != reinterpret_cast(FlashAddressSpaceSize) || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { - ccr.setADMODE(operatingMode); - ccr.setADSIZE(QUADSPI::CCR::Size::ThreeBytes); - } - ccr.setIMODE(operatingMode); - ccr.setINSTRUCTION(static_cast(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(FlashAddressSpaceSize)) { - QUADSPI.AR()->set(reinterpret_cast(address)); - } - - if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectWrite) { - for (size_t i=0; iset(data[i]); - } - } else if (functionalMode == QUADSPI::CCR::FunctionalMode::IndirectRead) { - for (size_t i=0; iget(); - } - } - - /* 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." */ - if (functionalMode != QUADSPI::CCR::FunctionalMode::MemoryMapped) { - while (QUADSPI.SR()->getBUSY()) { - } - } -} - -void init() { - initGPIO(); - initQSPI(); - initChip(); -} - -void shutdown() { - shutdownChip(); - shutdownQSPI(); - shutdownGPIO(); -} - -void initGPIO() { - for(const GPIOPin & g : QSPIPins) { - g.group().OSPEEDR()->setOutputSpeed(g.pin(), GPIO::OSPEEDR::OutputSpeed::High); - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), (g.pin() == 6 ? GPIO::AFR::AlternateFunction::AF10 : GPIO::AFR::AlternateFunction::AF9)); - } -} - -void shutdownGPIO() { - for(const GPIOPin & g : QSPIPins) { - g.group().OSPEEDR()->setOutputSpeed(g.pin(), GPIO::OSPEEDR::OutputSpeed::Low); - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); - g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); - } -} - -void initQSPI() { - // Enable QUADSPI AHB3 peripheral clock - RCC.AHB3ENR()->setQSPIEN(true); // TODO: move in Device::initClocks - // Configure controller for target device - class QUADSPI::DCR dcr(0); - dcr.setFSIZE(NumberOfAddressBitsInChip - 1); - dcr.setCSHT(ChipSelectHighTime - 1); - dcr.setCKMODE(true); - QUADSPI.DCR()->set(dcr); - class QUADSPI::CR cr(0); - cr.setPRESCALER(ClockFrequencyDivisor - 1); - cr.setEN(true); - QUADSPI.CR()->set(cr); -} - -void shutdownQSPI() { - RCC.AHB3ENR()->setQSPIEN(false); // TODO: move in Device::shutdownClocks -} - -void initChip() { - static bool firstPass = true; - /* The chip initially expects commands in SPI mode. We need to use SPI to tell - * it to switch to QPI. */ - if (firstPass && DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { - send_command(Command::WriteEnable, QUADSPI::CCR::OperatingMode::Single); - ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); - statusRegister2.setQE(true); - wait(QUADSPI::CCR::OperatingMode::Single); - send_write_command(Command::WriteStatusRegister2, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&statusRegister2), sizeof(statusRegister2), QUADSPI::CCR::OperatingMode::Single); - wait(QUADSPI::CCR::OperatingMode::Single); - send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); - wait(); - if (ClockFrequencyDivisor == 1) { - class ReadParameters : Register8 { - public: - /* Parameters sent along with SetReadParameters instruction in order - * to configure the number of dummy cycles for the QPI Read instructions. */ - using Register8::Register8; - REGS_BOOL_FIELD_W(P5, 1); - }; - ReadParameters readParameters(0); - readParameters.setP5(true); - send_write_command(Command::SetReadParameters, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&readParameters), sizeof(readParameters)); - } - firstPass = false; - } else { - // TODO - } - set_as_memory_mapped(); -} - -void shutdownChip() { - unset_memory_mapped_mode(); - send_command(Command::DeepPowerDown); - Timing::usleep(100); // TODO should be 3us when usleep adjusted -} - -int SectorAtAddress(uint32_t address) { - int i = address >> NumberOfAddressBitsIn64KbyteBlock; - if (i >= NumberOfSectors) { - return -1; - } - return i; -} - -void MassErase() { - unset_memory_mapped_mode(); - send_command(Command::WriteEnable); - wait(); - send_command(Command::ChipErase); - wait(); - set_as_memory_mapped(); -} - -void EraseSector(int i) { - assert(i >= 0 && i < NumberOfSectors); - unset_memory_mapped_mode(); - send_command(Command::WriteEnable); - wait(); - send_write_command(Command::Erase64KbyteBlock, reinterpret_cast(i << NumberOfAddressBitsIn64KbyteBlock), nullptr, 0); - wait(); - set_as_memory_mapped(); -} - -void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { - 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; - constexpr Command pageProgram = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? Command::PageProgram : Command::QuadPageProgram; - uint8_t offset = reinterpret_cast(destination) & (PageSize - 1); - size_t lengthThatFitsInPage = PageSize - offset; - while (length > 0) { - if (lengthThatFitsInPage > length) { - lengthThatFitsInPage = length; - } - send_command(Command::WriteEnable); - wait(); - send_write_command(pageProgram, destination, source, lengthThatFitsInPage); - length -= lengthThatFitsInPage; - destination += lengthThatFitsInPage; - source += lengthThatFitsInPage; - lengthThatFitsInPage = PageSize; - wait(); - } - set_as_memory_mapped(); -} - -} -} -} diff --git a/ion/src/f730/external_flash.h b/ion/src/f730/external_flash.h deleted file mode 100644 index 2f4af8b01..000000000 --- a/ion/src/f730/external_flash.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef ION_DEVICE_EXTERNAL_FLASH_H -#define ION_DEVICE_EXTERNAL_FLASH_H - -#include -#include -#include "regs/regs.h" - -// Quad-SPI on STM32 microcontroller -// https://www.st.com/resource/en/application_note/dm00227538.pdf - -// Adesto Technologies AT25SF641 -// https://www.adestotech.com/wp-content/uploads/AT25SF641_111.pdf - -/* External Flash Memory map Address space - * 8MiB chip 0x000000 - 0x7FFFFF - * 2^7 64KiB blocks 0x..0000 - 0x..FFFF - * 2^7 * 2 32KiB blocks 0x..0000 - 0x..7FFF or 0x..8000 - 0x..FFFF - * 2^7 * 2 * 2^3 4KiB blocks 0x...000 - 0x...FFF - * 2^7 * 2 * 2^3 * 2^4 256B pages 0x....00 - 0x....FF */ - -namespace Ion { -namespace ExternalFlash { -namespace Device { - -/* 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 - */ - -void init(); -void shutdown(); - -void initGPIO(); -void shutdownGPIO(); -void initQSPI(); -void shutdownQSPI(); -void initChip(); -void shutdownChip(); - -void MassErase(); - -constexpr int NumberOfSectors = 128; -int SectorAtAddress(uint32_t address); -void EraseSector(int i); - -void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); - -enum class Command : uint8_t { - ReadStatusRegister = 0x05, - WriteStatusRegister2 = 0x31, - WriteEnable = 0x06, - ReadData = 0x03, - FastRead = 0x0B, - FastReadQuadIO = 0xEB, - // Program previously erased memory areas as being "0" - PageProgram = 0x02, - QuadPageProgram = 0x33, - EnableQPI = 0x38, - // Erase the whole chip or a 64-Kbyte block as being "1" - ChipErase = 0xC7, - Erase64KbyteBlock = 0xD8, - SetReadParameters = 0xC0, - DeepPowerDown = 0xB9, - ReleaseDeepPowerDown = 0xAB -}; - -constexpr static uint32_t QSPIBaseAddress = 0x90000000; -constexpr static uint8_t NumberOfAddressBitsInChip = 23; -constexpr static uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; -constexpr static uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; - -constexpr static GPIOPin QSPIPins[] = { - GPIOPin(GPIOB, 2), GPIOPin(GPIOB, 6), GPIOPin(GPIOC, 9), GPIOPin(GPIOD,12), - GPIOPin(GPIOE, 2), GPIOPin(GPIOD,13) -}; - -} -} -} - -#endif diff --git a/ion/src/f730/flash.cpp b/ion/src/f730/flash.cpp deleted file mode 100644 index 602ae7dd9..000000000 --- a/ion/src/f730/flash.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include "flash.h" -#include - -namespace Ion { -namespace Flash { -namespace Device { - -static inline void wait() { - // Wait for pending Flash operations to complete - while (FLASH.SR()->getBSY()) { - } -} - -static void open() { - // Unlock the Flash configuration register if needed - if (FLASH.CR()->getLOCK()) { - FLASH.KEYR()->set(0x45670123); - FLASH.KEYR()->set(0xCDEF89AB); - } - assert(FLASH.CR()->getLOCK() == false); - - // Set the programming parallelism - FLASH.CR()->setPSIZE(MemoryAccessWidth); -} - -static void close() { - // Lock the Flash configuration register - assert(!FLASH.CR()->getMER()); - assert(!FLASH.CR()->getSER()); - assert(!FLASH.CR()->getPG()); - FLASH.CR()->setLOCK(true); - -#if 0 - // Purge Data and instruction cache - if (FLASH.ACR()->getDCEN()) { - FLASH.ACR()->setDCEN(false); - FLASH.ACR()->setDCRST(true); - FLASH.ACR()->setDCRST(false); - FLASH.ACR()->setDCEN(true); - } - if (FLASH.ACR()->getICEN()) { - FLASH.ACR()->setICEN(false); - FLASH.ACR()->setICRST(true); - FLASH.ACR()->setICRST(false); - FLASH.ACR()->setICEN(true); - } -#endif -} - -// Compile-time log2 -static inline constexpr size_t clog2(size_t input) { - return (input == 1) ? 0 : clog2(input/2)+1; -} - -// Align a pointer to a given type's boundaries -// Returns a value that is lower or equal to input -template -static inline T * align(void * input) { - size_t k = clog2(sizeof(T)); - return reinterpret_cast(reinterpret_cast(input) & ~((1< -static inline T eat(void * ptr) { - T * pointer = *reinterpret_cast(ptr); - T result = *pointer; - *reinterpret_cast(ptr) = pointer+1; - return result; -} - -static inline ptrdiff_t byte_offset(void * p1, void * p2) { - return reinterpret_cast(p2) - reinterpret_cast(p1); -} - -template -static inline T min(T i, T j) { - return (i| - * |-- HeaderDelta ->| - */ - - MemoryAccessType * alignedDestination = align(destination); - ptrdiff_t headerDelta = byte_offset(alignedDestination, destination); - assert(headerDelta >= 0 && headerDelta < static_cast(sizeof(MemoryAccessType))); - - if (headerDelta > 0) { - // At this point, alignedDestination < destination - // We'll then retrieve the current value at alignedDestination, fill it with - // bytes from source, and write it back at alignedDestination. - - // First, retrieve the current value at alignedDestination - MemoryAccessType header = *alignedDestination; - - // Then copy headerLength bytes from source and put them in the header - uint8_t * headerStart = reinterpret_cast(&header); - // Here's where source data shall start being copied in the header - uint8_t * headerDataStart = headerStart + headerDelta; - // And here's where it should end - uint8_t * headerDataEnd = min( - headerStart + sizeof(MemoryAccessType), // Either at the end of the header - headerDataStart + length // or whenever src runs out of data - ); - for (uint8_t * h = headerDataStart; h(&source); - } - - // Then eventually write the header back into the aligned destination - *alignedDestination++ = header; - } - - /* Step 2 - Copy the bulk of the data - * At this point, we can use aligned MemoryAccessType pointers. */ - - MemoryAccessType * lastAlignedDestination = align(destination + length); - while (alignedDestination < lastAlignedDestination) { - *alignedDestination++ = eat(&source); - } - - /* Step 3 - Copy a footer if needed - * Some unaligned data can be pending at the end. Let's take care of it like - * we did for the header. - * - * _alignedDst _Destination+length - * | | - * --+--------+--------+--------+--------+--------+--------+-- - * | || | | | || | - *---+--------+--------+--------+--------+--------+--------+-- - * |<------------ Footer ------------->| - * |- footerLength ->| - */ - - ptrdiff_t footerLength = byte_offset(alignedDestination, destination + length); - assert(footerLength < static_cast(sizeof(MemoryAccessType))); - if (footerLength > 0) { - assert(alignedDestination == lastAlignedDestination); - - // First, retrieve the current value at alignedDestination - MemoryAccessType footer = *alignedDestination; - - /* Then copy footerLength bytes from source and put them at the beginning of - * the footer */ - uint8_t * footerPointer = reinterpret_cast(&footer); - for (ptrdiff_t i=0; i(&source); - } - - // Then eventually write the footer back into the aligned destination - *alignedDestination = footer; - } -} - -int SectorAtAddress(uint32_t address) { - uint32_t sectorAddresses[NumberOfSectors+1] = { - 0x08000000, 0x08004000, 0x08008000, 0x0800C000, - 0x08010000, 0x08020000, 0x08040000, 0x08060000, - 0x08080000, 0x080A0000, 0x080C0000, 0x080E0000, - 0x08100000 - }; - for (int i=0; i= sectorAddresses[i] && address < sectorAddresses[i+1]) { - return i; - } - } - return -1; -} - -void MassErase() { - open(); - FLASH.CR()->setMER(true); - FLASH.CR()->setSTRT(true); - wait(); - FLASH.CR()->setMER(false); - close(); -} - -void EraseSector(int i) { - assert(i >= 0 && i < NumberOfSectors); - open(); - FLASH.CR()->setSNB(i); - FLASH.CR()->setSER(true); - FLASH.CR()->setSTRT(true); - wait(); - FLASH.CR()->setSNB(0); - FLASH.CR()->setSER(false); - close(); -} - -void WriteMemory(uint8_t * source, uint8_t * destination, size_t length) { - open(); - FLASH.CR()->setPG(true); - flash_memcpy(source, destination, length); - wait(); - FLASH.CR()->setPG(false); - close(); -} - -} -} -} diff --git a/ion/src/f730/flash.h b/ion/src/f730/flash.h deleted file mode 100644 index 6d8c9b657..000000000 --- a/ion/src/f730/flash.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef ION_DEVICE_FLASH_H -#define ION_DEVICE_FLASH_H - -#include -#include "regs/flash.h" - -namespace Ion { -namespace Flash { -namespace Device { - -void MassErase(); - -constexpr int NumberOfSectors = 12; -int SectorAtAddress(uint32_t address); -void EraseSector(int i); - -void WriteMemory(uint8_t * source, uint8_t * destination, size_t length); - -/* The Device is powered by a 2.8V LDO. This allows us to perform writes to the - * Flash 32 bits at once. */ -constexpr FLASH::CR::PSIZE MemoryAccessWidth = FLASH::CR::PSIZE::X32; -typedef uint32_t MemoryAccessType; - -} -} -} - -#endif diff --git a/ion/src/f730/keyboard.cpp b/ion/src/f730/keyboard.cpp deleted file mode 100644 index e148acbf7..000000000 --- a/ion/src/f730/keyboard.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* Keyboard initialization code - * - * The job of this code is to implement the "ion_key_state" function. - * - * The keyboard is a matrix that is laid out as follow: - * - * | PC0 | PC1 | PC2 | PC3 | PC4 | PC5 | - * -----+------+------+------+------+------+------+ - * PA0 | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 | - * -----+------+------+------+------+------+------+ - * PA1 | K_B1 | K_B2 | | | | | - * -----+------+------+------+------+------+------+ - * PA2 | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 | - * -----+------+------+------+------+------+------+ - * PA3 | K_D1 | K_D2 | K_D3 | K_D4 | K_D5 | K_D6 | - * -----+------+------+------+------+------+------+ - * PA4 | K_E1 | K_E2 | K_E3 | K_E4 | K_E5 | K_E6 | - * -----+------+------+------+------+------+------+ - * PA5 | K_F1 | K_F2 | K_F3 | K_F4 | K_F5 | | - * -----+------+------+------+------+------+------+ - * PA6 | K_G1 | K_G2 | K_G3 | K_G4 | K_G5 | | - * -----+------+------+------+------+------+------+ - * PA7 | K_H1 | K_H2 | K_H3 | K_H4 | K_H5 | | - * -----+------+------+------+------+------+------+ - * PA8 | K_I1 | K_I2 | K_I3 | K_I4 | K_I5 | | - * -----+------+------+------+------+------+------| - * - * We decide to drive the rows (PA0-8) and read the columns (PC0-5). - * - * To avoid short-circuits, the pins PA0-PA8 will not be standard outputs but - * only open-drain. Open drain means the pin is either driven low or left - * floating. - * When a user presses multiple keys, a connection between two rows can happen. - * If we don't use open drain outputs, this situation could trigger a short - * circuit between an output driving high and another driving low. - * - * If the outputs are open-drain, this means that the input must be pulled up. - * So if the input reads "1", this means the key is in fact *not* pressed, and - * if it reads "0" it means that there's a short to an open-drain output. Which - * means the corresponding key is pressed. - */ - -#include "keyboard.h" - -// Public Ion::Keyboard methods - -namespace Ion { -namespace Keyboard { - -State scan() { - uint64_t state = 0; - - for (uint8_t i=0; igetBitRange(Device::numberOfColumns-1,0); - - /* The key is down if the input is brought low by the output. In other - * words, we want to return true if the input is low (false). So we need to - * append 6 bits of (not columns) to state. */ - state = (state << Device::numberOfColumns) | (~columns & 0x3F); - } - - /* Last but not least, keys number 8, 9, 10, 11, 35, 41, 47 and 53 are not - * defined. Therefore we want to make sure those bits are forced to zero in - * whatever value we return. */ - state = state & 0x1F7DF7FFFFF0FF; - - return State(state); -} - -} -} - -// Private Ion::Keyboard::Device methods - -namespace Ion { -namespace Keyboard { -namespace Device { - -void init() { - for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Output); - RowGPIO.OTYPER()->setType(pin, GPIO::OTYPER::Type::OpenDrain); - } - - for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Input); - ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::Up); - } -} - -void shutdown() { - for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Analog); - RowGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None); - } - - for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Analog); - ColumnGPIO.PUPDR()->setPull(pin, GPIO::PUPDR::Pull::None); - } -} - -} -} -} diff --git a/ion/src/f730/keyboard.h b/ion/src/f730/keyboard.h deleted file mode 100644 index d1537bc5a..000000000 --- a/ion/src/f730/keyboard.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef ION_DEVICE_KEYBOARD_H -#define ION_DEVICE_KEYBOARD_H - -#include -#include -#include "regs/regs.h" - -namespace Ion { -namespace Keyboard { -namespace Device { - -/* Pin | Role | Mode - * -----+-------------------+-------------------- - * PA0 | Keyboard row A | Output, open drain - * PA1 | 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 - * 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 - */ - -void init(); -void shutdown(); - -constexpr GPIO RowGPIO = GPIOA; -constexpr uint8_t numberOfRows = 9; -constexpr uint8_t RowPins[numberOfRows] = {0, 1, 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}; - -inline uint8_t rowForKey(Key key) { - return (int)key/numberOfColumns; -} -inline uint8_t columnForKey(Key key) { - return (int)key%numberOfColumns; -} - -inline void activateRow(uint8_t row) { - /* In open-drain mode, a 0 in the register drives the pin low, and a 1 lets - * the pin floating (Hi-Z). So we want to set the current row to zero and all - * the others to 1. */ - uint16_t rowState = ~(1<setBitRange(numberOfRows-1, 0, rowState); - - // TODO: 100 us seems to work, but wasn't really calculated - Timing::usleep(100); -} - -inline bool columnIsActive(uint8_t column) { - return !(Device::ColumnGPIO.IDR()->getBitRange(column,column)); -} - -} -} -} - -#endif diff --git a/ion/src/f730/led.cpp b/ion/src/f730/led.cpp deleted file mode 100644 index a7ff247db..000000000 --- a/ion/src/f730/led.cpp +++ /dev/null @@ -1,133 +0,0 @@ -#include -#include -#include "device.h" -#include "led.h" -#include "regs/regs.h" - -// Public Ion::LED methods - -static KDColor sLedColor = KDColorBlack; - -KDColor Ion::LED::getColor() { - return sLedColor; -} - -void Ion::LED::setColor(KDColor c) { - sLedColor = c; - - /* Active all RGB colors */ - TIM3.CCMR()->setOC1M(TIM::CCMR::OCM::PWM1); - TIM3.CCMR()->setOC2M(TIM::CCMR::OCM::PWM1); - TIM3.CCMR()->setOC3M(TIM::CCMR::OCM::PWM1); - - /* Set the PWM duty cycles to display the right color */ - constexpr float maxColorValue = (float)((1 << 8) -1); - Device::setPeriodAndDutyCycles(Device::Mode::PWM, c.red()/maxColorValue, c.green()/maxColorValue, c.blue()/maxColorValue); -} - -void Ion::LED::setBlinking(uint16_t period, float dutyCycle) { - /* We want to use the PWM at a slow rate to display a seeable blink. - * Consequently, we do not use PWM to display the right color anymore but to - * blink. We cannot use the PWM to display the exact color so we 'project the - * color on 3 bits' : all colors have 2 states - active or not. */ - TIM3.CCMR()->setOC1M(sLedColor.red() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); - TIM3.CCMR()->setOC2M(sLedColor.green() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); - TIM3.CCMR()->setOC3M(sLedColor.blue() > 0 ? TIM::CCMR::OCM::PWM1 : TIM::CCMR::OCM::ForceInactive); - - Device::setPeriodAndDutyCycles(Device::Mode::Blink, dutyCycle, dutyCycle, dutyCycle, period); -} - -// Private Ion::Device::LED methods - -namespace Ion { -namespace LED { -namespace Device { - -void init() { - initGPIO(); - initTimer(); -} - -void shutdown() { - shutdownTimer(); - shutdownGPIO(); -} - -void initGPIO() { - /* RED_LED(PC7), GREEN_LED(PB1), and BLUE_LED(PB0) are driven using a timer, - * which is an alternate function. More precisely, we will use AF2, which maps - * PB0 to TIM3_CH3, PB5 to TIM3_CH2, and PB4 to TIM3_CH1. */ - for(const GPIOPin & g : RGBPins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF2); - } -} - -void shutdownGPIO() { - for(const GPIOPin & g : RGBPins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); - g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); - } -} - -void initTimer() { - // Output preload enable for channels 1 to 3 - TIM3.CCMR()->setOC1PE(true); - TIM3.CCMR()->setOC2PE(true); - TIM3.CCMR()->setOC3PE(true); - - // Auto-reload preload enable - TIM3.CR1()->setARPE(true); - - // Enable Capture/Compare for channel 1 to 3 - TIM3.CCER()->setCC1E(true); - TIM3.CCER()->setCC2E(true); - TIM3.CCER()->setCC3E(true); - - TIM3.BDTR()->setMOE(true); - - TIM3.CR1()->setCEN(true); -} - -void shutdownTimer() { - TIM3.CCMR()->setOC1M(TIM::CCMR::OCM::ForceInactive); - TIM3.CCMR()->setOC2M(TIM::CCMR::OCM::ForceInactive); - TIM3.CCMR()->setOC3M(TIM::CCMR::OCM::ForceInactive); -} - -/* Pulse width modulation mode allows you to generate a signal with a - * frequency determined by the value of the TIMx_ARR register and a duty cycle - * determined by the value of the TIMx_CCRx register. */ - -void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period) { - switch (mode) { - case Mode::PWM: - /* Let's set the prescaler to 1. Increasing the prescaler would slow down - * the modulation, which can be useful when debugging or when we want an - * actual blinking. */ - TIM3.PSC()->set(1); - TIM3.ARR()->set(PWMPeriod); - period = PWMPeriod; - break; - case Mode::Blink: - int systemClockFreq = 96; - /* We still want to do PWM, but at a rate slow enough to blink. Ideally, - * we want to pre-scale the period to be able to set it in milliseconds; - * however, as the prescaler is cap by 2^16-1, we divide it by a factor - * and correct the period consequently. */ - int factor = 2; - // TODO: explain the 2 ? - TIM3.PSC()->set(systemClockFreq*1000/factor); - period *= factor; - TIM3.ARR()->set(period); - break; - } - - TIM3.CCR1()->set(dutyCycleRed*period); - TIM3.CCR3()->set(dutyCycleBlue*period); - TIM3.CCR2()->set(dutyCycleGreen*period); -} - -} -} -} diff --git a/ion/src/f730/led.h b/ion/src/f730/led.h deleted file mode 100644 index 66574269d..000000000 --- a/ion/src/f730/led.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef ION_DEVICE_LED_H -#define ION_DEVICE_LED_H - -#include "regs/regs.h" - -namespace Ion { -namespace LED { -namespace Device { - -/* Pin | Role | Mode | Function - * -----+-------------------+-----------------------+---------- - * PB0 | LED blue | Alternate Function 2 | TIM3_CH3 - * PB5 | LED green | Alternate Function 2 | TIM3_CH2 - * PB4 | LED red | Alternate Function 2 | TIM3_CH1 - */ - -enum class Mode { - PWM, - Blink -}; - -enum class Color { - Red, - Green, - Blue -}; - -void init(); -void shutdown(); -void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period = 0); - -void initGPIO(); -void shutdownGPIO(); -void initTimer(); -void shutdownTimer(); - -constexpr static GPIOPin RGBPins[] = { - GPIOPin(GPIOB, 4), GPIOPin(GPIOB, 5), GPIOPin(GPIOB, 0) -}; - - -constexpr uint16_t PWMPeriod = 40000; - -} -} -} - -#endif diff --git a/ion/src/f730/log.cpp b/ion/src/f730/log.cpp deleted file mode 100644 index 25ee1e7e2..000000000 --- a/ion/src/f730/log.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include -#include "regs/itm.h" - -// We're printing using SWO. -// This is achieved by writing to the ITM register, which is sent through the -// Cortex Debug bus - -void ion_log_string(const char * message) { - char character = 0; - while ((character = *message++) != 0) { - if (ITM.TER()->get(0)) { - ITM.STIM(0)->set(character); - } - } -} diff --git a/ion/src/f730/power.cpp b/ion/src/f730/power.cpp deleted file mode 100644 index d18854bf5..000000000 --- a/ion/src/f730/power.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include -#include "battery.h" -#include "device.h" -#include "display.h" -#include "keyboard.h" -#include "led.h" -#include "usb.h" -#include "wakeup.h" -#include "regs/regs.h" - -void Ion::Power::suspend(bool checkIfPowerKeyReleased) { - bool isLEDActive = Ion::LED::getColor() != KDColorBlack; - if (checkIfPowerKeyReleased) { - /* Wait until power is released to avoid restarting just after suspending */ - bool isPowerDown = true; - while (isPowerDown) { - Keyboard::State scan = Keyboard::scan(); - isPowerDown = scan.keyDown(Keyboard::Key::B2); - } - } - Device::shutdownPeripherals(isLEDActive); - - PWR.CR1()->setLPDS(true); // Turn the regulator off. Takes longer to wake up. - PWR.CR1()->setFPDS(true); // Put the flash to sleep. Takes longer to wake up. - PWR.CR1()->setLPUDS(true); - PWR.CR1()->setUDEN(PWR::CR1::UnderDrive::Enable); - CM4.SCR()->setSLEEPDEEP(!isLEDActive); - - while (1) { -#if EPSILON_LED_WHILE_CHARGING - /* Update LEDS - * if the standby mode was stopped due to a "stop charging" event, we wait - * a while to be sure that the plug state of the USB is up-to-date. */ - msleep(200); - //Ion::LED::setCharging(Ion::USB::isPlugged(), Ion::Battery::isCharging()); -#endif - - WakeUp::Device::onPowerKeyDown(); - WakeUp::Device::onUSBPlugging(); -#if EPSILON_LED_WHILE_CHARGING - WakeUp::Device::onChargingEvent(); -#endif - - Device::shutdownClocks(isLEDActive); - - /* To enter sleep, we need to issue a WFE instruction, which waits for the - * event flag to be set and then clears it. However, the event flag might - * already be on. So the safest way to make sure we actually wait for a new - * event is to force the event flag to on (SEV instruction), use a first WFE - * to clear it, and then a second WFE to wait for a _new_ event. */ - asm("sev"); - asm("wfe"); - asm("nop"); - asm("wfe"); - - Device::initClocks(); - - Keyboard::Device::init(); - Keyboard::State scan = Keyboard::scan(); - Keyboard::Device::shutdown(); - - Ion::Keyboard::State OnlyPowerKeyDown = Keyboard::State(Keyboard::Key::B2); - if (scan == OnlyPowerKeyDown || USB::isPlugged()) { - // Wake up - break; - } - } - Device::initClocks(); - - Device::initPeripherals(); -} diff --git a/ion/src/f730/regs/adc.h b/ion/src/f730/regs/adc.h deleted file mode 100644 index 2881cf23b..000000000 --- a/ion/src/f730/regs/adc.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef REGS_ADC_H -#define REGS_ADC_H - -#include "register.h" - -class ADC { -public: - class SR : Register32 { - public: - REGS_BOOL_FIELD(EOC, 1); - }; - - class CR1 : Register32 { - public: - }; - - class CR2 : Register32 { - public: - REGS_BOOL_FIELD(ADON, 0); - REGS_BOOL_FIELD(SWSTART, 30); - }; - - class SMPR : Register64 { - /* The SMPR register doesn't exist per-se in the documentation. It is the - * consolidation of SMPR1 and SMPR2 which are two 32-bit registers. */ - public: - enum class SamplingTime { - Cycles3 = 0, - Cycles15 = 1, - Cycles28 = 2, - Cycles56 = 3, - Cycles84 = 4, - Cycles112 = 5, - Cycles144 = 6, - Cycles480 = 7 - }; - SamplingTime getSamplingTime(int channel) { - return (SamplingTime)getBitRange(3*channel+2, 3*channel); - } - void setSamplingTime(int channel, SamplingTime t) volatile { - setBitRange(3*channel+2, 3*channel, (uint64_t)t); - } - }; - - class SQR1 : Register32 { - public: - REGS_FIELD(L, uint8_t, 23, 20); - }; - - class SQR3 : Register32 { - public: - REGS_FIELD(SQ1, uint8_t, 4, 0); - }; - - class DR : public Register16 { - }; - - constexpr ADC() {}; - REGS_REGISTER_AT(SR, 0x0); - REGS_REGISTER_AT(CR2, 0x08); - REGS_REGISTER_AT(SMPR, 0x0C); - REGS_REGISTER_AT(SQR1, 0x2C); - REGS_REGISTER_AT(SQR3, 0x34); - REGS_REGISTER_AT(DR, 0x4C); -private: - constexpr uint32_t Base() const { - return 0x40012000; - }; -}; - -constexpr ADC ADC; - -#endif diff --git a/ion/src/f730/regs/cm4.h b/ion/src/f730/regs/cm4.h deleted file mode 100644 index d3b43d502..000000000 --- a/ion/src/f730/regs/cm4.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef REGS_CM4_H -#define REGS_CM4_H - -#include "register.h" - -class CM4 { -public: - // Vector table offset register - // http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf - class VTOR : Register32 { - public: - void setVTOR(void *address) volatile { setBitRange(29, 9, (uint32_t)address); } - }; - - // Coprocessor Access Control Register - // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BEHBJHIG.html - class CPACR : public Register32 { - public: - enum class Access { - Denied = 0, - PrivilegedOnly = 1, - Full = 3 - }; - void setAccess(int index, Access a) volatile { setBitRange(2*index+1, 2*index, (uint32_t)a); } - }; - - - // Application Interrupt and Reset Control Register - // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Cihehdge.html - class AIRCR : public Register32 { - public: - void requestReset() volatile { - set(0x5FA<<16 |(1<<2)); - } - }; - - // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/Cihhjgdh.html - class SCR : public Register32 { - public: - REGS_BOOL_FIELD(SLEEPDEEP, 2); - }; - - class CCR : public Register32 { - public: - REGS_BOOL_FIELD(IC, 17); - REGS_BOOL_FIELD(DC, 16); - }; - - class SYST_CSR : public Register32 { - public: - enum class CLKSOURCE : uint8_t { - AHB_DIV8 = 0, - AHB = 1 - }; - REGS_BOOL_FIELD(COUNTFLAG, 16); - REGS_TYPE_FIELD(CLKSOURCE, 2, 2); - REGS_BOOL_FIELD(TICKINT, 1); - REGS_BOOL_FIELD(ENABLE, 0); - }; - - class SYST_RVR : public Register32 { - public: - REGS_FIELD(RELOAD, uint32_t, 23, 0); - }; - - class SYST_CVR : public Register32 { - public: - REGS_FIELD(CURRENT, uint32_t, 23, 0); - }; - - class ICIALLU : public Register32 { - public: - using Register32::Register32; - }; - - class CSSELR : public Register32 { - public: - REGS_BOOL_FIELD(IND, 0); - }; - - class CCSIDR : public Register32 { - public: - REGS_FIELD(ASSOCIATIVITY, uint16_t, 12, 3); - REGS_FIELD(NUMSETS, uint16_t, 27, 13); - }; - - class DCISW : public Register32 { - public: - DCISW() : Register32(0) {} - REGS_FIELD(SET, uint16_t, 13, 5); - REGS_FIELD(WAY, uint8_t, 31, 30); - }; - - constexpr CM4() {}; - REGS_REGISTER_AT(SYST_CSR, 0x10); - REGS_REGISTER_AT(SYST_RVR, 0x14); - REGS_REGISTER_AT(SYST_CVR, 0x18); - REGS_REGISTER_AT(VTOR, 0xD08); - REGS_REGISTER_AT(AIRCR, 0xD0C); - REGS_REGISTER_AT(SCR, 0xD10); - REGS_REGISTER_AT(CCR, 0xD14); - REGS_REGISTER_AT(CCSIDR, 0xD80); - REGS_REGISTER_AT(CSSELR, 0xD84); - REGS_REGISTER_AT(CPACR, 0xD88); - REGS_REGISTER_AT(ICIALLU, 0xF50); - REGS_REGISTER_AT(DCISW, 0xF60); -private: - constexpr uint32_t Base() const { - return 0xE000E000; - } -}; - -constexpr CM4 CM4; - -#endif diff --git a/ion/src/f730/regs/crc.h b/ion/src/f730/regs/crc.h deleted file mode 100644 index 6ae4a2c10..000000000 --- a/ion/src/f730/regs/crc.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef REGS_CRC_H -#define REGS_CRC_H - -#include "register.h" - -class CRC { -public: - class DR : public Register32 { - }; - - class CR : Register32 { - public: - REGS_BOOL_FIELD(RESET, 0); - }; - - constexpr CRC() {}; - REGS_REGISTER_AT(DR, 0x00); - REGS_REGISTER_AT(CR, 0x08); -private: - constexpr uint32_t Base() const { - return 0x40023000; - }; -}; - -constexpr CRC CRC; - -#endif diff --git a/ion/src/f730/regs/dma.h b/ion/src/f730/regs/dma.h deleted file mode 100644 index de2acc702..000000000 --- a/ion/src/f730/regs/dma.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef REGS_DMA_H -#define REGS_DMA_H - -#include "register.h" - -class DMA { -public: - class LIFCR : public Register32 { - }; - class SCR : Register32 { - public: - enum class Burst : uint8_t { - Single = 0, - Incremental4 = 1, - Incremental8 = 2, - Incremental16 = 3 - }; - enum class DataSize : uint8_t { - Byte = 0, - HalfWord = 1, - Word = 2 - }; - enum class Direction : uint8_t { - PeripheralToMemory = 0, - MemoryToPeripheral = 1, - MemoryToMemory = 2 - }; - REGS_FIELD(CHSEL, uint8_t, 27, 25); - REGS_FIELD(MBURST, Burst, 24, 23); - REGS_FIELD(PBURST, Burst, 22, 21); - REGS_FIELD(MSIZE, DataSize, 14, 13); - REGS_FIELD(PSIZE, DataSize, 12, 11); - REGS_BOOL_FIELD(MINC, 10); - REGS_BOOL_FIELD(PINC, 9); - REGS_BOOL_FIELD(CIRC, 8); - REGS_FIELD(DIR, Direction, 7, 6); - REGS_BOOL_FIELD(EN, 0); - }; - class SNDTR : public Register32 { - }; - class SPAR : public Register32 { - }; - class SM0AR : public Register32 { - }; - - constexpr DMA(int i) : m_index(i) {} - //constexpr operator int() const { return m_index; } - REGS_REGISTER_AT(LIFCR, 0x08); - volatile SCR * SCR(int i ) const { return (class SCR *)(Base() + 0x10 + 0x18*i); }; - volatile SNDTR * SNDTR(int i ) const { return (class SNDTR *)(Base() + 0x14 + 0x18*i); }; - volatile SPAR * SPAR(int i ) const { return (class SPAR *)(Base() + 0x18 + 0x18*i); }; - volatile SM0AR * SM0AR(int i ) const { return (class SM0AR *)(Base() + 0x1C + 0x18*i); }; -private: - constexpr uint32_t Base() const { - return 0x40026000 + 0x400*m_index; - }; - int m_index; -}; - -constexpr DMA DMA1(0); -constexpr DMA DMA2(1); - -#endif diff --git a/ion/src/f730/regs/exti.h b/ion/src/f730/regs/exti.h deleted file mode 100644 index 46ee8550b..000000000 --- a/ion/src/f730/regs/exti.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef REGS_EXTI_H -#define REGS_EXTI_H - -#include "register.h" - -class EXTI { -public: - class MaskRegister : Register32 { - public: - bool get(int index) { return (bool)getBitRange(index, index); } - void set(int index, bool state) volatile { setBitRange(index, index, state); } - }; - - class IMR : public MaskRegister { }; - class EMR : public MaskRegister { }; - class RTSR : public MaskRegister { }; - class FTSR : public MaskRegister { }; - class PR : public MaskRegister { }; - - constexpr EXTI() {}; - REGS_REGISTER_AT(IMR, 0x00); - REGS_REGISTER_AT(EMR, 0x04); - REGS_REGISTER_AT(RTSR, 0x08); - REGS_REGISTER_AT(FTSR, 0x0C); - REGS_REGISTER_AT(PR, 0x14); -private: - constexpr uint32_t Base() const { - return 0x40013C00; - } -}; - -constexpr EXTI EXTI; - -#endif diff --git a/ion/src/f730/regs/flash.h b/ion/src/f730/regs/flash.h deleted file mode 100644 index 29fed1e84..000000000 --- a/ion/src/f730/regs/flash.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef REGS_FLASH_H -#define REGS_FLASH_H - -#include "register.h" - -class FLASH { -public: - class ACR : public Register32 { - public: - REGS_FIELD(LATENCY, uint8_t, 3, 0); - REGS_BOOL_FIELD(PRFTEN, 8); - REGS_BOOL_FIELD(ARTEN, 9); - REGS_BOOL_FIELD(ARTRST, 11); - }; - - class KEYR : public Register32 { - }; - - class CR : public Register32 { - public: - enum class PSIZE : uint8_t { - X8 = 0, - X16 = 1, - X32 = 2, - X64 = 3 - }; - REGS_BOOL_FIELD(PG, 0); - REGS_BOOL_FIELD(SER, 1); - REGS_BOOL_FIELD(MER, 2); - REGS_FIELD(SNB, uint8_t, 6, 3); - REGS_TYPE_FIELD(PSIZE, 9, 8); - REGS_BOOL_FIELD(STRT, 16); - REGS_BOOL_FIELD(LOCK, 31); - }; - - class SR : public Register32 { - public: - REGS_BOOL_FIELD(BSY, 16); - }; - - constexpr FLASH() {}; - REGS_REGISTER_AT(ACR, 0x00); - REGS_REGISTER_AT(KEYR, 0x04); - REGS_REGISTER_AT(SR, 0x0C); - REGS_REGISTER_AT(CR, 0x10); -private: - constexpr uint32_t Base() const { - return 0x40023C00; - } -}; - -constexpr FLASH FLASH; - -#endif diff --git a/ion/src/f730/regs/fmc.h b/ion/src/f730/regs/fmc.h deleted file mode 100644 index 302c5be74..000000000 --- a/ion/src/f730/regs/fmc.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef REGS_FMC_H -#define REGS_FMC_H - -#include "register.h" - -class FMC { -public: - class BCR : Register32 { - public: - enum class MTYP : uint8_t { - SRAM = 0, - PSRAM = 1, - NOR = 2 - }; - enum class MWID : uint8_t { - EIGHT_BITS = 0, - SIXTEEN_BITS = 1 - }; - REGS_BOOL_FIELD(MBKEN, 0); - REGS_BOOL_FIELD(MUXEN, 1); - REGS_TYPE_FIELD(MTYP, 3, 2); - REGS_TYPE_FIELD(MWID, 5, 4); - REGS_BOOL_FIELD(WREN, 12); - REGS_BOOL_FIELD(EXTMOD, 14); - }; - - class BTR : Register32 { - public: - enum class ACCMOD : uint8_t { - A = 0, - B = 1, - C = 2, - D = 3 - }; - REGS_FIELD(ADDSET, uint8_t, 3, 0); - REGS_FIELD(ADDHLD, uint8_t, 7, 4); - REGS_FIELD(DATAST, uint8_t, 15, 8); - REGS_FIELD(BUSTURN, uint8_t, 19, 16); - REGS_FIELD(CLKDIV, uint8_t, 23, 20); - REGS_FIELD(DATLAT, uint8_t, 27, 24); - REGS_TYPE_FIELD(ACCMOD, 29, 28); - }; - - class BWTR : Register32 { - public: - enum class ACCMOD : uint8_t { - A = 0, - B = 1, - C = 2, - D = 3 - }; - REGS_FIELD(ADDSET, uint8_t, 3, 0); - REGS_FIELD(ADDHLD, uint8_t, 7, 4); - REGS_FIELD(DATAST, uint8_t, 15, 8); - REGS_FIELD(BUSTURN, uint8_t, 19, 16); - REGS_FIELD(CLKDIV, uint8_t, 23, 20); - REGS_FIELD(DATLAT, uint8_t, 27, 24); - REGS_TYPE_FIELD(ACCMOD, 29, 28); - }; - - constexpr FMC() {} - volatile BCR * BCR(int index) const { - return (class BCR *)(Base() + 8*(index-1)); - } - volatile BTR * BTR(int index) const { - return (class BTR *)(Base() + 4 + 8*(index-1)); - } - volatile BWTR * BWTR(int index) const { - return (class BWTR *)(Base() + 0x104 + 8*(index-1)); - } -private: - constexpr uint32_t Base() const { - return 0xA0000000; - }; -}; - -constexpr FMC FMC; - -#endif diff --git a/ion/src/f730/regs/gpio.h b/ion/src/f730/regs/gpio.h deleted file mode 100644 index bf7300295..000000000 --- a/ion/src/f730/regs/gpio.h +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef REGS_GPIO_H -#define REGS_GPIO_H - -#include "register.h" - -class GPIO { -public: - class MODER : public Register32 { - public: - enum class Mode { - Input = 0, - Output = 1, - AlternateFunction = 2, - Analog = 3 - }; - Mode getMode(int index) { return (Mode)getBitRange(2*index+1, 2*index); } - void setMode(int index, Mode mode) volatile { setBitRange(2*index+1, 2*index, (uint32_t)mode); } - }; - - class OTYPER : Register32 { - public: - enum class Type { - PushPull = 0, - OpenDrain = 1 - }; - Type getType(int index) { return (Type)getBitRange(index, index); } - void setType(int index, Type type) volatile { setBitRange(index, index, (uint32_t)type); } - }; - - class OSPEEDR : Register32 { - // Datasheet, page 120, table 58 - public: - enum class OutputSpeed { - Low = 0, - Medium = 1, - Fast = 2, - High = 3 - }; - OutputSpeed getOutputSpeed(int index) { return (OutputSpeed)getBitRange(2*index+1, 2*index); } - void setOutputSpeed(int index, OutputSpeed speed) volatile { setBitRange(2*index+1, 2*index, (uint32_t)speed); } - }; - - class PUPDR : public Register32 { - public: - enum class Pull { - None = 0, - Up = 1, - Down = 2, - Reserved = 3 - }; - Pull getPull(int index) { return (Pull)getBitRange(2*index+1, 2*index); } - void setPull(int index, Pull pull) volatile { setBitRange(2*index+1, 2*index, (uint32_t)pull); } - }; - - class IDR : public Register32 { - public: - bool get(int index) volatile { return (bool)getBitRange(index, index); } - }; - - class ODR : public Register32 { - public: - bool get(int index) volatile { return (bool)getBitRange(index, index); } - void set(int index, bool state) volatile { setBitRange(index, index, state); } - }; - - class AFR : Register64 { - /* The AFR register doesn't exist per-se in the documentation. It is the - * consolidation of AFRL and AFRH which are two 32 bits registers. */ - public: - enum class AlternateFunction { - AF0 = 0, AF1 = 1, AF2 = 2, AF3 = 3, - AF4 = 4, AF5 = 5, AF6 = 6, AF7 = 7, - AF8 = 8, AF9 = 9, AF10 = 10, AF11 = 11, - AF12 = 12, AF13 = 13, AF14 = 14, AF15 = 15 - }; - AlternateFunction getAlternateFunction(int index) { - return (AlternateFunction)getBitRange(4*index+3, 4*index); - } - void setAlternateFunction(int index, AlternateFunction af) volatile { - setBitRange(4*index+3, 4*index, (uint64_t)af); - } - }; - - constexpr GPIO(int i) : m_index(i) {} - constexpr operator int() const { return m_index; } - REGS_REGISTER_AT(MODER, 0x00); - REGS_REGISTER_AT(OTYPER, 0x04); - REGS_REGISTER_AT(OSPEEDR, 0x08); - REGS_REGISTER_AT(PUPDR, 0x0C); - REGS_REGISTER_AT(IDR, 0x10); - REGS_REGISTER_AT(ODR, 0x14); - REGS_REGISTER_AT(AFR, 0x20); -private: - constexpr uint32_t Base() const { - return 0x40020000 + 0x400*m_index; - }; - int m_index; -}; - -constexpr GPIO GPIOA(0); -constexpr GPIO GPIOB(1); -constexpr GPIO GPIOC(2); -constexpr GPIO GPIOD(3); -constexpr GPIO GPIOE(4); -constexpr GPIO GPIOF(5); -constexpr GPIO GPIOG(6); -constexpr GPIO GPIOH(7); - -class GPIOPin { -public: - constexpr GPIOPin(GPIO group, uint8_t pin) : m_data(((group&0xF) << 4) | (pin&0xF)) {} - GPIO group() const { return GPIO(m_data>>4); } - uint8_t pin() const { return m_data & 0xF; } -private: - uint8_t m_data; -}; - -#endif diff --git a/ion/src/f730/regs/itm.h b/ion/src/f730/regs/itm.h deleted file mode 100644 index 196737ca3..000000000 --- a/ion/src/f730/regs/itm.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef REGS_ITM_H -#define REGS_ITM_H - -#include "register.h" - -// See ARM Cortex M4 TRM - -class ITM { -public: - class STIM : public Register8 { - }; - - // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0314h/Chdbicac.html - class TER : Register32 { - public: - bool get(int index) volatile { return (bool)getBitRange(index, index); } - }; - - constexpr ITM() {}; - volatile STIM * STIM(int i) const { - return (class STIM *)(Base() + 4*i); - }; - REGS_REGISTER_AT(TER, 0xE00); -private: - constexpr uint32_t Base() const { - return 0xE0000000; - } -}; - -constexpr ITM ITM; - -#endif diff --git a/ion/src/f730/regs/mpu.h b/ion/src/f730/regs/mpu.h deleted file mode 100644 index 2d82a5e10..000000000 --- a/ion/src/f730/regs/mpu.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef REGS_MPU_H -#define REGS_MPU_H - -#include "register.h" - -class MPU { -public: - class TYPER : Register32 { - public: - REGS_FIELD_R(IREGION, uint8_t, 23, 16); - REGS_FIELD_R(DREGION, uint8_t, 15, 8); - REGS_BOOL_FIELD_R(SEPARATE, 0); - }; - - class CTRL : Register32 { - public: - REGS_BOOL_FIELD(PRIVDEFENA, 2); - REGS_BOOL_FIELD(HFNMIENA, 1); - REGS_BOOL_FIELD(ENABLE, 0); - }; - - class RNR : Register32 { - public: - REGS_FIELD(REGION, uint8_t, 7, 0); - }; - - class RBAR : Register32 { - public: - void setADDR(uint32_t address) volatile { /* assert((address & 0b11111) == 0);*/ setBitRange(31, 5, address >> 5); } - REGS_BOOL_FIELD(VALID, 4); - REGS_FIELD(REGION, uint8_t, 3, 0); - }; - - class RASR : Register32 { - public: - REGS_BOOL_FIELD(XN, 28); - enum class AccessPermission : uint8_t { - NoAccess = 0, - PrivilegedRO = 5, - PrivilegedRW = 1, - PrivilegedRWUnprivilegedRO = 2, - RO = 6, - RW = 3 - }; - REGS_FIELD(AP, AccessPermission, 26, 24); - REGS_FIELD(TEX, uint8_t, 21, 19); - REGS_BOOL_FIELD(S, 18); // Shareable - REGS_BOOL_FIELD(C, 17); // Cacheable - REGS_BOOL_FIELD(B, 16); // Buffereable - REGS_FIELD(SRD, uint8_t, 15, 8); - enum class RegionSize : uint8_t { - Bytes32 = 0b00100, - Bytes64 = 0b00101, - Bytes128 = 0b00110, - KyloBytes1 = 0b01001, - MegaBytes1 = 0b10011, - _1MB = 19, - _32MB = 24, - GigaBytes1 = 0b11101, - GigaBytes4 = 0b11111 - }; - - REGS_FIELD(SIZE, RegionSize, 5, 1); - REGS_BOOL_FIELD(ENABLE, 0); - }; - - constexpr MPU() {}; - REGS_REGISTER_AT(TYPER, 0x0); - REGS_REGISTER_AT(CTRL, 0x04); - REGS_REGISTER_AT(RNR, 0x08); - REGS_REGISTER_AT(RBAR, 0x0C); - REGS_REGISTER_AT(RASR, 0x10); -private: - constexpr uint32_t Base() const { - return 0xE000ED90; - }; -}; - -constexpr MPU MPU; - -#endif diff --git a/ion/src/f730/regs/nvic.h b/ion/src/f730/regs/nvic.h deleted file mode 100644 index dd72ea0d5..000000000 --- a/ion/src/f730/regs/nvic.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef REGS_NVIC_H -#define REGS_NVIC_H - -#include "register.h" - -// http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf -class NVIC { -public: - class MaskRegister : Register32 { - public: - bool get(int index) { return (bool)getBitRange(index, index); } - void set(int index, bool state) volatile { setBitRange(index, index, state); } - }; - - class NVIC_ISER0 : public MaskRegister { }; - class NVIC_ISER1 : public MaskRegister { }; - class NVIC_ISER2 : public MaskRegister { }; - class NVIC_ICER0 : public MaskRegister { }; - class NVIC_ICER1 : public MaskRegister { }; - class NVIC_ICER2 : public MaskRegister { }; - - constexpr NVIC() {}; - REGS_REGISTER_AT(NVIC_ISER0, 0x00); - REGS_REGISTER_AT(NVIC_ISER1, 0x04); - REGS_REGISTER_AT(NVIC_ISER2, 0x08); - REGS_REGISTER_AT(NVIC_ICER0, 0x80); - REGS_REGISTER_AT(NVIC_ICER1, 0x84); - REGS_REGISTER_AT(NVIC_ICER2, 0x88); -private: - constexpr uint32_t Base() const { - return 0xE000E100; - } -}; - -constexpr NVIC NVIC; - -#endif diff --git a/ion/src/f730/regs/otg.h b/ion/src/f730/regs/otg.h deleted file mode 100644 index 5c4f0c89e..000000000 --- a/ion/src/f730/regs/otg.h +++ /dev/null @@ -1,195 +0,0 @@ -#ifndef REGS_OTG_H -#define REGS_OTG_H - -#include "register.h" - -class OTG { -public: - class GAHBCFG : public Register32 { - public: - REGS_BOOL_FIELD(GINTMSK, 0); - }; - - class GUSBCFG : public Register32 { - public: - REGS_BOOL_FIELD(PHYSEL, 6); - REGS_FIELD(TRDT, uint8_t, 13, 10); - REGS_BOOL_FIELD(FDMOD, 30); - }; - - class GRSTCTL : public Register32 { - public: - REGS_BOOL_FIELD(CSRST, 0); - REGS_BOOL_FIELD(RXFFLSH, 4); - REGS_BOOL_FIELD(TXFFLSH, 5); - REGS_FIELD(TXFNUM, uint8_t, 10, 6); - REGS_BOOL_FIELD(AHBIDL, 31); - }; - - class GINTSTS : public Register32 { - public: - using Register32::Register32; - REGS_BOOL_FIELD(MMIS, 1); - REGS_BOOL_FIELD(SOF, 3); - REGS_BOOL_FIELD(RXFLVL, 4); - REGS_BOOL_FIELD(USBSUSP, 11); - REGS_BOOL_FIELD(USBRST, 12); - REGS_BOOL_FIELD(ENUMDNE, 13); - REGS_BOOL_FIELD(IEPINT, 18); - REGS_BOOL_FIELD(WKUPINT, 31); - }; - - class GINTMSK : public Register32 { - public: - using Register32::Register32; - REGS_BOOL_FIELD(RXFLVLM, 4); - REGS_BOOL_FIELD(USBSUSPM, 11); - REGS_BOOL_FIELD(USBRST, 12); - REGS_BOOL_FIELD(ENUMDNEM, 13); - REGS_BOOL_FIELD(IEPINT, 18); - REGS_BOOL_FIELD(WUIM, 31); - }; - - class GRXSTSP : public Register32 { - public: - using Register32::Register32; - enum class PKTSTS { - GlobalOutNAK = 1, - OutReceived = 2, - OutTransferCompleted = 3, // After each Out Transaction - SetupTransactionCompleted = 4, // Supposed to be after each SETUP transaction - SetupReceived = 6 - }; - REGS_FIELD(EPNUM, uint8_t, 3, 0); - REGS_FIELD(BCNT, uint16_t, 14, 4); - PKTSTS getPKTSTS() volatile { return (PKTSTS)getBitRange(20, 17); } - }; - - class GRXFSIZ : public Register32 { - public: - REGS_FIELD(RXFD, uint16_t, 15, 0); - }; - - class DIEPTXF0 : public Register32 { - public: - REGS_FIELD(TX0FSA, uint16_t, 15, 0); - REGS_FIELD(TX0FD, uint16_t, 31, 16); - }; - - class GCCFG : public Register32 { - public: - REGS_BOOL_FIELD(PWRDWN, 16); - REGS_BOOL_FIELD(VBDEN, 21); - }; - - class DCFG : public Register32 { - public: - enum class DSPD { - FullSpeed = 3, - }; - void setDSPD(DSPD s) volatile { setBitRange(1, 0, (uint8_t)s); } - REGS_FIELD(DAD, uint8_t, 10, 4); - }; - - class DCTL : public Register32 { - public: - REGS_BOOL_FIELD(SDIS, 1); - }; - - class DIEPMSK : public Register32 { - public: - REGS_BOOL_FIELD(XFRCM, 0); - }; - - class DAINTMSK : public Register32 { - public: - REGS_FIELD(IEPM, uint16_t, 15, 0); - REGS_FIELD(OEPM, uint16_t, 31, 16); - }; - - class DIEPCTL0 : public Register32 { - public: - enum class MPSIZ { - Size64 = 0, - Size32 = 1, - Size16 = 2, - Size8 = 3 - }; - using Register32::Register32; - void setMPSIZ(MPSIZ s) volatile { setBitRange(1, 0, (uint8_t)s); } - REGS_BOOL_FIELD(STALL, 21); - REGS_FIELD(TXFNUM, uint8_t, 25, 22); - REGS_BOOL_FIELD(CNAK, 26); - REGS_BOOL_FIELD(SNAK, 27); - REGS_BOOL_FIELD(EPENA, 31); - }; - - class DOEPCTL0 : public Register32 { - public: - REGS_BOOL_FIELD(CNAK, 26); - REGS_BOOL_FIELD(SNAK, 27); - REGS_BOOL_FIELD(EPENA, 31); - }; - - class DIEPINT : public Register32 { - public: - REGS_BOOL_FIELD(XFRC, 0); - REGS_BOOL_FIELD(INEPNE, 6); - }; - - class DIEPTSIZ0 : public Register32 { - public: - using Register32::Register32; - REGS_FIELD(XFRSIZ, uint8_t, 6, 0); - REGS_FIELD(PKTCNT, uint8_t, 20, 19); - }; - - class DOEPTSIZ0 : public Register32 { - public: - using Register32::Register32; - REGS_FIELD(XFRSIZ, uint8_t, 6, 0); - REGS_BOOL_FIELD(PKTCNT, 19); - REGS_FIELD(STUPCNT, uint8_t, 30, 29); - }; - - class PCGCCTL : public Register32 { - public: - REGS_BOOL_FIELD(STPPCLK, 0); - REGS_BOOL_FIELD(GATEHCLK, 1); - }; - - class DFIFO0 : public Register32 { - }; - - constexpr OTG() {}; - REGS_REGISTER_AT(GAHBCFG, 0x008); - REGS_REGISTER_AT(GUSBCFG, 0x00C); - REGS_REGISTER_AT(GRSTCTL, 0x010); - REGS_REGISTER_AT(GINTSTS, 0x014); - REGS_REGISTER_AT(GINTMSK, 0x018); - REGS_REGISTER_AT(GRXSTSP, 0x020); - REGS_REGISTER_AT(GRXFSIZ, 0x024); - REGS_REGISTER_AT(DIEPTXF0, 0x28); - REGS_REGISTER_AT(GCCFG, 0x038); - REGS_REGISTER_AT(DCFG, 0x800); - REGS_REGISTER_AT(DCTL, 0x804); - REGS_REGISTER_AT(DIEPMSK, 0x810); - REGS_REGISTER_AT(DAINTMSK, 0x81C); - REGS_REGISTER_AT(DIEPCTL0, 0x900); - REGS_REGISTER_AT(DIEPTSIZ0, 0x910); - REGS_REGISTER_AT(DOEPCTL0, 0xB00); - REGS_REGISTER_AT(DOEPTSIZ0, 0xB10); - REGS_REGISTER_AT(PCGCCTL, 0xE00); - REGS_REGISTER_AT(DFIFO0, 0x1000); - constexpr volatile DIEPINT * DIEPINT(int i) const { - return (class DIEPINT *)(Base() + 0x908 + i*0x20); - } -private: - constexpr uint32_t Base() const { - return 0x50000000; - } -}; - -constexpr OTG OTG; - -#endif diff --git a/ion/src/f730/regs/pwr.h b/ion/src/f730/regs/pwr.h deleted file mode 100644 index f3cef32d7..000000000 --- a/ion/src/f730/regs/pwr.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef REGS_PWR_H -#define REGS_PWR_H - -#include "register.h" - -class PWR { -public: - class CR1 : Register32 { - public: - enum class UnderDrive { - Disable = 0, - Enable = 3 - }; - REGS_BOOL_FIELD(LPDS, 0); - REGS_BOOL_FIELD(PPDS, 1); - REGS_BOOL_FIELD(FPDS, 9); - REGS_BOOL_FIELD(LPUDS, 10); - REGS_BOOL_FIELD(MRUDS, 11); - REGS_FIELD_W(UDEN, UnderDrive, 19, 18); - }; - - constexpr PWR() {}; - REGS_REGISTER_AT(CR1, 0x00); -private: - constexpr uint32_t Base() const { - return 0x40007000; - }; -}; - -constexpr PWR PWR; - -#endif diff --git a/ion/src/f730/regs/quadspi.h b/ion/src/f730/regs/quadspi.h deleted file mode 100644 index 18d6c81f0..000000000 --- a/ion/src/f730/regs/quadspi.h +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef REGS_QUADSPI_H -#define REGS_QUADSPI_H - -#include "register.h" - -// Quad-SPI register map on STM32 microcontroller -// See section 12 of the STM32F412 reference manual - -class QUADSPI { -public: - class CR : public Register32 { // Control register - public: - using Register32::Register32; - REGS_BOOL_FIELD(EN, 0); - REGS_BOOL_FIELD(TCEN, 3); // Lower-power timeout counter enable in memory-mapped mode - REGS_BOOL_FIELD(SSHIFT, 4); - REGS_FIELD(PRESCALER, uint8_t, 31, 24); - }; - - class DCR : public Register32 { // Device configuration register - public: - using Register32::Register32; - REGS_BOOL_FIELD(CKMODE, 0); // MODE 0 or 3 - REGS_FIELD(CSHT, uint8_t, 10, 8); // Chip select minimum high time - REGS_FIELD(FSIZE, uint8_t, 20, 16); - }; - - class SR : Register32 { // Status register - public: - REGS_BOOL_FIELD(TEF, 0); // Transfer error flag - REGS_BOOL_FIELD(TCF, 1); // Transfer complete flag - REGS_BOOL_FIELD(BUSY, 5); - }; - - class FCR : Register32 { // Flag clear register - }; - - class DLR : public Register32 { // Data length register - // In indirect and status-polling modes - // Number of bytes to be transferred = DLR + 1 - }; - - class CCR : public Register32 { // Communication configuration register - public: - using Register32::Register32; - enum class Size : uint8_t { - OneByte = 0, - TwoBytes = 1, - ThreeBytes = 2, - FourBytes = 3 - }; - enum class OperatingMode : uint8_t { - NoData = 0, - Single = 1, - Dual = 2, - Quad = 3 - }; - enum class FunctionalMode : uint8_t { - IndirectWrite = 0, - IndirectRead = 1, - StatusPolling = 2, - MemoryMapped = 3 - }; - REGS_FIELD(INSTRUCTION, uint8_t, 7, 0); - REGS_FIELD(IMODE, OperatingMode, 9, 8); - REGS_FIELD(ADMODE, OperatingMode, 11, 10); - REGS_FIELD(ADSIZE, Size, 13, 12); - REGS_FIELD(ABMODE, OperatingMode, 15, 14); - REGS_FIELD(ABSIZE, Size, 17, 16); - REGS_FIELD(DCYC, uint8_t, 22, 18); - REGS_FIELD(DMODE, OperatingMode, 25, 24); - REGS_FIELD(FMODE, FunctionalMode, 27, 26); - REGS_BOOL_FIELD(SIOO, 28); - }; - - class AR : public Register32 { // Address register - }; - - class ABR : public Register32 { // Alternate bytes register - }; - - class DR : public Register8 { // Data register - /* In indirect (read or write) mode, any data must be written to or read from - * the data register, which supports byte, halfword and word access - * (aligned to the bottom of the register). - * N bytes read/written remove/add N bytes from/to the FIFO. - */ - }; - - class PSMKR : Register32 { // Polling status mask register - }; - - class PSMAR : Register32 { // Polling status match register - }; - - class PIR : Register32 { // Polling interval register - }; - - class LPTR : public Register16 { // Low-power timeout register - // Period before putting the Flash memory in lower-power state (in memory-mapped mode) - }; - - constexpr QUADSPI() {}; - - REGS_REGISTER_AT(CR, 0x00); - REGS_REGISTER_AT(DCR, 0x04); - REGS_REGISTER_AT(SR, 0x08); - REGS_REGISTER_AT(FCR, 0x0C); - REGS_REGISTER_AT(DLR, 0x10); - REGS_REGISTER_AT(CCR, 0x14); - REGS_REGISTER_AT(AR, 0x18); - REGS_REGISTER_AT(ABR, 0x1C); - REGS_REGISTER_AT(DR, 0x20); - REGS_REGISTER_AT(PSMKR, 0x24); - REGS_REGISTER_AT(PSMAR, 0x28); - REGS_REGISTER_AT(PIR, 0x2C); - REGS_REGISTER_AT(LPTR, 0x30); -private: - constexpr uint32_t Base() const { - return 0xA0001000; - } -}; - -constexpr QUADSPI QUADSPI; - -#endif diff --git a/ion/src/f730/regs/rcc.h b/ion/src/f730/regs/rcc.h deleted file mode 100644 index 576dea281..000000000 --- a/ion/src/f730/regs/rcc.h +++ /dev/null @@ -1,150 +0,0 @@ -#ifndef REGS_RCC_H -#define REGS_RCC_H - -#include "register.h" - -class RCC { -public: - class CR : public Register32 { - public: - REGS_BOOL_FIELD(HSION, 0); - REGS_BOOL_FIELD(HSEON, 16); - REGS_BOOL_FIELD_R(HSERDY, 17); - REGS_BOOL_FIELD(PLLON, 24); - REGS_BOOL_FIELD(PLLRDY, 25); - }; - - class PLLCFGR : public Register32 { - public: - REGS_FIELD(PLLM, uint8_t, 5, 0); - REGS_FIELD(PLLN, uint16_t, 14, 6); - REGS_FIELD(PLLP, uint8_t, 17, 16); - enum class PLLSRC { - HSI = 0, - HSE = 1 - }; - void setPLLSRC(PLLSRC s) volatile { setBitRange(22, 22, (uint8_t)s); } - REGS_FIELD(PLLQ, uint8_t, 27, 24); - REGS_FIELD(PLLR, uint8_t, 30, 28); - }; - - class CFGR : public Register32 { - public: - enum class SW { - HSI = 0, - HSE = 1, - PLL = 2 - }; - void setSW(SW s) volatile { setBitRange(1, 0, (uint8_t)s); } - SW getSWS() volatile { return (SW)getBitRange(3,2); } - enum class AHBPrescaler { - SysClk = 0, - SysClkDividedBy2 = 8, - SysClkDividedBy4 = 9, - SysClkDividedBy8 = 10, - SysClkDividedBy16 = 11, - SysClkDividedBy64 = 12, - SysClkDividedBy128 = 13, - SysClkDividedBy256 = 14, - SysClkDividedBy512 = 15 - }; - void setHPRE(AHBPrescaler p) volatile { setBitRange(7, 4, (uint32_t)p); } - enum class APBPrescaler{ - AHB = 0, - AHBDividedBy2 = 4, - AHBDividedBy4 = 5, - AHBDividedBy8 = 6, - AHBDividedBy16 = 7 - }; - void setPPRE1(APBPrescaler r) volatile { setBitRange(12, 10, (uint32_t)r); } - void setPPRE2(APBPrescaler r) volatile { setBitRange(15, 13, (uint32_t)r); } - }; - - class AHB1ENR : public Register32 { - public: - using Register32::Register32; - REGS_BOOL_FIELD(GPIOAEN, 0); - REGS_BOOL_FIELD(GPIOBEN, 1); - REGS_BOOL_FIELD(GPIOCEN, 2); - REGS_BOOL_FIELD(GPIODEN, 3); - REGS_BOOL_FIELD(GPIOEEN, 4); - REGS_BOOL_FIELD(GPIOFEN, 5); - REGS_BOOL_FIELD(GPIOGEN, 6); - REGS_BOOL_FIELD(GPIOHEN, 7); - REGS_BOOL_FIELD(CRCEN, 12); - REGS_BOOL_FIELD(DMA1EN, 21); - REGS_BOOL_FIELD(DMA2EN, 22); - }; - - class AHB2ENR : Register32 { - public: - REGS_BOOL_FIELD(RNGEN, 6); - REGS_BOOL_FIELD(OTGFSEN, 7); - }; - - class AHB3ENR : Register32 { - public: - REGS_BOOL_FIELD(FMCEN, 0); - REGS_BOOL_FIELD(QSPIEN, 1); - }; - - class APB1ENR : public Register32 { - public: - using Register32::Register32; - REGS_BOOL_FIELD(TIM3EN, 1); - REGS_BOOL_FIELD(SPI3EN, 15); - REGS_BOOL_FIELD(USART3EN, 18); - REGS_BOOL_FIELD(PWREN, 28); - }; - - class APB2ENR : public Register32 { - public: - using Register32::Register32; - REGS_BOOL_FIELD(TIM1EN, 0); - REGS_BOOL_FIELD(USART1EN, 4); - REGS_BOOL_FIELD(ADC1EN, 8); - REGS_BOOL_FIELD(SDMMC1EN, 11); - REGS_BOOL_FIELD(SPI1EN, 12); - REGS_BOOL_FIELD(SYSCFGEN, 14); - }; - - class AHB1LPENR : public Register32 { - public: - using Register32::Register32; - REGS_BOOL_FIELD(GPIOBLPEN, 1); - REGS_BOOL_FIELD(GPIOCLPEN, 2); - }; - - class APB1LPENR : public Register32 { - public: - using Register32::Register32; - REGS_BOOL_FIELD(TIM3LPEN, 1); - }; - - class DCKCFGR2 : Register32 { - public: - REGS_BOOL_FIELD(CK48MSEL, 27); - REGS_BOOL_FIELD(CKSDIOSEL, 28); - }; - - constexpr RCC() {}; - REGS_REGISTER_AT(CR, 0x00); - REGS_REGISTER_AT(PLLCFGR, 0x04); - REGS_REGISTER_AT(CFGR, 0x08); - REGS_REGISTER_AT(AHB1ENR, 0x30); - REGS_REGISTER_AT(AHB2ENR, 0x34); - REGS_REGISTER_AT(AHB3ENR, 0x38); - REGS_REGISTER_AT(APB1ENR, 0x40); - REGS_REGISTER_AT(APB2ENR, 0x44); - REGS_REGISTER_AT(AHB1LPENR, 0x50); - REGS_REGISTER_AT(APB1LPENR, 0x60); - REGS_REGISTER_AT(DCKCFGR2, 0x94); -private: - constexpr uint32_t Base() const { - return 0x40023800; - } -}; - -constexpr RCC RCC; - -#endif diff --git a/ion/src/f730/regs/register.h b/ion/src/f730/regs/register.h deleted file mode 100644 index 5ed38d779..000000000 --- a/ion/src/f730/regs/register.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef REGS_REGISTER_H -#define REGS_REGISTER_H - -#include -#include - -template -class Register { -public: - Register() = delete; - Register(T v) : m_value(v) {} - void set(Register value) volatile { - m_value = value.m_value; - } - void set(T value) volatile { - m_value = value; - } - T get() volatile { - return m_value; - } - void setBitRange(uint8_t high, uint8_t low, T value) volatile { - m_value = bit_range_set_value(high, low, m_value, value); - } - T getBitRange(uint8_t high, uint8_t low) volatile { - /* "Shift behavior is undefined if the right operand is negative, or greater - * than or equal to the length in bits of the promoted left operand" according - * to C++ spec. */ - assert(low < 8*sizeof(T)); - return (m_value & bit_range_mask(high,low)) >> low; - } -protected: - static constexpr T bit_range_mask(uint8_t high, uint8_t low) { - // Same comment as for getBitRange: we should assert (high-low+1) < 8*sizeof(T) - return ((((T)1)<<(high-low+1))-1)< Register8; -typedef Register Register16; -typedef Register Register32; -typedef Register Register64; - -#define REGS_FIELD_R(name,type,high,low) type get##name() volatile { return (type)getBitRange(high,low); }; -#define REGS_FIELD_W(name,type,high,low) void set##name(type v) volatile { static_assert(sizeof(type) <= 4, "Invalid size"); setBitRange(high, low, static_cast(v)); }; -#define REGS_FIELD(name,type,high,low) REGS_FIELD_R(name,type,high,low); REGS_FIELD_W(name,type,high,low); -#define REGS_TYPE_FIELD(name,high,low) REGS_FIELD(name,name,high,low) -#define REGS_BOOL_FIELD(name,bit) REGS_FIELD(name,bool,bit,bit) -#define REGS_BOOL_FIELD_R(name,bit) REGS_FIELD_R(name,bool,bit,bit) -#define REGS_BOOL_FIELD_W(name,bit) REGS_FIELD_W(name,bool,bit,bit) -#define REGS_REGISTER_AT(name, offset) constexpr volatile name * name() const { return (class name *)(Base() + offset); }; - -#endif diff --git a/ion/src/f730/regs/regs.h b/ion/src/f730/regs/regs.h deleted file mode 100644 index cfa644dd7..000000000 --- a/ion/src/f730/regs/regs.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef REGS_REGS_H -#define REGS_REGS_H - -#include "adc.h" -#include "cm4.h" -#include "crc.h" -#include "dma.h" -#include "exti.h" -#include "flash.h" -#include "fmc.h" -#include "gpio.h" -#include "itm.h" -#include "mpu.h" -#include "nvic.h" -#include "pwr.h" -#include "rcc.h" -#include "rng.h" -#include "otg.h" -#include "quadspi.h" -#include "sdio.h" -#include "spi.h" -#include "syscfg.h" -#include "tim.h" -#include "usart.h" - -#endif diff --git a/ion/src/f730/regs/rng.h b/ion/src/f730/regs/rng.h deleted file mode 100644 index 6aa2d6505..000000000 --- a/ion/src/f730/regs/rng.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef REGS_RNG_H -#define REGS_RNG_H - -#include "register.h" - -class RNG { -public: - class CR : Register32 { - public: - REGS_BOOL_FIELD(RNGEN, 2); - }; - - class SR : Register32 { - public: - REGS_BOOL_FIELD(DRDY, 0); - }; - - class DR : public Register32 { - }; - - constexpr RNG() {}; - REGS_REGISTER_AT(CR, 0x00); - REGS_REGISTER_AT(SR, 0x04); - REGS_REGISTER_AT(DR, 0x08); -private: - constexpr uint32_t Base() const { - return 0x50060800; - } -}; - -constexpr RNG RNG; - -#endif diff --git a/ion/src/f730/regs/sdio.h b/ion/src/f730/regs/sdio.h deleted file mode 100644 index bb8149175..000000000 --- a/ion/src/f730/regs/sdio.h +++ /dev/null @@ -1,112 +0,0 @@ -#ifndef REGS_SDIO_H -#define REGS_SDIO_H - -#include "register.h" - -class SDIO { -public: - class POWER : Register32 { - public: - enum class PWRCTRL : uint8_t { - Off = 0, - On = 3 - }; - REGS_TYPE_FIELD(PWRCTRL, 1, 0); - }; - - class CLKCR : Register32 { - public: - enum class WIDBUS : uint8_t { - Default = 0, - FourBits = 1, - EightBits = 2 - }; - REGS_FIELD(CLKDIV, uint8_t, 7, 0); - REGS_BOOL_FIELD(CLKEN, 8); - REGS_BOOL_FIELD(PWRSAV, 9); - REGS_BOOL_FIELD(BYPASS, 10); - REGS_TYPE_FIELD(WIDBUS, 12, 11); - REGS_BOOL_FIELD(NEGEDGE, 13); - REGS_BOOL_FIELD(HWFC_EN, 14); - }; - - class ARG : public Register32 { - }; - - class CMD : public Register32 { - public: - using Register32::Register32; - enum class WAITRESP : uint8_t { - None = 0, - Short = 1, - Long = 3 - }; - REGS_FIELD(CMDINDEX, uint8_t, 5, 0); - REGS_TYPE_FIELD(WAITRESP, 7, 6); - REGS_BOOL_FIELD(WAITINT, 8); - REGS_BOOL_FIELD(WAITPEND, 9); - REGS_BOOL_FIELD(CPSMEN, 10); - }; - - class RESP : public Register32 { - }; - - class STA : Register32 { - public: - REGS_BOOL_FIELD_R(CCRCFAIL, 0); - REGS_BOOL_FIELD_R(DCRCFAIL, 1); - REGS_BOOL_FIELD_R(CTIMEOUT, 2); - REGS_BOOL_FIELD_R(DTIMEOUT, 3); - REGS_BOOL_FIELD_R(TXUNDERR, 4); - REGS_BOOL_FIELD_R(RXOVERR, 5); - REGS_BOOL_FIELD_R(CMDREND, 6); - REGS_BOOL_FIELD_R(CMDSENT, 7); - REGS_BOOL_FIELD_R(DATAEND, 8); - REGS_BOOL_FIELD_R(DBCKEND, 10); - REGS_BOOL_FIELD_R(CMDACT, 11); - REGS_BOOL_FIELD_R(TXACT, 12); - REGS_BOOL_FIELD_R(RXACT, 13); - REGS_BOOL_FIELD_R(TXFIFOHE, 14); - REGS_BOOL_FIELD_R(RXFIFOHF, 15); - REGS_BOOL_FIELD_R(TXFIFOF, 16); - REGS_BOOL_FIELD_R(RXFIFOF, 17); - REGS_BOOL_FIELD_R(TXFIFOE, 18); - REGS_BOOL_FIELD_R(RXFIFOE, 19); - REGS_BOOL_FIELD_R(TXDAVL, 20); - REGS_BOOL_FIELD_R(RXDAVL, 21); - REGS_BOOL_FIELD_R(SDIOIT, 22); - }; - - class ICR : public Register32 { - public: - using Register32::Register32; - REGS_BOOL_FIELD_W(CCRCFAILC, 0); - REGS_BOOL_FIELD_W(DCRCFAILC, 1); - REGS_BOOL_FIELD_W(CTIMEOUTC, 2); - REGS_BOOL_FIELD_W(DTIMEOUTC, 3); - REGS_BOOL_FIELD_W(TXUNDERRC, 4); - REGS_BOOL_FIELD_W(RXOVERRC, 5); - REGS_BOOL_FIELD_W(CMDRENDC, 6); - REGS_BOOL_FIELD_W(CMDSENTC, 7); - REGS_BOOL_FIELD_W(DATAENDC, 8); - REGS_BOOL_FIELD_W(DBCKENDC, 10); - REGS_BOOL_FIELD_W(SDIOITC, 22); - }; - - constexpr SDIO() {}; - REGS_REGISTER_AT(POWER, 0x00); - REGS_REGISTER_AT(CLKCR, 0x04); - REGS_REGISTER_AT(ARG, 0x08); - REGS_REGISTER_AT(CMD, 0x0C); - volatile RESP * RESP(int i ) const { return (class RESP *)(Base() + 0x10+4*i); }; - REGS_REGISTER_AT(STA, 0x34); - REGS_REGISTER_AT(ICR, 0x38); -private: - constexpr uint32_t Base() const { - return 0x40012C00; - } -}; - -constexpr SDIO SDIO; - -#endif diff --git a/ion/src/f730/regs/spi.h b/ion/src/f730/regs/spi.h deleted file mode 100644 index abe050b97..000000000 --- a/ion/src/f730/regs/spi.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef REGS_SPI_H -#define REGS_SPI_H - -#include "register.h" - -class SPI { -public: - class CR1 : Register16 { - public: - REGS_BOOL_FIELD(MSTR, 2); - REGS_BOOL_FIELD(SPE, 6); - REGS_BOOL_FIELD(LSBFIRST, 7); - REGS_BOOL_FIELD(SSI, 8); - REGS_BOOL_FIELD(SSM, 9); - REGS_BOOL_FIELD(RXONLY, 10); - REGS_BOOL_FIELD(DFF, 11); - }; - class CR2 : Register16 { - public: - REGS_BOOL_FIELD(RXDMAEN, 0); - }; - class SR : Register16 { - public: - REGS_BOOL_FIELD(RXNE, 0); - REGS_BOOL_FIELD(TXE, 1); - }; - class DR : public Register16 { - }; - - constexpr SPI(int i) : m_index(i) {} - constexpr operator int() const { return m_index; } - REGS_REGISTER_AT(CR1, 0x00); - REGS_REGISTER_AT(CR2, 0x04); - REGS_REGISTER_AT(SR, 0x08); - REGS_REGISTER_AT(DR, 0x0C); -private: - constexpr uint32_t Base() const { - return ((uint32_t []){0x40013000, 0x40003800, 0x40003C00})[m_index-1]; - }; - int m_index; -}; - -constexpr SPI SPI1(1); - -#endif diff --git a/ion/src/f730/regs/syscfg.h b/ion/src/f730/regs/syscfg.h deleted file mode 100644 index 0690b9430..000000000 --- a/ion/src/f730/regs/syscfg.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef REGS_SYSCFG_H -#define REGS_SYSCFG_H - -#include "register.h" - -#include "gpio.h" - -class SYSCFG { -public: - class MEMRMP : Register32 { - public: - enum class MemMode : uint8_t { - MainFlashmemory = 0, - SystemFlashmemory = 1, - EmbeddedSRAM = 3 - }; - REGS_FIELD(MEM_MODE, MemMode, 1, 0); - }; - class EXTICR1 : Register32 { - public: - void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*index+3, 4*index, (uint32_t)gpio); } - }; - class EXTICR2 : Register32 { - public: - void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*(index-4)+3, 4*(index-4), (uint32_t)gpio); } - }; - class EXTICR3 : Register32 { - public: - void setEXTI(int index, GPIO gpio) volatile { setBitRange(4*(index-8)+3, 4*(index-8), (uint32_t)gpio); } - }; - constexpr SYSCFG() {}; - REGS_REGISTER_AT(MEMRMP, 0x00); - REGS_REGISTER_AT(EXTICR1, 0x08); - REGS_REGISTER_AT(EXTICR2, 0x0C); - REGS_REGISTER_AT(EXTICR3, 0x10); -private: - constexpr uint32_t Base() const { - return 0x40013800; - } -}; - -constexpr SYSCFG SYSCFG; - -#endif diff --git a/ion/src/f730/regs/tim.h b/ion/src/f730/regs/tim.h deleted file mode 100644 index d76f8e293..000000000 --- a/ion/src/f730/regs/tim.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef REGS_TIM_H -#define REGS_TIM_H - -#include "register.h" - -template -class TIM { -public: - class CR1 : Register16 { - public: - REGS_BOOL_FIELD(CEN, 0); - REGS_BOOL_FIELD(ARPE, 7); - }; - - class CCMR : Register64 { - /* We're declaring CCMR as a 64 bits register. CCMR doesn't exsist per se, - * it is in fact the consolidation of CCMR1 and CCMR2. Both are 16 bits - * registers, so one could expect the consolidation to be 32 bits. However, - * both CCMR1 and CCMR2 live on 32-bits boundaries, so the consolidation has - * to be 64 bits wide, even though we'll only use 32 bits out of 64. */ - public: - enum class CC1S : uint8_t { - OUTPUT = 0, - INPUT_TI2 = 1, - INPUT_TI1 = 2, - INPUT_TRC = 3 - }; - enum class OCM : uint8_t { - Frozen = 0, - ActiveOnMatch = 1, - InactiveOnMatch = 2, - Toggle = 3, - ForceInactive = 4, - ForceActive = 5, - PWM1 = 6, - PWM2 = 7 - }; - typedef OCM OC1M; - typedef OCM OC2M; - typedef OCM OC3M; - typedef OCM OC4M; - REGS_BOOL_FIELD(OC1PE, 3); - REGS_TYPE_FIELD(OC1M, 6, 4); - REGS_BOOL_FIELD(OC2PE, 11); - REGS_TYPE_FIELD(OC2M, 14, 12); - REGS_BOOL_FIELD(OC3PE, 35); - REGS_TYPE_FIELD(OC3M, 38, 36); - REGS_BOOL_FIELD(OC4PE, 43); - REGS_TYPE_FIELD(OC4M, 46, 44); - }; - - class CCER : Register16 { - public: - REGS_BOOL_FIELD(CC1E, 0); - REGS_BOOL_FIELD(CC2E, 4); - REGS_BOOL_FIELD(CC3E, 8); - REGS_BOOL_FIELD(CC4E, 12); - }; - - class BDTR : Register16 { - public: - REGS_BOOL_FIELD(MOE, 15); - }; - - class PSC : public Register16 {}; - class ARR : public Register16 {}; - class CCR1 : public RegisterWidth {}; - class CCR2 : public RegisterWidth {}; - class CCR3 : public RegisterWidth {}; - class CCR4 : public RegisterWidth {}; - - constexpr TIM(int i) : m_index(i) {} - REGS_REGISTER_AT(CR1, 0x0); - REGS_REGISTER_AT(CCMR, 0x18); - REGS_REGISTER_AT(CCER, 0x20); - REGS_REGISTER_AT(PSC, 0x28); - REGS_REGISTER_AT(ARR, 0x2C); - REGS_REGISTER_AT(CCR1, 0x34); - REGS_REGISTER_AT(CCR2, 0x38); - REGS_REGISTER_AT(CCR3, 0x3C); - REGS_REGISTER_AT(CCR4, 0x40); - REGS_REGISTER_AT(BDTR, 0x44); -private: - constexpr uint32_t Base() const { - return (m_index == 1 ? 0x40010000 : - (m_index <= 7 ? 0x40000000 + 0x400*(m_index-2) : - (m_index == 8 ? 0x40010400 : - (m_index <= 11 ? 0x40014000 + 0x400*(m_index-9) : - 0x40001800 + 0x400*(m_index-12) - ) - ) - ) - ); - } - int m_index; -}; - -constexpr TIM TIM3(3); - -#endif diff --git a/ion/src/f730/regs/usart.h b/ion/src/f730/regs/usart.h deleted file mode 100644 index cb454c2d6..000000000 --- a/ion/src/f730/regs/usart.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef REGS_USART_H -#define REGS_USART_H - -#include "register.h" - -class USART { -public: - class SR : Register32 { - public: - REGS_BOOL_FIELD(RXNE, 5); - REGS_BOOL_FIELD(TXE, 7); - }; - - class DR : Register32 { - public: - uint16_t get() volatile { - return (uint16_t)getBitRange(8, 0); - } - void set(uint16_t v) volatile { - setBitRange(8, 0, v); - } - }; - - class BRR : Register32 { - public: - REGS_FIELD(DIV_FRAC, uint8_t, 3, 0); - REGS_FIELD(DIV_MANTISSA, uint16_t, 15, 4); - }; - - class CR1 : Register32 { - public: - REGS_BOOL_FIELD(UE, 13); - REGS_BOOL_FIELD(TE, 3); - REGS_BOOL_FIELD(RE, 2); - }; - - constexpr USART(int i) : m_index(i) {} - constexpr operator int() const { return m_index; } - REGS_REGISTER_AT(SR, 0x00); - REGS_REGISTER_AT(DR, 0x04); - REGS_REGISTER_AT(BRR, 0x08); - REGS_REGISTER_AT(CR1, 0x0C); -private: - constexpr uint32_t Base() const { - return ((uint32_t []){0x40011000, 0x40004400, 0x40004800})[m_index-1]; - }; - int m_index; -}; - -#endif diff --git a/ion/src/f730/sd_card.cpp b/ion/src/f730/sd_card.cpp deleted file mode 100644 index e677c63be..000000000 --- a/ion/src/f730/sd_card.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include "sd_card.h" -#include "regs/regs.h" -extern "C" { -#include -} - -namespace Ion { -namespace SDCard { -namespace Device { - -void init() { - initGPIO(); - initCard(); -} - -void initGPIO() { - // Configure GPIOs to use AF - GPIOA.MODER()->setMode(8, GPIO::MODER::Mode::AlternateFunction); - GPIOB.MODER()->setMode(4, GPIO::MODER::Mode::AlternateFunction); - GPIOB.MODER()->setMode(5, GPIO::MODER::Mode::AlternateFunction); - GPIOB.MODER()->setMode(15, GPIO::MODER::Mode::AlternateFunction); - GPIOC.MODER()->setMode(10, GPIO::MODER::Mode::AlternateFunction); - GPIOD.MODER()->setMode(2, GPIO::MODER::Mode::AlternateFunction); - - // More precisely, AF12 which correspond to SDIO alternate functions - GPIOA.AFR()->setAlternateFunction(8, GPIO::AFR::AlternateFunction::AF12); - GPIOB.AFR()->setAlternateFunction(4, GPIO::AFR::AlternateFunction::AF12); - GPIOB.AFR()->setAlternateFunction(5, GPIO::AFR::AlternateFunction::AF12); - GPIOB.AFR()->setAlternateFunction(15, GPIO::AFR::AlternateFunction::AF12); - GPIOC.AFR()->setAlternateFunction(10, GPIO::AFR::AlternateFunction::AF12); - GPIOD.AFR()->setAlternateFunction(2, GPIO::AFR::AlternateFunction::AF12); -} - -void initCard() { - - // Power on - SDIO.POWER()->setPWRCTRL(SDIO::POWER::PWRCTRL::On); - while (SDIO.POWER()->getPWRCTRL() != SDIO::POWER::PWRCTRL::On) { - } - - // Clock set - SDIO.CLKCR()->setCLKDIV(254); - SDIO.CLKCR()->setCLKEN(true); - - sendCommand(0, 0); - // CMD8 : 0b0001 = 2.7 - 3.6V - // 0xB7 = Pattern to see back in response - sendCommand(8, 0x1B7); - - assert(SDIO.RESP(1)->get() == 0x1B7); -} - -void sendCommand(uint32_t cmd, uint32_t arg) { - class SDIO::ICR icr(0); - icr.setCCRCFAILC(true); - icr.setCTIMEOUTC(true); - icr.setCMDRENDC(true); - icr.setCMDSENTC(true); - SDIO.ICR()->set(icr); - - SDIO.ARG()->set(arg); - - SDIO::CMD::WAITRESP responseType = SDIO::CMD::WAITRESP::Short; - switch (cmd) { - case 0: - responseType = SDIO::CMD::WAITRESP::None; - break; - case 2: - case 9: - case 10: - responseType = SDIO::CMD::WAITRESP::Long; - default: - break; - } - - class SDIO::CMD command(0); - command.setCMDINDEX(cmd); - command.setCPSMEN(true); - command.setWAITRESP(responseType); - SDIO.CMD()->set(command); - - if (responseType == SDIO::CMD::WAITRESP::None) { - // Wait for timeout or command sent - while (!SDIO.STA()->getCTIMEOUT() && !SDIO.STA()->getCMDSENT()) { - } - } else { - while (!SDIO.STA()->getCTIMEOUT() && !SDIO.STA()->getCMDREND() && !SDIO.STA()->getCCRCFAIL()) { - } - } - -} - -} -} -} diff --git a/ion/src/f730/sd_card.h b/ion/src/f730/sd_card.h deleted file mode 100644 index 1a2babfb7..000000000 --- a/ion/src/f730/sd_card.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ION_DEVICE_SD_CARD_H -#define ION_DEVICE_SD_CARD_H - -extern "C" { -#include -} - -namespace Ion { -namespace SDCard { -namespace Device { - -/* Pin | Role | Mode | Function - * -----+-------------------+-----------------------+---------- - * PA8 | SDIO D1 | Alternate Function 12 | SDIO_D1 - * PB4 | SDIO D0 | Alternate Function 12 | SDIO_D0 - * PB5 | SDIO D3 | Alternate Function 12 | SDIO_D3 - * PB15 | SDIO CLK | Alternate Function 12 | SDIO_CK - * PC10 | SDIO D2 | Alternate Function 12 | SDIO_D2 - * PD2 | SDIO CMD | Alternate Function 12 | SDIO_CMD - */ - -void init(); -void initGPIO(); - -void initCard(); - -void sendCommand(uint32_t cmd, uint32_t arg); - -} -} -} - -#endif diff --git a/ion/src/f730/stack.cpp b/ion/src/f730/stack.cpp deleted file mode 100644 index 141e222b5..000000000 --- a/ion/src/f730/stack.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -extern const void * _stack_start; -extern const void * _stack_end; - -bool Ion::stackSafe() { - volatile int stackDummy; - volatile void * stackPointer = &stackDummy; - return (stackPointer >= &_stack_end && stackPointer <= &_stack_start); -} diff --git a/ion/src/f730/swd.cpp b/ion/src/f730/swd.cpp deleted file mode 100644 index 008dc8110..000000000 --- a/ion/src/f730/swd.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "swd.h" -#include "regs/regs.h" - -namespace Ion { -namespace SWD { -namespace Device { - -void init() { - for(const GPIOPin & g : Pins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::AlternateFunction); - g.group().AFR()->setAlternateFunction(g.pin(), GPIO::AFR::AlternateFunction::AF0); - } -} - -void shutdown() { - for(const GPIOPin & g : Pins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); - g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); - } -} - -} -} -} diff --git a/ion/src/f730/swd.h b/ion/src/f730/swd.h deleted file mode 100644 index 49ea414c8..000000000 --- a/ion/src/f730/swd.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef ION_DEVICE_SWD_H -#define ION_DEVICE_SWD_H - -#include "regs/regs.h" - -namespace Ion { -namespace SWD { -namespace Device { - -/* Pin | Role | Mode - * -----+-------------------+--------------------- - * PA13 | SWDIO | Alternate Function 0 - * PA14 | SWCLK | Alternate Function 0 - * PB3 | SWO | Alternate Function 0 - */ - -void init(); -void shutdown(); - -constexpr static GPIOPin Pins[] = { - GPIOPin(GPIOA, 13), GPIOPin(GPIOA, 14), GPIOPin(GPIOB, 3) -}; - -} -} -} - -#endif diff --git a/ion/src/f730/timing.cpp b/ion/src/f730/timing.cpp deleted file mode 100644 index 98f3ff874..000000000 --- a/ion/src/f730/timing.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "timing.h" - -namespace Ion { -namespace Timing { - -/* TODO: The delay methods 'msleep' and 'usleep' are currently dependent on the - * optimizations chosen by the compiler. To prevent that and to gain in - * precision, we could use the controller cycle counter (Systick). */ - -void msleep(uint32_t ms) { - for (volatile uint32_t i=0; i<39343*ms; i++) { - __asm volatile("nop"); - } -} -void usleep(uint32_t us) { - for (volatile uint32_t i=0; i<39*us; i++) { - __asm volatile("nop"); - } -} - -uint64_t millis() { - return Ion::Timing::Device::MillisElapsed; -} - -} -} - -namespace Ion { -namespace Timing { -namespace Device { - -volatile uint64_t MillisElapsed = 0; - -} -} -} diff --git a/ion/src/f730/timing.h b/ion/src/f730/timing.h deleted file mode 100644 index 017bbd31a..000000000 --- a/ion/src/f730/timing.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef ION_DEVICE_TIMING_H -#define ION_DEVICE_TIMING_H - -#include - -namespace Ion { -namespace Timing { -namespace Device { - -extern volatile uint64_t MillisElapsed; - -} -} -} - -#endif diff --git a/ion/src/f730/usb.cpp b/ion/src/f730/usb.cpp deleted file mode 100644 index 1a76cc4e1..000000000 --- a/ion/src/f730/usb.cpp +++ /dev/null @@ -1,187 +0,0 @@ -#include -#include "usb.h" -#include -#include "device.h" -#include "display.h" -#include "regs/regs.h" -#include - -namespace Ion { -namespace USB { - -bool isPlugged() { - return Device::VbusPin.group().IDR()->get(Device::VbusPin.pin()); -} - -bool isEnumerated() { - /* Note: This implementation is not perfect. One would assume isEnumerated to - * return true for as long as the device is enumerated. But the GINTSTS - * register will be cleared in the poll() routine. */ - return OTG.GINTSTS()->getENUMDNE(); -} - -void clearEnumerationInterrupt() { - OTG.GINTSTS()->setENUMDNE(true); -} - -void enable() { - // Get out of soft-disconnected state - OTG.DCTL()->setSDIS(false); -} - -void disable() { - // Get into soft-disconnected state - OTG.DCTL()->setSDIS(true); -} - -} -} - -namespace Ion { -namespace USB { -namespace Device { - -void init() { - initGPIO(); - initOTG(); -} - -void shutdown() { - shutdownOTG(); - shutdownGPIO(); -} - -static inline void DEBUGTOGGLE() { - bool state = GPIOC.ODR()->get(11); - GPIOC.ODR()->set(11, !state); -} - -#include - -void initGPIO() { - - // DEBUG GPIO pin - GPIOC.MODER()->setMode(11, GPIO::MODER::Mode::Output); - GPIOC.ODR()->set(11, false); - - /* Configure the GPIO - * The VBUS pin is connected to the USB VBUS port. To read if the USB is - * plugged, the pin must be pulled down. */ - // FIXME: Understand how the Vbus pin really works! -#if 0 - VbusPin.group().MODER()->setMode(VbusPin.pin(), GPIO::MODER::Mode::Input); - VbusPin.group().PUPDR()->setPull(VbusPin.pin(), GPIO::PUPDR::Pull::Down); -#else - VbusPin.group().MODER()->setMode(VbusPin.pin(), GPIO::MODER::Mode::AlternateFunction); - VbusPin.group().AFR()->setAlternateFunction(VbusPin.pin(), GPIO::AFR::AlternateFunction::AF10); -#endif - - DmPin.group().MODER()->setMode(DmPin.pin(), GPIO::MODER::Mode::AlternateFunction); - DmPin.group().AFR()->setAlternateFunction(DmPin.pin(), GPIO::AFR::AlternateFunction::AF10); - - DpPin.group().MODER()->setMode(DpPin.pin(), GPIO::MODER::Mode::AlternateFunction); - DpPin.group().AFR()->setAlternateFunction(DpPin.pin(), GPIO::AFR::AlternateFunction::AF10); -} - -void shutdownGPIO() { - constexpr static GPIOPin USBPins[] = {DpPin, DmPin, VbusPin}; - for (const GPIOPin & g : USBPins) { - g.group().MODER()->setMode(g.pin(), GPIO::MODER::Mode::Analog); - g.group().PUPDR()->setPull(g.pin(), GPIO::PUPDR::Pull::None); - } -} - -void initOTG() { - // Wait for AHB idle - while (!OTG.GRSTCTL()->getAHBIDL()) { - } - - /* Core soft reset: Clears the interrupts and many of the CSR register bits, - * resets state machines, flushes the FIFOs and terminates USB transactions.*/ - OTG.GRSTCTL()->setCSRST(true); - while (OTG.GRSTCTL()->getCSRST()) { - } - - /* Enable the transceiver module of the PHY. It must be done to allow any USB - * operation */ - OTG.GCCFG()->setPWRDWN(true); - - /* Enable VBUS sensing comparators to detect valid levels for USB operation. - * This is used for instance to end the session if the host is switched off.*/ - OTG.GCCFG()->setVBDEN(true); - - // Force peripheral only mode - OTG.GUSBCFG()->setFDMOD(true); - - // Configure the USB turnaround time, depending on the AHB clock speed (96MHz) - OTG.GUSBCFG()->setTRDT(0x6); - - // Clear the interrupts - OTG.GINTSTS()->set(0); - - // Full speed device - OTG.DCFG()->setDSPD(OTG::DCFG::DSPD::FullSpeed); - - /* RxFIFO size. The value is in terms of 32-bit words. - * According to the reference manual, it should be, at minimum: - * (4 * number of control endpoints + 6) - * To receive SETUP packets on control endpoint - * + ((largest USB packet used / 4) + 1) - * To receive 1 USB packet + 1 packet status - * + (2 * number of OUT endpoints) - * Transfer complete status information - * + 1 for Global NAK - * So, for the calculator: (4*1+6) + (64/4 + 1) + (2*1) + 1 = 30 - * As the RAM size is 1.25kB, the size should be at most 320, minus the space - * for the Tx FIFOs. - * However, we tested and found that only values between 40 and 255 actually - * work. We arbitrarily chose 128. */ - OTG.GRXFSIZ()->setRXFD(128); - - // Unmask the interrupt line assertions - OTG.GAHBCFG()->setGINTMSK(true); - - // Restart the PHY clock - OTG.PCGCCTL()->setSTPPCLK(false); - - // Pick which interrupts we're interested in - class OTG::GINTMSK intMask(0); // Reset value - intMask.setENUMDNEM(true); // Speed enumeration done - intMask.setRXFLVLM(true); // Receive FIFO non empty - intMask.setIEPINT(true); // IN endpoint interrupt - OTG.GINTMSK()->set(intMask); - - // Unmask IN interrupts for endpoint 0 only - OTG.DAINTMSK()->setIEPM(1); - - /* Unmask the IN transfer completed interrupt for all endpoints. This - * interrupt warns that a IN transaction happened on the endpoint. */ - OTG.DIEPMSK()->setXFRCM(true); - - /* To communicate with a USB host, the device still needs to get out of soft- - * disconnected state (SDIS in the DCTL register). We do this when we detect - * that the USB cable is plugged. */ -} - -void shutdownOTG() { - // Core soft reset - OTG.GRSTCTL()->setCSRST(true); - while (OTG.GRSTCTL()->getCSRST()) { - } - - // Get into soft-disconnected state - OTG.DCTL()->setSDIS(true); - - // Stop the PHY clock - OTG.PCGCCTL()->setSTPPCLK(true); - - // Stop VBUS sensing - OTG.GCCFG()->setVBDEN(false); - - // Disable the transceiver module of the PHY - OTG.GCCFG()->setPWRDWN(false); -} - -} -} -} diff --git a/ion/src/f730/usb.h b/ion/src/f730/usb.h deleted file mode 100644 index 7c462cd47..000000000 --- a/ion/src/f730/usb.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef ION_DEVICE_USB_H -#define ION_DEVICE_USB_H - -#include "regs/regs.h" -#include "ion.h" -#include "usb/calculator.h" - -namespace Ion { -namespace USB { -namespace Device { - -/* Pin | Role | Mode | Function - * -----+-------------------+-----------------------+---------- - * PA9 | VBUS | Input, pulled down//TODO | Low = unplugged, high = plugged - * PA11 | USB D- | Alternate Function 10 | - * PA12 | USB D+ | Alternate Function 10 | - */ - -constexpr static GPIOPin VbusPin = GPIOPin(GPIOA, 9); -constexpr static GPIOPin DmPin = GPIOPin(GPIOA, 11); -constexpr static GPIOPin DpPin = GPIOPin(GPIOA, 12); - -void init(); -void shutdown(); -void initGPIO(); -void shutdownGPIO(); -void initOTG(); -void shutdownOTG(); - -} -} -} - -#endif diff --git a/ion/src/f730/usb/Makefile b/ion/src/f730/usb/Makefile deleted file mode 100644 index 7d2b990a7..000000000 --- a/ion/src/f730/usb/Makefile +++ /dev/null @@ -1,138 +0,0 @@ -<<<<<<< HEAD:ion/src/f730/usb/Makefile -usb_objs += $(addprefix ion/src/f730/usb/, \ - calculator.o \ - dfu_interface.o\ -) - -usb_objs += $(addprefix ion/src/f730/usb/stack/, \ - device.o\ - endpoint0.o \ - interface.o\ - request_recipient.o\ - setup_packet.o\ - streamable.o\ -) - -usb_objs += $(addprefix ion/src/f730/usb/stack/descriptor/, \ - bos_descriptor.o\ - configuration_descriptor.o \ - descriptor.o\ - device_descriptor.o\ - device_capability_descriptor.o\ - dfu_functional_descriptor.o\ - extended_compat_id_descriptor.o \ - interface_descriptor.o\ - language_id_string_descriptor.o \ - microsoft_os_string_descriptor.o\ - platform_device_capability_descriptor.o\ - string_descriptor.o\ - url_descriptor.o\ - webusb_platform_descriptor.o\ -======= -usb_src += $(addprefix ion/src/device/usb/, \ - calculator.cpp \ - dfu_interface.cpp \ -) - -usb_src += $(addprefix ion/src/device/usb/stack/, \ - device.cpp \ - endpoint0.cpp \ - interface.cpp \ - request_recipient.cpp \ - setup_packet.cpp \ - streamable.cpp \ -) - -usb_src += $(addprefix ion/src/device/usb/stack/descriptor/, \ - bos_descriptor.cpp \ - configuration_descriptor.cpp \ - descriptor.cpp \ - device_capability_descriptor.cpp \ - device_descriptor.cpp \ - dfu_functional_descriptor.cpp \ - extended_compat_id_descriptor.cpp \ - interface_descriptor.cpp \ - language_id_string_descriptor.cpp \ - microsoft_os_string_descriptor.cpp \ - platform_device_capability_descriptor.cpp \ - string_descriptor.cpp \ - url_descriptor.cpp \ - webusb_platform_descriptor.cpp \ ->>>>>>> caef2e8c6fa246205cc829d623f15a8dd5b1505b:ion/src/device/usb/Makefile -) - -EPSILON_USB_DFU_XIP ?= 0 - -ifeq ($(EPSILON_USB_DFU_XIP),1) - -<<<<<<< HEAD:ion/src/f730/usb/Makefile -objs += ion/src/f730/usb/dfu_xip.o -objs += $(usb_objs) - -else - -dfu_objs += liba/src/assert.o -dfu_objs += liba/src/strlen.o -dfu_objs += liba/src/strlcpy.o -dfu_objs += liba/src/memset.o -dfu_objs += liba/src/memcpy.o -dfu_objs += libaxx/src/cxxabi/pure_virtual.o -dfu_objs += ion/src/f730/usb/boot.o -dfu_objs += ion/src/f730/keyboard.o -dfu_objs += ion/src/f730/device.o -dfu_objs += ion/src/f730/usb.o -dfu_objs += ion/src/f730/base64.o -dfu_objs += ion/src/f730/flash.o -dfu_objs += ion/src/device/external_flash.o -dfu_objs += ion/src/f730/timing.o - -ion/src/f730/usb/dfu.elf: LDSCRIPT = ion/src/f730/usb/dfu.ld -ion/src/f730/usb/dfu.elf: $(usb_objs) $(dfu_objs) - -ion/src/f730/usb/dfu.o: ion/src/f730/usb/dfu.bin -======= -src += ion/src/device/usb/dfu_xip.cpp -src += $(usb_src) - -else - -dfu_src += liba/src/assert.cpp -dfu_src += liba/src/strlen.cpp -dfu_src += liba/src/strlcpy.cpp -dfu_src += liba/src/memset.cpp -dfu_src += liba/src/memcpy.cpp -dfu_src += libaxx/src/cxxabi/pure_virtual.cpp -dfu_src += ion/src/device/usb/boot.cpp -dfu_src += ion/src/device/keyboard.cpp -dfu_src += ion/src/device/device.cpp -dfu_src += ion/src/device/usb.cpp -dfu_src += ion/src/device/base64.cpp -dfu_src += ion/src/device/flash.cpp -dfu_src += ion/src/device/timing.cpp - -$(BUILD_DIR)/ion/src/device/usb/dfu.elf: LDSCRIPT = ion/src/device/usb/dfu.ld -$(BUILD_DIR)/ion/src/device/usb/dfu.elf: $(call object_for,$(usb_src)) $(call object_for,$(dfu_src)) - -# This command embeds a binary file into an object one. -# This allows us to embed standalone code (the dfu routines) into the final -# executable, and easily relocate it to RAM for execution. The objcopy command -# that turns binary data into an ELF object generates three symbols (start, size -# and end), but prefixes them with a mangled file path. To have consistent names -# we simply "cd" into the directory. This assumes input and output lives in the -# same directory. -$(BUILD_DIR)/ion/src/device/usb/dfu.o: $(BUILD_DIR)/ion/src/device/usb/dfu.bin ->>>>>>> caef2e8c6fa246205cc829d623f15a8dd5b1505b:ion/src/device/usb/Makefile - @echo "OBJCOPY $@" - $(Q) cd $(dir $<) ; $(OBJCOPY) -I binary -O elf32-littlearm -B arm --rename-section .data=.rodata --redefine-sym _binary_dfu_bin_start=_dfu_bootloader_flash_start --redefine-sym _binary_dfu_bin_end=_dfu_bootloader_flash_end $(notdir $<) $(notdir $@) - -<<<<<<< HEAD:ion/src/f730/usb/Makefile -objs += ion/src/f730/usb/dfu.o -objs += ion/src/f730/usb/dfu_relocated.o - -products += $(usb_objs) $(addprefix ion/src/f730/usb/dfu, .elf .bin) -======= -src += ion/src/device/usb/dfu.cpp -src += ion/src/device/usb/dfu_relocated.cpp ->>>>>>> caef2e8c6fa246205cc829d623f15a8dd5b1505b:ion/src/device/usb/Makefile - -endif diff --git a/ion/src/f730/usb/boot.cpp b/ion/src/f730/usb/boot.cpp deleted file mode 100644 index 0eb8d71d6..000000000 --- a/ion/src/f730/usb/boot.cpp +++ /dev/null @@ -1,2 +0,0 @@ -extern "C" void abort() { -} diff --git a/ion/src/f730/usb/calculator.cpp b/ion/src/f730/usb/calculator.cpp deleted file mode 100644 index 47150f053..000000000 --- a/ion/src/f730/usb/calculator.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include "calculator.h" -#include -#include -#include -#include - -namespace Ion { -namespace USB { -namespace Device { - -void Calculator::PollAndReset(bool exitWithKeyboard) { - char serialNumber[Ion::Device::SerialNumberLength+1]; - Ion::Device::copySerialNumber(serialNumber); - Calculator c(serialNumber); - - /* Leave DFU mode if the Back key is pressed, the calculator unplugged or the - * USB core soft-disconnected. */ - Ion::Keyboard::Key exitKey = Ion::Keyboard::Key::A6; - uint8_t exitKeyRow = Ion::Keyboard::Device::rowForKey(exitKey); - uint8_t exitKeyColumn = Ion::Keyboard::Device::columnForKey(exitKey); - - Ion::Keyboard::Device::activateRow(exitKeyRow); - - while (!(exitWithKeyboard && Ion::Keyboard::Device::columnIsActive(exitKeyColumn)) && - Ion::USB::isPlugged() && - !c.isSoftDisconnected()) { - c.poll(); - } - if (!c.isSoftDisconnected()) { - c.detach(); - } - if (c.resetOnDisconnect()) { - /* We don't perform a core reset because at this point in time the USB cable - * is most likely plugged in. Doing a full core reset would be the clean - * thing to do but would therefore result in the device entering the ROMed - * DFU bootloader, which we want to avoid. By performing a jump-reset, we - * will enter the newly flashed firmware. */ - Ion::Device::jumpReset(); - } -} - -Descriptor * Calculator::descriptor(uint8_t type, uint8_t index) { - /* Special case: Microsoft OS String Descriptor should be returned when - * searching for string descriptor at index 0xEE. */ - if (type == m_microsoftOSStringDescriptor.type() && index == 0xEE) { - return &m_microsoftOSStringDescriptor; - } - int typeCount = 0; - for (size_t i=0; itype() != type) { - continue; - } - if (typeCount == index) { - return descriptor; - } else { - typeCount++; - } - } - return nullptr; -} - -bool Calculator::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - if (Device::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { - return true; - } - if (request->requestType() == SetupPacket::RequestType::Vendor) { - if (request->bRequest() == k_webUSBVendorCode && request->wIndex() == 2) { - // This is a WebUSB, GET_URL request - assert(request->wValue() == k_webUSBLandingPageIndex); - return getURLCommand(transferBuffer, transferBufferLength, transferBufferMaxLength); - } - if (request->bRequest() == k_microsoftOSVendorCode && request->wIndex() == 0x0004) { - // This is a Microsoft OS descriptor, Extended Compat ID request - assert(request->wValue() == 0); - return getExtendedCompatIDCommand(transferBuffer, transferBufferLength, transferBufferMaxLength); - } - } - return false; -} - -bool Calculator::getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - *transferBufferLength = m_workshopURLDescriptor.copy(transferBuffer, transferBufferMaxLength); - return true; -} - -bool Calculator::getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - *transferBufferLength = m_extendedCompatIdDescriptor.copy(transferBuffer, transferBufferMaxLength); - return true; -} - -} -} -} diff --git a/ion/src/f730/usb/calculator.h b/ion/src/f730/usb/calculator.h deleted file mode 100644 index 81b8c9b2a..000000000 --- a/ion/src/f730/usb/calculator.h +++ /dev/null @@ -1,166 +0,0 @@ -#ifndef ION_DEVICE_USB_CALCULATOR_H -#define ION_DEVICE_USB_CALCULATOR_H - -#include -#include -#include "dfu_interface.h" -#include "stack/device.h" -#include "stack/descriptor/bos_descriptor.h" -#include "stack/descriptor/configuration_descriptor.h" -#include "stack/descriptor/descriptor.h" -#include "stack/descriptor/device_descriptor.h" -#include "stack/descriptor/dfu_functional_descriptor.h" -#include "stack/descriptor/extended_compat_id_descriptor.h" -#include "stack/descriptor/interface_descriptor.h" -#include "stack/descriptor/language_id_string_descriptor.h" -#include "stack/descriptor/microsoft_os_string_descriptor.h" -#include "stack/descriptor/string_descriptor.h" -#include "stack/descriptor/url_descriptor.h" -#include "stack/descriptor/webusb_platform_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class Calculator : public Device { -public: - static void PollAndReset(bool exitWithKeyboard) - __attribute__((section(".dfu_entry_point"))) // Needed to pinpoint this symbol in the linker script - __attribute__((used)) // Make sure this symbol is not discarded at link time - ; // Return true if reset is needed - Calculator(const char * serialNumber) : - Device(&m_dfuInterface), - m_deviceDescriptor( - 0x0210, /* bcdUSB: USB Specification Number which the device complies - * to. Must be greater than 0x0200 to use the BOS. */ - 0, // bDeviceClass: The class is defined by the interface. - 0, // bDeviceSUBClass: The subclass is defined by the interface. - 0, // bDeviceProtocol: The protocol is defined by the interface. - 64, // bMaxPacketSize0: Maximum packet size for endpoint 0 - 0x0483, // idVendor - 0xA291, // idProduct - 0x0100, // bcdDevice: Device Release Number - 1, // iManufacturer: Index of the manufacturer name string, see m_descriptor - 2, // iProduct: Index of the product name string, see m_descriptor - 3, // iSerialNumber: Index of the SerialNumber string, see m_descriptor - 1), // bNumConfigurations - m_dfuFunctionalDescriptor( - 0b0011, /* bmAttributes: - * - bitWillDetach: If true, the device will perform a bus - * detach-attach sequence when it receives a DFU_DETACH - * request. The host must not issue a USB Reset. - * - bitManifestationTolerant: if true, the device is able to - * communicate via USB after Manifestation phase. The - * manifestation phase implies a reset in the calculator, so, - * even if the device is still plugged, it needs to be - * re-enumerated to communicate. - * - bitCanUpload - * - bitCanDnload */ - 0, /* wDetachTimeOut: Time, in milliseconds, that the device in APP - * mode will wait after receipt of the DFU_DETACH request before - * switching to DFU mode. It does not apply to the calculator.*/ - 2048, // wTransferSize: Maximum number of bytes that the device can accept per control-write transaction - 0x0100),// bcdDFUVersion - m_interfaceDescriptor( - 0, // bInterfaceNumber - k_dfuInterfaceAlternateSetting, // bAlternateSetting - 0, // bNumEndpoints: Other than endpoint 0 - 0xFE, // bInterfaceClass: DFU (http://www.usb.org/developers/defined_class) - 1, // bInterfaceSubClass: DFU - 2, // bInterfaceProtocol: DFU Mode (not DFU Runtime, which would be 1) - 4, // iInterface: Index of the Interface string, see m_descriptor - &m_dfuFunctionalDescriptor), - m_configurationDescriptor( - 9 + 9 + 9, // wTotalLength: configuration descriptor + interface descriptor + dfu functional descriptor lengths - 1, // bNumInterfaces - k_bConfigurationValue, // bConfigurationValue - 0, // iConfiguration: No string descriptor for the configuration - 0x80, /* bmAttributes: - * Bit 7: Reserved, set to 1 - * Bit 6: Self Powered - * Bit 5: Remote Wakeup (allows the device to wake up the host when the host is in suspend) - * Bit 4..0: Reserved, set to 0 */ - 0x32, // bMaxPower: half of the Maximum Power Consumption - &m_interfaceDescriptor), - m_webUSBPlatformDescriptor( - k_webUSBVendorCode, - k_webUSBLandingPageIndex), - m_bosDescriptor( - 5 + 24, // wTotalLength: BOS descriptor + webusb platform descriptor lengths - 1, // bNumDeviceCapabilities - &m_webUSBPlatformDescriptor), - m_languageStringDescriptor(), - m_manufacturerStringDescriptor("NumWorks"), - m_productStringDescriptor("NumWorks Calculator"), - m_serialNumberStringDescriptor(serialNumber), - m_interfaceStringDescriptor("@Flash/0x08000000/04*016Kg,01*064Kg,07*128Kg/0x90000000/64*064Kg,64*064Kg"), - //m_interfaceStringDescriptor("@SRAM/0x20000000/01*256Ke"), - /* Switch to this descriptor to use dfu-util to write in the SRAM. - * FIXME Should be an alternate Interface. */ - m_microsoftOSStringDescriptor(k_microsoftOSVendorCode), - m_workshopURLDescriptor(URLDescriptor::Scheme::HTTPS, "workshop.numworks.com"), - m_extendedCompatIdDescriptor("WINUSB"), - m_descriptors{ - &m_deviceDescriptor, // Type = Device, Index = 0 - &m_configurationDescriptor, // Type = Configuration, Index = 0 - &m_languageStringDescriptor, // Type = String, Index = 0 - &m_manufacturerStringDescriptor, // Type = String, Index = 1 - &m_productStringDescriptor, // Type = String, Index = 2 - &m_serialNumberStringDescriptor, // Type = String, Index = 3 - &m_interfaceStringDescriptor, // Type = String, Index = 4 - &m_bosDescriptor // Type = BOS, Index = 0 - }, - m_dfuInterface(this, &m_ep0, k_dfuInterfaceAlternateSetting) - { - } -protected: - virtual Descriptor * descriptor(uint8_t type, uint8_t index) override; - virtual void setActiveConfiguration(uint8_t configurationIndex) override { - assert(configurationIndex == k_bConfigurationValue); - } - virtual uint8_t getActiveConfiguration() override { - return k_bConfigurationValue; - } - bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; - -private: - static constexpr uint8_t k_bConfigurationValue = 1; - static constexpr uint8_t k_dfuInterfaceAlternateSetting = 0; - static constexpr uint8_t k_webUSBVendorCode = 1; - static constexpr uint8_t k_webUSBLandingPageIndex = 1; - static constexpr uint8_t k_microsoftOSVendorCode = 2; - - // WebUSB and MicrosoftOSDescriptor commands - bool getURLCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); - bool getExtendedCompatIDCommand(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); - - // Descriptors - DeviceDescriptor m_deviceDescriptor; - DFUFunctionalDescriptor m_dfuFunctionalDescriptor; - InterfaceDescriptor m_interfaceDescriptor; - ConfigurationDescriptor m_configurationDescriptor; - WebUSBPlatformDescriptor m_webUSBPlatformDescriptor; - BOSDescriptor m_bosDescriptor; - LanguageIDStringDescriptor m_languageStringDescriptor; - StringDescriptor m_manufacturerStringDescriptor; - StringDescriptor m_productStringDescriptor; - StringDescriptor m_serialNumberStringDescriptor; - StringDescriptor m_interfaceStringDescriptor; - MicrosoftOSStringDescriptor m_microsoftOSStringDescriptor; - URLDescriptor m_workshopURLDescriptor; - ExtendedCompatIDDescriptor m_extendedCompatIdDescriptor; - - Descriptor * m_descriptors[8]; - /* m_descriptors contains only descriptors that sould be returned via the - * method descriptor(uint8_t type, uint8_t index), so do not count descriptors - * included in other descriptors or returned by other functions. */ - - // Interface - DFUInterface m_dfuInterface; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/dfu.ld b/ion/src/f730/usb/dfu.ld deleted file mode 100644 index 934772384..000000000 --- a/ion/src/f730/usb/dfu.ld +++ /dev/null @@ -1,50 +0,0 @@ -/* DFU transfers can serve two purposes: - * - Transfering RAM data between the machine and the host, e.g. Python scripts - * - Upgrading the flash memory to perform a software update - * - * The second case raises a huge issue: code cannot be executed from memory that - * is being modified. We're solving this issue by copying the DFU code in RAM. - * - * This linker script will generate some code that expects to be executed from a - * fixed address in RAM. The corresponding instructions will be embedded in the - * main Epsilon ELF file, and copied to that address before execution. - * - * This address needs to live in RAM, and needs to be temporarily overwriteable - * when the program is being run. Epsilon has a large stack to allow deeply - * recursive code to run. But when doing DFU transfers it is safe to assume we - * will need very little stack space. We're therefore using the topmost 8K of - * the stack reserved by Epsilon. - * - * Last but not least, we'll want to jump to a known entry point when running - * the DFU code (namely, Ion::USB::Device::Calculator::Poll). We're simply - * making sure this is the first symbol output. */ - -EPSILON_STACK_END = 0x20000000 + 256K - 32K; - -MEMORY { - RAM_BUFFER (rw) : ORIGIN = EPSILON_STACK_END, LENGTH = 8K -} - -SECTIONS { - .text : { - . = ALIGN(4); - KEEP(*(.dfu_entry_point)) - *(.text) - *(.text.*) - } >RAM_BUFFER - - .rodata : { - *(.rodata) - *(.rodata.*) - } >RAM_BUFFER - - /DISCARD/ : { - /* For now, we do not need .bss and .data sections. This allows us to simply - * skip any rt0-style initialization and jump straight into the PollAndReset - * routine. */ - *(.bss) - *(.bss.*) - *(.data) - *(.data.*) - } -} diff --git a/ion/src/f730/usb/dfu_interface.cpp b/ion/src/f730/usb/dfu_interface.cpp deleted file mode 100644 index b6e6fde24..000000000 --- a/ion/src/f730/usb/dfu_interface.cpp +++ /dev/null @@ -1,286 +0,0 @@ -#include "dfu_interface.h" -#include -#include -#include - -namespace Ion { -namespace USB { -namespace Device { - -static inline uint32_t min(uint32_t x, uint32_t y) { return (xpush(m_bStatus); - c->push(m_bwPollTimeout[2]); - c->push(m_bwPollTimeout[1]); - c->push(m_bwPollTimeout[0]); - c->push(m_bState); - c->push(m_iString); -} - -void DFUInterface::StateData::push(Channel * c) const { - c->push(m_bState); -} - -void DFUInterface::wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { - if (request->bRequest() == (uint8_t) DFURequest::Download) { - // Handle a download request - if (request->wValue() == 0) { - // The request is a special command - switch (transferBuffer[0]) { - case (uint8_t) DFUDownloadCommand::SetAddressPointer: - setAddressPointerCommand(request, transferBuffer, *transferBufferLength); - return; - case (uint8_t) DFUDownloadCommand::Erase: - eraseCommand(transferBuffer, *transferBufferLength); - return; - default: - m_state = State::dfuERROR; - m_status = Status::errSTALLEDPKT; - return; - } - } - if (request->wValue() == 1) { - m_ep0->stallTransaction(); - return; - } - if (request->wLength() > 0) { - // The request is a "real" download. Compute the writing address. - m_writeAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; - // Store the received data until we copy it on the flash. - memcpy(m_largeBuffer, transferBuffer, *transferBufferLength); - m_largeBufferLength = *transferBufferLength; - m_state = State::dfuDNLOADSYNC; - } - } -} - -void DFUInterface::wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) { - if (request->bRequest() == (uint8_t) DFURequest::GetStatus) { - // Do any needed action after the GetStatus request. - if (m_state == State::dfuMANIFEST) { - // Leave DFU routine: Leave DFU, reset device, jump to application code - leaveDFUAndReset(); - } else if (m_state == State::dfuDNBUSY) { - if (m_largeBufferLength != 0) { - // Here, copy the data from the transfer buffer to the flash memory - writeOnMemory(); - } - changeAddressPointerIfNeeded(); - eraseMemoryIfNeeded(); - m_state = State::dfuDNLOADIDLE; - } - } -} - -bool DFUInterface::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - if (Interface::processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { - return true; - } - switch (request->bRequest()) { - case (uint8_t) DFURequest::Detach: - m_device->detach(); - return true; - case (uint8_t) DFURequest::Download: - return processDownloadRequest(request->wLength(), transferBufferLength); - case (uint8_t) DFURequest::Upload: - return processUploadRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t) DFURequest::GetStatus: - return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t) DFURequest::ClearStatus: - return clearStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t) DFURequest::GetState: - return getState(transferBuffer, transferBufferLength, transferBufferMaxLength); - case (uint8_t) DFURequest::Abort: - return dfuAbort(transferBufferLength); - } - return false; -} - -bool DFUInterface::processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength) { - if (m_state != State::dfuIDLE && m_state != State::dfuDNLOADIDLE) { - m_state = State::dfuERROR; - m_status = Status::errNOTDONE; - m_ep0->stallTransaction(); - return false; - } - if (wLength == 0) { - // Leave DFU routine: Reset the device and jump to application code - m_state = State::dfuMANIFESTSYNC; - } else { - // Prepare to receive the download data - m_ep0->clearForOutTransactions(wLength); - m_state = State::dfuDNLOADSYNC; - } - return true; -} - -bool DFUInterface::processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - if (m_state != State::dfuIDLE && m_state != State::dfuUPLOADIDLE) { - m_ep0->stallTransaction(); - return false; - } - if (request->wValue() == 0) { - /* The host requests to read the commands supported by the bootloader. After - * receiving this command, the device should returns N bytes representing - * the command codes for : - * Get command / Set Address Pointer / Erase / Read Unprotect - * We no not need it for now. */ - return false; - } else if (request->wValue() == 1) { - m_ep0->stallTransaction(); - return false; - } else { - /* We decided to never protect Read operation. Else we would have to check - * here it is not protected before reading. */ - - // Compute the reading address - uint32_t readAddress = (request->wValue() - 2) * Endpoint0::MaxTransferSize + m_addressPointer; - // Copy the requested memory zone into the transfer buffer. - uint16_t copySize = min(transferBufferMaxLength, request->wLength()); - memcpy(transferBuffer, (void *)readAddress, copySize); - *transferBufferLength = copySize; - } - m_state = State::dfuUPLOADIDLE; - return true; -} - -void DFUInterface::setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength) { - assert(transferBufferLength == 5); - // Compute the new address but change it after the next getStatus request. - m_potentialNewAddressPointer = transferBuffer[1] - + (transferBuffer[2] << 8) - + (transferBuffer[3] << 16) - + (transferBuffer[4] << 24); - m_state = State::dfuDNLOADSYNC; -} - -void DFUInterface::changeAddressPointerIfNeeded() { - if (m_potentialNewAddressPointer == 0) { - // There was no address change waiting. - return; - } - // If there is a new address pointer waiting, change the pointer address. - m_addressPointer = m_potentialNewAddressPointer; - m_potentialNewAddressPointer = 0; - m_state = State::dfuDNLOADIDLE; - m_status = Status::OK; -} - -void DFUInterface::eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength) { - /* We determine whether the commands asks for a mass erase or which sector to - * erase. The erase must be done after the next getStatus request. */ - m_state = State::dfuDNLOADSYNC; - - if (transferBufferLength == 1) { - // Mass erase - m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors; - return; - } - - // Sector erase - assert(transferBufferLength == 5); - - uint32_t eraseAddress = transferBuffer[1] - + (transferBuffer[2] << 8) - + (transferBuffer[3] << 16) - + (transferBuffer[4] << 24); - - if (eraseAddress >= k_flashStartAddress && eraseAddress <= k_flashEndAddress) { - m_erasePage = Flash::Device::SectorAtAddress(eraseAddress); - } else if (eraseAddress >= k_externalFlashStartAddress && eraseAddress <= k_externalFlashEndAddress) { - m_erasePage = Flash::Device::NumberOfSectors + ExternalFlash::Device::SectorAtAddress(eraseAddress - k_externalFlashStartAddress); - } else { - // Unrecognized sector - m_state = State::dfuERROR; - m_status = Status::errTARGET; - } -} - - -void DFUInterface::eraseMemoryIfNeeded() { - if (m_erasePage < 0) { - // There was no erase waiting. - return; - } - - if (m_erasePage == Flash::Device::NumberOfSectors + ExternalFlash::Device::NumberOfSectors) { - Flash::Device::MassErase(); - ExternalFlash::Device::MassErase(); - } else if (m_erasePage < Flash::Device::NumberOfSectors) { - Flash::Device::EraseSector(m_erasePage); - } else { - ExternalFlash::Device::EraseSector(m_erasePage - Flash::Device::NumberOfSectors); - } - - /* Put an out of range value in m_erasePage to indicate that no erase is - * waiting. */ - m_erasePage = -1; - m_state = State::dfuDNLOADIDLE; - m_status = Status::OK; -} - -void DFUInterface::writeOnMemory() { - if (m_writeAddress >= k_flashStartAddress && m_writeAddress <= k_flashEndAddress) { - // Write to the Flash memory - Flash::Device::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress), m_largeBufferLength); - } else if (m_writeAddress >= k_sramStartAddress && m_writeAddress <= k_sramEndAddress) { - // Write on SRAM - // FIXME We should check that we are not overriding the current instructions. - memcpy((void *)m_writeAddress, m_largeBuffer, m_largeBufferLength); - } else if (m_writeAddress >= k_externalFlashStartAddress && m_writeAddress <= k_externalFlashEndAddress) { - ExternalFlash::Device::WriteMemory(m_largeBuffer, reinterpret_cast(m_writeAddress) - k_externalFlashStartAddress, m_largeBufferLength); - } else { - // Invalid write address - m_largeBufferLength = 0; - m_state = State::dfuERROR; - m_status = Status::errTARGET; - return; - } - - // Reset the buffer length - m_largeBufferLength = 0; - // Change the interface state and status - m_state = State::dfuDNLOADIDLE; - m_status = Status::OK; -} - - -bool DFUInterface::getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - // Change the status if needed - if (m_state == State::dfuMANIFESTSYNC) { - m_state = State::dfuMANIFEST; - } else if (m_state == State::dfuDNLOADSYNC) { - m_state = State::dfuDNBUSY; - } - // Copy the status on the TxFifo - *transferBufferLength = StatusData(m_status, m_state).copy(transferBuffer, transferBufferMaxLength); - return true; -} - -bool DFUInterface::clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - m_status = Status::OK; - m_state = State::dfuIDLE; - return getStatus(request, transferBuffer, transferBufferLength, transferBufferMaxLength); -} - -bool DFUInterface::getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize) { - *transferBufferLength = StateData(m_state).copy(transferBuffer, maxSize); - return true; -} - -bool DFUInterface::dfuAbort(uint16_t * transferBufferLength) { - m_status = Status::OK; - m_state = State::dfuIDLE; - *transferBufferLength = 0; - return true; -} - -void DFUInterface::leaveDFUAndReset() { - m_device->setResetOnDisconnect(true); - m_device->detach(); -} - -} -} -} diff --git a/ion/src/f730/usb/dfu_interface.h b/ion/src/f730/usb/dfu_interface.h deleted file mode 100644 index 7fce4bf40..000000000 --- a/ion/src/f730/usb/dfu_interface.h +++ /dev/null @@ -1,174 +0,0 @@ -#ifndef ION_DEVICE_USB_DFU_INTERFACE_H -#define ION_DEVICE_USB_DFU_INTERFACE_H - -#include -#include -#include "stack/device.h" -#include "stack/interface.h" -#include "stack/endpoint0.h" -#include "stack/setup_packet.h" -#include "stack/streamable.h" - -namespace Ion { -namespace USB { -namespace Device { - -class DFUInterface : public Interface { - -public: - DFUInterface(Device * device, Endpoint0 * ep0, uint8_t bInterfaceAlternateSetting) : - Interface(ep0), - m_device(device), - m_status(Status::OK), - m_state(State::dfuIDLE), - m_addressPointer(0), - m_potentialNewAddressPointer(0), - m_erasePage(-1), - m_largeBuffer{0}, - m_largeBufferLength(0), - m_writeAddress(0), - m_bInterfaceAlternateSetting(bInterfaceAlternateSetting) - { - } - void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; - void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) override; - -protected: - void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) override { - assert(interfaceAlternativeIndex == m_bInterfaceAlternateSetting); - } - uint8_t getActiveInterfaceAlternative() override { - return m_bInterfaceAlternateSetting; - } - bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; - -private: - // DFU Request Codes - enum class DFURequest { - Detach = 0, - Download = 1, - Upload = 2, - GetStatus = 3, - ClearStatus = 4, - GetState = 5, - Abort = 6 - }; - - // DFU Download Commmand Codes - enum class DFUDownloadCommand { - GetCommand = 0x00, - SetAddressPointer = 0x21, - Erase = 0x41, - ReadUnprotect = 0x92 - }; - - enum class Status : uint8_t { - OK = 0x00, - errTARGET = 0x01, - errFILE = 0x02, - errWRITE = 0x03, - errERASE = 0x04, - errCHECK_ERASED = 0x05, - errPROG = 0x06, - errVERIFY = 0x07, - errADDRESS = 0x08, - errNOTDONE = 0x09, - errFIRMWARE = 0x0A, - errVENDOR = 0x0B, - errUSBR = 0x0C, - errPOR = 0x0D, - errUNKNOWN = 0x0E, - errSTALLEDPKT = 0x0F - }; - - enum class State : uint8_t { - appIDLE = 0, - appDETACH = 1, - dfuIDLE = 2, - dfuDNLOADSYNC = 3, - dfuDNBUSY = 4, - dfuDNLOADIDLE = 5, - dfuMANIFESTSYNC = 6, - dfuMANIFEST = 7, - dfuMANIFESTWAITRESET = 8, - dfuUPLOADIDLE = 9, - dfuERROR = 10 - }; - - class StatusData : public Streamable { - public: - StatusData(Status status, State state, uint32_t pollTimeout = 1) : - /* We put a default pollTimeout value of 1ms: if the device is busy, the - * host has to wait 1ms before sending a getStatus Request. */ - m_bStatus((uint8_t)status), - m_bwPollTimeout{uint8_t((pollTimeout>>16) & 0xFF), uint8_t((pollTimeout>>8) & 0xFF), uint8_t(pollTimeout & 0xFF)}, - m_bState((uint8_t)state), - m_iString(0) - { - } - protected: - void push(Channel * c) const override; - private: - uint8_t m_bStatus; // Status resulting from the execution of the most recent request - uint8_t m_bwPollTimeout[3]; // m_bwPollTimeout is 24 bits - uint8_t m_bState; // State of the device immediately following transmission of this response - uint8_t m_iString; - }; - - class StateData : public Streamable { - public: - StateData(State state) : m_bState((uint8_t)state) {} - protected: - void push(Channel * c) const override; - private: - uint8_t m_bState; // Current state of the device - }; - - /* The Flash and SRAM addresses are in flash.ld. However, dfu_interface is - * linked with dfu.ld, so we cannot access the values. */ - constexpr static uint32_t k_flashStartAddress = 0x08000000; - constexpr static uint32_t k_flashEndAddress = 0x08100000; - constexpr static uint32_t k_sramStartAddress = 0x20000000; - constexpr static uint32_t k_sramEndAddress = 0x20040000; - constexpr static uint32_t k_externalFlashStartAddress = 0x90000000; - constexpr static uint32_t k_externalFlashEndAddress = 0x90800000; - - // Download and upload - bool processDownloadRequest(uint16_t wLength, uint16_t * transferBufferLength); - bool processUploadRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); - // Address pointer - void setAddressPointerCommand(SetupPacket * request, uint8_t * transferBuffer, uint16_t transferBufferLength); - void changeAddressPointerIfNeeded(); - // Access memory - void eraseCommand(uint8_t * transferBuffer, uint16_t transferBufferLength); - void eraseMemoryIfNeeded(); - void writeOnMemory(); - void unlockFlashMemory(); - void lockFlashMemoryAndPurgeCaches(); - // Status - bool getStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); - bool clearStatus(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); - // State - bool getState(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t maxSize); - // Abort - bool dfuAbort(uint16_t * transferBufferLength); - // Leave DFU - void leaveDFUAndReset(); - - Device * m_device; - Status m_status; - State m_state; - uint32_t m_addressPointer; - uint32_t m_potentialNewAddressPointer; - int32_t m_erasePage; - uint8_t m_largeBuffer[Endpoint0::MaxTransferSize]; - uint16_t m_largeBufferLength; - uint32_t m_writeAddress; - uint8_t m_bInterfaceAlternateSetting; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/dfu_relocated.cpp b/ion/src/f730/usb/dfu_relocated.cpp deleted file mode 100644 index 6f4191ea8..000000000 --- a/ion/src/f730/usb/dfu_relocated.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include -#include -#include -#include - -extern char _stack_end; -extern char _dfu_bootloader_flash_start; -extern char _dfu_bootloader_flash_end; - -namespace Ion { -namespace USB { - -typedef void (*PollFunctionPointer)(bool exitWithKeyboard); - -void DFU() { - - /* DFU transfers can serve two purposes: - * - Transfering RAM data between the machine and a host, e.g. Python scripts - * - Upgrading the flash memory to perform a software update - * - * The second case raises a huge issue: code cannot be executed from memory - * that is being modified. We're solving this issue by copying the DFU code in - * RAM. - * - * The new DFU address in RAM needs to be temporarily overwriteable when the - * program is being run. Epsilon has a large stack to allow deeply recursive - * code to run, but when doing DFU transfers it is safe to assume we will need - * very little stack space. We're therefore using the topmost 8K of the stack - * reserved by Epsilon. */ - - /* 1- The stack being in reverse order, the end of the stack will be the - * beginning of the DFU bootloader copied in RAM. */ - - size_t dfu_bootloader_size = &_dfu_bootloader_flash_end - &_dfu_bootloader_flash_start; - char * dfu_bootloader_ram_start = reinterpret_cast(&_stack_end); - assert(&_stack_end == (void *)(0x20000000 + 256*1024 - 32*1024)); - - /* 2- Verify there is enough free space on the stack to copy the DFU code. */ - - char foo; - char * stackPointer = &foo; - if (dfu_bootloader_ram_start + dfu_bootloader_size > stackPointer) { - // There is not enough room on the stack to copy the DFU bootloader. - return; - } - - /* 3- Copy the DFU bootloader from Flash to RAM. */ - - memcpy(dfu_bootloader_ram_start, &_dfu_bootloader_flash_start, dfu_bootloader_size); - - /* 4- Disable all interrupts - * The interrupt service routines live in the Flash and could be overwritten - * by garbage during a firmware upgrade opration, so we disable them. */ - Device::shutdownSysTick(); - - /* 5- Jump to DFU bootloader code. We made sure in the linker script that the - * first function we want to call is at the beginning of the DFU code. */ - - PollFunctionPointer dfu_bootloader_entry = reinterpret_cast(dfu_bootloader_ram_start); - - /* To have the right debug symbols for the reallocated code, break here and: - * - Get the address of the new .text section - * In a terminal: arm-none-eabi-readelf -a ion/src/f730/usb/dfu.elf - * - Delete the current symbol table - * symbol-file - * - Add the new symbol table, with the address of the new .text section - * add-symbol-file ion/src/f730/usb/dfu.elf 0x20038000 - */ - - dfu_bootloader_entry(true); - - /* 5- Restore interrupts */ - Device::initSysTick(); - - /* 6- That's all. The DFU bootloader on the stack is now dead code that will - * be overwritten when the stack grows. */ -} - -} -} diff --git a/ion/src/f730/usb/dfu_xip.cpp b/ion/src/f730/usb/dfu_xip.cpp deleted file mode 100644 index ad2ef68bb..000000000 --- a/ion/src/f730/usb/dfu_xip.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "calculator.h" -#include "../device.h" - -namespace Ion { -namespace USB { - -void DFU() { - Ion::USB::Device::Calculator::PollAndReset(true); -} - -} -} diff --git a/ion/src/f730/usb/flasher.cpp b/ion/src/f730/usb/flasher.cpp deleted file mode 100644 index 71cebf261..000000000 --- a/ion/src/f730/usb/flasher.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "../regs/regs.h" -#include "../usb/calculator.h" -#include - -void ion_main(int argc, char * argv[]) { - Ion::Display::pushRectUniform(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColor::RGB24(0xFFFF00)); - while (true) { - Ion::USB::enable(); - while (!Ion::USB::isEnumerated()) { - } - Ion::USB::Device::Calculator::PollAndReset(false); - } -} diff --git a/ion/src/f730/usb/flasher.ld b/ion/src/f730/usb/flasher.ld deleted file mode 100644 index 5621192b3..000000000 --- a/ion/src/f730/usb/flasher.ld +++ /dev/null @@ -1,77 +0,0 @@ -/* Create a USB DFU firmware that runs from RAM. - * Flashing using ST's ROMed DFU bootloader is reliable but very slow. To make - * flashing faster, we can leverage ST's bootloader to copy a small "flasher" in - * RAM, and run it from there. - * Caution: ST's bootloader uses some RAM, so we want to stay off of that memory - * region. Per AN2606, section 47, it's using 0x20003000 - 0x2003FFFF; We'll try - * to play safe and avoid the first 32KB of RAM. */ - -MEMORY { - RAM_BUFFER (rw) : ORIGIN = 0x20000000 + 32K, LENGTH = 256K - 32K -} - -/* The stack is quite large, and should really be equal to Epsilon's. Indeed, - * we're making the Calculator object live on the stack, and it's quite large - * (about 4K just for this single object). Using a stack too small will result - * in some memory being overwritten (for instance, vtables that live in the - * .rodata section). */ - -STACK_SIZE = 32K; - -SECTIONS { - .isr_vector_table ORIGIN(RAM_BUFFER) : { - KEEP(*(.isr_vector_table)) - } >RAM_BUFFER - - .text : { - . = ALIGN(4); - *(.text) - *(.text.*) - } >RAM_BUFFER - - .init_array : { - . = ALIGN(4); - _init_array_start = .; - KEEP (*(.init_array*)) - _init_array_end = .; - } >RAM_BUFFER - - .rodata : { - . = ALIGN(4); - *(.rodata) - *(.rodata.*) - } >RAM_BUFFER - - .data : { - . = ALIGN(4); - *(.data) - *(.data.*) - } >RAM_BUFFER - - .bss : { - . = ALIGN(4); - _bss_section_start_ram = .; - *(.bss) - *(.bss.*) - *(COMMON) - _bss_section_end_ram = .; - } >RAM_BUFFER - - .stack : { - . = ALIGN(8); - _stack_end = .; - . += (STACK_SIZE - 8); - . = ALIGN(8); - _stack_start = .; - } >RAM_BUFFER - - .phony : { - /* We won't do dynamic memory allocation */ - _heap_start = .; - _heap_end = .; - /* Effectively bypass copying .data to RAM */ - _data_section_start_flash = .; - _data_section_start_ram = .; - _data_section_end_ram = .; - } >RAM_BUFFER -} diff --git a/ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp deleted file mode 100644 index 069e6d56e..000000000 --- a/ion/src/f730/usb/stack/descriptor/bos_descriptor.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "bos_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -void BOSDescriptor::push(Channel * c) const { - Descriptor::push(c); - c->push(m_wTotalLength); - c->push(m_bNumDeviceCaps); - for (uint8_t i = 0; i < m_bNumDeviceCaps; i++) { - m_deviceCapabilities[i].push(c); - } -} - -uint8_t BOSDescriptor::bLength() const { - return Descriptor::bLength() + sizeof(uint16_t) + sizeof(uint8_t); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/bos_descriptor.h b/ion/src/f730/usb/stack/descriptor/bos_descriptor.h deleted file mode 100644 index ce2626099..000000000 --- a/ion/src/f730/usb/stack/descriptor/bos_descriptor.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_BOS_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_BOS_DESCRIPTOR_H - -#include "descriptor.h" -#include "device_capability_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class BOSDescriptor : public Descriptor { -public: - constexpr BOSDescriptor( - uint16_t wTotalLength, - uint8_t bNumDeviceCapabilities, - const DeviceCapabilityDescriptor * deviceCapabilities) : - Descriptor(0x0F), - m_wTotalLength(wTotalLength), - m_bNumDeviceCaps(bNumDeviceCapabilities), - m_deviceCapabilities(deviceCapabilities) - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - uint16_t m_wTotalLength; - uint8_t m_bNumDeviceCaps; - const DeviceCapabilityDescriptor * m_deviceCapabilities; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp deleted file mode 100644 index 3981837b5..000000000 --- a/ion/src/f730/usb/stack/descriptor/configuration_descriptor.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "configuration_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -void ConfigurationDescriptor::push(Channel * c) const { - Descriptor::push(c); - c->push(m_wTotalLength); - c->push(m_bNumInterfaces); - c->push(m_bConfigurationValue); - c->push(m_iConfiguration); - c->push(m_bmAttributes); - c->push(m_bMaxPower); - for (uint8_t i = 0; i < m_bNumInterfaces; i++) { - m_interfaces[i].push(c); - } -} - -uint8_t ConfigurationDescriptor::bLength() const { - return Descriptor::bLength() + sizeof(uint16_t) + 5*sizeof(uint8_t); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/configuration_descriptor.h b/ion/src/f730/usb/stack/descriptor/configuration_descriptor.h deleted file mode 100644 index fc43d59fb..000000000 --- a/ion/src/f730/usb/stack/descriptor/configuration_descriptor.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_CONFIGURATION_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_CONFIGURATION_DESCRIPTOR_H - -#include "descriptor.h" -#include "interface_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class ConfigurationDescriptor : public Descriptor { -public: - constexpr ConfigurationDescriptor( - uint16_t wTotalLength, - uint8_t bNumInterfaces, - uint8_t bConfigurationValue, - uint8_t iConfiguration, - uint8_t bmAttributes, - uint8_t bMaxPower, - const InterfaceDescriptor * interfaces) : - Descriptor(0x02), - m_wTotalLength(wTotalLength), - m_bNumInterfaces(bNumInterfaces), - m_bConfigurationValue(bConfigurationValue), - m_iConfiguration(iConfiguration), - m_bmAttributes(bmAttributes), - m_bMaxPower(bMaxPower), - m_interfaces(interfaces) - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - uint16_t m_wTotalLength; - uint8_t m_bNumInterfaces; - uint8_t m_bConfigurationValue; - uint8_t m_iConfiguration; - uint8_t m_bmAttributes; - uint8_t m_bMaxPower; - const InterfaceDescriptor * m_interfaces; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/descriptor.cpp b/ion/src/f730/usb/stack/descriptor/descriptor.cpp deleted file mode 100644 index cda9e7a77..000000000 --- a/ion/src/f730/usb/stack/descriptor/descriptor.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "descriptor.h" -#include - -namespace Ion { -namespace USB { -namespace Device { - -void Descriptor::push(Channel * c) const { - c->push(bLength()); - c->push(m_bDescriptorType); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/descriptor.h b/ion/src/f730/usb/stack/descriptor/descriptor.h deleted file mode 100644 index 9af940211..000000000 --- a/ion/src/f730/usb/stack/descriptor/descriptor.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_DESCRIPTOR_H - -#include "../streamable.h" - -namespace Ion { -namespace USB { -namespace Device { - -class InterfaceDescriptor; - -class Descriptor : public Streamable { - friend class InterfaceDescriptor; -public: - constexpr Descriptor(uint8_t bDescriptorType) : - m_bDescriptorType(bDescriptorType) - { - } - uint8_t type() const { return m_bDescriptorType; } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const { return 2*sizeof(uint8_t); } -private: - uint8_t m_bDescriptorType; -}; - - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp deleted file mode 100644 index e3ab162d4..000000000 --- a/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "device_capability_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -void DeviceCapabilityDescriptor::push(Channel * c) const { - Descriptor::push(c); - c->push(m_bDeviceCapabilityType); -} - -uint8_t DeviceCapabilityDescriptor::bLength() const { - return Descriptor::bLength() + sizeof(uint8_t); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h b/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h deleted file mode 100644 index cf600c9f1..000000000 --- a/ion/src/f730/usb/stack/descriptor/device_capability_descriptor.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_DEVICE_CAPABLITY_DESCRIPTOR_H - -#include "descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class BOSDescriptor; - -class DeviceCapabilityDescriptor : public Descriptor { - friend class BOSDescriptor; -public: - constexpr DeviceCapabilityDescriptor(uint8_t bDeviceCapabilityType) : - Descriptor(0x10), - m_bDeviceCapabilityType(bDeviceCapabilityType) - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - uint8_t m_bDeviceCapabilityType; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/device_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/device_descriptor.cpp deleted file mode 100644 index 1ce33847a..000000000 --- a/ion/src/f730/usb/stack/descriptor/device_descriptor.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "device_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -void DeviceDescriptor::push(Channel * c) const { - Descriptor::push(c); - c->push(m_bcdUSB); - c->push(m_bDeviceClass); - c->push(m_bDeviceSubClass); - c->push(m_bDeviceProtocol); - c->push(m_bMaxPacketSize0); - c->push(m_idVendor); - c->push(m_idProduct); - c->push(m_bcdDevice); - c->push(m_iManufacturer); - c->push(m_iProduct); - c->push(m_iSerialNumber); - c->push(m_bNumConfigurations); -} - -uint8_t DeviceDescriptor::bLength() const { - return Descriptor::bLength() + sizeof(uint16_t) + 4*sizeof(uint8_t) + 3*sizeof(uint16_t) + 4*sizeof(uint8_t); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/device_descriptor.h b/ion/src/f730/usb/stack/descriptor/device_descriptor.h deleted file mode 100644 index d41241348..000000000 --- a/ion/src/f730/usb/stack/descriptor/device_descriptor.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_DEVICE_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_DEVICE_DESCRIPTOR_H - -#include "descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class DeviceDescriptor : public Descriptor { -public: - constexpr DeviceDescriptor( - uint16_t bcdUSB, - uint8_t bDeviceClass, - uint8_t bDeviceSubClass, - uint8_t bDeviceProtocol, - uint8_t bMaxPacketSize0, - uint16_t idVendor, - uint16_t idProduct, - uint16_t bcdDevice, - uint8_t iManufacturer, - uint8_t iProduct, - uint8_t iSerialNumber, - uint8_t bNumConfigurations) : - Descriptor(0x01), - m_bcdUSB(bcdUSB), - m_bDeviceClass(bDeviceClass), - m_bDeviceSubClass(bDeviceSubClass), - m_bDeviceProtocol(bDeviceProtocol), - m_bMaxPacketSize0(bMaxPacketSize0), - m_idVendor(idVendor), - m_idProduct(idProduct), - m_bcdDevice(bcdDevice), - m_iManufacturer(iManufacturer), - m_iProduct(iProduct), - m_iSerialNumber(iSerialNumber), - m_bNumConfigurations(bNumConfigurations) - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - uint16_t m_bcdUSB; - uint8_t m_bDeviceClass; - uint8_t m_bDeviceSubClass; - uint8_t m_bDeviceProtocol; - uint8_t m_bMaxPacketSize0; - uint16_t m_idVendor; - uint16_t m_idProduct; - uint16_t m_bcdDevice; - uint8_t m_iManufacturer; - uint8_t m_iProduct; - uint8_t m_iSerialNumber; - uint8_t m_bNumConfigurations; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp deleted file mode 100644 index 0d531965a..000000000 --- a/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "dfu_functional_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -void DFUFunctionalDescriptor::push(Channel * c) const { - Descriptor::push(c); - c->push(m_bmAttributes); - c->push(m_wDetachTimeOut); - c->push(m_wTransferSize); - c->push(m_bcdDFUVersion); -} - -uint8_t DFUFunctionalDescriptor::bLength() const { - return Descriptor::bLength() + sizeof(uint8_t) + 3*sizeof(uint16_t); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h b/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h deleted file mode 100644 index 5d51caa5a..000000000 --- a/ion/src/f730/usb/stack/descriptor/dfu_functional_descriptor.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_DFU_FUNCTIONAL_DESCRIPTOR_H - -#include "descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class DFUFunctionalDescriptor : public Descriptor { -public: - constexpr DFUFunctionalDescriptor( - uint8_t bmAttributes, - uint16_t wDetachTimeOut, - uint16_t wTransferSize, - uint16_t bcdDFUVersion) : - Descriptor(0x21), - m_bmAttributes(bmAttributes), - m_wDetachTimeOut(wDetachTimeOut), - m_wTransferSize(wTransferSize), - m_bcdDFUVersion(bcdDFUVersion) - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - uint8_t m_bmAttributes; - uint16_t m_wDetachTimeOut; - uint16_t m_wTransferSize; - uint16_t m_bcdDFUVersion; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp deleted file mode 100644 index 287ac8dd7..000000000 --- a/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "extended_compat_id_descriptor.h" -#include - -namespace Ion { -namespace USB { -namespace Device { - -ExtendedCompatIDDescriptor::ExtendedCompatIDDescriptor(const char * compatibleID) : - m_dwLength(sizeof(uint32_t) - + 2*sizeof(uint16_t) - + sizeof(uint8_t) - + k_reserved1Size * sizeof(uint8_t) - + 2*sizeof(uint8_t) - + k_compatibleIDSize * sizeof(uint8_t) - + k_compatibleIDSize * sizeof(uint8_t) - + k_reserved2Size * sizeof(uint8_t)), - m_bcdVersion(0x0100), // Microsoft OS Descriptors version 1 - m_wIndex(Index), - m_bCount(1), // We assume one function only. - m_reserved1{0, 0, 0, 0, 0, 0, 0}, - m_bFirstInterfaceNumber(0), - m_bReserved(1), - m_subCompatibleID{0, 0, 0, 0, 0, 0, 0, 0}, - m_reserved2{0, 0, 0, 0, 0, 0} -{ - /* Compatible ID has size k_compatibleIDSize, and any unused bytes should be - * filled with 0. */ - size_t compatibleIDSize = strlen(compatibleID); - size_t compatibleIDCopySize = k_compatibleIDSize < compatibleIDSize ? k_compatibleIDSize : compatibleIDSize; - for (size_t i = 0; i < compatibleIDCopySize; i++) { - m_compatibleID[i] = compatibleID[i]; - } - for (size_t i = compatibleIDCopySize; i < k_compatibleIDSize; i++) { - m_compatibleID[i] = 0; - } -} - -void ExtendedCompatIDDescriptor::push(Channel * c) const { - c->push(m_dwLength); - c->push(m_bcdVersion); - c->push(m_wIndex); - c->push(m_bCount); - for (uint8_t i = 0; i < k_reserved1Size; i++) { - c->push(m_reserved1[i]); - } - c->push(m_bFirstInterfaceNumber); - c->push(m_bReserved); - for (uint8_t i = 0; i < k_compatibleIDSize; i++) { - c->push(m_compatibleID[i]); - } - for (uint8_t i = 0; i < k_compatibleIDSize; i++) { - c->push(m_subCompatibleID[i]); - } - for (uint8_t i = 0; i < k_reserved2Size; i++) { - c->push(m_reserved2[i]); - } -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h b/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h deleted file mode 100644 index 199c39640..000000000 --- a/ion/src/f730/usb/stack/descriptor/extended_compat_id_descriptor.h +++ /dev/null @@ -1,43 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_EXTENDED_COMPAT_ID_DESCRIPTOR_H - -#include "../streamable.h" - -namespace Ion { -namespace USB { -namespace Device { - -/* We use this descriptor to tell the Windows OS that the device should be - * treated as a WinUSB device. The Extended Compat ID Descriptor can set - * differents compat IDs according to the interface and function of the device, - * but we assume there is only one. */ - -class ExtendedCompatIDDescriptor : public Streamable { -public: - static constexpr uint8_t Index = 0x0004; - ExtendedCompatIDDescriptor(const char * compatibleID); -protected: - void push(Channel * c) const override; -private: - constexpr static uint8_t k_reserved1Size = 7; - constexpr static uint8_t k_compatibleIDSize = 8; - constexpr static uint8_t k_reserved2Size = 6; - // Header - uint32_t m_dwLength; // The length, in bytes, of the complete extended compat ID descriptor - uint16_t m_bcdVersion; // The descriptor’s version number, in binary coded decimal format - uint16_t m_wIndex; // An index that identifies the particular OS feature descriptor - uint8_t m_bCount; // The number of function sections - uint8_t m_reserved1[k_reserved1Size]; - // Function - uint8_t m_bFirstInterfaceNumber; // The interface or function number - uint8_t m_bReserved; - uint8_t m_compatibleID[k_compatibleIDSize]; - uint8_t m_subCompatibleID[k_compatibleIDSize]; - uint8_t m_reserved2[k_reserved2Size]; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp deleted file mode 100644 index f4bb7d739..000000000 --- a/ion/src/f730/usb/stack/descriptor/interface_descriptor.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "interface_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -void InterfaceDescriptor::push(Channel * c) const { - Descriptor::push(c); - c->push(m_bInterfaceNumber); - c->push(m_bAlternateSetting); - c->push(m_bNumEndpoints); - c->push(m_bInterfaceClass); - c->push(m_bInterfaceSubClass); - c->push(m_bInterfaceProtocol); - c->push(m_iInterface); - if (m_additionalDescriptor != nullptr) { - m_additionalDescriptor->push(c); - } -} - -uint8_t InterfaceDescriptor::bLength() const { - return Descriptor::bLength() + 7*sizeof(uint8_t); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/interface_descriptor.h b/ion/src/f730/usb/stack/descriptor/interface_descriptor.h deleted file mode 100644 index 2d1418c0d..000000000 --- a/ion/src/f730/usb/stack/descriptor/interface_descriptor.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_INTERFACE_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_INTERFACE_DESCRIPTOR_H - -#include "descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class ConfigurationDescriptor; - -class InterfaceDescriptor : public Descriptor { - friend class ConfigurationDescriptor; -public: - constexpr InterfaceDescriptor( - uint8_t bInterfaceNumber, - uint8_t bAlternateSetting, - uint8_t bNumEndpoints, - uint8_t bInterfaceClass, - uint8_t bInterfaceSubClass, - uint8_t bInterfaceProtocol, - uint8_t iInterface, - Descriptor * additionalDescriptor) : - Descriptor(0x04), - m_bInterfaceNumber(bInterfaceNumber), - m_bAlternateSetting(bAlternateSetting), - m_bNumEndpoints(bNumEndpoints), - m_bInterfaceClass(bInterfaceClass), - m_bInterfaceSubClass(bInterfaceSubClass), - m_bInterfaceProtocol(bInterfaceProtocol), - m_iInterface(iInterface), - m_additionalDescriptor(additionalDescriptor) - /* There could be more than one additional descriptor, but we do not need - * this for now. */ - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - uint8_t m_bInterfaceNumber; - uint8_t m_bAlternateSetting; - uint8_t m_bNumEndpoints; - uint8_t m_bInterfaceClass; - uint8_t m_bInterfaceSubClass; - uint8_t m_bInterfaceProtocol; - uint8_t m_iInterface; - const Descriptor * m_additionalDescriptor; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp deleted file mode 100644 index 49dec8e10..000000000 --- a/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "language_id_string_descriptor.h" -#include - -namespace Ion { -namespace USB { -namespace Device { - -void LanguageIDStringDescriptor::push(Channel * c) const { - Descriptor::push(c); - c->push((uint16_t)(0x0409)); -} - -uint8_t LanguageIDStringDescriptor::bLength() const { - return Descriptor::bLength() + sizeof(uint16_t); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h b/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h deleted file mode 100644 index 69533bdf5..000000000 --- a/ion/src/f730/usb/stack/descriptor/language_id_string_descriptor.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_LANGUAGE_ID_STRING_DESCRIPTOR_H - -#include "descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -// For now this LanguageIDStringDescriptor only ever returns American English - -class LanguageIDStringDescriptor : public Descriptor { -public: - constexpr LanguageIDStringDescriptor() : - Descriptor(0x03) { } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp deleted file mode 100644 index 2d119dc09..000000000 --- a/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "microsoft_os_string_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -void MicrosoftOSStringDescriptor::push(Channel * c) const { - StringDescriptor::push(c); - c->push(m_bMSVendorCode); - c->push(m_bPad); -} - -uint8_t MicrosoftOSStringDescriptor::bLength() const { - return StringDescriptor::bLength() + 2 * sizeof(uint8_t); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h b/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h deleted file mode 100644 index 9bf2295b6..000000000 --- a/ion/src/f730/usb/stack/descriptor/microsoft_os_string_descriptor.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_MICROSOFT_OS_STRING_DESCRIPTOR_H - -#include "string_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class MicrosoftOSStringDescriptor : public StringDescriptor { -public: - constexpr MicrosoftOSStringDescriptor(uint8_t bMSVendorCode) : - StringDescriptor("MSFT100"), - m_bMSVendorCode(bMSVendorCode), - m_bPad(0) - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - uint8_t m_bMSVendorCode; - uint8_t m_bPad; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp deleted file mode 100644 index b3739ec6e..000000000 --- a/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "platform_device_capability_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -void PlatformDeviceCapabilityDescriptor::push(Channel * c) const { - DeviceCapabilityDescriptor::push(c); - c->push(m_bReserved); - for (int i = 0; i < k_platformCapabilityUUIDSize; i++) { - c->push(m_platformCapabilityUUID[i]); - } -} - -uint8_t PlatformDeviceCapabilityDescriptor::bLength() const { - return DeviceCapabilityDescriptor::bLength() + sizeof(uint8_t) + k_platformCapabilityUUIDSize*sizeof(uint8_t); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h b/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h deleted file mode 100644 index 76087b565..000000000 --- a/ion/src/f730/usb/stack/descriptor/platform_device_capability_descriptor.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_PLATFORM_DEVICE_CAPABLITY_DESCRIPTOR_H - -#include "device_capability_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class PlatformDeviceCapabilityDescriptor : public DeviceCapabilityDescriptor { -public: - constexpr PlatformDeviceCapabilityDescriptor(const uint8_t platformCapabilityUUID[]) : - DeviceCapabilityDescriptor(0x05), - m_bReserved(0), - m_platformCapabilityUUID{ - platformCapabilityUUID[0], - platformCapabilityUUID[1], - platformCapabilityUUID[2], - platformCapabilityUUID[3], - platformCapabilityUUID[4], - platformCapabilityUUID[5], - platformCapabilityUUID[6], - platformCapabilityUUID[7], - platformCapabilityUUID[8], - platformCapabilityUUID[9], - platformCapabilityUUID[10], - platformCapabilityUUID[11], - platformCapabilityUUID[12], - platformCapabilityUUID[13], - platformCapabilityUUID[14], - platformCapabilityUUID[15]} - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - constexpr static uint8_t k_platformCapabilityUUIDSize = 16; - uint8_t m_bReserved; - uint8_t m_platformCapabilityUUID[k_platformCapabilityUUIDSize]; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/string_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/string_descriptor.cpp deleted file mode 100644 index c4ee5068e..000000000 --- a/ion/src/f730/usb/stack/descriptor/string_descriptor.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "string_descriptor.h" -#include - -namespace Ion { -namespace USB { -namespace Device { - -void StringDescriptor::push(Channel * c) const { - Descriptor::push(c); - const char * stringPointer = m_string; - while (*stringPointer != 0) { - uint16_t stringAsUTF16CodePoint = *stringPointer; - c->push(stringAsUTF16CodePoint); - stringPointer++; - } -} - -uint8_t StringDescriptor::bLength() const { - // The script is returned in UTF-16, hence the multiplication. - return Descriptor::bLength() + 2*strlen(m_string); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/string_descriptor.h b/ion/src/f730/usb/stack/descriptor/string_descriptor.h deleted file mode 100644 index 296ac79ca..000000000 --- a/ion/src/f730/usb/stack/descriptor/string_descriptor.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_STRING_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_STRING_DESCRIPTOR_H - -#include "descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class StringDescriptor : public Descriptor { -public: - constexpr StringDescriptor(const char * string) : - Descriptor(0x03), - m_string(string) - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - const char * m_string; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/url_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/url_descriptor.cpp deleted file mode 100644 index 3001d8143..000000000 --- a/ion/src/f730/usb/stack/descriptor/url_descriptor.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "url_descriptor.h" -#include - -namespace Ion { -namespace USB { -namespace Device { - -void URLDescriptor::push(Channel * c) const { - Descriptor::push(c); - c->push(m_bScheme); - const char * stringPointer = m_string; - while (*stringPointer != 0) { - c->push(*stringPointer); - stringPointer++; - } -} - -uint8_t URLDescriptor::bLength() const { - // The script is returned in UTF-8. - return Descriptor::bLength() + sizeof(uint8_t) + strlen(m_string); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/url_descriptor.h b/ion/src/f730/usb/stack/descriptor/url_descriptor.h deleted file mode 100644 index d9e39af29..000000000 --- a/ion/src/f730/usb/stack/descriptor/url_descriptor.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_URL_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_URL_DESCRIPTOR_H - -#include "descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class URLDescriptor : public Descriptor { -public: - enum class Scheme { - HTTP = 0, - HTTPS = 1, - IncludedInURL = 255 - }; - - constexpr URLDescriptor(Scheme scheme, const char * url) : - Descriptor(0x03), - m_bScheme((uint8_t)scheme), - m_string(url) - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - uint8_t m_bScheme; - const char * m_string; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp b/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp deleted file mode 100644 index e4a4ff548..000000000 --- a/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "webusb_platform_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -constexpr uint8_t WebUSBPlatformDescriptor::k_webUSBUUID[]; - -void WebUSBPlatformDescriptor::push(Channel * c) const { - PlatformDeviceCapabilityDescriptor::push(c); - c->push(m_bcdVersion); - c->push(m_bVendorCode); - c->push(m_iLandingPage); -} - -uint8_t WebUSBPlatformDescriptor::bLength() const { - return PlatformDeviceCapabilityDescriptor::bLength() + sizeof(uint16_t) + 2*sizeof(uint8_t); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h b/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h deleted file mode 100644 index 89d39c712..000000000 --- a/ion/src/f730/usb/stack/descriptor/webusb_platform_descriptor.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef ION_DEVICE_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H -#define ION_DEVICE_USB_STACK_WEBUSB_PLATFORM_DESCRIPTOR_H - -#include "platform_device_capability_descriptor.h" - -namespace Ion { -namespace USB { -namespace Device { - -class WebUSBPlatformDescriptor : public PlatformDeviceCapabilityDescriptor { -public: - constexpr WebUSBPlatformDescriptor(uint8_t bVendorCode, uint8_t iLandingPage) : - PlatformDeviceCapabilityDescriptor(k_webUSBUUID), - m_bcdVersion(0x0100), - m_bVendorCode(bVendorCode), - m_iLandingPage(iLandingPage) - { - } -protected: - void push(Channel * c) const override; - virtual uint8_t bLength() const override; -private: - /* Little-endian encoding of {3408B638-09A9-47A0-8BFD-A0768815B665}. - * See https://wicg.github.io/webusb/#webusb-platform-capability-descriptor */ - constexpr static uint8_t k_webUSBUUID[] = { - 0x38, 0xB6, 0x08, 0x34, 0xA9, 0x09, 0xA0, 0x47, - 0x8B, 0xFD, 0xA0, 0x76, 0x88, 0x15, 0xB6, 0x65}; - uint16_t m_bcdVersion; - uint8_t m_bVendorCode; - uint8_t m_iLandingPage; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/device.cpp b/ion/src/f730/usb/stack/device.cpp deleted file mode 100644 index cd2d7fb5b..000000000 --- a/ion/src/f730/usb/stack/device.cpp +++ /dev/null @@ -1,156 +0,0 @@ -#include "device.h" -#include - -namespace Ion { -namespace USB { -namespace Device { - -static inline uint16_t min(uint16_t x, uint16_t y) { return (xget()); - - /* SETUP or OUT transaction - * If the Rx FIFO is not empty, there is a SETUP or OUT transaction. - * The interrupt is done AFTER THE HANSDHAKE of the transaction. */ - if (intsts.getRXFLVL()) { - class OTG::GRXSTSP grxstsp(OTG.GRXSTSP()->get()); - - // Store the packet status - OTG::GRXSTSP::PKTSTS pktsts = grxstsp.getPKTSTS(); - - // We only use endpoint 0 - assert(grxstsp.getEPNUM() == 0); - - if (pktsts == OTG::GRXSTSP::PKTSTS::OutTransferCompleted || pktsts == OTG::GRXSTSP::PKTSTS::SetupTransactionCompleted) { - // There is no data associated with this interrupt. - return; - } - - assert(pktsts != OTG::GRXSTSP::PKTSTS::GlobalOutNAK); - /* We did not enable the GONAKEFFM (Global OUT NAK effective mask) bit in - * GINTSTS, so we should never get this interrupt. */ - - assert(pktsts == OTG::GRXSTSP::PKTSTS::OutReceived || pktsts == OTG::GRXSTSP::PKTSTS::SetupReceived); - - TransactionType type = (pktsts == OTG::GRXSTSP::PKTSTS::OutReceived) ? TransactionType::Out : TransactionType::Setup; - - if (type == TransactionType::Setup && OTG.DIEPTSIZ0()->getPKTCNT()) { - // SETUP received but there is a packet in the Tx FIFO. Flush it. - m_ep0.flushTxFifo(); - } - - // Save the received packet byte count - m_ep0.setReceivedPacketSize(grxstsp.getBCNT()); - - if (type == TransactionType::Setup) { - m_ep0.readAndDispatchSetupPacket(); - } else { - assert(type == TransactionType::Out); - m_ep0.processOUTpacket(); - } - - m_ep0.discardUnreadData(); - } - - /* IN transactions. - * The interrupt is done AFTER THE HANSDHAKE of the transaction. */ - if (OTG.DIEPINT(0)->getXFRC()) { // We only check endpoint 0. - m_ep0.processINpacket(); - // Clear the Transfer Completed Interrupt - OTG.DIEPINT(0)->setXFRC(true); - } - - // Handle USB RESET. ENUMDNE = **SPEED** Enumeration Done - if (intsts.getENUMDNE()) { - // Clear the ENUMDNE bit - OTG.GINTSTS()->setENUMDNE(true); - /* After a USB reset, the host talks to the device by sending messages to - * address 0; */ - setAddress(0); - // Flush the FIFOs - m_ep0.reset(); - m_ep0.setup(); - /* In setup(), we should set the MPSIZ field in OTG_DIEPCTL0 to the maximum - * packet size depending on the enumeration speed (found in OTG_DSTS). We - * should always get FullSpeed, so we set the packet size accordingly. */ - } -} - -bool Device::isSoftDisconnected() const { - return OTG.DCTL()->getSDIS(); -} - -void Device::detach() { - // Get in soft-disconnected state - OTG.DCTL()->setSDIS(true); -} - -bool Device::processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - // Device only handles standard requests. - if (request->requestType() != SetupPacket::RequestType::Standard) { - return false; - } - switch (request->bRequest()) { - case (int) Request::GetStatus: - return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); - case (int) Request::SetAddress: - // Make sure the request is adress is valid. - assert(request->wValue() < 128); - /* According to the reference manual, the address should be set after the - * Status stage of the current transaction, but this is not true. - * It should be set here, after the Data stage. */ - setAddress(request->wValue()); - *transferBufferLength = 0; - return true; - case (int) Request::GetDescriptor: - return getDescriptor(request, transferBuffer, transferBufferLength, transferBufferMaxLength); - case (int) Request::SetConfiguration: - *transferBufferLength = 0; - return setConfiguration(request); - case (int) Request::GetConfiguration: - return getConfiguration(transferBuffer, transferBufferLength); - } - return false; -} - -bool Device::getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - *transferBufferLength = min(2, transferBufferMaxLength); - for (int i = 0; i<*transferBufferLength; i++) { - transferBuffer[i] = 0; // No remote wakeup, not self-powered. - } - return true; -} - -void Device::setAddress(uint8_t address) { - OTG.DCFG()->setDAD(address); -} - -bool Device::getDescriptor(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - Descriptor * wantedDescriptor = descriptor(request->descriptorType(), request->descriptorIndex()); - if (wantedDescriptor == nullptr) { - return false; - } - *transferBufferLength = wantedDescriptor->copy(transferBuffer, transferBufferMaxLength); - return true; -} - -bool Device::getConfiguration(uint8_t * transferBuffer, uint16_t * transferBufferLength) { - *transferBufferLength = 1; - transferBuffer[0] = getActiveConfiguration(); - return true; -} - -bool Device::setConfiguration(SetupPacket * request) { - // We support one configuration only - setActiveConfiguration(request->wValue()); - /* There is one configuration only, we no need to set it again, just reset the - * endpoint. */ - m_ep0.reset(); - return true; -} - -} -} -} diff --git a/ion/src/f730/usb/stack/device.h b/ion/src/f730/usb/stack/device.h deleted file mode 100644 index 6bc112940..000000000 --- a/ion/src/f730/usb/stack/device.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef ION_DEVICE_USB_DEVICE_H -#define ION_DEVICE_USB_DEVICE_H - -#include "descriptor/descriptor.h" -#include "endpoint0.h" -#include "interface.h" -#include "request_recipient.h" -#include "setup_packet.h" - -namespace Ion { -namespace USB { -namespace Device { - -// We only handle control transfers, on EP0. -class Device : public RequestRecipient { -public: - Device(Interface * interface) : - RequestRecipient(&m_ep0), - m_ep0(this, interface), - m_resetOnDisconnect(false) - { - } - void poll(); - bool isSoftDisconnected() const; - void detach(); - bool resetOnDisconnect() { return m_resetOnDisconnect; } - void setResetOnDisconnect(bool reset) { m_resetOnDisconnect = reset; } -protected: - virtual Descriptor * descriptor(uint8_t type, uint8_t index) = 0; - virtual void setActiveConfiguration(uint8_t configurationIndex) = 0; - virtual uint8_t getActiveConfiguration() = 0; - bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; - Endpoint0 m_ep0; -private: - // USB Standard Device Request Codes - enum class Request { - GetStatus = 0, - ClearFeature = 1, - SetFeature = 3, - SetAddress = 5, - GetDescriptor = 6, - SetDescriptor = 7, - GetConfiguration = 8, - SetConfiguration = 9, - }; - - enum class TransactionType { - Setup, - In, - Out - }; - - void setAddress(uint8_t address); - bool getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); - bool getDescriptor(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); - bool getConfiguration(uint8_t * transferBuffer, uint16_t * transferBufferLength); - bool setConfiguration(SetupPacket * request); - - bool m_resetOnDisconnect; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/endpoint0.cpp b/ion/src/f730/usb/stack/endpoint0.cpp deleted file mode 100644 index 957a577f2..000000000 --- a/ion/src/f730/usb/stack/endpoint0.cpp +++ /dev/null @@ -1,344 +0,0 @@ -#include "endpoint0.h" -#include -#include -#include "device.h" -#include "interface.h" -#include "request_recipient.h" - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - -namespace Ion { -namespace USB { -namespace Device { - -void Endpoint0::setup() { - // Setup the IN direction - - // Reset the device IN endpoint 0 transfer size register - class OTG::DIEPTSIZ0 dieptsiz0(0); - /* Transfer size. The core interrupts the application only after it has - * exhausted the transfer size amount of data. The transfer size is set to the - * maximum packet size, to be interrupted at the end of each packet. */ - dieptsiz0.setXFRSIZ(k_maxPacketSize); - OTG.DIEPTSIZ0()->set(dieptsiz0); - - // Reset the device IN endpoint 0 control register - class OTG::DIEPCTL0 diepctl0(0); // Reset value - // Set the maximum packet size - diepctl0.setMPSIZ(OTG::DIEPCTL0::MPSIZ::Size64); - // Set the NAK bit: all IN transactions on endpoint 0 receive a NAK answer - diepctl0.setSNAK(true); - // Enable the endpoint - diepctl0.setEPENA(true); - OTG.DIEPCTL0()->set(diepctl0); - - // Setup the OUT direction - - setupOut(); - // Set the NAK bit - OTG.DOEPCTL0()->setSNAK(true); - // Enable the endpoint - enableOut(); - - // Setup the Tx FIFO - - /* Tx FIFO depth - * We process each packet as soon as it arrives, so we only need - * k_maxPacketSize bytes. TX0FD being in terms of 32-bit words, we divide - * k_maxPacketSize by 4. */ - OTG.DIEPTXF0()->setTX0FD(k_maxPacketSize/4); - /* Tx FIFO RAM start address. It starts just after the Rx FIFOso the value is - * Rx FIFO start address (0) + Rx FIFO depth. the Rx FIFO depth is set in - * usb.cpp, but because the code is linked separately, we cannot get it. */ - OTG.DIEPTXF0()->setTX0FSA(128); -} - -void Endpoint0::setupOut() { - class OTG::DOEPTSIZ0 doeptsiz0(0); - // Number of back-to-back SETUP data packets the endpoint can receive - doeptsiz0.setSTUPCNT(1); - // Packet count, false if a packet is written into the Rx FIFO - doeptsiz0.setPKTCNT(true); - /* Transfer size. The core interrupts the application only after it has - * exhausted the transfer size amount of data. The transfer size is set to the - * maximum packet size, to be interrupted at the end of each packet. */ - doeptsiz0.setXFRSIZ(64); - OTG.DOEPTSIZ0()->set(doeptsiz0); -} - -void Endpoint0::setOutNAK(bool nak) { - m_forceNAK = nak; - /* We need to keep track of the NAK state of the endpoint to use the value - * after a setupOut in poll() of device.cpp. */ - if (nak) { - OTG.DOEPCTL0()->setSNAK(true); - } else { - OTG.DOEPCTL0()->setCNAK(true); - } -} - -void Endpoint0::enableOut() { - OTG.DOEPCTL0()->setEPENA(true); -} - -void Endpoint0::reset() { - flushTxFifo(); - flushRxFifo(); -} - -void Endpoint0::readAndDispatchSetupPacket() { - setOutNAK(true); - - // Read the 8-bytes Setup packet - if (readPacket(m_largeBuffer, sizeof(SetupPacket)) != sizeof(SetupPacket)) { - stallTransaction(); - return; - }; - - m_request = SetupPacket(m_largeBuffer); - uint16_t maxBufferLength = MIN(m_request.wLength(), MaxTransferSize); - - // Forward the request to the request recipient - uint8_t type = static_cast(m_request.recipientType()); - if (type == 0) { - // Device recipient - m_requestRecipients[0]->processSetupRequest(&m_request, m_largeBuffer, &m_transferBufferLength, maxBufferLength); - } else { - // Interface recipient - m_requestRecipients[1]->processSetupRequest(&m_request, m_largeBuffer, &m_transferBufferLength, maxBufferLength); - } -} - -void Endpoint0::processINpacket() { - switch (m_state) { - case State::DataIn: - sendSomeData(); - break; - case State::LastDataIn: - m_state = State::StatusOut; - // Prepare to receive the OUT Data[] transaction. - setOutNAK(false); - break; - case State::StatusIn: - { - m_state = State::Idle; - // All the data has been received. Callback the request recipient. - uint8_t type = static_cast(m_request.recipientType()); - if (type == 0) { - // Device recipient - m_requestRecipients[0]->wholeDataReceivedCallback(&m_request, m_largeBuffer, &m_transferBufferLength); - } else { - // Interface recipient - m_requestRecipients[1]->wholeDataReceivedCallback(&m_request, m_largeBuffer, &m_transferBufferLength); - } - } - break; - default: - stallTransaction(); - } -} - -void Endpoint0::processOUTpacket() { - switch (m_state) { - case State::DataOut: - if (receiveSomeData() < 0) { - break; - } - if ((m_request.wLength() - m_transferBufferLength) <= k_maxPacketSize) { - m_state = State::LastDataOut; - } - break; - case State::LastDataOut: - if (receiveSomeData() < 0) { - break; - } - // Send the DATA1[] to the host. - writePacket(NULL, 0); - m_state = State::StatusIn; - break; - case State::StatusOut: - { - // Read the DATA1[] sent by the host. - readPacket(NULL, 0); - m_state = State::Idle; - // All the data has been sent. Callback the request recipient. - uint8_t type = static_cast(m_request.recipientType()); - if (type == 0) { - // Device recipient - m_requestRecipients[0]->wholeDataSentCallback(&m_request, m_largeBuffer, &m_transferBufferLength); - } else { - // Interface recipient - m_requestRecipients[1]->wholeDataSentCallback(&m_request, m_largeBuffer, &m_transferBufferLength); - } - } - break; - default: - stallTransaction(); - } -} - -void Endpoint0::flushTxFifo() { - // Set IN endpoint NAK - OTG.DIEPCTL0()->setSNAK(true); - - // Wait for core to respond - while (!OTG.DIEPINT(0)->getINEPNE()) { - } - - // Get the Tx FIFO number - uint32_t fifo = OTG.DIEPCTL0()->getTXFNUM(); - - // Wait for AHB idle - while (!OTG.GRSTCTL()->getAHBIDL()) { - } - - // Flush Tx FIFO - OTG.GRSTCTL()->setTXFNUM(fifo); - OTG.GRSTCTL()->setTXFFLSH(true); - - // Reset packet counter - OTG.DIEPTSIZ0()->set(0); - - // Wait for the flush - while (OTG.GRSTCTL()->getTXFFLSH()) { - } -} - -void Endpoint0::flushRxFifo() { - // Set OUT endpoint NAK - OTG.DOEPCTL0()->setSNAK(true); - - // Wait for AHB idle - while (!OTG.GRSTCTL()->getAHBIDL()) { - } - - // Flush Rx FIFO - OTG.GRSTCTL()->setRXFFLSH(true); - - // Reset packet counter - OTG.DOEPTSIZ0()->set(0); - - // Wait for the flush - while (OTG.GRSTCTL()->getRXFFLSH()) { - } -} - -void Endpoint0::discardUnreadData() { - for (int i = 0; i < m_receivedPacketSize; i += 4) { - OTG.DFIFO0()->get(); - } - m_receivedPacketSize = 0; -} - -void Endpoint0::sendSomeData() { - if (k_maxPacketSize < m_transferBufferLength) { - // More than one packet needs to be sent - writePacket(m_largeBuffer + m_bufferOffset, k_maxPacketSize); - m_state = State::DataIn; - m_bufferOffset += k_maxPacketSize; - m_transferBufferLength -= k_maxPacketSize; - return; - } - // Last data packet sent - writePacket(m_largeBuffer + m_bufferOffset, m_transferBufferLength); - if (m_zeroLengthPacketNeeded) { - m_state = State::DataIn; - } else { - m_state = State::LastDataIn; - } - m_bufferOffset = 0; - m_zeroLengthPacketNeeded = false; - m_transferBufferLength = 0; -} - -void Endpoint0::clearForOutTransactions(uint16_t wLength) { - m_transferBufferLength = 0; - m_state = (wLength > k_maxPacketSize) ? State::DataOut : State::LastDataOut; - setOutNAK(false); -} - -uint16_t Endpoint0::receiveSomeData() { - // If it is the first chunk of data to be received, m_transferBufferLength is 0. - uint16_t packetSize = MIN(k_maxPacketSize, m_request.wLength() - m_transferBufferLength); - uint16_t sizeOfPacketRead = readPacket(m_largeBuffer + m_transferBufferLength, packetSize); - if (sizeOfPacketRead != packetSize) { - stallTransaction(); - return -1; - } - m_transferBufferLength += packetSize; - return packetSize; -} - -uint16_t Endpoint0::readPacket(void * buffer, uint16_t length) { - uint32_t * buffer32 = (uint32_t *) buffer; - uint16_t buffer32Length = MIN(length, m_receivedPacketSize); - - int i; - // The RX FIFO is read 4 bytes by 4 bytes - for (i = buffer32Length; i >= 4; i -= 4) { - *buffer32++ = OTG.DFIFO0()->get(); - m_receivedPacketSize -= 4; - } - - if (i) { - /* If there are remaining bytes that should be read, read the next 4 bytes - * and copy only the wanted bytes. */ - uint32_t extraData = OTG.DFIFO0()->get(); - memcpy(buffer32, &extraData, i); - if (m_receivedPacketSize < 4) { - m_receivedPacketSize = 0; - } else { - m_receivedPacketSize -= 4; - } - } - return buffer32Length; -} - -uint16_t Endpoint0::writePacket(const void * buffer, uint16_t length) { - const uint32_t * buffer32 = (uint32_t *) buffer; - - // Return if there is already a packet waiting to be read in the TX FIFO - if (OTG.DIEPTSIZ0()->getPKTCNT()) { - return 0; - } - - // Enable transmission - - class OTG::DIEPTSIZ0 dieptsiz0(0); - // Indicate that the Transfer Size is one packet - dieptsiz0.setPKTCNT(1); - // Indicate the length of the Transfer Size - dieptsiz0.setXFRSIZ(length); - OTG.DIEPTSIZ0()->set(dieptsiz0); - // Enable the endpoint - OTG.DIEPCTL0()->setEPENA(true); - // Clear the NAK bit - OTG.DIEPCTL0()->setCNAK(true); - - // Copy the buffer to the TX FIFO by writing data 32bits by 32 bits. - for (int i = length; i > 0; i -= 4) { - OTG.DFIFO0()->set(*buffer32++); - } - - return length; -} - -void Endpoint0::stallTransaction() { - OTG.DIEPCTL0()->setSTALL(true); - m_state = State::Idle; -} - -void Endpoint0::computeZeroLengthPacketNeeded() { - if (m_transferBufferLength - && m_transferBufferLength < m_request.wLength() - && m_transferBufferLength % k_maxPacketSize == 0) - { - m_zeroLengthPacketNeeded = true; - return; - } - m_zeroLengthPacketNeeded = false; -} - -} -} -} diff --git a/ion/src/f730/usb/stack/endpoint0.h b/ion/src/f730/usb/stack/endpoint0.h deleted file mode 100644 index 2088aaadd..000000000 --- a/ion/src/f730/usb/stack/endpoint0.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef ION_DEVICE_USB_ENDPOINT0_H -#define ION_DEVICE_USB_ENDPOINT0_H - -#include "setup_packet.h" - -namespace Ion { -namespace USB { -namespace Device { - -class RequestRecipient; - -class Endpoint0 { -public: - enum class State { - Idle, - Stalled, - DataIn, - LastDataIn, - StatusIn, - DataOut, - LastDataOut, - StatusOut, - }; - - constexpr static int k_maxPacketSize = 64; - constexpr static int MaxTransferSize = 2048; - - constexpr Endpoint0(RequestRecipient * device, RequestRecipient * interface) : - m_forceNAK(false), - m_bufferOffset(0), - m_transferBufferLength(0), - m_receivedPacketSize(0), - m_zeroLengthPacketNeeded(false), - m_request(), - m_requestRecipients{device, interface}, - m_state(State::Idle), - m_largeBuffer{0} - { - } - void setup(); - void setupOut(); - void setOutNAK(bool nak); - void enableOut(); - void reset(); - bool NAKForced() const { return m_forceNAK; } - void readAndDispatchSetupPacket(); - void processINpacket(); - void processOUTpacket(); - void flushTxFifo(); - void flushRxFifo(); - void setReceivedPacketSize(uint16_t size) { m_receivedPacketSize = size; } - void discardUnreadData(); - void stallTransaction(); - void computeZeroLengthPacketNeeded(); - void setState(State state) { m_state = state; } - void sendSomeData(); // Writes the next data packet and updates the state. - void clearForOutTransactions(uint16_t wLength); - -private: - uint16_t receiveSomeData(); - uint16_t readPacket(void * buffer, uint16_t length); - uint16_t writePacket(const void * buffer, uint16_t length); - - bool m_forceNAK; - int m_bufferOffset; // When sending large data stored in the buffer, the offset keeps tracks of which data packet should be sent next. - uint16_t m_transferBufferLength; - uint16_t m_receivedPacketSize; - bool m_zeroLengthPacketNeeded; - SetupPacket m_request; - RequestRecipient * m_requestRecipients[2]; - State m_state; - uint8_t m_largeBuffer[MaxTransferSize]; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/interface.cpp b/ion/src/f730/usb/stack/interface.cpp deleted file mode 100644 index 3fa95947e..000000000 --- a/ion/src/f730/usb/stack/interface.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "interface.h" - -namespace Ion { -namespace USB { -namespace Device { - -static inline uint16_t min(uint16_t x, uint16_t y) { return (xrequestType() != SetupPacket::RequestType::Standard) { - return false; - } - switch (request->bRequest()) { - case (int) Request::GetStatus: - return getStatus(transferBuffer, transferBufferLength, transferBufferMaxLength); - case (int) Request::SetInterface: - return setInterface(request, transferBufferLength); - case (int) Request::GetInterface: - return getInterface(transferBuffer, transferBufferLength, transferBufferMaxLength); - case (int) Request::ClearFeature: - return clearFeature(transferBufferLength); - case (int) Request::SetFeature: - return setFeature(transferBufferLength); - } - return false; -} - -bool Interface::getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - *transferBufferLength = min(2, transferBufferMaxLength); - for (int i = 0; i<*transferBufferLength; i++) { - transferBuffer[i] = 0; // Reserved, must be set to 0 - } - return true; -} - -bool Interface::getInterface(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - *transferBufferLength = min(1, transferBufferMaxLength);; - if (*transferBufferLength > 0) { - transferBuffer[0] = getActiveInterfaceAlternative(); - } - return true; -} - -bool Interface::setInterface(SetupPacket * request, uint16_t * transferBufferLength) { - // We support one interface only - setActiveInterfaceAlternative(request->wValue()); - // There is one interface alternative only, we no need to set it again. - *transferBufferLength = 0; - return true; -} - -bool Interface::clearFeature(uint16_t * transferBufferLength) { - // Not needed for now - *transferBufferLength = 0; - return true; -} - -bool Interface::setFeature(uint16_t * transferBufferLength) { - // Not needed for now - *transferBufferLength = 0; - return true; -} - -} -} -} diff --git a/ion/src/f730/usb/stack/interface.h b/ion/src/f730/usb/stack/interface.h deleted file mode 100644 index 40376e197..000000000 --- a/ion/src/f730/usb/stack/interface.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef ION_DEVICE_USB_INTERFACE_H -#define ION_DEVICE_USB_INTERFACE_H - -#include "endpoint0.h" -#include "request_recipient.h" -#include "setup_packet.h" - -namespace Ion { -namespace USB { -namespace Device { - -class Interface : public RequestRecipient { -public: - Interface(Endpoint0 * ep0) : - RequestRecipient(ep0) - { - } -protected: - virtual void setActiveInterfaceAlternative(uint8_t interfaceAlternativeIndex) = 0; - virtual uint8_t getActiveInterfaceAlternative() = 0; - bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) override; -private: - // USB Standard Interface Request Codes - enum class Request { - GetStatus = 0, - ClearFeature = 1, - SetFeature = 3, - GetInterface = 10, - SetInterface = 11, - }; - bool getStatus(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); - bool getInterface(uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); - bool setInterface(SetupPacket * request, uint16_t * transferBufferLength); - bool clearFeature(uint16_t * transferBufferLength); - bool setFeature(uint16_t * transferBufferLength); -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/request_recipient.cpp b/ion/src/f730/usb/stack/request_recipient.cpp deleted file mode 100644 index c00745763..000000000 --- a/ion/src/f730/usb/stack/request_recipient.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "request_recipient.h" - -namespace Ion { -namespace USB { -namespace Device { - -bool RequestRecipient::processSetupRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) { - if (request->followingTransaction() == SetupPacket::TransactionType::InTransaction) { - // There is no data stage in this transaction, or the data stage will be in IN direction. - if (!processSetupInRequest(request, transferBuffer, transferBufferLength, transferBufferMaxLength)) { - m_ep0->stallTransaction(); - return false; - } - if (*transferBufferLength > 0) { - m_ep0->computeZeroLengthPacketNeeded(); - m_ep0->sendSomeData(); - } else { - m_ep0->sendSomeData(); - // On seeing a zero length packet, sendSomeData changed endpoint0 state to - // LastDataIn, but it should be StatusIn as there was no data stage. - m_ep0->setState(Endpoint0::State::StatusIn); - } - } else { - // The following transaction will be an OUT transaction. - m_ep0->clearForOutTransactions(request->wLength()); - } - return true; -} - -} -} -} diff --git a/ion/src/f730/usb/stack/request_recipient.h b/ion/src/f730/usb/stack/request_recipient.h deleted file mode 100644 index 7f0fc8819..000000000 --- a/ion/src/f730/usb/stack/request_recipient.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ION_DEVICE_USB_REQUEST_RECIPIENT_H -#define ION_DEVICE_USB_REQUEST_RECIPIENT_H - -#include "endpoint0.h" -#include "setup_packet.h" - -namespace Ion { -namespace USB { -namespace Device { - -class RequestRecipient { -public: - RequestRecipient(Endpoint0 * ep0): - m_ep0(ep0) - { - } - bool processSetupRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength); - virtual void wholeDataReceivedCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {} - virtual void wholeDataSentCallback(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength) {} -protected: - virtual bool processSetupInRequest(SetupPacket * request, uint8_t * transferBuffer, uint16_t * transferBufferLength, uint16_t transferBufferMaxLength) = 0; - Endpoint0 * m_ep0; -}; - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/setup_packet.cpp b/ion/src/f730/usb/stack/setup_packet.cpp deleted file mode 100644 index d879b59e9..000000000 --- a/ion/src/f730/usb/stack/setup_packet.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "setup_packet.h" -#include - -namespace Ion { -namespace USB { -namespace Device { - -SetupPacket::SetupPacket(void * buffer) { - memcpy(this, buffer, sizeof(SetupPacket)); -} - -SetupPacket::TransactionType SetupPacket::followingTransaction() const { - if (m_wLength == 0 || (m_bmRequestType & 0b10000000) != 0) { - return TransactionType::InTransaction; - } else { - return TransactionType::OutTransaction; - } -} - -SetupPacket::RequestType SetupPacket::requestType() const { - return (RequestType) ((m_bmRequestType & 0b01100000) >> 5); -} - -SetupPacket::RecipientType SetupPacket::recipientType() const { - return (RecipientType) (m_bmRequestType & 0b00001111); -} - -int SetupPacket::descriptorIndex() { - return m_wValue & 0xFF; -} - -int SetupPacket::descriptorType() { - return m_wValue >> 8; -} - -} -} -} diff --git a/ion/src/f730/usb/stack/setup_packet.h b/ion/src/f730/usb/stack/setup_packet.h deleted file mode 100644 index 054ff81e9..000000000 --- a/ion/src/f730/usb/stack/setup_packet.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef ION_DEVICE_USB_SETUP_PACKET_H -#define ION_DEVICE_USB_SETUP_PACKET_H - -#include - -namespace Ion { -namespace USB { -namespace Device { - -class SetupPacket { -public: - enum class TransactionType { - SetupTransaction, - InTransaction, - OutTransaction - }; - - enum class RequestType { - Standard = 0, - Class = 1, - Vendor = 2 - }; - - enum class RecipientType { - Device = 0, - Interface = 1, - Endpoint = 2, - Other = 3 - }; - - constexpr SetupPacket() : - m_bmRequestType(0), - m_bRequest(0), - m_wValue(0), - m_wIndex(0), - m_wLength(0) - { - } - - SetupPacket(void * buffer); - TransactionType followingTransaction() const; - RequestType requestType() const; - RecipientType recipientType() const; - int descriptorIndex(); - int descriptorType(); - uint8_t bmRequestType() { return m_bmRequestType; } - uint8_t bRequest() { return m_bRequest; } - uint16_t wValue() { return m_wValue; } - uint16_t wIndex() { return m_wIndex; } - uint16_t wLength() { return m_wLength; } -private: - uint8_t m_bmRequestType; - uint8_t m_bRequest; - uint16_t m_wValue; - uint16_t m_wIndex; - uint16_t m_wLength; -}; - -static_assert(sizeof(SetupPacket) == 8, "SetupData must be packed"); - -} -} -} - -#endif diff --git a/ion/src/f730/usb/stack/streamable.cpp b/ion/src/f730/usb/stack/streamable.cpp deleted file mode 100644 index 3f4677df8..000000000 --- a/ion/src/f730/usb/stack/streamable.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "streamable.h" - -namespace Ion { -namespace USB { -namespace Device { - -uint16_t Streamable::copy(void * target, size_t maxSize) const { - Channel c(target, maxSize); - push(&c); - return maxSize - c.sizeLeft(); -} - -} -} -} diff --git a/ion/src/f730/usb/stack/streamable.h b/ion/src/f730/usb/stack/streamable.h deleted file mode 100644 index 3a653ef1e..000000000 --- a/ion/src/f730/usb/stack/streamable.h +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef ION_DEVICE_USB_STREAMABLE_H -#define ION_DEVICE_USB_STREAMABLE_H - -#include -#include - -namespace Ion { -namespace USB { -namespace Device { - -class Streamable { -public: - uint16_t copy(void * target, size_t maxSize) const; -protected: - class Channel { - public: - Channel(void * pointer, size_t maxSize) : - m_pointer(pointer), - m_sizeLeft(maxSize) - { - } - template - void push(T data) { - if (m_sizeLeft >= sizeof(T)) { - T * typedPointer = static_cast(m_pointer); - *typedPointer++ = data; // Actually push the data - m_pointer = static_cast(typedPointer); - m_sizeLeft -= sizeof(T); - } - } - size_t sizeLeft() { return m_sizeLeft; } - private: - void * m_pointer; - size_t m_sizeLeft; - }; - virtual void push(Channel * c) const = 0; -}; - - -} -} -} - -#endif diff --git a/ion/src/f730/wakeup.cpp b/ion/src/f730/wakeup.cpp deleted file mode 100644 index b67c95921..000000000 --- a/ion/src/f730/wakeup.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "wakeup.h" - -#include "battery.h" -#include "usb.h" -#include "keyboard.h" - -#include "regs/syscfg.h" -#include "regs/exti.h" - -namespace Ion { -namespace WakeUp { -namespace Device { - -void onChargingEvent() { - Battery::Device::initGPIO(); - - /* Warning: pins with the same number in different groups cannot be set as - * source input for EXTI at the same time. Here, EXTICR1 register is filled - * between position 12-15 (charging pin = 3) with - * 0000 (ChargingGPIO = group A). */ - SYSCFG.EXTICR1()->setEXTI(Battery::Device::ChargingPin.pin(), Battery::Device::ChargingPin.group()); - - EXTI.EMR()->set(Battery::Device::ChargingPin.pin(), true); - - /* We need to detect when the battery stops charging. We set the - * wake up event on the rising edge. */ - EXTI.RTSR()->set(Battery::Device::ChargingPin.pin(), true); -} - -void onUSBPlugging() { - USB::Device::VbusPin.group().MODER()->setMode(USB::Device::VbusPin.pin(), GPIO::MODER::Mode::Input); - USB::Device::VbusPin.group().PUPDR()->setPull(USB::Device::VbusPin.pin(), GPIO::PUPDR::Pull::Down); - /* Here, EXTICR3 register is filled between position 4-7 (Vbus pin = 9) with - * 0000 (Vbus GPIO = group A). */ - SYSCFG.EXTICR3()->setEXTI(USB::Device::VbusPin.pin(), USB::Device::VbusPin.group()); - - EXTI.EMR()->set(USB::Device::VbusPin.pin(), true); - EXTI.FTSR()->set(USB::Device::VbusPin.pin(), true); - EXTI.RTSR()->set(USB::Device::VbusPin.pin(), true); -} - - -void onPowerKeyDown() { - Keyboard::Key key = Keyboard::Key::B2; - uint8_t rowPin = Keyboard::Device::RowPins[Keyboard::Device::rowForKey(key)]; - Keyboard::Device::RowGPIO.MODER()->setMode(rowPin, GPIO::MODER::Mode::Output); - Keyboard::Device::RowGPIO.OTYPER()->setType(rowPin, GPIO::OTYPER::Type::OpenDrain); - Keyboard::Device::RowGPIO.ODR()->set(rowPin, 0); - - uint8_t column = Keyboard::Device::columnForKey(key); - uint8_t columnPin = Keyboard::Device::ColumnPins[column]; - - Keyboard::Device::ColumnGPIO.MODER()->setMode(columnPin, GPIO::MODER::Mode::Input); - Keyboard::Device::ColumnGPIO.PUPDR()->setPull(columnPin, GPIO::PUPDR::Pull::Up); - - /* Here, EXTICR1 register is filled between position 8-11 (column pin = 2) with - * 0010 (ColumnGPIO = group C). */ - - SYSCFG.EXTICR1()->setEXTI(columnPin, Keyboard::Device::ColumnGPIO); - - EXTI.EMR()->set(columnPin, true); - - /* When the key is pressed, it will go from 1 (because it's pulled up) to - * zero (because it's connected to the open-drain output. In other words, - * we're waiting for a falling edge. */ - EXTI.FTSR()->set(columnPin, true); -} - -} -} -} diff --git a/ion/src/f730/wakeup.h b/ion/src/f730/wakeup.h deleted file mode 100644 index 36c35af70..000000000 --- a/ion/src/f730/wakeup.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef ION_DEVICE_WAKE_UP_H -#define ION_DEVICE_WAKE_UP_H - -namespace Ion { -namespace WakeUp { -namespace Device { - -/* All wakeup functions can be called together without overwriting the same - * register. All togethed, they will set SYSCFG and EXTi registers as follow: - * - * GPIO Pin Number|EXTI_EMR|EXTI_FTSR|EXTI_RTSR|EXTICR1|EXTICR2|EXTICR3| Wake up - * ---------------+--------+---------+---------+-------+-------+-------+------------------------- - * 0 | 1 | 0 | 1 | A | ***** | ***** | Rising edge GPIO A pin 0 - * 1 | 1 | 1 | 0 | C | ***** | ***** | Falling edge GPIO C pin 1 - * 2 | 0 | 0 | 0 | A | ***** | ***** | - * 3 | 0 | 0 | 0 | A | ***** | ***** | - * 4 | 0 | 0 | 0 | ***** | A | ***** | - * 5 | 0 | 0 | 0 | ***** | A | ***** | - * 6 | 0 | 0 | 0 | ***** | A | ***** | - * 7 | 0 | 0 | 0 | ***** | A | ***** | - * 8 | 0 | 0 | 0 | ***** | ***** | A | - * 9 | 1 | 1 | 1 | ***** | ***** | A | Falling/Rising edge GPIO A pin 9 - * 10 | 0 | 0 | 0 | ***** | ***** | A | - * 11 | 0 | 0 | 0 | ***** | ***** | A | - * 12 | 0 | 0 | 0 | ***** | ***** | ***** | - * 13 | 0 | 0 | 0 | ***** | ***** | ***** | - * 14 | 0 | 0 | 0 | ***** | ***** | ***** | - * 15 | 0 | 0 | 0 | ***** | ***** | ***** | - */ - -void onChargingEvent(); -void onUSBPlugging(); -void onPowerKeyDown(); - -} -} -} - -#endif From 478d49b4163d9135b95b3abf5dacfcaaa75c9ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 21 Mar 2019 14:23:51 +0100 Subject: [PATCH 0184/1750] [ion] DFU leave instruction triggers a core reset on N0101 and a soft reset on N0100 (to avoid booting on ST bootloader as the device is plugged) --- ion/src/device/n0100/Makefile | 2 ++ ion/src/device/n0100/usb/stack/device.cpp | 20 ++++++++++++++++++++ ion/src/device/n0101/Makefile | 2 ++ ion/src/device/n0101/usb/stack/device.cpp | 16 ++++++++++++++++ ion/src/device/shared/usb/Makefile | 1 + ion/src/device/shared/usb/calculator.cpp | 8 +------- ion/src/device/shared/usb/stack/device.h | 1 + 7 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 ion/src/device/n0100/usb/stack/device.cpp create mode 100644 ion/src/device/n0101/usb/stack/device.cpp diff --git a/ion/src/device/n0100/Makefile b/ion/src/device/n0100/Makefile index 931bd69d0..8b0271c7b 100644 --- a/ion/src/device/n0100/Makefile +++ b/ion/src/device/n0100/Makefile @@ -2,4 +2,6 @@ ion_device_src += $(addprefix ion/src/device/n0100/drivers/, \ board.cpp \ ) +ion_device_src += ion/src/device/n0101/usb/stack/device.cpp + LDSCRIPT ?= ion/src/device/n0100/flash.ld diff --git a/ion/src/device/n0100/usb/stack/device.cpp b/ion/src/device/n0100/usb/stack/device.cpp new file mode 100644 index 000000000..c868116ed --- /dev/null +++ b/ion/src/device/n0100/usb/stack/device.cpp @@ -0,0 +1,20 @@ +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +void Device::leave() { + /* We don't perform a core reset because at this point in time the USB cable + * is most likely plugged in. Doing a full core reset would be the clean + * thing to do but would therefore result in the device entering the ROMed + * DFU bootloader, which we want to avoid. By performing a jump-reset, we + * will enter the newly flashed firmware. */ + Ion::Device::Reset::jump(); +} + +} +} +} + diff --git a/ion/src/device/n0101/Makefile b/ion/src/device/n0101/Makefile index 5f1bf5b13..de1b95697 100644 --- a/ion/src/device/n0101/Makefile +++ b/ion/src/device/n0101/Makefile @@ -3,4 +3,6 @@ ion_device_src += $(addprefix ion/src/device/n0101/drivers/, \ cache.cpp \ ) +ion_device_src += ion/src/device/n0101/usb/stack/device.cpp + LDSCRIPT ?= ion/src/device/n0101/flash.ld diff --git a/ion/src/device/n0101/usb/stack/device.cpp b/ion/src/device/n0101/usb/stack/device.cpp new file mode 100644 index 000000000..08ca6a9f4 --- /dev/null +++ b/ion/src/device/n0101/usb/stack/device.cpp @@ -0,0 +1,16 @@ +#include +#include + +namespace Ion { +namespace Device { +namespace USB { + +void Device::leave() { + Ion::Device::Reset::core(); +} + +} +} +} + + diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index 7c77dff0d..b61dbdb6b 100644 --- a/ion/src/device/shared/usb/Makefile +++ b/ion/src/device/shared/usb/Makefile @@ -48,6 +48,7 @@ dfu_src += liba/src/memcpy.c dfu_src += libaxx/src/cxxabi/pure_virtual.cpp dfu_src += ion/src/device/shared/usb/boot.cpp dfu_src += ion/src/device/$(MODEL)/drivers/cache.cpp +dfu_src += ion/src/device/$(MODEL)/usb/stack/device.cpp dfu_src += $(addprefix ion/src/device/shared/drivers/, \ base64.cpp \ external_flash.cpp \ diff --git a/ion/src/device/shared/usb/calculator.cpp b/ion/src/device/shared/usb/calculator.cpp index 0ad5a285c..60a63185e 100644 --- a/ion/src/device/shared/usb/calculator.cpp +++ b/ion/src/device/shared/usb/calculator.cpp @@ -1,7 +1,6 @@ #include "calculator.h" #include #include -#include #include namespace Ion { @@ -30,12 +29,7 @@ void Calculator::PollAndReset(bool exitWithKeyboard) { c.detach(); } if (c.resetOnDisconnect()) { - /* We don't perform a core reset because at this point in time the USB cable - * is most likely plugged in. Doing a full core reset would be the clean - * thing to do but would therefore result in the device entering the ROMed - * DFU bootloader, which we want to avoid. By performing a jump-reset, we - * will enter the newly flashed firmware. */ - Ion::Device::Reset::jump(); + c.leave(); } } diff --git a/ion/src/device/shared/usb/stack/device.h b/ion/src/device/shared/usb/stack/device.h index 64d97d0fb..e1bcb3daf 100644 --- a/ion/src/device/shared/usb/stack/device.h +++ b/ion/src/device/shared/usb/stack/device.h @@ -23,6 +23,7 @@ public: void poll(); bool isSoftDisconnected() const; void detach(); + void leave(); bool resetOnDisconnect() { return m_resetOnDisconnect; } void setResetOnDisconnect(bool reset) { m_resetOnDisconnect = reset; } protected: From e0be38681b21884c884f4297895b7c0368fb8b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 21 Mar 2019 15:40:15 +0100 Subject: [PATCH 0185/1750] [scripts] Fix elf2dfu.py to sort internal/external sections on their address rather than on their names --- scripts/device/elf2dfu.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/device/elf2dfu.py b/scripts/device/elf2dfu.py index 2e5fcd31f..a1b606b01 100644 --- a/scripts/device/elf2dfu.py +++ b/scripts/device/elf2dfu.py @@ -11,8 +11,8 @@ import argparse # arm-none-eabi-objdump -h -w file.elf # arm-none-eabi-objcopy -O binary -j .data file.elf file.bin -def loadable_sections(elf_file, section_name_appendix = ""): - objdump_section_headers_pattern = re.compile("^\s+\d+\s+(\.[\w\.]+"+section_name_appendix+")\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+([0-9a-f]+).*LOAD", flags=re.MULTILINE) +def loadable_sections(elf_file, address_prefix = ""): + objdump_section_headers_pattern = re.compile("^\s+\d+\s+(\.[\w\.]+)\s+([0-9a-f]+)\s+([0-9a-f]+)\s+("+address_prefix+"[0-9a-f]+)\s+([0-9a-f]+).*LOAD", flags=re.MULTILINE) objdump_output = subprocess.check_output(["arm-none-eabi-objdump", "-h", "-w", elf_file]) sections = [] for (name, size, vma, lma, offset) in re.findall(objdump_section_headers_pattern, objdump_output): @@ -47,7 +47,11 @@ def print_sections(sections): print("%s-%s: %s, %s" % (hex(s['lma']), hex(s['lma'] + s['size'] - 1), s['name'], "{:,} bytes".format(s['size']))) def elf2dfu(elf_file, usb_vid_pid, dfu_file, verbose): - external_block = {'name': "external", 'sections': loadable_sections(elf_file, "external")} + external_address_prefix = "9"; # External addresses start with 0x9 + # We don't sort sections on their names (.external, .internal) but on their + # addresses because some sections like dfu_entry_point can either be the + # internal or the external flash depending on build flags (ie EPSILON_USB_DFU_XIP) + external_block = {'name': "external", 'sections': loadable_sections(elf_file, external_address_prefix)} internal_block = {'name': "internal", 'sections': [s for s in loadable_sections(elf_file) if s not in external_block['sections']]} blocks = [external_block, internal_block] if verbose: From 1676dc35bf10a02d474310dfe7051ee911b5e204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 22 Mar 2019 15:44:30 +0100 Subject: [PATCH 0186/1750] [ion/n0101] Update frequency ration for 8MHz quartz --- ion/src/device/n0101/drivers/board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index cd56f5e98..c692864f0 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -100,7 +100,7 @@ void initClocks() { * for use in different parts of the system. */ // Configure the PLL ratios and use HSE as a PLL input - RCC.PLLCFGR()->setPLLM(25); + RCC.PLLCFGR()->setPLLM(8); RCC.PLLCFGR()->setPLLN(384); RCC.PLLCFGR()->setPLLQ(8); RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); From 5babf98007de37faecd24baefeef90d8212b5b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 22 Mar 2019 15:45:31 +0100 Subject: [PATCH 0187/1750] [scripts] Default device build is for n0101 --- scripts/platform.device.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/platform.device.mak b/scripts/platform.device.mak index 405fbaa36..610c9ed20 100644 --- a/scripts/platform.device.mak +++ b/scripts/platform.device.mak @@ -1,4 +1,4 @@ -MODEL ?= n0100 +MODEL ?= n0101 USE_LIBA = 1 EXE = elf EPSILON_BOOT_PROMPT = update From d34c5d3df9dfd4172a896dbf764dd3889f0abb84 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Fri, 22 Mar 2019 13:22:41 +0100 Subject: [PATCH 0188/1750] [ion/device/drivers/external_flash] Clarify ChipSelectHighTime --- ion/src/device/shared/drivers/external_flash.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 44067a184..cbcdf54d3 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -91,8 +91,8 @@ public: }; static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; +static constexpr int AHBClockFrequency = 192; // MHz static constexpr int ClockFrequencyDivisor = 2; -static constexpr int ChipSelectHighTime = (ClockFrequencyDivisor == 1) ? 3 : (ClockFrequencyDivisor == 2) ? 2 : 1; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength); @@ -238,6 +238,10 @@ static void initQSPI() { // Configure controller for target device class QUADSPI::DCR dcr(0); dcr.setFSIZE(NumberOfAddressBitsInChip - 1); + /* According to the device's datasheet (see Sections 8.7 and 8.8), the CS + * signal should stay high (deselect the device) for t_SHSL = 30ns at least. + * */ + constexpr int ChipSelectHighTime = (30 * AHBClockFrequency + ClockFrequencyDivisor * 1000 - 1) / (ClockFrequencyDivisor * 1000); dcr.setCSHT(ChipSelectHighTime - 1); dcr.setCKMODE(true); QUADSPI.DCR()->set(dcr); From 9b50942996a0348230906d14f0b6f7650e06a5f7 Mon Sep 17 00:00:00 2001 From: Ruben Dashyan Date: Fri, 22 Mar 2019 15:07:33 +0100 Subject: [PATCH 0189/1750] [ion/device/drivers/external_flash] Fix dummy cycles for Fast Read in QPI mode --- ion/src/device/shared/drivers/external_flash.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index cbcdf54d3..6c927dae0 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -93,6 +93,8 @@ public: static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; static constexpr int AHBClockFrequency = 192; // MHz static constexpr int ClockFrequencyDivisor = 2; +static constexpr bool ajustNumberOfDummyCycles = AHBClockFrequency < 80 * ClockFrequencyDivisor; +static constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad && ajustNumberOfDummyCycles) ? 4 : 2; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength); @@ -149,14 +151,13 @@ static void set_as_memory_mapped() { * * It goes low, only if the low-power timeout counter is enabled. * (Flash memories tend to consume more when nCS is held low.) */ - constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Single) ? 8 : (ClockFrequencyDivisor > 1) ? 4 : 6; send_command_full( QUADSPI::CCR::FunctionalMode::MemoryMapped, DefaultOperatingMode, Command::FastReadQuadIO, reinterpret_cast(FlashAddressSpaceSize), 0xA0, 1, - 2, //FIXME + FastReadDummyCycles, nullptr, 0 ); } @@ -170,7 +171,7 @@ static void unset_memory_mapped_mode() { Command::FastReadQuadIO, 0, ~(0xA0), 1, - 2, //FIXME + FastReadDummyCycles, &dummyData, 1 ); } @@ -263,7 +264,7 @@ static void initChip() { wait(QUADSPI::CCR::OperatingMode::Single); send_command(Command::EnableQPI, QUADSPI::CCR::OperatingMode::Single); wait(); - if (ClockFrequencyDivisor == 1) { + if (ajustNumberOfDummyCycles) { class ReadParameters : Register8 { public: /* Parameters sent along with SetReadParameters instruction in order From 4d5729dde3735bb17f1079803067955313402947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 13 Mar 2019 09:58:20 +0100 Subject: [PATCH 0190/1750] [bench] bench.bin target builds for RAM --- ion/src/device/Makefile | 2 +- ion/src/device/bench/Makefile | 5 +++-- ion/src/device/bench/runner.cpp | 5 +++++ ion/src/device/shared/ram.ld | 23 +++++++++++++---------- scripts/targets.device.mak | 9 +++++++++ 5 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 ion/src/device/bench/runner.cpp diff --git a/ion/src/device/Makefile b/ion/src/device/Makefile index ab53af54c..5f53122ff 100644 --- a/ion/src/device/Makefile +++ b/ion/src/device/Makefile @@ -15,7 +15,7 @@ src += $(addprefix ion/src/shared/, \ # events_replay.o and dummy/events_modifier.o ION_DEVICE_SFLAGS = -Iion/src/device/$(MODEL) -Iion/src/device/shared -$(call object_for,$(ion_device_src) $(dfu_src) $(usb_src)): SFLAGS += $(ION_DEVICE_SFLAGS) +$(call object_for,$(ion_device_src) $(dfu_src) $(usb_src) $(bench_src)): SFLAGS += $(ION_DEVICE_SFLAGS) src += $(ion_device_src) # When using the register.h C++ file in production mode, we expect the compiler diff --git a/ion/src/device/bench/Makefile b/ion/src/device/bench/Makefile index c882e1393..b6a8e0e14 100644 --- a/ion/src/device/bench/Makefile +++ b/ion/src/device/bench/Makefile @@ -1,10 +1,11 @@ -src += $(addprefix ion/src/device/bench/, \ +bench_src += $(addprefix ion/src/device/bench/, \ bench.cpp \ command_handler.cpp \ command_list.cpp \ + runner.cpp \ ) -src += $(addprefix ion/src/device/bench/command/, \ +bench_src += $(addprefix ion/src/device/bench/command/, \ adc.cpp \ backlight.cpp \ charge.cpp \ diff --git a/ion/src/device/bench/runner.cpp b/ion/src/device/bench/runner.cpp new file mode 100644 index 000000000..d76c82c3c --- /dev/null +++ b/ion/src/device/bench/runner.cpp @@ -0,0 +1,5 @@ +#include + +void ion_main(int argc, char * argv[]) { + Ion::Device::Bench::run(); +} diff --git a/ion/src/device/shared/ram.ld b/ion/src/device/shared/ram.ld index 5621192b3..619f1fcd3 100644 --- a/ion/src/device/shared/ram.ld +++ b/ion/src/device/shared/ram.ld @@ -1,20 +1,23 @@ -/* Create a USB DFU firmware that runs from RAM. - * Flashing using ST's ROMed DFU bootloader is reliable but very slow. To make - * flashing faster, we can leverage ST's bootloader to copy a small "flasher" in - * RAM, and run it from there. +/* Create a firmware that runs from RAM. * Caution: ST's bootloader uses some RAM, so we want to stay off of that memory * region. Per AN2606, section 47, it's using 0x20003000 - 0x2003FFFF; We'll try - * to play safe and avoid the first 32KB of RAM. */ + * to play safe and avoid the first 32KB of RAM. + * + * This is used to: + * - Flash faster. Flashing using ST's ROMed DFU bootloader is reliable but + * very slow. To make flashing faster, we can leverage ST's bootloader to copy + * a small "flasher" in RAM, and run it from there. + * - Run the bench software from the RAM. */ MEMORY { RAM_BUFFER (rw) : ORIGIN = 0x20000000 + 32K, LENGTH = 256K - 32K } -/* The stack is quite large, and should really be equal to Epsilon's. Indeed, - * we're making the Calculator object live on the stack, and it's quite large - * (about 4K just for this single object). Using a stack too small will result - * in some memory being overwritten (for instance, vtables that live in the - * .rodata section). */ +/* The stack is quite large: we put it equal to Epsilon's. + * Indeed, when building the flasher, we're making the USB::Calculator object + * live on the stack, and it's quite large (about 4K just for this single + * object). Using a stack too small would result in some memory being + * overwritten (for instance, vtables that live in the .rodata section). */ STACK_SIZE = 32K; diff --git a/scripts/targets.device.mak b/scripts/targets.device.mak index 0d3aa95ad..b65af8a2c 100644 --- a/scripts/targets.device.mak +++ b/scripts/targets.device.mak @@ -60,3 +60,12 @@ else $(BUILD_DIR)/flasher.$(EXE): @echo "Error: flasher.elf requires EPSILON_DEVICE_BENCH=0 EPSILON_USB_DFU_XIP=1" endif + +#TODO Do not build all apps... Put elsewhere? +ifeq ($(EPSILON_USB_DFU_XIP)$(EPSILON_DEVICE_BENCH),11) +$(BUILD_DIR)/bench.$(EXE): LDSCRIPT = ion/src/$(PLATFORM)/shared/ram.ld +$(BUILD_DIR)/bench.$(EXE): $(objs) $(call object_for,$(bench_src)) +else +$(BUILD_DIR)/bench.$(EXE): + @echo "Error: bench.bin requires EPSILON_DEVICE_BENCH=1 EPSILON_USB_DFU_XIP=1" +endif From 2772fe5f289f5435338dd26a82f208beab59293f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 12 Mar 2019 15:37:26 +0100 Subject: [PATCH 0191/1750] [bench] RESET command --- ion/src/device/bench/Makefile | 1 + ion/src/device/bench/bench.cpp | 1 + ion/src/device/bench/command/command.h | 1 + ion/src/device/bench/command/reset.cpp | 24 ++++++++++++++++++++++++ 4 files changed, 27 insertions(+) create mode 100644 ion/src/device/bench/command/reset.cpp diff --git a/ion/src/device/bench/Makefile b/ion/src/device/bench/Makefile index b6a8e0e14..6e440194c 100644 --- a/ion/src/device/bench/Makefile +++ b/ion/src/device/bench/Makefile @@ -17,6 +17,7 @@ bench_src += $(addprefix ion/src/device/bench/command/, \ mcu_serial.cpp \ ping.cpp \ print.cpp \ + reset.cpp \ suspend.cpp \ vblank.cpp \ ) diff --git a/ion/src/device/bench/bench.cpp b/ion/src/device/bench/bench.cpp index c48b7c93d..21feab251 100644 --- a/ion/src/device/bench/bench.cpp +++ b/ion/src/device/bench/bench.cpp @@ -18,6 +18,7 @@ constexpr CommandHandler handles[] = { CommandHandler("MCU_SERIAL", Command::MCUSerial), CommandHandler("PING", Command::Ping), CommandHandler("PRINT", Command::Print), + CommandHandler("RESET", Command::Reset), CommandHandler("SUSPEND", Command::Suspend), CommandHandler("VBLANK", Command::VBlank), CommandHandler(nullptr, nullptr) diff --git a/ion/src/device/bench/command/command.h b/ion/src/device/bench/command/command.h index c749d3728..fe796e341 100644 --- a/ion/src/device/bench/command/command.h +++ b/ion/src/device/bench/command/command.h @@ -20,6 +20,7 @@ void LED(const char * input); void MCUSerial(const char * input); void Ping(const char * input); void Print(const char * input); +void Reset(const char * input); void Suspend(const char * input); void VBlank(const char * input); diff --git a/ion/src/device/bench/command/reset.cpp b/ion/src/device/bench/command/reset.cpp new file mode 100644 index 000000000..91bc71a0e --- /dev/null +++ b/ion/src/device/bench/command/reset.cpp @@ -0,0 +1,24 @@ +#include "command.h" +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Reset(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + /* We jump instead of calling "core()", because the '6' button is pressed on + * the bench and calling "core" would reboot on the bootloader. */ + reply(sOK); + Ion::Device::Reset::jump(); +} + +} +} +} +} From 91dafd73ff7659ee468f1d18e8c8f40275fca96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 13 Mar 2019 16:21:48 +0100 Subject: [PATCH 0192/1750] [ion] Ion::Device::reset::jump() jumps to the initialisation vector --- ion/src/device/n0100/flash.ld | 2 +- ion/src/device/n0101/flash.ld | 2 +- ion/src/device/shared/drivers/reset.cpp | 6 ++++-- ion/src/device/shared/ram.ld | 1 + ion/src/device/shared/usb/dfu.ld | 4 ++++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ion/src/device/n0100/flash.ld b/ion/src/device/n0100/flash.ld index e5ddcc9e4..80f02b63d 100644 --- a/ion/src/device/n0100/flash.ld +++ b/ion/src/device/n0100/flash.ld @@ -30,7 +30,7 @@ SECTIONS { * 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_start = .; KEEP(*(.isr_vector_table)) } >FLASH diff --git a/ion/src/device/n0101/flash.ld b/ion/src/device/n0101/flash.ld index c396ac067..c75d5c2af 100644 --- a/ion/src/device/n0101/flash.ld +++ b/ion/src/device/n0101/flash.ld @@ -38,7 +38,7 @@ SECTIONS { * 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_start = .; KEEP(*(.isr_vector_table)) } >INTERNAL_FLASH diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index 924dfd993..e0dfc6010 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -2,6 +2,8 @@ #include #include +extern void * _isr_start; + namespace Ion { namespace Device { namespace Reset { @@ -19,8 +21,8 @@ void jump() { Ion::Device::Cache::disableDCache(); Ion::Device::Cache::disableICache(); - uint32_t * stackPointerAddress = reinterpret_cast(0x08000000); - uint32_t * resetHandlerAddress = reinterpret_cast(0x08000004); + uint32_t * stackPointerAddress = reinterpret_cast(&_isr_start); + uint32_t * resetHandlerAddress = stackPointerAddress + 1; /* Jump to the reset service routine after having reset the stack pointer. * Both addresses are fetched from the base of the Flash memory, just like a diff --git a/ion/src/device/shared/ram.ld b/ion/src/device/shared/ram.ld index 619f1fcd3..1003ee25a 100644 --- a/ion/src/device/shared/ram.ld +++ b/ion/src/device/shared/ram.ld @@ -23,6 +23,7 @@ STACK_SIZE = 32K; SECTIONS { .isr_vector_table ORIGIN(RAM_BUFFER) : { + _isr_start = .; KEEP(*(.isr_vector_table)) } >RAM_BUFFER diff --git a/ion/src/device/shared/usb/dfu.ld b/ion/src/device/shared/usb/dfu.ld index 934772384..66a250f74 100644 --- a/ion/src/device/shared/usb/dfu.ld +++ b/ion/src/device/shared/usb/dfu.ld @@ -25,6 +25,10 @@ MEMORY { RAM_BUFFER (rw) : ORIGIN = EPSILON_STACK_END, LENGTH = 8K } +/* The DFU needs to know to which address it should jump to after a leave + * command. This address is used in Ion::Device::Reset::jump(). */ +_isr_start = 0x08000000; + SECTIONS { .text : { . = ALIGN(4); From cb1c4debc2cc29276b96c40d0270e187fe1f7c34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 15 Mar 2019 11:11:10 +0100 Subject: [PATCH 0193/1750] [ion/drivers/display] Add details about the FSMC --- ion/src/device/shared/drivers/display.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/ion/src/device/shared/drivers/display.cpp b/ion/src/device/shared/drivers/display.cpp index 17bbcfea8..7aa232b7f 100644 --- a/ion/src/device/shared/drivers/display.cpp +++ b/ion/src/device/shared/drivers/display.cpp @@ -191,7 +191,8 @@ void initFSMC() { /* Set up the FSMC control registers. * We address the LCD panel as if it were an SRAM module, using a 16bits wide * bus, non-multiplexed. - * The STM32 FSMC supports two kinds of memory access modes : + * The STM32 FSMC supports two kinds of memory access modes (see Reference + * Manual) : * - Base modes (1 and 2), which use the same timings for reads and writes * - Extended modes (named A to D), which can be customized further. * The LCD panel can be written to faster than it can be read from, therefore @@ -214,19 +215,23 @@ void initFSMC() { * A16 | D/CX * Dn | Dn * - * We need to set the values of the BTR and BWTR which gives the timings for + * We need to set the values of the BTR and BWTR which give the timings for * reading and writing. Note that the STM32 datasheet doesn't take into * account the time needed to actually switch from one logic state to another, - * whereas the ST7789V one does, so we'll add T(R) and T(F) as needed. + * whereas the ST7789V one does, so we'll add T(R) (Rising) and T(F) (Falling) + * as needed. * Last but not least, timings on the STM32 have to be expressed in terms of - * HCLK = 1/96MHz = 10.42ns. + * HCLK. * - We'll pick Mode A which corresponds to SRAM with OE toggling - * - ADDSET = T(AST) + T(F) = 0ns + 15ns = 2 HCLK + * - ADDSET = Duration of the first access phase for read accesses. + * = T(AST) + T(F) = 0ns + 15ns = 2 HCLK * - ADDHLD is unused in this mode, set to 0 - * - DATAST(read) = T(RDLFM) + T(R) = 355ns + 15ns = 36 HCLK - * DATAST(write) = T(WRL) + T(R) = 15ns + 15ns = 3 HCLK - * - BUSTURN(read) = T(RDHFM) + T(F) = 90ns + 15ns = 10 HCLK - * BUSTURN(write) = T(RDHFM) + T(F) = 15ns + 15ns = 3 HCLK + * - DATAST = Duration of the second access phase for read accesses. + * DATAST(read) = T(RDLFM) + T(R) = 355ns + 15ns = 36 HCLK + * DATAST(write) = T(WRL) + T(R) = 15ns + 15ns = 3 HCLK + * - BUSTURN = Time between NEx high to NEx low + * BUSTURN(read) = T(RDHFM) + T(F) = 90ns + 15ns = 10 HCLK + * BUSTURN(write) = T(RDHFM) + T(F) = 15ns + 15ns = 3 HCLK */ // Read timing from the LCD From 8f1f3d5af08b38bd0c8c04f08945ab139ac8286b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 18 Mar 2019 11:48:16 +0100 Subject: [PATCH 0194/1750] [external_flash] WriteMemory source argument is const --- ion/src/device/shared/drivers/external_flash.cpp | 6 +++--- ion/src/device/shared/drivers/external_flash.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 6c927dae0..e519bcff7 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -110,7 +110,7 @@ static inline void send_command(Command c, QUADSPI::CCR::OperatingMode operating ); } -static inline void send_write_command(Command c, uint8_t * address, uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { +static inline void send_write_command(Command c, uint8_t * address, const uint8_t * data, size_t dataLength, QUADSPI::CCR::OperatingMode operatingMode = DefaultOperatingMode) { send_command_full( QUADSPI::CCR::FunctionalMode::IndirectWrite, operatingMode, @@ -118,7 +118,7 @@ static inline void send_write_command(Command c, uint8_t * address, uint8_t * da address, 0, 0, 0, - data, dataLength + const_cast(data), dataLength ); } @@ -346,7 +346,7 @@ void EraseSector(int i) { set_as_memory_mapped(); } -void WriteMemory(uint8_t * destination, uint8_t * source, size_t length) { +void WriteMemory(uint8_t * destination, const uint8_t * source, size_t length) { if (Config::NumberOfSectors == 0) { return; } diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index 52f4dd11c..bef87106c 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -37,7 +37,7 @@ void shutdown(); void MassErase(); int SectorAtAddress(uint32_t address); void EraseSector(int i); -void WriteMemory(uint8_t * destination, uint8_t * source, size_t length); +void WriteMemory(uint8_t * destination, const uint8_t * source, size_t length); } } From c4c2643b53ebf1c898722a823ba2357f399374a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 18 Mar 2019 14:24:10 +0100 Subject: [PATCH 0195/1750] [bench] Poincare::init at in run --- ion/src/device/bench/bench.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ion/src/device/bench/bench.cpp b/ion/src/device/bench/bench.cpp index 21feab251..ad8fe10d3 100644 --- a/ion/src/device/bench/bench.cpp +++ b/ion/src/device/bench/bench.cpp @@ -1,5 +1,6 @@ #include "bench.h" #include +#include #include #include "command_list.h" @@ -29,6 +30,8 @@ constexpr const CommandList sCommandList = CommandList(handles); constexpr int kMaxCommandLength = 255; void run() { + // Init the pool! Used in printFloatToText + Poincare::Init(); KDContext * ctx = KDIonContext::sharedContext(); ctx->fillRect(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColorWhite); ctx->drawString("BENCH", KDPoint((320-50)/2, (240-18)/2)); From a656b80089644ae2c39bce8a1ee2b3d8049d35ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 21 Mar 2019 13:18:17 +0100 Subject: [PATCH 0196/1750] [ion] Calibrate msleep / usleep for N0101 --- ion/src/device/n0101/drivers/config/timing.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ion/src/device/n0101/drivers/config/timing.h b/ion/src/device/n0101/drivers/config/timing.h index 747611b5c..fcd1b4ff2 100644 --- a/ion/src/device/n0101/drivers/config/timing.h +++ b/ion/src/device/n0101/drivers/config/timing.h @@ -8,12 +8,12 @@ namespace Device { namespace Timing { namespace Config { -// TODO: calibrate msleep -constexpr static int LoopsPerMillisecond = 8852; -constexpr static int LoopsPerMicrosecond = 9; +constexpr static int LoopsPerMillisecond = 4426; +constexpr static int LoopsPerMicrosecond = 36; // CPU clock is 96 MHz, and systick clock source is divided by 8 // To get 1 ms systick overflow we need to reset it to // 96 000 000 (Hz) / 8 / 1 000 (ms/s) - 1 (because the counter resets *after* counting to 0) +// TODO: calibrate systick constexpr static int SysTickPerMillisecond = 12000; From b413da0d9162c4d4fada3a4da02fa855e44299a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 22 Mar 2019 16:36:25 +0100 Subject: [PATCH 0197/1750] [ion] Fix USART communication: wait for transmission done in writeLine --- ion/include/ion/console.h | 1 + ion/src/device/shared/drivers/console.cpp | 7 ++++++- ion/src/device/shared/regs/usart.h | 1 + ion/src/shared/console_line.cpp | 3 +++ ion/src/shared/console_stdio.cpp | 5 +++++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ion/include/ion/console.h b/ion/include/ion/console.h index 1e5092862..ddfc0deef 100644 --- a/ion/include/ion/console.h +++ b/ion/include/ion/console.h @@ -10,6 +10,7 @@ char readChar(); // The lines are NULL-terminated void writeLine(const char * line); void readLine(char * line, int maxLineLength); +bool transmissionDone(); } } diff --git a/ion/src/device/shared/drivers/console.cpp b/ion/src/device/shared/drivers/console.cpp index 97ce27331..3c83e0ea2 100644 --- a/ion/src/device/shared/drivers/console.cpp +++ b/ion/src/device/shared/drivers/console.cpp @@ -18,9 +18,14 @@ char readChar() { } void writeChar(char c) { + Config::Port.TDR()->set(c); + // Wait until the write is done while (Config::Port.SR()->getTXE() == 0) { } - Config::Port.TDR()->set(c); +} + +bool transmissionDone() { + return Config::Port.SR()->getTC() == 1; } } diff --git a/ion/src/device/shared/regs/usart.h b/ion/src/device/shared/regs/usart.h index 58b5a35cc..1501716e0 100644 --- a/ion/src/device/shared/regs/usart.h +++ b/ion/src/device/shared/regs/usart.h @@ -13,6 +13,7 @@ public: class SR : Register32 { public: REGS_BOOL_FIELD(RXNE, 5); + REGS_BOOL_FIELD(TC, 6); REGS_BOOL_FIELD(TXE, 7); }; diff --git a/ion/src/shared/console_line.cpp b/ion/src/shared/console_line.cpp index 7574ee7f3..7aa81d016 100644 --- a/ion/src/shared/console_line.cpp +++ b/ion/src/shared/console_line.cpp @@ -5,6 +5,7 @@ namespace Console { char readChar(); void writeChar(char c); +bool transmissionDone(); void writeLine(const char * line) { while (*line != 0) { @@ -12,6 +13,8 @@ void writeLine(const char * line) { } writeChar('\r'); writeChar('\n'); + while (!transmissionDone()) { + } } void readLine(char * line, int maxLineLength) { diff --git a/ion/src/shared/console_stdio.cpp b/ion/src/shared/console_stdio.cpp index fda0fa3ab..e77659f71 100644 --- a/ion/src/shared/console_stdio.cpp +++ b/ion/src/shared/console_stdio.cpp @@ -13,5 +13,10 @@ void writeChar(char c) { fflush(stdout); } +bool transmissionDone() { + // Always true because we flush after each writeChar + return true; +} + } } From 44a992c2834e83a41554c4dae5fb4827c151a8d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 22 Mar 2019 17:02:50 +0100 Subject: [PATCH 0198/1750] [ion] Add DisplayInversion for n0101 --- ion/src/device/n0100/drivers/config/display.h | 3 +++ ion/src/device/n0101/drivers/config/display.h | 2 ++ ion/src/device/shared/drivers/display.cpp | 3 +++ 3 files changed, 8 insertions(+) diff --git a/ion/src/device/n0100/drivers/config/display.h b/ion/src/device/n0100/drivers/config/display.h index 42b15187b..26e4ef633 100644 --- a/ion/src/device/n0100/drivers/config/display.h +++ b/ion/src/device/n0100/drivers/config/display.h @@ -56,6 +56,9 @@ constexpr static int DMAStream = 0; constexpr static int HCLKFrequencyInMHz = 96; +constexpr static bool DisplayInversion = false; + + } } } diff --git a/ion/src/device/n0101/drivers/config/display.h b/ion/src/device/n0101/drivers/config/display.h index dc4d7b1dc..397a42da3 100644 --- a/ion/src/device/n0101/drivers/config/display.h +++ b/ion/src/device/n0101/drivers/config/display.h @@ -28,6 +28,8 @@ constexpr static int DMAStream = 0; constexpr static int HCLKFrequencyInMHz = 192; +constexpr static bool DisplayInversion = true; + } } } diff --git a/ion/src/device/shared/drivers/display.cpp b/ion/src/device/shared/drivers/display.cpp index 7aa232b7f..7ba1a93cd 100644 --- a/ion/src/device/shared/drivers/display.cpp +++ b/ion/src/device/shared/drivers/display.cpp @@ -263,6 +263,9 @@ void initPanel() { send_command(Command::TearingEffectLineOn, 0x00); send_command(Command::FrameRateControl, 0x1E); // 40 Hz frame rate + if (Config::DisplayInversion) { + send_command(Command::DisplayInversionOn); + } send_command(Command::DisplayOn); } From f4def0b9df5558a0582c590153f2c412e6fe1645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 22 Mar 2019 17:05:45 +0100 Subject: [PATCH 0199/1750] [ion] Remove and comment Timing::init, to fix later --- ion/src/device/shared/drivers/board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index f37af00e5..7a6bd1887 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -38,7 +38,7 @@ void initPeripherals() { USB::init(); Console::init(); SWD::init(); - Timing::init(); + //Timing::init(); TODO FIXME ExternalFlash::init(); } From da1a9feade4afdb139d793aa9ad2ab28a397ada8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 22 Mar 2019 17:27:02 +0100 Subject: [PATCH 0200/1750] [bench] Do not light up the LED on init --- ion/src/device/bench/bench.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/ion/src/device/bench/bench.cpp b/ion/src/device/bench/bench.cpp index ad8fe10d3..87629f4c0 100644 --- a/ion/src/device/bench/bench.cpp +++ b/ion/src/device/bench/bench.cpp @@ -36,7 +36,6 @@ void run() { ctx->fillRect(KDRect(0,0,Ion::Display::Width,Ion::Display::Height), KDColorWhite); ctx->drawString("BENCH", KDPoint((320-50)/2, (240-18)/2)); char command[kMaxCommandLength]; - Ion::LED::setColor(KDColorBlue); while (true) { Ion::Console::readLine(command, kMaxCommandLength); const CommandHandler * ch = sCommandList.dispatch(command); From dcd558512a17bf9a64deba4919f8da2141f6ac11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 22 Mar 2019 17:38:01 +0100 Subject: [PATCH 0201/1750] [bench] WRITE_EXTERNAL command --- ion/src/device/bench/Makefile | 1 + ion/src/device/bench/bench.cpp | 1 + ion/src/device/bench/command/command.h | 1 + .../device/bench/command/write_external.cpp | 104 ++++++++++++++++++ 4 files changed, 107 insertions(+) create mode 100644 ion/src/device/bench/command/write_external.cpp diff --git a/ion/src/device/bench/Makefile b/ion/src/device/bench/Makefile index 6e440194c..7c48c245a 100644 --- a/ion/src/device/bench/Makefile +++ b/ion/src/device/bench/Makefile @@ -20,4 +20,5 @@ bench_src += $(addprefix ion/src/device/bench/command/, \ reset.cpp \ suspend.cpp \ vblank.cpp \ + write_external.cpp \ ) diff --git a/ion/src/device/bench/bench.cpp b/ion/src/device/bench/bench.cpp index 87629f4c0..41ed2cf61 100644 --- a/ion/src/device/bench/bench.cpp +++ b/ion/src/device/bench/bench.cpp @@ -22,6 +22,7 @@ constexpr CommandHandler handles[] = { CommandHandler("RESET", Command::Reset), CommandHandler("SUSPEND", Command::Suspend), CommandHandler("VBLANK", Command::VBlank), + CommandHandler("WRITE_EXTERNAL", Command::WriteExternal), CommandHandler(nullptr, nullptr) }; diff --git a/ion/src/device/bench/command/command.h b/ion/src/device/bench/command/command.h index fe796e341..4c2048570 100644 --- a/ion/src/device/bench/command/command.h +++ b/ion/src/device/bench/command/command.h @@ -23,6 +23,7 @@ void Print(const char * input); void Reset(const char * input); void Suspend(const char * input); void VBlank(const char * input); +void WriteExternal(const char * input); extern const char * const sOK; extern const char * const sKO; diff --git a/ion/src/device/bench/command/write_external.cpp b/ion/src/device/bench/command/write_external.cpp new file mode 100644 index 000000000..59bf89c5d --- /dev/null +++ b/ion/src/device/bench/command/write_external.cpp @@ -0,0 +1,104 @@ +#include "command.h" +#include +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +/* Input must be of the form ??????:data, ?????? being the size of the data in + * hex */ + +#if 1 +void WriteExternal(const char * input) { + // Check the input format + if (input != nullptr) { + reply(sSyntaxError); + return; + } + + const char * data = "0987654321098764321098765432109876543210987654321"; + uint8_t * externalFlashStart = reinterpret_cast(0x90000000); + int dataSize = 10; + + // Copy the data in the external flash + Ion::Device::ExternalFlash::EraseSector(0); + Ion::Device::ExternalFlash::WriteMemory( + reinterpret_cast(0), + reinterpret_cast(data), + dataSize); + + // Check it was correctly copied + for (int i = 0; i < dataSize; i++) { + if (*(externalFlashStart + i) != data[i]) { + reply(sKO); + return; + } + } + reply(sOK); +} + + +#else +void WriteExternal(const char * input) { + // Check the input format + if (input == nullptr || input[0] != '0' || input[1] != 'x') { + reply(sSyntaxError); + return; + } + constexpr int sizeStart = 2; + constexpr int dataSizeLength = 6; + for (int i = sizeStart; i < sizeStart + dataSizeLength; i++) { + if (!isHex(input[i])) { + reply(sSyntaxError); + return; + } + } + if (input[sizeStart + dataSizeLength] != ':') { + reply(sSyntaxError); + return; + } + + // Get the data size + char dataSizeBuffer[dataSizeLength + 1]; + for (int i = 0; i < dataSizeLength; i++) { + dataSizeBuffer[i] = input[sizeStart + i]; + } + dataSizeBuffer[dataSizeLength] = 0; + uint32_t dataSize = hexNumber(dataSizeBuffer); + int dataIndex = sizeStart + dataSizeLength + 1; + + uint8_t * externalFlashStart = reinterpret_cast(0x90000000); // TODO reinterpret_cast(Ion::Device::ExternalFlash::QSPIBaseAddress) + + // Copy the data in the external flash + uint32_t writeOffset = 0x00000000; + // Erase the flash + int firstSector = Ion::Device::ExternalFlash::SectorAtAddress(writeOffset); + int lastSector = Ion::Device::ExternalFlash::SectorAtAddress(writeOffset + dataSize); + assert(firstSector <= lastSector); + for (int i = firstSector; i <= lastSector; i++) { + Ion::Device::ExternalFlash::EraseSector(i); + } + // Write + Ion::Device::ExternalFlash::WriteMemory( + reinterpret_cast(writeOffset), + reinterpret_cast(input + dataIndex), + dataSize); + + // Check it was correctly copied + for (uint32_t i = 0; i < dataSize; i++) { + if (*(externalFlashStart + writeOffset + i) != input[dataIndex+i]) { + reply(sKO); + return; + } + } + reply(sOK); +} +#endif + +} +} +} +} From 39bf02033c1d9e5fd199973ef3a9cf3c0a3e9ec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 22 Mar 2019 17:44:50 +0100 Subject: [PATCH 0202/1750] [ion] ExternalFlash::deinit --- ion/src/device/shared/drivers/external_flash.cpp | 11 +++++++++++ ion/src/device/shared/drivers/external_flash.h | 1 + ion/src/device/shared/regs/rcc.h | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index e519bcff7..6598e22dc 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -64,6 +64,7 @@ enum class Command : uint8_t { PageProgram = 0x02, QuadPageProgram = 0x33, EnableQPI = 0x38, + Reset = 0x99, // Erase the whole chip or a 64-Kbyte block as being "1" ChipErase = 0xC7, Erase64KbyteBlock = 0xD8, @@ -289,6 +290,16 @@ void init() { initChip(); } +void deinit() { + if (Config::NumberOfSectors == 0) { + return; + } + // Reset the controller + RCC.AHB3RSTR()->setQSPIRST(true); + RCC.AHB3RSTR()->setQSPIRST(false); + send_command(Command::Reset, QUADSPI::CCR::OperatingMode::Quad); +} + static void shutdownGPIO() { for(const AFGPIOPin & p : Config::Pins) { p.group().OSPEEDR()->setOutputSpeed(p.pin(), GPIO::OSPEEDR::OutputSpeed::Low); diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index bef87106c..750ccae49 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -33,6 +33,7 @@ namespace ExternalFlash { void init(); void shutdown(); +void deinit(); void MassErase(); int SectorAtAddress(uint32_t address); diff --git a/ion/src/device/shared/regs/rcc.h b/ion/src/device/shared/regs/rcc.h index 83df96053..73a446f9d 100644 --- a/ion/src/device/shared/regs/rcc.h +++ b/ion/src/device/shared/regs/rcc.h @@ -64,6 +64,11 @@ public: void setPPRE2(APBPrescaler r) volatile { setBitRange(15, 13, (uint32_t)r); } }; + class AHB3RSTR : Register32 { + public: + REGS_BOOL_FIELD(QSPIRST, 1); + }; + class AHB1ENR : public Register32 { public: using Register32::Register32; @@ -136,6 +141,7 @@ public: REGS_REGISTER_AT(CR, 0x00); REGS_REGISTER_AT(PLLCFGR, 0x04); REGS_REGISTER_AT(CFGR, 0x08); + REGS_REGISTER_AT(AHB3RSTR, 0x18); REGS_REGISTER_AT(AHB1ENR, 0x30); REGS_REGISTER_AT(AHB2ENR, 0x34); REGS_REGISTER_AT(AHB3ENR, 0x38); From 992222e163f1386c0f9d71d85d9f35e3ccf4ec71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 22 Mar 2019 17:51:33 +0100 Subject: [PATCH 0203/1750] [ion] Before a jump reset, deinit the flash and shutdown the board --- ion/src/device/shared/drivers/board.cpp | 2 +- ion/src/device/shared/drivers/reset.cpp | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index 7a6bd1887..6b4c6fefc 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -44,7 +44,7 @@ void initPeripherals() { void shutdownPeripherals(bool keepLEDAwake) { ExternalFlash::shutdown(); - Timing::shutdown(); + //Timing::shutdown(); TODO FIXME SWD::shutdown(); Console::shutdown(); USB::shutdown(); diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index e0dfc6010..90d7e62e2 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -1,6 +1,8 @@ #include "reset.h" #include #include +#include +#include extern void * _isr_start; @@ -16,19 +18,23 @@ void core() { } void jump() { - // TODO: shutdown all clocks and peripherial to mimic a hardware reset // Disable cache before reset Ion::Device::Cache::disableDCache(); Ion::Device::Cache::disableICache(); - uint32_t * stackPointerAddress = reinterpret_cast(&_isr_start); - uint32_t * resetHandlerAddress = stackPointerAddress + 1; + /* Shutdown all clocks and periherals to mimic a hardware reset. Special + * deinit for he external flash (it needs a reset command). */ + ExternalFlash::deinit(); + Board::shutdown(); /* Jump to the reset service routine after having reset the stack pointer. * Both addresses are fetched from the base of the Flash memory, just like a * real reset would. These operations should be made at once, otherwise the C * compiler might emit some instructions that modify the stack inbetween. */ + uint32_t * stackPointerAddress = reinterpret_cast(&_isr_start); + uint32_t * resetHandlerAddress = stackPointerAddress + 1; + asm volatile ( "msr MSP, %[stackPointer] ; bx %[resetHandler]" : : From 8a7adc9377818caa2bebaaa5ffa1b9a9d2152123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 25 Mar 2019 10:56:25 +0100 Subject: [PATCH 0204/1750] [ion] Fix memory-mapped read after indirect operation on external flash Workaround from ST's Errata sheet ES0360 Rev 3 --- ion/src/device/shared/drivers/external_flash.cpp | 16 ++++++++++++++++ ion/src/device/shared/regs/quadspi.h | 1 + 2 files changed, 17 insertions(+) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 6598e22dc..031dd6e02 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -178,6 +178,22 @@ static void unset_memory_mapped_mode() { } static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, 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()) { + } + } + } + } + class QUADSPI::CCR ccr(0); ccr.setFMODE(functionalMode); if (data != nullptr || functionalMode == QUADSPI::CCR::FunctionalMode::MemoryMapped) { diff --git a/ion/src/device/shared/regs/quadspi.h b/ion/src/device/shared/regs/quadspi.h index c2b924eea..99806df93 100644 --- a/ion/src/device/shared/regs/quadspi.h +++ b/ion/src/device/shared/regs/quadspi.h @@ -16,6 +16,7 @@ public: public: using Register32::Register32; REGS_BOOL_FIELD(EN, 0); + REGS_BOOL_FIELD(ABORT, 1); REGS_BOOL_FIELD(TCEN, 3); // Lower-power timeout counter enable in memory-mapped mode REGS_BOOL_FIELD(SSHIFT, 4); REGS_FIELD(PRESCALER, uint8_t, 31, 24); From 44b5114c3fe55128c14b416d0a8a0a0ac4bdad4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 25 Mar 2019 11:22:35 +0100 Subject: [PATCH 0205/1750] [ion/n0101] On the external flash, fix leaving memory-mapped mode. --- ion/src/device/shared/drivers/external_flash.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 031dd6e02..5c4412c0d 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -192,6 +192,15 @@ static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADS } } } + } 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()) { + } } class QUADSPI::CCR ccr(0); @@ -237,7 +246,8 @@ static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADS /* 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." */ + * 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()) { } From 90ee156f5b5c7848c8bbb279a276e9e6cf13f640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 25 Mar 2019 11:27:59 +0100 Subject: [PATCH 0206/1750] [ion/n0101] Add assertion on BUSY state of the external flash --- ion/src/device/shared/drivers/external_flash.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 5c4412c0d..ecd133bb4 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -203,6 +203,8 @@ static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADS } } + 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) { From fe7cb58a7ce058f19276dd5ad6bcf57aaabd13b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 26 Mar 2019 09:38:03 +0100 Subject: [PATCH 0207/1750] [bench] Do twice the write memory test It failed before due to a missing abort command for the external flash --- ion/src/device/bench/command/write_external.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ion/src/device/bench/command/write_external.cpp b/ion/src/device/bench/command/write_external.cpp index 59bf89c5d..81b554d58 100644 --- a/ion/src/device/bench/command/write_external.cpp +++ b/ion/src/device/bench/command/write_external.cpp @@ -24,6 +24,20 @@ void WriteExternal(const char * input) { int dataSize = 10; // Copy the data in the external flash + Ion::Device::ExternalFlash::EraseSector(0); + Ion::Device::ExternalFlash::WriteMemory( + reinterpret_cast(0), + reinterpret_cast(data) , + dataSize); + + // Check it was correctly copied + for (int i = 0; i < dataSize; i++) { + if (*(externalFlashStart + i) != data[i]) { + reply(sKO); + return; + } + } + Ion::Device::ExternalFlash::EraseSector(0); Ion::Device::ExternalFlash::WriteMemory( reinterpret_cast(0), From ef6f3f0ca0732414f16e28eddedad2f06cc7ac28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 25 Mar 2019 17:44:49 +0100 Subject: [PATCH 0208/1750] [ion] ExternalFlash: fix bugs --- ion/src/device/shared/drivers/external_flash.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index ecd133bb4..e8bb60133 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -73,7 +73,7 @@ enum class Command : uint8_t { ReleaseDeepPowerDown = 0xAB }; -static constexpr uint8_t NumberOfAddressBitsInChip = 23; +static constexpr uint8_t NumberOfAddressBitsInChip = 23; // 2^23 Bytes = 8 MBytes static constexpr uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; static constexpr uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; @@ -94,7 +94,7 @@ public: static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; static constexpr int AHBClockFrequency = 192; // MHz static constexpr int ClockFrequencyDivisor = 2; -static constexpr bool ajustNumberOfDummyCycles = AHBClockFrequency < 80 * ClockFrequencyDivisor; +static constexpr bool ajustNumberOfDummyCycles = AHBClockFrequency > 80 * ClockFrequencyDivisor; static constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad && ajustNumberOfDummyCycles) ? 4 : 2; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength); @@ -299,7 +299,7 @@ static void initChip() { /* Parameters sent along with SetReadParameters instruction in order * to configure the number of dummy cycles for the QPI Read instructions. */ using Register8::Register8; - REGS_BOOL_FIELD_W(P5, 1); + REGS_BOOL_FIELD_W(P5, 5); }; ReadParameters readParameters(0); readParameters.setP5(true); From 11c3103f0dd332c069d7b2106b3c606e5d3fcd83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 26 Mar 2019 17:27:10 +0100 Subject: [PATCH 0209/1750] [ion] Fix Timing (theorytically) --- ion/src/device/n0100/drivers/config/timing.h | 2 +- ion/src/device/n0101/drivers/config/timing.h | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ion/src/device/n0100/drivers/config/timing.h b/ion/src/device/n0100/drivers/config/timing.h index 27757c153..6856481b0 100644 --- a/ion/src/device/n0100/drivers/config/timing.h +++ b/ion/src/device/n0100/drivers/config/timing.h @@ -14,7 +14,7 @@ constexpr static int LoopsPerMillisecond = 8852; constexpr static int LoopsPerMicrosecond = 9; // CPU clock is 96 MHz, and systick clock source is divided by 8 // To get 1 ms systick overflow we need to reset it to -// 96 000 000 (Hz) / 8 / 1 000 (ms/s) - 1 (because the counter resets *after* counting to 0) +// 96 000 000 (Hz) / 8 / 1 000 (ms/s) constexpr static int SysTickPerMillisecond = 12000; diff --git a/ion/src/device/n0101/drivers/config/timing.h b/ion/src/device/n0101/drivers/config/timing.h index fcd1b4ff2..93b53cee3 100644 --- a/ion/src/device/n0101/drivers/config/timing.h +++ b/ion/src/device/n0101/drivers/config/timing.h @@ -12,10 +12,8 @@ constexpr static int LoopsPerMillisecond = 4426; constexpr static int LoopsPerMicrosecond = 36; // CPU clock is 96 MHz, and systick clock source is divided by 8 // To get 1 ms systick overflow we need to reset it to -// 96 000 000 (Hz) / 8 / 1 000 (ms/s) - 1 (because the counter resets *after* counting to 0) -// TODO: calibrate systick -constexpr static int SysTickPerMillisecond = 12000; - +// 192 000 000 (Hz) / 8 / 1 000 (ms/s) +constexpr static int SysTickPerMillisecond = 24000; } } From 59ae13bf6ce3b616e9a94b669930c403fc773c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 26 Mar 2019 17:28:21 +0100 Subject: [PATCH 0210/1750] [ion] Add PWR regs (to enable Overdrive) --- ion/src/device/shared/regs/pwr.h | 20 ++++++++++++++++++++ ion/src/device/shared/regs/rcc.h | 1 + 2 files changed, 21 insertions(+) diff --git a/ion/src/device/shared/regs/pwr.h b/ion/src/device/shared/regs/pwr.h index 917d3736d..47f07fc69 100644 --- a/ion/src/device/shared/regs/pwr.h +++ b/ion/src/device/shared/regs/pwr.h @@ -18,6 +18,14 @@ public: #if REGS_PWR_CONFIG_ADDITIONAL_FIELDS REGS_BOOL_FIELD(LPUDS, 10); REGS_BOOL_FIELD(MRUDS, 11); + enum class Voltage { + Scale3 = 0x01, + Scale2 = 0x10, + Scale1 = 0x11 + }; + REGS_FIELD_W(VOS, Voltage, 15, 14); + REGS_BOOL_FIELD_W(ODEN, 16); + REGS_BOOL_FIELD_W(ODSWEN, 17); enum class UnderDrive { Disable = 0, Enable = 3 @@ -26,8 +34,20 @@ public: #endif }; +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS + class CSR1 : Register32 { + public: + REGS_BOOL_FIELD_R(VOSRDY, 14); + REGS_BOOL_FIELD_R(ODRDY, 16); + REGS_BOOL_FIELD_R(ODSWRDY, 17); + }; +#endif + constexpr PWR() {}; REGS_REGISTER_AT(CR, 0x00); +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS + REGS_REGISTER_AT(CSR1, 0x04); +#endif private: constexpr uint32_t Base() const { return 0x40007000; diff --git a/ion/src/device/shared/regs/rcc.h b/ion/src/device/shared/regs/rcc.h index 73a446f9d..7aa872af7 100644 --- a/ion/src/device/shared/regs/rcc.h +++ b/ion/src/device/shared/regs/rcc.h @@ -12,6 +12,7 @@ public: class CR : public Register32 { public: REGS_BOOL_FIELD(HSION, 0); + REGS_BOOL_FIELD(HSIRDY, 1); REGS_BOOL_FIELD(HSEON, 16); REGS_BOOL_FIELD_R(HSERDY, 17); REGS_BOOL_FIELD(PLLON, 24); From 8ca194734dee1fdd500016c5861970eb0b9de620 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 26 Mar 2019 17:29:21 +0100 Subject: [PATCH 0211/1750] [ion] N0101: enable overdrive in initClock --- ion/src/device/n0101/drivers/board.cpp | 78 ++++++++++++++++---------- 1 file changed, 49 insertions(+), 29 deletions(-) diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index c692864f0..a92c6309a 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -69,7 +69,55 @@ void init() { void initClocks() { /* System clock - * Configure the CPU at 96 MHz, APB2 and USB at 48 MHz. */ + * 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); + // Choose Voltage scale 1 + PWR.CR()->setVOS(PWR::CR::Voltage::Scale1); + //while (!PWR.CSR1()->getVOSRDY()) {} + + /* 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(8); + RCC.PLLCFGR()->setPLLN(384); + RCC.PLLCFGR()->setPLLQ(8); + RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); + + /* If you want to considerably slow down the whole machine uniformely, which + * can be very useful to diagnose performance issues, just uncomment the line + * below. Note that even booting takes a few seconds, so don't be surprised + * if the screen is black for a short while upon booting. */ + //RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy2); + + // Enable the PLL and wait for it to be ready + RCC.CR()->setPLLON(true); + + PWR.CR()->setODEN(true); + while(!PWR.CSR1()->getODRDY()) { + } + + PWR.CR()->setODSWEN(true); + while(!PWR.CSR1()->getODSWRDY()) { + } /* 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 @@ -85,38 +133,11 @@ void initClocks() { /* Enable the ART */ FLASH.ACR()->setARTEN(true); - /* 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 HSE and wait for it to be ready - RCC.CR()->setHSEON(true); - while(!RCC.CR()->getHSERDY()) { - } - - /* Given the crystal used on our device, the HSE will oscillate at 25 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(8); - RCC.PLLCFGR()->setPLLN(384); - RCC.PLLCFGR()->setPLLQ(8); - RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); // 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy4); // 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz RCC.CFGR()->setPPRE2(RCC::CFGR::APBPrescaler::AHBDividedBy2); - /* If you want to considerably slow down the whole machine uniformely, which - * can be very useful to diagnose performance issues, just uncomment the line - * below. Note that even booting takes a few seconds, so don't be surprised - * if the screen is black for a short while upon booting. */ - // RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy128); - - // Enable the PLL and wait for it to be ready - RCC.CR()->setPLLON(true); while(!RCC.CR()->getPLLRDY()) { } @@ -151,7 +172,6 @@ void initClocks() { // APB1 bus // We're using TIM3 for the LEDs RCC.APB1ENR()->setTIM3EN(true); - RCC.APB1ENR()->setPWREN(true); // APB2 bus class RCC::APB2ENR apb2enr(0); // Reset value From 9ab2eea78f468edffa21bf22c8902e10d6bb8623 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 26 Mar 2019 17:29:53 +0100 Subject: [PATCH 0212/1750] [ion] n0101 Timings: approximatly calibrate msleep and usleep --- ion/src/device/n0101/drivers/config/timing.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ion/src/device/n0101/drivers/config/timing.h b/ion/src/device/n0101/drivers/config/timing.h index 93b53cee3..4eaba832a 100644 --- a/ion/src/device/n0101/drivers/config/timing.h +++ b/ion/src/device/n0101/drivers/config/timing.h @@ -8,9 +8,9 @@ namespace Device { namespace Timing { namespace Config { -constexpr static int LoopsPerMillisecond = 4426; -constexpr static int LoopsPerMicrosecond = 36; -// CPU clock is 96 MHz, and systick clock source is divided by 8 +constexpr static int LoopsPerMillisecond = 4811; +constexpr static int LoopsPerMicrosecond = 38; +// CPU clock is 192 MHz, and systick clock source is divided by 8 // To get 1 ms systick overflow we need to reset it to // 192 000 000 (Hz) / 8 / 1 000 (ms/s) constexpr static int SysTickPerMillisecond = 24000; From c0bc675f02272852566365071a44ca6b2d5a0516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 26 Mar 2019 17:31:03 +0100 Subject: [PATCH 0213/1750] [ion] External Flash: fix typos --- ion/src/device/shared/drivers/external_flash.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index e8bb60133..73fba22f4 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -33,7 +33,7 @@ using namespace Regs; * | peripheral | | External | * | | read | flash | * AHB <-- | data <-- 32-byte | <-- | memory | - * matrix --> | regsiter --> FIFO | --> | | + * matrix --> | register --> FIFO | --> | | * +----------------------+ write +------------+ * * Any data transmitted to or from the external flash memory go through a 32-byte FIFO. @@ -94,7 +94,7 @@ public: static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; static constexpr int AHBClockFrequency = 192; // MHz static constexpr int ClockFrequencyDivisor = 2; -static constexpr bool ajustNumberOfDummyCycles = AHBClockFrequency > 80 * ClockFrequencyDivisor; +static constexpr bool ajustNumberOfDummyCycles = AHBClockFrequency > (80 * ClockFrequencyDivisor); static constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad && ajustNumberOfDummyCycles) ? 4 : 2; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength); From 1c227c0952798a4089ba55638cec26aa902d998e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 26 Mar 2019 17:32:15 +0100 Subject: [PATCH 0214/1750] [ion] Start fixing tests on ExternalFlash --- .../device/shared/drivers/external_flash.cpp | 2 -- .../device/shared/drivers/external_flash.h | 3 +++ ion/test/external_flash.cpp | 23 ++++++++++--------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 73fba22f4..0b7c17b62 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -73,9 +73,7 @@ enum class Command : uint8_t { ReleaseDeepPowerDown = 0xAB }; -static constexpr uint8_t NumberOfAddressBitsInChip = 23; // 2^23 Bytes = 8 MBytes static constexpr uint8_t NumberOfAddressBitsIn64KbyteBlock = 16; -static constexpr uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; class ExternalFlashStatusRegister { public: diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index 750ccae49..671fcb155 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -40,6 +40,9 @@ int SectorAtAddress(uint32_t address); void EraseSector(int i); void WriteMemory(uint8_t * destination, const uint8_t * source, size_t length); +static constexpr uint8_t NumberOfAddressBitsInChip = 23; // 2^23 Bytes = 8 MBytes +static constexpr uint32_t FlashAddressSpaceSize = 1 << NumberOfAddressBitsInChip; + } } } diff --git a/ion/test/external_flash.cpp b/ion/test/external_flash.cpp index 72caef964..da74aff24 100644 --- a/ion/test/external_flash.cpp +++ b/ion/test/external_flash.cpp @@ -1,13 +1,14 @@ #include #include #include -#include "ion/src/device/external_flash.h" +#include +#include #include "ion/include/ion/timing.h" // Choose some not too uniform data to program and test the external flash memory with. static inline uint8_t expected_value_at(uint8_t * ptr) { - uint32_t address = reinterpret_cast(ptr) - Ion::ExternalFlash::Device::StartAddress; + uint32_t address = reinterpret_cast(ptr) - Ion::Device::ExternalFlash::Config::StartAddress; return (address / 0x10000) + (address / 0x100) + address; // Example: the value expected at the address 0x123456 is 0x12 + 0x34 + 0x56. } @@ -31,8 +32,8 @@ static inline void check(volatile T * p, int repeat) { template void test(int accessType, int repeat) { - uint8_t * start = reinterpret_cast(Ion::ExternalFlash::Device::StartAddress); - uint8_t * end = reinterpret_cast(Ion::ExternalFlash::Device::StartAddress + Ion::ExternalFlash::Device::FlashAddressSpaceSize); + uint8_t * start = reinterpret_cast(Ion::Device::ExternalFlash::Config::StartAddress); + uint8_t * end = reinterpret_cast(Ion::Device::ExternalFlash::Config::StartAddress + Ion::Device::ExternalFlash::FlashAddressSpaceSize); // Forward sequential access if (accessType == 0) { @@ -52,10 +53,10 @@ void test(int accessType, int repeat) { // Random access if (accessType == 2) { - T * endT = reinterpret_cast(Ion::ExternalFlash::Device::StartAddress + Ion::ExternalFlash::Device::FlashAddressSpaceSize); - for (size_t i=0; i> (32 - Ion::ExternalFlash::Device::NumberOfAddressBitsInChip); - volatile T * q = reinterpret_cast(randomAddr + Ion::ExternalFlash::Device::StartAddress); + T * endT = reinterpret_cast(Ion::Device::ExternalFlash::Config::StartAddress + Ion::Device::ExternalFlash::FlashAddressSpaceSize); + for (size_t i=0; i> (32 - Ion::Device::ExternalFlash::NumberOfAddressBitsInChip); + volatile T * q = reinterpret_cast(randomAddr + Ion::Device::ExternalFlash::Config::StartAddress); if (q <= endT - 1) { check(q, repeat); } @@ -89,7 +90,7 @@ static void printElapsedTime(uint64_t startTime) { QUIZ_CASE(ion_ext_flash_erase) { #if TEST_EXT_FLASH_REPROGRAM uint64_t startTime = Ion::Timing::millis(); - Ion::ExternalFlash::Device::MassErase(); + Ion::Device::ExternalFlash::MassErase(); printElapsedTime(startTime); #endif } @@ -101,9 +102,9 @@ QUIZ_CASE(ion_ext_flash_program) { for (int page = 0; page < (1<<15); page++) { uint8_t buffer[256]; for (int byte = 0; byte < 256; byte++) { - buffer[byte] = expected_value_at(reinterpret_cast(Ion::ExternalFlash::Device::StartAddress + page * 256 + byte)); + buffer[byte] = expected_value_at(reinterpret_cast(Ion::Device::ExternalFlash::Config::StartAddress + page * 256 + byte)); } - Ion::ExternalFlash::Device::WriteMemory(reinterpret_cast(page * 256), buffer, 256); + Ion::Device::ExternalFlash::WriteMemory(reinterpret_cast(page * 256), buffer, 256); } printElapsedTime(startTime); #endif From 6fbf8fe196917f5868f5b9c329123b432f03ae2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 26 Mar 2019 17:33:32 +0100 Subject: [PATCH 0215/1750] [scripts] Add method in gdb script (load_isr, use_dfu_symbol_file) --- scripts/device/gdb_script.gdb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/device/gdb_script.gdb b/scripts/device/gdb_script.gdb index 64447d4a4..8e59218d0 100644 --- a/scripts/device/gdb_script.gdb +++ b/scripts/device/gdb_script.gdb @@ -17,6 +17,20 @@ define armex x/i *(int *)($msp+24) end +define load_isr + set $sp = *(InitialisationVector) +# Warning: InitialisationVector is a uint32_t*, so InitialisationVector+1 points to the next 32-bit value + set $pc = *(InitialisationVector+1) +end + + +define use_dfu_symbol_file +# Discard previous symbol file + symbol-file +# Load new symbol file + add-symbol-file build/device/ion/src/device/shared/usb/dfu.elf 0x20038000 +end + document armex ARMv7 Exception entry behavior. xPSR, ReturnAddress, LR (R14), R12, R3, R2, R1, and R0 From 6df5a606035163e4085567e734a2b8dec880d49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 28 Mar 2019 15:29:22 +0100 Subject: [PATCH 0216/1750] [ion] N0101: fix VTOR initialisation (location of interrupt vector) --- ion/src/device/n0101/drivers/board.cpp | 8 +++++--- ion/src/device/shared/regs/cm4.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index a92c6309a..d132e6562 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -3,6 +3,8 @@ #include #include +extern void * InitialisationVector; + // Public Ion methods const char * Ion::fccId() { @@ -46,10 +48,10 @@ void init() { initMPU(); initClocks(); - // Ensure right location of interrupt vectors // The bootloader leaves its own after flashing - SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory); - CM4.VTOR()->setVTOR((void*) 0); + //SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory); + // Ensure right location of interrupt vectors + CM4.VTOR()->setVTOR((void*)&InitialisationVector); // Put all inputs as Analog Input, No pull-up nor pull-down // Except for the SWD port (PB3, PA13, PA14) diff --git a/ion/src/device/shared/regs/cm4.h b/ion/src/device/shared/regs/cm4.h index d20f5a587..ef1271350 100644 --- a/ion/src/device/shared/regs/cm4.h +++ b/ion/src/device/shared/regs/cm4.h @@ -13,7 +13,7 @@ public: // http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf class VTOR : Register32 { public: - void setVTOR(void *address) volatile { setBitRange(29, 9, (uint32_t)address); } + void setVTOR(void *address) volatile { setBitRange(29, 9, (uint32_t)address >> 9); } }; // Coprocessor Access Control Register From 9da962171de5ede4291fb2e3dffc5f5d24008f0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 28 Mar 2019 15:30:18 +0100 Subject: [PATCH 0217/1750] [ion] N0101: fix External flash comments (QUADSPI pins) --- ion/src/device/n0101/drivers/config/external_flash.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/n0101/drivers/config/external_flash.h b/ion/src/device/n0101/drivers/config/external_flash.h index ea77ac250..737923863 100644 --- a/ion/src/device/n0101/drivers/config/external_flash.h +++ b/ion/src/device/n0101/drivers/config/external_flash.h @@ -7,7 +7,7 @@ * -----+----------------------+-----------------------+----------------- * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS - * PC8 | QUADSPI BK1_IO2/WP | Alternate Function 9 | QUADSPI_BK1_IO2 + * 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 From 14e0e213aba1904b06ae6723c09404fadb839a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 28 Mar 2019 15:48:50 +0100 Subject: [PATCH 0218/1750] [ion] Fix systick interruption --- ion/src/device/shared/boot/rt0.cpp | 4 +--- ion/src/device/shared/drivers/board.cpp | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/ion/src/device/shared/boot/rt0.cpp b/ion/src/device/shared/boot/rt0.cpp index 482cff27f..448fea2ae 100644 --- a/ion/src/device/shared/boot/rt0.cpp +++ b/ion/src/device/shared/boot/rt0.cpp @@ -89,7 +89,5 @@ void start() { } void __attribute__((interrupt)) isr_systick() { -#warning BOO - // FIXME: TODO// - //Ion::Timing::Device::MillisElapsed++; + Ion::Device::Timing::MillisElapsed++; } diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index 6b4c6fefc..f37af00e5 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -38,13 +38,13 @@ void initPeripherals() { USB::init(); Console::init(); SWD::init(); - //Timing::init(); TODO FIXME + Timing::init(); ExternalFlash::init(); } void shutdownPeripherals(bool keepLEDAwake) { ExternalFlash::shutdown(); - //Timing::shutdown(); TODO FIXME + Timing::shutdown(); SWD::shutdown(); Console::shutdown(); USB::shutdown(); From f30cbb40bf95d1f3289961a9b20a80f8c77bb9c1 Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Fri, 29 Mar 2019 10:14:27 -0700 Subject: [PATCH 0219/1750] [ion/device/n0101] Put libgcc in the internal flash Indeed, code from aeabi can be emitted anytime by the compiler, including when the external flash has not yet been configured. --- ion/src/device/n0101/flash.ld | 1 + 1 file changed, 1 insertion(+) diff --git a/ion/src/device/n0101/flash.ld b/ion/src/device/n0101/flash.ld index c75d5c2af..f79ef1e06 100644 --- a/ion/src/device/n0101/flash.ld +++ b/ion/src/device/n0101/flash.ld @@ -60,6 +60,7 @@ SECTIONS { *(.text._ZN3Ion*) *(.text._ZNV3Ion*) *(.text._ZNK3Ion*) + */libgcc.a:(.text) } >INTERNAL_FLASH .rodata.internal : { From edda57c334f8ac4f86f97a63f0bdd791e8bc2bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 1 Apr 2019 10:03:56 +0200 Subject: [PATCH 0220/1750] [ion] N0101: init L1 cache after initing the external flash --- ion/src/device/n0101/drivers/board.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index d132e6562..d3da1658d 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -44,7 +44,6 @@ void initMPU() { void init() { initFPU(); - initL1Cache(); initMPU(); initClocks(); @@ -65,6 +64,8 @@ void init() { } initPeripherals(); + // Initiate L1 cache after initiating the external flash + initL1Cache(); // TODO if EPSILON_DEVICE_BENCH, run bench? See n0100 } From d746aa7de3d04bc33d5078ad7d33f0bf2433ac23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 4 Apr 2019 13:56:21 +0200 Subject: [PATCH 0221/1750] [ion/N0101] Add comment to slow down the system clock to 24MHz --- ion/src/device/n0101/drivers/board.cpp | 13 ++++++++++--- ion/src/device/shared/regs/rcc.h | 8 +++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index d3da1658d..8bdaeb6af 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -106,10 +106,17 @@ void initClocks() { RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); /* If you want to considerably slow down the whole machine uniformely, which - * can be very useful to diagnose performance issues, just uncomment the line - * below. Note that even booting takes a few seconds, so don't be surprised + * can be very useful to diagnose performance issues, change the PLL + * configuration to: + * RCC.PLLCFGR()->setPLLM(8); + * RCC.PLLCFGR()->setPLLN(192); + * RCC.PLLCFGR()->setPLLP(RCC::PLLCFGR::PLLP::PLLP8); + * RCC.PLLCFGR()->setPLLQ(4); + * RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); + * HCLK will be set to 24 MHz. + * Don't forget to change ExternalFlash::AHBClockFrequency + * Note that even booting takes a few seconds, so don't be surprised * if the screen is black for a short while upon booting. */ - //RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy2); // Enable the PLL and wait for it to be ready RCC.CR()->setPLLON(true); diff --git a/ion/src/device/shared/regs/rcc.h b/ion/src/device/shared/regs/rcc.h index 7aa872af7..780337cd1 100644 --- a/ion/src/device/shared/regs/rcc.h +++ b/ion/src/device/shared/regs/rcc.h @@ -23,7 +23,13 @@ public: public: REGS_FIELD(PLLM, uint8_t, 5, 0); REGS_FIELD(PLLN, uint16_t, 14, 6); - REGS_FIELD(PLLP, uint8_t, 17, 16); + enum class PLLP { + PLLP2 = 0b00, + PLLP4 = 0b01, + PLLP6 = 0b10, + PLLP8 = 0b11 + }; + void setPLLP(PLLP s) volatile { setBitRange(17, 16, (uint8_t)s); } enum class PLLSRC { HSI = 0, HSE = 1 From d5129f004e2d4817b7dd16e6f04e1b9ef5f96c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 4 Apr 2019 16:27:09 +0200 Subject: [PATCH 0222/1750] [ion] Clocks: clean clocks init --- ion/src/device/n0100/drivers/board.cpp | 6 +-- ion/src/device/n0100/drivers/config/clocks.h | 41 +++++++++++++++++ ion/src/device/n0101/drivers/board.cpp | 20 ++------- ion/src/device/n0101/drivers/config/clocks.h | 45 +++++++++++++++++++ .../device/shared/drivers/external_flash.cpp | 6 +-- 5 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 ion/src/device/n0100/drivers/config/clocks.h create mode 100644 ion/src/device/n0101/drivers/config/clocks.h diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index ad5b0b21e..676cea315 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -86,11 +86,11 @@ void initClocks() { * and the required 48 MHz USB clock. */ // Configure the PLL ratios and use HSE as a PLL input - RCC.PLLCFGR()->setPLLM(25); - RCC.PLLCFGR()->setPLLQ(4); + RCC.PLLCFGR()->setPLLM(Clocks::Config::PLL_M); + RCC.PLLCFGR()->setPLLQ(Clocks::Config::PLL_Q); RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); // 96 MHz is too fast for APB1. Divide it by two to reach 48 MHz - RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy2); + RCC.CFGR()->setPPRE1(Clocks::Config::APBP1rescaler); /* If you want to considerably slow down the whole machine uniformely, which * can be very useful to diagnose performance issues, just uncomment the line diff --git a/ion/src/device/n0100/drivers/config/clocks.h b/ion/src/device/n0100/drivers/config/clocks.h new file mode 100644 index 000000000..acba1a15e --- /dev/null +++ b/ion/src/device/n0100/drivers/config/clocks.h @@ -0,0 +1,41 @@ +#ifndef ION_DEVICE_N0100_CONFIG_CLOCKS_H +#define ION_DEVICE_N0100_CONFIG_CLOCKS_H + +#include + +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 = 25 + * PLL_N = 144 + * PLL_P_Reg = Regs::RCC::PLLCFGR::PLLP::PLLP6 + * PLL_Q = 3 + * + * 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 = 25; +constexpr static int PLL_M = 25; +constexpr static int PLL_Q = 4; +//constexpr static int PLL_N = 192; +//constexpr static Regs::RCC::PLLCFGR::PLLP PLL_P_Reg = Regs::RCC::PLLCFGR::PLLP::PLLP2; +//constexpr static int PLL_P = 2*(int)PLL_P_Reg; +//constexpr static int SYSCLKFrequency = ((HSE/PLL_M)*PLL_N)/PLL_P; +//constexpr static int AHB_prescaler = 1; +//constexpr static int HCLKFrequency = SYSCLKFrequency/AHB_prescaler; +//constexpr static int AHBFrequency = HCLKFrequency; +constexpr static int APB1Prescaler = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy2; + +} +} +} +} + +#endif + diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 8bdaeb6af..721e9f068 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -100,24 +101,11 @@ void initClocks() { * for use in different parts of the system. */ // Configure the PLL ratios and use HSE as a PLL input - RCC.PLLCFGR()->setPLLM(8); - RCC.PLLCFGR()->setPLLN(384); - RCC.PLLCFGR()->setPLLQ(8); + 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); - /* 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: - * RCC.PLLCFGR()->setPLLM(8); - * RCC.PLLCFGR()->setPLLN(192); - * RCC.PLLCFGR()->setPLLP(RCC::PLLCFGR::PLLP::PLLP8); - * RCC.PLLCFGR()->setPLLQ(4); - * RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); - * HCLK will be set to 24 MHz. - * Don't forget to change ExternalFlash::AHBClockFrequency - * Note that even booting takes a few seconds, so don't be surprised - * if the screen is black for a short while upon booting. */ - // Enable the PLL and wait for it to be ready RCC.CR()->setPLLON(true); diff --git a/ion/src/device/n0101/drivers/config/clocks.h b/ion/src/device/n0101/drivers/config/clocks.h new file mode 100644 index 000000000..3474b3046 --- /dev/null +++ b/ion/src/device/n0101/drivers/config/clocks.h @@ -0,0 +1,45 @@ +#ifndef ION_DEVICE_N0101_CONFIG_CLOCKS_H +#define ION_DEVICE_N0101_CONFIG_CLOCKS_H + +#include + +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 AHB_prescaler = 1; +constexpr static int HCLKFrequency = SYSCLKFrequency/AHB_prescaler; +constexpr static int AHBFrequency = HCLKFrequency; +constexpr static int LoopsPerMillisecond = 4811; +constexpr static int LoopsPerMicrosecond = 38; +// CPU clock is 192 MHz, and systick clock source is divided by 8 +// To get 1 ms systick overflow we need to reset it to +// 192 000 000 (Hz) / 8 / 1 000 (ms/s) +constexpr static int SysTickPerMillisecond = 24000; + +} +} +} +} + +#endif diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 0b7c17b62..4eb3129d2 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -1,5 +1,6 @@ #include "external_flash.h" #include +#include #include namespace Ion { @@ -90,9 +91,8 @@ public: }; static constexpr QUADSPI::CCR::OperatingMode DefaultOperatingMode = QUADSPI::CCR::OperatingMode::Quad; -static constexpr int AHBClockFrequency = 192; // MHz static constexpr int ClockFrequencyDivisor = 2; -static constexpr bool ajustNumberOfDummyCycles = AHBClockFrequency > (80 * ClockFrequencyDivisor); +static constexpr bool ajustNumberOfDummyCycles = Clocks::Config::AHBFrequency > (80 * ClockFrequencyDivisor); static constexpr int FastReadDummyCycles = (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad && ajustNumberOfDummyCycles) ? 4 : 2; static void send_command_full(QUADSPI::CCR::FunctionalMode functionalMode, QUADSPI::CCR::OperatingMode operatingMode, Command c, uint8_t * address, uint32_t altBytes, size_t numberOfAltBytes, uint8_t dummyCycles, uint8_t * data, size_t dataLength); @@ -269,7 +269,7 @@ static void initQSPI() { /* According to the device's datasheet (see Sections 8.7 and 8.8), the CS * signal should stay high (deselect the device) for t_SHSL = 30ns at least. * */ - constexpr int ChipSelectHighTime = (30 * AHBClockFrequency + ClockFrequencyDivisor * 1000 - 1) / (ClockFrequencyDivisor * 1000); + constexpr int ChipSelectHighTime = (30 * Clocks::Config::AHBFrequency + ClockFrequencyDivisor * 1000 - 1) / (ClockFrequencyDivisor * 1000); dcr.setCSHT(ChipSelectHighTime - 1); dcr.setCKMODE(true); QUADSPI.DCR()->set(dcr); From 731b6148b22474e27d3c98962b0eea25a8cf7930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 4 Apr 2019 17:39:45 +0200 Subject: [PATCH 0223/1750] [ion] Build ion/src/device/stack.cpp to fix build when DEBUG=1 --- ion/src/device/shared/Makefile | 4 ++++ ion/src/device/{ => shared}/stack.cpp | 0 2 files changed, 4 insertions(+) rename ion/src/device/{ => shared}/stack.cpp (100%) diff --git a/ion/src/device/shared/Makefile b/ion/src/device/shared/Makefile index f0f076d04..d13ec99b6 100644 --- a/ion/src/device/shared/Makefile +++ b/ion/src/device/shared/Makefile @@ -1,3 +1,7 @@ include ion/src/device/shared/boot/Makefile include ion/src/device/shared/usb/Makefile include ion/src/device/shared/drivers/Makefile + +ion_device_src += $(addprefix ion/src/device/shared/, \ + stack.cpp \ +) diff --git a/ion/src/device/stack.cpp b/ion/src/device/shared/stack.cpp similarity index 100% rename from ion/src/device/stack.cpp rename to ion/src/device/shared/stack.cpp From 33bccf010936b4baf76651aad124a3cdac5bff0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 4 Apr 2019 17:54:56 +0200 Subject: [PATCH 0224/1750] [ion] Change name: CM4 --> Cortex --- ion/src/device/n0100/drivers/board.cpp | 2 +- ion/src/device/n0101/drivers/board.cpp | 2 +- ion/src/device/n0101/drivers/cache.cpp | 28 +++++++++---------- ion/src/device/shared/drivers/board.cpp | 4 +-- ion/src/device/shared/drivers/power.cpp | 2 +- ion/src/device/shared/drivers/reset.cpp | 2 +- ion/src/device/shared/drivers/timing.cpp | 14 +++++----- .../device/shared/regs/{cm4.h => cortex.h} | 10 +++---- ion/src/device/shared/regs/regs.h | 2 +- 9 files changed, 33 insertions(+), 33 deletions(-) rename ion/src/device/shared/regs/{cm4.h => cortex.h} (96%) diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index 676cea315..5002877ee 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -24,7 +24,7 @@ void init() { // Ensure right location of interrupt vectors // The bootloader leaves its own after flashing SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory); - CM4.VTOR()->setVTOR((void*) 0); + CORTEX.VTOR()->setVTOR((void*) 0); // Put all inputs as Analog Input, No pull-up nor pull-down // Except for the SWD port (PB3, PA13, PA14) diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 721e9f068..9e79fb899 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -51,7 +51,7 @@ void init() { // The bootloader leaves its own after flashing //SYSCFG.MEMRMP()->setMEM_MODE(SYSCFG::MEMRMP::MemMode::MainFlashmemory); // Ensure right location of interrupt vectors - CM4.VTOR()->setVTOR((void*)&InitialisationVector); + CORTEX.VTOR()->setVTOR((void*)&InitialisationVector); // Put all inputs as Analog Input, No pull-up nor pull-down // Except for the SWD port (PB3, PA13, PA14) diff --git a/ion/src/device/n0101/drivers/cache.cpp b/ion/src/device/n0101/drivers/cache.cpp index ae3fff882..e4be16935 100644 --- a/ion/src/device/n0101/drivers/cache.cpp +++ b/ion/src/device/n0101/drivers/cache.cpp @@ -7,16 +7,16 @@ namespace Cache { using namespace Regs; void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disable) { - CM4.CSSELR()->set(0); + CORTEX.CSSELR()->set(0); dsb(); // Associativity = 6 - uint32_t sets = CM4.CCSIDR()->getNUMSETS(); - uint32_t ways = CM4.CCSIDR()->getASSOCIATIVITY(); + uint32_t sets = CORTEX.CCSIDR()->getNUMSETS(); + uint32_t ways = CORTEX.CCSIDR()->getASSOCIATIVITY(); if (disable) { - CM4.CCR()->setDC(false); + CORTEX.CCR()->setDC(false); dsb(); } @@ -25,21 +25,21 @@ void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disab do { if (clean) { if (invalidate) { - class CM4::DCCISW dccisw; + class CORTEX::DCCISW dccisw; dccisw.setSET(sets); dccisw.setWAY(w); - CM4.DCCISW()->set(dccisw); + CORTEX.DCCISW()->set(dccisw); } else { - class CM4::DCCSW dccsw; + class CORTEX::DCCSW dccsw; dccsw.setSET(sets); dccsw.setWAY(w); - CM4.DCCSW()->set(dccsw); + CORTEX.DCCSW()->set(dccsw); } } else if (invalidate) { - class CM4::DCISW dcisw; + class CORTEX::DCISW dcisw; dcisw.setSET(sets); dcisw.setWAY(w); - CM4.DCISW()->set(dcisw); + CORTEX.DCISW()->set(dcisw); } } while (w-- != 0); } while (sets-- != 0); @@ -58,7 +58,7 @@ void cleanDCache() { void enableDCache() { invalidateDCache(); - CM4.CCR()->setDC(true); + CORTEX.CCR()->setDC(true); dsb(); isb(); } @@ -70,14 +70,14 @@ void disableDCache() { void invalidateICache() { dsb(); isb(); - CM4.ICIALLU()->set(0); + CORTEX.ICIALLU()->set(0); dsb(); isb(); } void enableICache() { invalidateICache(); - CM4.CCR()->setIC(true); + CORTEX.CCR()->setIC(true); dsb(); isb(); } @@ -85,7 +85,7 @@ void enableICache() { void disableICache() { dsb(); isb(); - CM4.CCR()->setIC(false); + CORTEX.CCR()->setIC(false); invalidateICache(); } diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index f37af00e5..aeb49969d 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -24,8 +24,8 @@ void shutdown() { void initFPU() { // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BABDBFBJ.html - CM4.CPACR()->setAccess(10, CM4::CPACR::Access::Full); - CM4.CPACR()->setAccess(11, CM4::CPACR::Access::Full); + CORTEX.CPACR()->setAccess(10, CORTEX::CPACR::Access::Full); + CORTEX.CPACR()->setAccess(11, CORTEX::CPACR::Access::Full); // FIXME: The pipeline should be flushed at this point } diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index ec25cec1b..accaa09c6 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -32,7 +32,7 @@ void suspend(bool checkIfPowerKeyReleased) { PWR.CR()->setUDEN(PWR::CR::UnderDrive::Enable); #endif - CM4.SCR()->setSLEEPDEEP(!isLEDActive); + CORTEX.SCR()->setSLEEPDEEP(!isLEDActive); while (1) { #if EPSILON_LED_WHILE_CHARGING diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index 90d7e62e2..3ff587b83 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -14,7 +14,7 @@ using namespace Regs; void core() { // Perform a full core reset - CM4.AIRCR()->requestReset(); + CORTEX.AIRCR()->requestReset(); } void jump() { diff --git a/ion/src/device/shared/drivers/timing.cpp b/ion/src/device/shared/drivers/timing.cpp index e2a30d100..2c19b5d8b 100644 --- a/ion/src/device/shared/drivers/timing.cpp +++ b/ion/src/device/shared/drivers/timing.cpp @@ -42,16 +42,16 @@ using namespace Regs; volatile uint64_t MillisElapsed = 0; void init() { - CM4.SYST_RVR()->setRELOAD(Config::SysTickPerMillisecond - 1); // Remove 1 because the counter resets *after* counting to 0 - CM4.SYST_CVR()->setCURRENT(0); - CM4.SYST_CSR()->setCLKSOURCE(CM4::SYST_CSR::CLKSOURCE::AHB_DIV8); - CM4.SYST_CSR()->setTICKINT(true); - CM4.SYST_CSR()->setENABLE(true); + CORTEX.SYST_RVR()->setRELOAD(Config::SysTickPerMillisecond - 1); // Remove 1 because the counter resets *after* counting to 0 + CORTEX.SYST_CVR()->setCURRENT(0); + CORTEX.SYST_CSR()->setCLKSOURCE(CORTEX::SYST_CSR::CLKSOURCE::AHB_DIV8); + CORTEX.SYST_CSR()->setTICKINT(true); + CORTEX.SYST_CSR()->setENABLE(true); } void shutdown() { - CM4.SYST_CSR()->setENABLE(false); - CM4.SYST_CSR()->setTICKINT(false); + CORTEX.SYST_CSR()->setENABLE(false); + CORTEX.SYST_CSR()->setTICKINT(false); } } diff --git a/ion/src/device/shared/regs/cm4.h b/ion/src/device/shared/regs/cortex.h similarity index 96% rename from ion/src/device/shared/regs/cm4.h rename to ion/src/device/shared/regs/cortex.h index ef1271350..05a91e659 100644 --- a/ion/src/device/shared/regs/cm4.h +++ b/ion/src/device/shared/regs/cortex.h @@ -1,5 +1,5 @@ -#ifndef REGS_CM4_H -#define REGS_CM4_H +#ifndef REGS_CORTEX_H +#define REGS_CORTEX_H #include "register.h" @@ -7,7 +7,7 @@ namespace Ion { namespace Device { namespace Regs { -class CM4 { +class CORTEX { public: // Vector table offset register // http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf @@ -109,7 +109,7 @@ public: REGS_FIELD(WAY, uint8_t, 31, 30); }; - constexpr CM4() {}; + constexpr CORTEX() {}; REGS_REGISTER_AT(SYST_CSR, 0x10); REGS_REGISTER_AT(SYST_RVR, 0x14); REGS_REGISTER_AT(SYST_CVR, 0x18); @@ -130,7 +130,7 @@ private: } }; -constexpr CM4 CM4; +constexpr CORTEX CORTEX; } } diff --git a/ion/src/device/shared/regs/regs.h b/ion/src/device/shared/regs/regs.h index c77040ee7..e65613c03 100644 --- a/ion/src/device/shared/regs/regs.h +++ b/ion/src/device/shared/regs/regs.h @@ -2,7 +2,7 @@ #define REGS_REGS_H #include "adc.h" -#include "cm4.h" +#include "cortex.h" #include "crc.h" #include "dma.h" #include "exti.h" From bb14d37f2a9d338c403ebe113387996faa5e1819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 5 Apr 2019 09:39:39 +0200 Subject: [PATCH 0225/1750] [ion] Add specific configuration for regs/cortex.h for model N0100 and N0101 --- ion/src/device/n0100/regs/config/cortex.h | 6 ++ ion/src/device/n0101/regs/config/cortex.h | 6 ++ ion/src/device/shared/regs/cortex.h | 87 +++++++++++++---------- 3 files changed, 60 insertions(+), 39 deletions(-) create mode 100644 ion/src/device/n0100/regs/config/cortex.h create mode 100644 ion/src/device/n0101/regs/config/cortex.h diff --git a/ion/src/device/n0100/regs/config/cortex.h b/ion/src/device/n0100/regs/config/cortex.h new file mode 100644 index 000000000..c19a734c0 --- /dev/null +++ b/ion/src/device/n0100/regs/config/cortex.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0100_REGS_CONFIG_CORTEX_H +#define ION_DEVICE_N0100_REGS_CONFIG_CORTEX_H + +#define REGS_CORTEX_CONFIG_CACHE 0 + +#endif diff --git a/ion/src/device/n0101/regs/config/cortex.h b/ion/src/device/n0101/regs/config/cortex.h new file mode 100644 index 000000000..80e3e0977 --- /dev/null +++ b/ion/src/device/n0101/regs/config/cortex.h @@ -0,0 +1,6 @@ +#ifndef ION_DEVICE_N0101_REGS_CONFIG_CORTEX_H +#define ION_DEVICE_N0101_REGS_CONFIG_CORTEX_H + +#define REGS_CORTEX_CONFIG_CACHE 1 + +#endif diff --git a/ion/src/device/shared/regs/cortex.h b/ion/src/device/shared/regs/cortex.h index 05a91e659..22ae782a7 100644 --- a/ion/src/device/shared/regs/cortex.h +++ b/ion/src/device/shared/regs/cortex.h @@ -2,6 +2,7 @@ #define REGS_CORTEX_H #include "register.h" +#include namespace Ion { namespace Device { @@ -9,6 +10,28 @@ namespace Regs { class CORTEX { public: + class SYST_CSR : public Register32 { + public: + enum class CLKSOURCE : uint8_t { + AHB_DIV8 = 0, + AHB = 1 + }; + REGS_BOOL_FIELD(COUNTFLAG, 16); + REGS_TYPE_FIELD(CLKSOURCE, 2, 2); + REGS_BOOL_FIELD(TICKINT, 1); + REGS_BOOL_FIELD(ENABLE, 0); + }; + + class SYST_RVR : public Register32 { + public: + REGS_FIELD(RELOAD, uint32_t, 23, 0); + }; + + class SYST_CVR : public Register32 { + public: + REGS_FIELD(CURRENT, uint32_t, 23, 0); + }; + // Vector table offset register // http://www.st.com/content/ccc/resource/technical/document/programming_manual/6c/3a/cb/e7/e4/ea/44/9b/DM00046982.pdf/files/DM00046982.pdf/jcr:content/translations/en.DM00046982.pdf class VTOR : Register32 { @@ -16,18 +39,6 @@ public: void setVTOR(void *address) volatile { setBitRange(29, 9, (uint32_t)address >> 9); } }; - // Coprocessor Access Control Register - // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BEHBJHIG.html - class CPACR : public Register32 { - public: - enum class Access { - Denied = 0, - PrivilegedOnly = 1, - Full = 3 - }; - void setAccess(int index, Access a) volatile { setBitRange(2*index+1, 2*index, (uint32_t)a); } - }; - // Application Interrupt and Reset Control Register // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/Cihehdge.html @@ -50,42 +61,35 @@ public: REGS_BOOL_FIELD(DC, 16); }; - class SYST_CSR : public Register32 { +#if REGS_CORTEX_CONFIG_CACHE + class CCSIDR : public Register32 { public: - enum class CLKSOURCE : uint8_t { - AHB_DIV8 = 0, - AHB = 1 - }; - REGS_BOOL_FIELD(COUNTFLAG, 16); - REGS_TYPE_FIELD(CLKSOURCE, 2, 2); - REGS_BOOL_FIELD(TICKINT, 1); - REGS_BOOL_FIELD(ENABLE, 0); - }; - - class SYST_RVR : public Register32 { - public: - REGS_FIELD(RELOAD, uint32_t, 23, 0); - }; - - class SYST_CVR : public Register32 { - public: - REGS_FIELD(CURRENT, uint32_t, 23, 0); - }; - - class ICIALLU : public Register32 { - public: - using Register32::Register32; + REGS_FIELD(ASSOCIATIVITY, uint16_t, 12, 3); + REGS_FIELD(NUMSETS, uint16_t, 27, 13); }; class CSSELR : public Register32 { public: REGS_BOOL_FIELD(IND, 0); }; +#endif - class CCSIDR : public Register32 { + // Coprocessor Access Control Register + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/BEHBJHIG.html + class CPACR : public Register32 { public: - REGS_FIELD(ASSOCIATIVITY, uint16_t, 12, 3); - REGS_FIELD(NUMSETS, uint16_t, 27, 13); + enum class Access { + Denied = 0, + PrivilegedOnly = 1, + Full = 3 + }; + void setAccess(int index, Access a) volatile { setBitRange(2*index+1, 2*index, (uint32_t)a); } + }; + +#if REGS_CORTEX_CONFIG_CACHE + class ICIALLU : public Register32 { + public: + using Register32::Register32; }; class DCISW : public Register32 { @@ -108,6 +112,7 @@ public: REGS_FIELD(SET, uint16_t, 13, 5); REGS_FIELD(WAY, uint8_t, 31, 30); }; +#endif constexpr CORTEX() {}; REGS_REGISTER_AT(SYST_CSR, 0x10); @@ -117,13 +122,17 @@ public: REGS_REGISTER_AT(AIRCR, 0xD0C); REGS_REGISTER_AT(SCR, 0xD10); REGS_REGISTER_AT(CCR, 0xD14); +#if REGS_CORTEX_CONFIG_CACHE REGS_REGISTER_AT(CCSIDR, 0xD80); REGS_REGISTER_AT(CSSELR, 0xD84); +#endif REGS_REGISTER_AT(CPACR, 0xD88); +#if REGS_CORTEX_CONFIG_CACHE REGS_REGISTER_AT(ICIALLU, 0xF50); REGS_REGISTER_AT(DCISW, 0xF60); REGS_REGISTER_AT(DCCSW, 0xF6C); REGS_REGISTER_AT(DCCISW, 0xF74); +#endif private: constexpr uint32_t Base() const { return 0xE000E000; From eecfac290c9db997b72a5f70a157b6b8c51fac7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 5 Apr 2019 09:41:49 +0200 Subject: [PATCH 0226/1750] [ion] Add regs in Cortex (to activate fault interruption) --- ion/src/device/shared/regs/cortex.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ion/src/device/shared/regs/cortex.h b/ion/src/device/shared/regs/cortex.h index 22ae782a7..79797c087 100644 --- a/ion/src/device/shared/regs/cortex.h +++ b/ion/src/device/shared/regs/cortex.h @@ -61,6 +61,13 @@ public: REGS_BOOL_FIELD(DC, 16); }; + class SHCRS : public Register32 { + public: + REGS_BOOL_FIELD(USGFAULTENA, 18); + REGS_BOOL_FIELD(BUSFAULTENA, 17); + REGS_BOOL_FIELD(MEMFAULTENA, 16); + }; + #if REGS_CORTEX_CONFIG_CACHE class CCSIDR : public Register32 { public: @@ -122,6 +129,7 @@ public: REGS_REGISTER_AT(AIRCR, 0xD0C); REGS_REGISTER_AT(SCR, 0xD10); REGS_REGISTER_AT(CCR, 0xD14); + REGS_REGISTER_AT(SHCRS, 0xD24); #if REGS_CORTEX_CONFIG_CACHE REGS_REGISTER_AT(CCSIDR, 0xD80); REGS_REGISTER_AT(CSSELR, 0xD84); From b1c3c2813e45bf8d8d05009ce24800ac6907e2c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 4 Apr 2019 10:10:22 +0200 Subject: [PATCH 0227/1750] [ion] ExternalFlash: reset QUADSPI peripheral --- ion/src/device/shared/drivers/external_flash.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 4eb3129d2..2de53139d 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -263,7 +263,10 @@ static void initGPIO() { static void initQSPI() { // Enable QUADSPI AHB3 peripheral clock RCC.AHB3ENR()->setQSPIEN(true); - // Configure controller for target device + /* Reset QSPI peripheral */ + RCC.AHB3RSTR()->setQSPIRST(true); + RCC.AHB3RSTR()->setQSPIRST(false); + // Configure controller for target device class QUADSPI::DCR dcr(0); dcr.setFSIZE(NumberOfAddressBitsInChip - 1); /* According to the device's datasheet (see Sections 8.7 and 8.8), the CS From f5ce18fe261fb577bc263d487bbc1c826ecdcafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 4 Apr 2019 10:11:02 +0200 Subject: [PATCH 0228/1750] [ion] ExternalFlash: fix comment --- ion/src/device/shared/drivers/external_flash.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index 671fcb155..2c64afc3e 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -21,16 +21,6 @@ namespace Ion { namespace Device { namespace ExternalFlash { -/* Pin | Role | Mode | Function - * -----+----------------------+-----------------------+----------------- - * PB2 | QUADSPI CLK | Alternate Function 9 | QUADSPI_CLK - * PB6 | QUADSPI BK1_NCS | Alternate Function 10 | QUADSPI_BK1_NCS - * PC8 | 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 - */ - void init(); void shutdown(); void deinit(); From a12bb26d40e4e3d430a5774ddff9e8586511d6b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 4 Apr 2019 10:11:26 +0200 Subject: [PATCH 0229/1750] [ion] MPU regs --- ion/src/device/shared/regs/mpu.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/ion/src/device/shared/regs/mpu.h b/ion/src/device/shared/regs/mpu.h index e89120891..afca991ca 100644 --- a/ion/src/device/shared/regs/mpu.h +++ b/ion/src/device/shared/regs/mpu.h @@ -53,15 +53,18 @@ public: REGS_BOOL_FIELD(B, 16); // Buffereable REGS_FIELD(SRD, uint8_t, 15, 8); enum class RegionSize : uint8_t { - Bytes32 = 0b00100, - Bytes64 = 0b00101, - Bytes128 = 0b00110, - KyloBytes1 = 0b01001, - MegaBytes1 = 0b10011, + _32B = 0b00100, + _64B = 0b00101, + _128B = 0b00110, + _1KB = 0b01001, + _64KB = 0b01111, _1MB = 19, + _2MB = 20, + _4MB = 21, + _8MB = 22, _32MB = 24, - GigaBytes1 = 0b11101, - GigaBytes4 = 0b11111 + _1GB = 0b11101, + _4GB = 0b11111 }; REGS_FIELD(SIZE, RegionSize, 5, 1); From 1e3a1477ebc3ef4e653b7f892b606bb65341bc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 4 Apr 2019 13:43:38 +0200 Subject: [PATCH 0230/1750] [ion] N0101: configure MPU for QUASPI L1 cache can issue speculative reads to any QUADSPI address (among the 256MB). We configure the cache to prevent such accesses --- ion/src/device/n0101/drivers/board.cpp | 42 +++++++++++++++++++++++++- ion/src/device/shared/regs/mpu.h | 1 + 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 9e79fb899..51894eae6 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -26,7 +26,18 @@ void initL1Cache() { } void initMPU() { - // Configure MPU settings for the FMC memory area + // 1. Disable the MPU + // 1.1 Memory barrier + asm volatile("dmb 0xF":::"memory"); + + // 1.2 Disable fault exceptions + CM4.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 MPU.RNR()->setREGION(0x00); MPU.RBAR()->setADDR(0x60000000); @@ -39,8 +50,37 @@ void initMPU() { MPU.RASR()->setB(0); MPU.RASR()->setENABLE(true); + // 2.2 Configure MPU regions for the QUADSPI peripheral + // TODO: lengthy comment + MPU.RNR()->setREGION(0x01); + 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(0x02); + 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. + asm volatile("dsb 0xF":::"memory"); + asm volatile("isb 0xF":::"memory"); } void init() { diff --git a/ion/src/device/shared/regs/mpu.h b/ion/src/device/shared/regs/mpu.h index afca991ca..f4f8303b5 100644 --- a/ion/src/device/shared/regs/mpu.h +++ b/ion/src/device/shared/regs/mpu.h @@ -63,6 +63,7 @@ public: _4MB = 21, _8MB = 22, _32MB = 24, + _256MB = 27, _1GB = 0b11101, _4GB = 0b11111 }; From d11e3050a34b0e2bcada5380b0ae5d0d5b668bd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 5 Apr 2019 10:13:57 +0200 Subject: [PATCH 0231/1750] [ion] Clean MPU initiation --- ion/src/device/n0101/drivers/board.cpp | 17 ++++++++++++----- ion/src/device/n0101/drivers/cache.h | 8 ++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 51894eae6..02fec8b2c 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -28,10 +28,10 @@ void initL1Cache() { void initMPU() { // 1. Disable the MPU // 1.1 Memory barrier - asm volatile("dmb 0xF":::"memory"); + Cache::dmb(); // 1.2 Disable fault exceptions - CM4.SHCRS()->setMEMFAULTENA(false); + CORTEX.SHCRS()->setMEMFAULTENA(false); // 1.3 Disable the MPU and clear the control register MPU.CTRL()->setENABLE(false); @@ -51,7 +51,14 @@ void initMPU() { MPU.RASR()->setENABLE(true); // 2.2 Configure MPU regions for the QUADSPI peripheral - // TODO: lengthy comment + /* 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(0x01); MPU.RBAR()->setADDR(0x90000000); MPU.RASR()->setSIZE(MPU::RASR::RegionSize::_256MB); @@ -79,8 +86,8 @@ void initMPU() { MPU.CTRL()->setENABLE(true); // 3. Data/instruction synchronisation barriers to ensure that the new MPU configuration is used by subsequent instructions. - asm volatile("dsb 0xF":::"memory"); - asm volatile("isb 0xF":::"memory"); + Cache::dsb(); + Cache::isb(); } void init() { diff --git a/ion/src/device/n0101/drivers/cache.h b/ion/src/device/n0101/drivers/cache.h index 60d4d4b61..cc074008a 100644 --- a/ion/src/device/n0101/drivers/cache.h +++ b/ion/src/device/n0101/drivers/cache.h @@ -7,6 +7,14 @@ 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() { From 62c663ef17fa6640bee79e2da4d9675cf06b3f9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 5 Apr 2019 14:20:14 +0200 Subject: [PATCH 0232/1750] [ion/led] Fix blue/green LEDs inversion --- ion/src/device/shared/drivers/led.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/shared/drivers/led.cpp b/ion/src/device/shared/drivers/led.cpp index 3a72a5dde..7644040b7 100644 --- a/ion/src/device/shared/drivers/led.cpp +++ b/ion/src/device/shared/drivers/led.cpp @@ -127,8 +127,8 @@ void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, } TIM3.CCR(Ion::Device::LED::Config::RedChannel)->set(dutyCycleRed*period); - TIM3.CCR(Ion::Device::LED::Config::GreenChannel)->set(dutyCycleBlue*period); - TIM3.CCR(Ion::Device::LED::Config::BlueChannel)->set(dutyCycleGreen*period); + TIM3.CCR(Ion::Device::LED::Config::GreenChannel)->set(dutyCycleGreen*period); + TIM3.CCR(Ion::Device::LED::Config::BlueChannel)->set(dutyCycleBlue*period); } } From 07fc3721ee92c71d7c51c155267a8da0bb6ee428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 5 Apr 2019 17:13:56 +0200 Subject: [PATCH 0233/1750] [ion] Fix getPlatformEvent (return None event by default) --- ion/src/device/shared/drivers/events_keyboard_platform.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/device/shared/drivers/events_keyboard_platform.cpp b/ion/src/device/shared/drivers/events_keyboard_platform.cpp index 4c2b1a605..6e756fbae 100644 --- a/ion/src/device/shared/drivers/events_keyboard_platform.cpp +++ b/ion/src/device/shared/drivers/events_keyboard_platform.cpp @@ -24,6 +24,8 @@ Event getPlatformEvent() { return Events::USBEnumeration; } } + + return Event(); } } From 92a55bc5074450c50df4bcda337f0255c83f200e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 5 Apr 2019 17:14:26 +0200 Subject: [PATCH 0234/1750] [ion] Keyboard: cheat to get user key-pressing on B3 layout as B2 in the keyboard matrix --- ion/include/ion/keyboard.h | 2 +- ion/src/device/n0100/drivers/config/keyboard.h | 4 ++++ ion/src/device/n0101/drivers/config/keyboard.h | 7 +++++++ ion/src/device/shared/drivers/keyboard.cpp | 3 +++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/ion/include/ion/keyboard.h b/ion/include/ion/keyboard.h index 384954d14..0bfa662bd 100644 --- a/ion/include/ion/keyboard.h +++ b/ion/include/ion/keyboard.h @@ -11,7 +11,7 @@ namespace Keyboard { enum class Key : uint8_t { A1=0, A2=1, A3=2, A4=3, A5=4, A6=5, - B1=6, B2=7, /* B3=8, B4=9, B5=10, B6=11, */ + B1=6, B2=7, B3=8, /*B4=9, B5=10, B6=11, */ C1=12, C2=13, C3=14, C4=15, C5=16, C6=17, D1=18, D2=19, D3=20, D4=21, D5=22, D6=23, E1=24, E2=25, E3=26, E4=27, E5=28, E6=29, diff --git a/ion/src/device/n0100/drivers/config/keyboard.h b/ion/src/device/n0100/drivers/config/keyboard.h index 8a4028d14..4c55b8729 100644 --- a/ion/src/device/n0100/drivers/config/keyboard.h +++ b/ion/src/device/n0100/drivers/config/keyboard.h @@ -37,6 +37,10 @@ constexpr GPIO ColumnGPIO = GPIOC; constexpr uint8_t numberOfColumns = 6; constexpr uint8_t ColumnPins[numberOfColumns] = {0, 1, 2, 3, 4, 5}; +inline uint64_t cheat(uint64_t state) { + return state; +} + } } } diff --git a/ion/src/device/n0101/drivers/config/keyboard.h b/ion/src/device/n0101/drivers/config/keyboard.h index 41b57c43f..cf8a23bef 100644 --- a/ion/src/device/n0101/drivers/config/keyboard.h +++ b/ion/src/device/n0101/drivers/config/keyboard.h @@ -2,6 +2,7 @@ #define ION_DEVICE_N0101_CONFIG_KEYBOARD_H #include +#include /* Pin | Role | Mode * -----+-------------------+-------------------- @@ -37,6 +38,12 @@ constexpr GPIO ColumnGPIO = GPIOC; constexpr uint8_t numberOfColumns = 6; constexpr uint8_t ColumnPins[numberOfColumns] = {0, 1, 2, 3, 4, 5}; +/* The key B2 is actually on B3 in hardware on model N0101. To avoid some extra + * work, we switch B2 and B3 bit in the bit array 'state'. */ +inline uint64_t cheat(uint64_t state) { + return state & Ion::Keyboard::State(Ion::Keyboard::Key::B3) ? state | Ion::Keyboard::State(Ion::Keyboard::Key::B2) : state; +} + } } } diff --git a/ion/src/device/shared/drivers/keyboard.cpp b/ion/src/device/shared/drivers/keyboard.cpp index 6e27a8cc8..aa5e3d480 100644 --- a/ion/src/device/shared/drivers/keyboard.cpp +++ b/ion/src/device/shared/drivers/keyboard.cpp @@ -62,6 +62,9 @@ State scan() { state = (state << 6) | (~columns & 0x3F); } + // On model N0101, the B2 key is located on B3. We cheat here to avoid extra re-work. + state = Config::cheat(state); + /* Last but not least, keys number 8, 9, 10, 11, 35, 41, 47 and 53 are not * defined. Therefore we want to make sure those bits are forced to zero in * whatever value we return. */ From 57e963733d88dd2b369f7f188b3c89a81fe21014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 4 Apr 2019 10:09:26 +0200 Subject: [PATCH 0235/1750] [ion] QUADSPI: send command 'reset' before enabling initing chip --- ion/src/device/shared/drivers/external_flash.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index 2de53139d..a21e0face 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -65,6 +65,7 @@ enum class Command : uint8_t { PageProgram = 0x02, QuadPageProgram = 0x33, EnableQPI = 0x38, + EnableReset = 0x66, Reset = 0x99, // Erase the whole chip or a 64-Kbyte block as being "1" ChipErase = 0xC7, @@ -283,6 +284,11 @@ static void initQSPI() { } static void initChip() { + // Reset + send_command(Command::EnableReset, QUADSPI::CCR::OperatingMode::Single); + send_command(Command::Reset, QUADSPI::CCR::OperatingMode::Single); + Ion::Timing::usleep(30); // 30us conservative + /* The chip initially expects commands in SPI mode. We need to use SPI to tell * it to switch to QPI. */ if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { From 612e8d16bffbcc9f2510c4b0c376352b42dbca76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 5 Apr 2019 18:12:21 +0200 Subject: [PATCH 0236/1750] [ion] ExternalFlash: reset QUASPI peripheral and external chip when shutting down --- .../device/shared/drivers/external_flash.cpp | 31 +++++++------------ .../device/shared/drivers/external_flash.h | 1 - ion/src/device/shared/drivers/reset.cpp | 4 +-- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index a21e0face..bcb705c37 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -264,9 +264,7 @@ static void initGPIO() { static void initQSPI() { // Enable QUADSPI AHB3 peripheral clock RCC.AHB3ENR()->setQSPIEN(true); - /* Reset QSPI peripheral */ - RCC.AHB3RSTR()->setQSPIRST(true); - RCC.AHB3RSTR()->setQSPIRST(false); + // Configure controller for target device class QUADSPI::DCR dcr(0); dcr.setFSIZE(NumberOfAddressBitsInChip - 1); @@ -284,11 +282,6 @@ static void initQSPI() { } static void initChip() { - // Reset - send_command(Command::EnableReset, QUADSPI::CCR::OperatingMode::Single); - send_command(Command::Reset, QUADSPI::CCR::OperatingMode::Single); - Ion::Timing::usleep(30); // 30us conservative - /* The chip initially expects commands in SPI mode. We need to use SPI to tell * it to switch to QPI. */ if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { @@ -325,16 +318,6 @@ void init() { initChip(); } -void deinit() { - if (Config::NumberOfSectors == 0) { - return; - } - // Reset the controller - RCC.AHB3RSTR()->setQSPIRST(true); - RCC.AHB3RSTR()->setQSPIRST(false); - send_command(Command::Reset, QUADSPI::CCR::OperatingMode::Quad); -} - static void shutdownGPIO() { for(const AFGPIOPin & p : Config::Pins) { p.group().OSPEEDR()->setOutputSpeed(p.pin(), GPIO::OSPEEDR::OutputSpeed::Low); @@ -345,11 +328,21 @@ static void shutdownGPIO() { static void shutdownChip() { unset_memory_mapped_mode(); + // Reset + send_command(Command::EnableReset); + send_command(Command::Reset); + Ion::Timing::usleep(30); + + // Sleep deep send_command(Command::DeepPowerDown); - Timing::usleep(100); // TODO should be 3us when usleep adjusted + 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 } diff --git a/ion/src/device/shared/drivers/external_flash.h b/ion/src/device/shared/drivers/external_flash.h index 2c64afc3e..e41ec0bea 100644 --- a/ion/src/device/shared/drivers/external_flash.h +++ b/ion/src/device/shared/drivers/external_flash.h @@ -23,7 +23,6 @@ namespace ExternalFlash { void init(); void shutdown(); -void deinit(); void MassErase(); int SectorAtAddress(uint32_t address); diff --git a/ion/src/device/shared/drivers/reset.cpp b/ion/src/device/shared/drivers/reset.cpp index 3ff587b83..9c65073b1 100644 --- a/ion/src/device/shared/drivers/reset.cpp +++ b/ion/src/device/shared/drivers/reset.cpp @@ -22,9 +22,7 @@ void jump() { Ion::Device::Cache::disableDCache(); Ion::Device::Cache::disableICache(); - /* Shutdown all clocks and periherals to mimic a hardware reset. Special - * deinit for he external flash (it needs a reset command). */ - ExternalFlash::deinit(); + /* Shutdown all clocks and periherals to mimic a hardware reset. */ Board::shutdown(); /* Jump to the reset service routine after having reset the stack pointer. From c60cf81c9d7b32ae7d7c6e0059b7c690dfe5c088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 8 Apr 2019 14:02:48 +0200 Subject: [PATCH 0237/1750] [ion] Regs: define new RCC registers --- ion/src/device/n0100/regs/config/rcc.h | 7 ++ ion/src/device/n0101/regs/config/rcc.h | 7 ++ ion/src/device/shared/regs/rcc.h | 120 ++++++++++++++++++++++++- 3 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 ion/src/device/n0100/regs/config/rcc.h create mode 100644 ion/src/device/n0101/regs/config/rcc.h diff --git a/ion/src/device/n0100/regs/config/rcc.h b/ion/src/device/n0100/regs/config/rcc.h new file mode 100644 index 000000000..960becbb0 --- /dev/null +++ b/ion/src/device/n0100/regs/config/rcc.h @@ -0,0 +1,7 @@ +#ifndef ION_DEVICE_N0100_REGS_CONFIG_RCC_H +#define ION_DEVICE_N0100_REGS_CONFIG_RCC_H + +#if REGS_RCC_CONFIG_F730 0 +#if REGS_RCC_CONFIG_F412 1 + +#endif diff --git a/ion/src/device/n0101/regs/config/rcc.h b/ion/src/device/n0101/regs/config/rcc.h new file mode 100644 index 000000000..a5309b636 --- /dev/null +++ b/ion/src/device/n0101/regs/config/rcc.h @@ -0,0 +1,7 @@ +#ifndef ION_DEVICE_N0101_REGS_CONFIG_RCC_H +#define ION_DEVICE_N0101_REGS_CONFIG_RCC_H + +#define REGS_RCC_CONFIG_F730 1 +#define REGS_RCC_CONFIG_F412 0 + +#endif diff --git a/ion/src/device/shared/regs/rcc.h b/ion/src/device/shared/regs/rcc.h index 780337cd1..c47ed733b 100644 --- a/ion/src/device/shared/regs/rcc.h +++ b/ion/src/device/shared/regs/rcc.h @@ -2,6 +2,7 @@ #define REGS_RCC_H #include "register.h" +#include namespace Ion { namespace Device { @@ -92,14 +93,16 @@ public: REGS_BOOL_FIELD(DMA2EN, 22); }; - class AHB2ENR : Register32 { + class AHB2ENR : public Register32 { public: + using Register32::Register32; REGS_BOOL_FIELD(RNGEN, 6); REGS_BOOL_FIELD(OTGFSEN, 7); }; - class AHB3ENR : Register32 { + class AHB3ENR : public Register32 { public: + using Register32::Register32; REGS_BOOL_FIELD(FSMCEN, 0); REGS_BOOL_FIELD(QSPIEN, 1); }; @@ -128,14 +131,124 @@ public: class AHB1LPENR : public Register32 { public: using Register32::Register32; + REGS_BOOL_FIELD(GPIOALPEN, 0); REGS_BOOL_FIELD(GPIOBLPEN, 1); REGS_BOOL_FIELD(GPIOCLPEN, 2); + REGS_BOOL_FIELD(GPIODLPEN, 3); + REGS_BOOL_FIELD(GPIOELPEN, 4); + REGS_BOOL_FIELD(GPIOFLPEN, 5); + REGS_BOOL_FIELD(GPIOGLPEN, 6); + REGS_BOOL_FIELD(GPIOHLPEN, 7); + REGS_BOOL_FIELD(GPIOILPEN, 8); + REGS_BOOL_FIELD(CRCLPEN, 12); +#if REGS_RCC_CONFIG_F730 + REGS_BOOL_FIELD(AXILPEN, 13); +#endif + REGS_BOOL_FIELD(FLITFLPEN, 15); + REGS_BOOL_FIELD(SRAM1LPEN, 16); +#if REGS_RCC_CONFIG_F730 + REGS_BOOL_FIELD(SRAM2LPEN, 17); + REGS_BOOL_FIELD(BKPSRAMLPEN, 18); + REGS_BOOL_FIELD(DTCMLPEN, 20); +#endif + REGS_BOOL_FIELD(DMA1LPEN, 21); + REGS_BOOL_FIELD(DMA2LPEN, 22); +#if REGS_RCC_CONFIG_F730 + REGS_BOOL_FIELD(OTGHSLPEN, 29); + REGS_BOOL_FIELD(OTGHSULPILPEN, 30); +#endif + }; + + class AHB2LPENR : public Register32 { + public: + using Register32::Register32; +#if REGS_RCC_CONFIG_F730 + REGS_BOOL_FIELD(AESLPEN, 4); +#endif + REGS_BOOL_FIELD(RNGLPEN, 6); + REGS_BOOL_FIELD(OTGFSLPEN, 7); + }; + + class AHB3LPENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(FMCLPEN, 0); + REGS_BOOL_FIELD(QSPILPEN, 1); }; class APB1LPENR : public Register32 { public: using Register32::Register32; + REGS_BOOL_FIELD(TIM2LPEN, 0); REGS_BOOL_FIELD(TIM3LPEN, 1); + REGS_BOOL_FIELD(TIM4LPEN, 2); + REGS_BOOL_FIELD(TIM5LPEN, 3); + REGS_BOOL_FIELD(TIM6LPEN, 4); + REGS_BOOL_FIELD(TIM7LPEN, 5); + REGS_BOOL_FIELD(TIM12LPEN, 6); + REGS_BOOL_FIELD(TIM13LPEN, 7); + REGS_BOOL_FIELD(TIM14LPEN, 8); +#if REGS_RCC_CONFIG_F730 + REGS_BOOL_FIELD(LPTIM1LPEN, 9); +#endif + REGS_BOOL_FIELD(RTCAPBLPEN, 10); + REGS_BOOL_FIELD(WWDGLPEN, 11); + REGS_BOOL_FIELD(SPI2LPEN, 14); + REGS_BOOL_FIELD(SPI3LPEN, 15); + REGS_BOOL_FIELD(USART2LPEN, 17); + REGS_BOOL_FIELD(USART3LPEN, 18); +#if REGS_RCC_CONFIG_F730 + REGS_BOOL_FIELD(USART4LPEN, 19); + REGS_BOOL_FIELD(USART5LPEN, 20); +#elif REGS_RCC_CONFIG_F412 + REGS_BOOL_FIELD(I2CFMP1LPEN, 24); + REGS_BOOL_FIELD(CAN2LPEN, 26); +#endif + REGS_BOOL_FIELD(I2C1LPEN, 21); + REGS_BOOL_FIELD(I2C2LPEN, 22); + REGS_BOOL_FIELD(I2C3LPEN, 23); + REGS_BOOL_FIELD(CAN1LPEN, 25); + REGS_BOOL_FIELD(PWRLPEN, 28); +#if REGS_RCC_CONFIG_F730 + REGS_BOOL_FIELD(OTGHSLPEN, 29); + REGS_BOOL_FIELD(OTGHSULPILPEN, 30); +#endif + }; + + class APB2LPENR : public Register32 { + public: + using Register32::Register32; + REGS_BOOL_FIELD(TIM1LPEN, 0); + REGS_BOOL_FIELD(TIM8LPEN, 1); + REGS_BOOL_FIELD(USART1LPEN, 4); + REGS_BOOL_FIELD(USART6LPEN, 5); +#if REGS_RCC_CONFIG_F730 + REGS_BOOL_FIELD(SDMMC2LPEN, 7); +#endif + REGS_BOOL_FIELD(ADC1LPEN, 8); +#if REGS_RCC_CONFIG_F730 + REGS_BOOL_FIELD(ADC2LPEN, 9); + REGS_BOOL_FIELD(ADC3LPEN, 10); + REGS_BOOL_FIELD(SDMMC1LPEN, 11); +#elif REGS_RCC_CONFIG_F412 + REGS_BOOL_FIELD(SDIOLPEN, 11); +#endif + REGS_BOOL_FIELD(SPI1LPEN, 12); + REGS_BOOL_FIELD(SPI4LPEN, 13); + REGS_BOOL_FIELD(SYSCFGLPEN, 14); +#if REGS_RCC_CONFIG_F412 + REGS_BOOL_FIELD(EXTITEN, 15); +#endif + REGS_BOOL_FIELD(TIM9LPEN, 16); + REGS_BOOL_FIELD(TIM10LPEN, 17); + REGS_BOOL_FIELD(TIM11LPEN, 18); + REGS_BOOL_FIELD(SPI5LPEN, 20); +#if REGS_RCC_CONFIG_F730 + REGS_BOOL_FIELD(SAI1LPEN, 22); + REGS_BOOL_FIELD(SAI2LPEN, 23); +#elif REGS_RCC_CONFIG_F412 + REGS_BOOL_FIELD(DFSDM1LPEN, 24); +#endif }; class DCKCFGR2 : Register32 { @@ -155,7 +268,10 @@ public: REGS_REGISTER_AT(APB1ENR, 0x40); REGS_REGISTER_AT(APB2ENR, 0x44); REGS_REGISTER_AT(AHB1LPENR, 0x50); + REGS_REGISTER_AT(AHB2LPENR, 0x54); + REGS_REGISTER_AT(AHB3LPENR, 0x58); REGS_REGISTER_AT(APB1LPENR, 0x60); + REGS_REGISTER_AT(APB2LPENR, 0x64); REGS_REGISTER_AT(DCKCFGR2, 0x94); private: constexpr uint32_t Base() const { From 8592a0f6e833546d607b8fd3f5f5a9c930a46ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 8 Apr 2019 14:04:14 +0200 Subject: [PATCH 0238/1750] [ion] ShutdownClocks shutdown all clocks --- ion/src/device/n0100/drivers/board.cpp | 8 ++++++-- ion/src/device/n0101/drivers/board.cpp | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index 5002877ee..6d5f2652b 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -153,6 +153,12 @@ void shutdownClocks(bool keepLEDAwake) { // APB2 bus RCC.APB2ENR()->set(0x00008000); // Reset value + // AHB2 bus + RCC.AHB2ENR()->set(0); // Reset value + + // AHB3 bus + RCC.AHB3ENR()->set(0); // Reset value + // APB1 class RCC::APB1ENR apb1enr(0x00000400); // Reset value // AHB1 bus @@ -164,8 +170,6 @@ void shutdownClocks(bool keepLEDAwake) { } RCC.APB1ENR()->set(apb1enr); RCC.AHB1ENR()->set(ahb1enr); - - RCC.AHB3ENR()->setFSMCEN(false); } } diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 02fec8b2c..17214df07 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -230,6 +230,12 @@ 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 @@ -240,8 +246,6 @@ void shutdownClocks(bool keepLEDAwake) { } RCC.APB1ENR()->set(apb1enr); RCC.AHB1ENR()->set(ahb1enr); - - RCC.AHB3ENR()->setFSMCEN(false); } } From 4e2b668e896b09d98c2094db8a7a891581b715db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 8 Apr 2019 14:04:55 +0200 Subject: [PATCH 0239/1750] [ion] WakeUp: wake up on rising AND falling edge of VbusPin --- ion/src/device/shared/drivers/wakeup.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/shared/drivers/wakeup.cpp b/ion/src/device/shared/drivers/wakeup.cpp index c22804ae3..536d19276 100644 --- a/ion/src/device/shared/drivers/wakeup.cpp +++ b/ion/src/device/shared/drivers/wakeup.cpp @@ -36,9 +36,9 @@ void onUSBPlugging() { SYSCFG.EXTICR3()->setEXTI(USB::Config::VbusPin.pin(), USB::Config::VbusPin.group()); EXTI.EMR()->set(USB::Config::VbusPin.pin(), true); -#if EPSILON_LED_WHILE_CHARGING +//#if EPSILON_LED_WHILE_CHARGING EXTI.FTSR()->set(USB::Config::VbusPin.pin(), true); -#endif +//#endif EXTI.RTSR()->set(USB::Config::VbusPin.pin(), true); } From 63e7c20233879e8bb42379ac4bc85c1f7e1cc91b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 8 Apr 2019 14:05:24 +0200 Subject: [PATCH 0240/1750] [ion] Regs: fix PWR --- ion/src/device/shared/regs/pwr.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/shared/regs/pwr.h b/ion/src/device/shared/regs/pwr.h index 47f07fc69..d441febe1 100644 --- a/ion/src/device/shared/regs/pwr.h +++ b/ion/src/device/shared/regs/pwr.h @@ -15,9 +15,9 @@ public: REGS_BOOL_FIELD(LPDS, 0); REGS_BOOL_FIELD(PPDS, 1); REGS_BOOL_FIELD(FPDS, 9); + REGS_BOOL_FIELD(LPUDS, 10); // Called LPLVDS in N0100 + REGS_BOOL_FIELD(MRUDS, 11); // Called MRLVDS in N100 #if REGS_PWR_CONFIG_ADDITIONAL_FIELDS - REGS_BOOL_FIELD(LPUDS, 10); - REGS_BOOL_FIELD(MRUDS, 11); enum class Voltage { Scale3 = 0x01, Scale2 = 0x10, From 609d343da4d6d18779f84241b82f2f768176a959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 8 Apr 2019 14:07:34 +0200 Subject: [PATCH 0241/1750] [ion] Power: re-design suspend implementation --- ion/src/device/shared/drivers/power.cpp | 187 +++++++++++++++++++++--- ion/src/device/shared/drivers/power.h | 16 ++ 2 files changed, 183 insertions(+), 20 deletions(-) create mode 100644 ion/src/device/shared/drivers/power.h diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index accaa09c6..e2a2ab4ea 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -6,14 +6,172 @@ #include #include #include +#include namespace Ion { namespace Power { using namespace Device::Regs; +void configWakeUp() { + Device::WakeUp::onPowerKeyDown(); + Device::WakeUp::onUSBPlugging(); +#if EPSILON_LED_WHILE_CHARGING + Device::WakeUp::onChargingEvent(); +#endif +} + +void stopConfiguration() { + // This is done differently on the models + PWR.CR()->setMRUDS(true); // Main regulator in Low Voltage and Flash memory in Deep Sleep mode when the device is in Stop mode + PWR.CR()->setLPUDS(true); // Low-power regulator in under-drive mode if LPDS bit is set and Flash memory in power-down when the device is in Stop under-drive mode + PWR.CR()->setLPDS(true); // Low-power Voltage regulator on. Takes longer to wake up. + PWR.CR()->setFPDS(true); // Put the flash to sleep. Takes longer to wake up. +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS + PWR.CR()->setUDEN(PWR::CR::UnderDrive::Enable); +#endif + + CORTEX.SCR()->setSLEEPDEEP(true); + + Device::Board::shutdownPeripherals(); + + configWakeUp(); + + Device::Board::shutdownClocks(); +} + +void sleepConfiguration() { + // AHB1 peripheral clock enable in low-power mode register +#if REGS_RCC_CONFIG_F730 + class RCC::AHB1LPENR ahb1lpenr(0x7EF7B7FF); // Reset value +#elif REGS_RCC_CONFIG_F412 + class RCC::AHB1LPENR ahb1lpenr(0x006190FF); // Reset value +#endif + 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); +#if REGS_RCC_CONFIG_F730 + ahb1lpenr.setAXILPEN(false); + ahb1lpenr.setSRAM2LPEN(false); + ahb1lpenr.setBKPSRAMLPEN(false); + ahb1lpenr.setDTCMLPEN(false); + ahb1lpenr.setOTGHSLPEN(false); + ahb1lpenr.setOTGHSULPILPEN(false); +#endif + RCC.AHB1LPENR()->set(ahb1lpenr); + + // AHB2 peripheral clock enable in low-power mode register +#if REGS_RCC_CONFIG_F730 + class RCC::AHB2LPENR ahb2lpenr(0x000000F1); // Reset value +#elif REGS_RCC_CONFIG_F412 + class RCC::AHB2LPENR ahb2lpenr(0x000000C0); // Reset value +#endif + ahb2lpenr.setOTGFSLPEN(false); + ahb2lpenr.setRNGLPEN(false); +#if REGS_RCC_CONFIG_F730 + ahb2lpenr.setAESLPEN(false); +#endif + 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 +#if REGS_RCC_CONFIG_F730 + class RCC::APB1LPENR apb1lpenr(0xFFFFCBFF); // Reset value +#elif REGS_RCC_CONFIG_F412 + class RCC::APB1LPENR apb1lpenr(0x17E6CDFF); // Reset value +#endif + 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); +#if REGS_RCC_CONFIG_F730 + apb1lpenr.setLPTIM1LPEN(false); + apb1lpenr.setUSART4LPEN(false); + apb1lpenr.setUSART5LPEN(false); + apb1lpenr.setOTGHSLPEN(false); + apb1lpenr.setOTGHSULPILPEN(false); +#elif REGS_RCC_CONFIG_F412 + apb1lpenr.setI2CFMP1LPEN(false); + apb1lpenr.setCAN2LPEN(false); +#endif + RCC.APB1LPENR()->set(apb1lpenr); + + // APB2 peripheral clock enable in low-power mode register +#if REGS_RCC_CONFIG_F730 + class RCC::APB2LPENR apb2lpenr(0x04F77F33); // Reset value +#elif REGS_RCC_CONFIG_F412 + class RCC::APB2LPENR apb2lpenr(0x0117F933); // Reset value +#endif + 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); +#if REGS_RCC_CONFIG_F730 + apb2lpenr.setSDMMC2LPEN(false); + apb2lpenr.setADC2LPEN(false); + apb2lpenr.setADC3LPEN(false); + apb2lpenr.setSAI1LPEN(false); + apb2lpenr.setSAI2LPEN(false); +#elif REGS_RCC_CONFIG_F412 + apb2lpenr.setSDIOLPEN(false); + apb2lpenr.setEXTITEN(false); + apb2lpenr.setDFSDM1LPEN(false); +#endif + RCC.APB2LPENR()->set(apb2lpenr); + + CORTEX.SCR()->setSLEEPDEEP(false); + + Device::Board::shutdownPeripherals(true); + + configWakeUp(); + + Device::Board::shutdownClocks(true); +} + void suspend(bool checkIfPowerKeyReleased) { bool isLEDActive = Ion::LED::getColor() != KDColorBlack; + bool isPlugged = USB::isPlugged(); + if (checkIfPowerKeyReleased) { /* Wait until power is released to avoid restarting just after suspending */ bool isPowerDown = true; @@ -22,34 +180,23 @@ void suspend(bool checkIfPowerKeyReleased) { isPowerDown = scan.keyDown(Keyboard::Key::B2); } } - Device::Board::shutdownPeripherals(isLEDActive); - // This is done differently on the models - PWR.CR()->setLPDS(true); // Turn the regulator off. Takes longer to wake up. - PWR.CR()->setFPDS(true); // Put the flash to sleep. Takes longer to wake up. -#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS - PWR.CR()->setLPUDS(true); - PWR.CR()->setUDEN(PWR::CR::UnderDrive::Enable); -#endif - - CORTEX.SCR()->setSLEEPDEEP(!isLEDActive); while (1) { + // Configure low-power mode + if (isLEDActive) { + sleepConfiguration(); + } else { + stopConfiguration(); + } #if EPSILON_LED_WHILE_CHARGING /* Update LEDS * if the standby mode was stopped due to a "stop charging" event, we wait * a while to be sure that the plug state of the USB is up-to-date. */ msleep(200); - //Ion::LED::setCharging(Ion::USB::isPlugged(), Ion::Battery::isCharging()); + Ion::LED::setCharging(Ion::USB::isPlugged(), Ion::Battery::isCharging()); #endif - Device::WakeUp::onPowerKeyDown(); - Device::WakeUp::onUSBPlugging(); -#if EPSILON_LED_WHILE_CHARGING - Device::WakeUp::onChargingEvent(); -#endif - - Device::Board::shutdownClocks(isLEDActive); /* To enter sleep, we need to issue a WFE instruction, which waits for the * event flag to be set and then clears it. However, the event flag might @@ -65,13 +212,13 @@ void suspend(bool checkIfPowerKeyReleased) { Device::Keyboard::init(); Keyboard::State scan = Keyboard::scan(); - Device::Keyboard::shutdown(); Ion::Keyboard::State OnlyPowerKeyDown = Keyboard::State(Keyboard::Key::B2); - if (scan == OnlyPowerKeyDown || USB::isPlugged()) { + if (scan == OnlyPowerKeyDown || (!isPlugged && USB::isPlugged())) { // Wake up break; } + isPlugged = USB::isPlugged(); } Device::Board::initClocks(); diff --git a/ion/src/device/shared/drivers/power.h b/ion/src/device/shared/drivers/power.h new file mode 100644 index 000000000..dc63fe525 --- /dev/null +++ b/ion/src/device/shared/drivers/power.h @@ -0,0 +1,16 @@ +#ifndef ION_DEVICE_SHARED_POWER_H +#define ION_DEVICE_SHARED_POWER_H + +namespace Ion { +namespace Device { +namespace Power { + +void stopConfiguration(); +void sleepConfiguration(); +//void standby(); + +} +} +} + +#endif From d9daeef62bec05377a940f52d098585089f979f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 8 Apr 2019 15:21:13 +0200 Subject: [PATCH 0242/1750] [ion] initClock: set Voltage scale after enabling PLL --- ion/src/device/n0101/drivers/board.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 17214df07..6469d91be 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -139,9 +139,6 @@ void initClocks() { // Enable PWR peripheral clock RCC.APB1ENR()->setPWREN(true); - // Choose Voltage scale 1 - PWR.CR()->setVOS(PWR::CR::Voltage::Scale1); - //while (!PWR.CSR1()->getVOSRDY()) {} /* 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 @@ -156,6 +153,7 @@ void initClocks() { // Enable the PLL and wait for it to be ready RCC.CR()->setPLLON(true); + // Enable Over-drive PWR.CR()->setODEN(true); while(!PWR.CSR1()->getODRDY()) { } @@ -164,6 +162,10 @@ void initClocks() { while(!PWR.CSR1()->getODSWRDY()) { } + // Choose Voltage scale 1 + PWR.CR()->setVOS(PWR::CR::Voltage::Scale1); + while (!PWR.CSR1()->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. From be419c50ee682973fb3e1dd2f5927328b1baa507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 8 Apr 2019 15:27:30 +0200 Subject: [PATCH 0243/1750] [ion] board: define a normal frequency --- ion/src/device/shared/drivers/board.cpp | 2 ++ ion/src/device/shared/drivers/power.cpp | 9 +++++++++ ion/src/device/shared/drivers/timing.cpp | 11 ++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index aeb49969d..d99616c62 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -17,6 +17,8 @@ namespace Board { using namespace Regs; +Frequency sNormalFrequency = Frequency::High; + void shutdown() { shutdownPeripherals(); shutdownClocks(); diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index e2a2ab4ea..381994592 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -9,6 +9,15 @@ #include namespace Ion { + +namespace Device { +namespace Board { + +extern Device::Board::Frequency sNormalFrequency; + +} +} + namespace Power { using namespace Device::Regs; diff --git a/ion/src/device/shared/drivers/timing.cpp b/ion/src/device/shared/drivers/timing.cpp index 2c19b5d8b..399dc0f2d 100644 --- a/ion/src/device/shared/drivers/timing.cpp +++ b/ion/src/device/shared/drivers/timing.cpp @@ -4,6 +4,15 @@ #include namespace Ion { + +namespace Device { +namespace Board { + +extern Device::Board::Frequency sNormalFrequency; + +} +} + namespace Timing { using namespace Device::Timing; @@ -18,7 +27,7 @@ void msleep(uint32_t ms) { for (volatile uint32_t i=0; i Date: Mon, 8 Apr 2019 15:28:00 +0200 Subject: [PATCH 0244/1750] [ion] Sleep mode: decrease normal frequency, disable over-drive, decrease voltage scale --- ion/src/device/shared/drivers/power.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 381994592..fbeb5848d 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -50,6 +50,20 @@ void stopConfiguration() { } void sleepConfiguration() { + // Decrease HCLK frequency + Device::Board::sNormalFrequency = Device::Board::Frequency::Low; + Device::Board::setClockFrequency(Device::Board::sNormalFrequency); + + // Disable over-drive + PWR.CR()->setODSWEN(false); + while(!PWR.CSR1()->getODSWRDY()) { + } + PWR.CR()->setODEN(true); + + // Choose Voltage scale 3 + PWR.CR()->setVOS(PWR::CR::Voltage::Scale3); + while (!PWR.CSR1()->getVOSRDY()) {} + // AHB1 peripheral clock enable in low-power mode register #if REGS_RCC_CONFIG_F730 class RCC::AHB1LPENR ahb1lpenr(0x7EF7B7FF); // Reset value @@ -229,6 +243,8 @@ void suspend(bool checkIfPowerKeyReleased) { } isPlugged = USB::isPlugged(); } + + Device::Board::sNormalFrequency = Device::Board::Frequency::High; Device::Board::initClocks(); Device::Board::initPeripherals(); From d737a76acc231b0440a0fb47b3f9fcf7c15cbbce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 5 Apr 2019 14:29:55 +0200 Subject: [PATCH 0245/1750] [bench] Sleep, Stop and Stanby commands --- ion/src/device/bench/Makefile | 4 +++- ion/src/device/bench/bench.cpp | 4 +++- ion/src/device/bench/command/command.h | 4 +++- .../bench/command/{suspend.cpp => sleep.cpp} | 4 ++-- ion/src/device/bench/command/standby.cpp | 22 +++++++++++++++++++ ion/src/device/bench/command/stop.cpp | 22 +++++++++++++++++++ 6 files changed, 55 insertions(+), 5 deletions(-) rename ion/src/device/bench/command/{suspend.cpp => sleep.cpp} (70%) create mode 100644 ion/src/device/bench/command/standby.cpp create mode 100644 ion/src/device/bench/command/stop.cpp diff --git a/ion/src/device/bench/Makefile b/ion/src/device/bench/Makefile index 7c48c245a..b9c0a4e3e 100644 --- a/ion/src/device/bench/Makefile +++ b/ion/src/device/bench/Makefile @@ -18,7 +18,9 @@ bench_src += $(addprefix ion/src/device/bench/command/, \ ping.cpp \ print.cpp \ reset.cpp \ - suspend.cpp \ + sleep.cpp \ + stop.cpp \ + standby.cpp \ vblank.cpp \ write_external.cpp \ ) diff --git a/ion/src/device/bench/bench.cpp b/ion/src/device/bench/bench.cpp index 41ed2cf61..e2aa49542 100644 --- a/ion/src/device/bench/bench.cpp +++ b/ion/src/device/bench/bench.cpp @@ -20,7 +20,9 @@ constexpr CommandHandler handles[] = { CommandHandler("PING", Command::Ping), CommandHandler("PRINT", Command::Print), CommandHandler("RESET", Command::Reset), - CommandHandler("SUSPEND", Command::Suspend), + CommandHandler("SLEEP", Command::Sleep), + CommandHandler("STOP", Command::Stop), + CommandHandler("STANDBY", Command::Standby), CommandHandler("VBLANK", Command::VBlank), CommandHandler("WRITE_EXTERNAL", Command::WriteExternal), CommandHandler(nullptr, nullptr) diff --git a/ion/src/device/bench/command/command.h b/ion/src/device/bench/command/command.h index 4c2048570..4dad0476a 100644 --- a/ion/src/device/bench/command/command.h +++ b/ion/src/device/bench/command/command.h @@ -21,7 +21,9 @@ void MCUSerial(const char * input); void Ping(const char * input); void Print(const char * input); void Reset(const char * input); -void Suspend(const char * input); +void Sleep(const char * input); +void Stop(const char * input); +void Standby(const char * input); void VBlank(const char * input); void WriteExternal(const char * input); diff --git a/ion/src/device/bench/command/suspend.cpp b/ion/src/device/bench/command/sleep.cpp similarity index 70% rename from ion/src/device/bench/command/suspend.cpp rename to ion/src/device/bench/command/sleep.cpp index a494501c0..76f51c019 100644 --- a/ion/src/device/bench/command/suspend.cpp +++ b/ion/src/device/bench/command/sleep.cpp @@ -6,14 +6,14 @@ namespace Device { namespace Bench { namespace Command { -void Suspend(const char * input) { +void Sleep(const char * input) { if (input != nullptr) { reply(sSyntaxError); return; } reply(sOK); Ion::Timing::msleep(100); - Ion::Power::suspend(); + //Ion::Power::sleep(); TODO Decomment once the method exists } } diff --git a/ion/src/device/bench/command/standby.cpp b/ion/src/device/bench/command/standby.cpp new file mode 100644 index 000000000..486313706 --- /dev/null +++ b/ion/src/device/bench/command/standby.cpp @@ -0,0 +1,22 @@ +#include "command.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Standby(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + reply(sOK); + Ion::Timing::msleep(100); + //Ion::Power::standby(); TODO Decomment once the method exists +} + +} +} +} +} diff --git a/ion/src/device/bench/command/stop.cpp b/ion/src/device/bench/command/stop.cpp new file mode 100644 index 000000000..6a310a1e3 --- /dev/null +++ b/ion/src/device/bench/command/stop.cpp @@ -0,0 +1,22 @@ +#include "command.h" +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +void Stop(const char * input) { + if (input != nullptr) { + reply(sSyntaxError); + return; + } + reply(sOK); + Ion::Timing::msleep(100); + Ion::Power::suspend(); //Ion::Power::stop(); TODO Decomment once the method exists +} + +} +} +} +} From 12aaa8c8a5886a841fd7f83ab6b9f61a597e3e2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 8 Apr 2019 17:14:14 +0200 Subject: [PATCH 0246/1750] [escher] Decomment waitForVBlank --- escher/src/window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/escher/src/window.cpp b/escher/src/window.cpp index 1b94c65c9..618b6fba4 100644 --- a/escher/src/window.cpp +++ b/escher/src/window.cpp @@ -13,7 +13,7 @@ void Window::redraw(bool force) { if (force) { markRectAsDirty(bounds()); } - //Ion::Display::waitForVBlank(); + Ion::Display::waitForVBlank(); View::redraw(bounds()); } From 46c8b3b480645bfbec838f24457939d85a2b269b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 8 Apr 2019 17:56:12 +0200 Subject: [PATCH 0247/1750] [scripts] Target to build an internal and an external .bin --- scripts/targets.device.mak | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/targets.device.mak b/scripts/targets.device.mak index b65af8a2c..e57c2d42a 100644 --- a/scripts/targets.device.mak +++ b/scripts/targets.device.mak @@ -69,3 +69,9 @@ else $(BUILD_DIR)/bench.$(EXE): @echo "Error: bench.bin requires EPSILON_DEVICE_BENCH=1 EPSILON_USB_DFU_XIP=1" endif + +.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 .data.external $< $(basename $<).external.bin + $(Q) $(OBJCOPY) -O binary -j .text.internal -j .rodata.internal -j .data.internal $< $(basename $<).internal.bin From 0fcd26e11f308dc5f19b726e5e339ee2e7adb410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 8 Apr 2019 17:33:15 +0200 Subject: [PATCH 0248/1750] [ion] Fix comments on Keyboard --- .../device/n0100/drivers/config/keyboard.h | 24 +++++++++++ .../device/n0101/drivers/config/keyboard.h | 40 ++++++++++++++----- ion/src/device/shared/drivers/keyboard.cpp | 3 +- 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/ion/src/device/n0100/drivers/config/keyboard.h b/ion/src/device/n0100/drivers/config/keyboard.h index 4c55b8729..d51673dd5 100644 --- a/ion/src/device/n0100/drivers/config/keyboard.h +++ b/ion/src/device/n0100/drivers/config/keyboard.h @@ -20,8 +20,32 @@ * PE6 | Keyboard row G | Output, open drain * PE7 | Keyboard row H | Output, open drain * PE8 | 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_B2 | | | | | + * -+------+------+------+------+------+------+ + * | 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 { diff --git a/ion/src/device/n0101/drivers/config/keyboard.h b/ion/src/device/n0101/drivers/config/keyboard.h index cf8a23bef..099ef5a60 100644 --- a/ion/src/device/n0101/drivers/config/keyboard.h +++ b/ion/src/device/n0101/drivers/config/keyboard.h @@ -12,15 +12,37 @@ * PC3 | Keyboard column 4 | Input, pulled up * PC4 | Keyboard column 5 | Input, pulled up * PC5 | Keyboard column 6 | Input, pulled up - * PE0 | Keyboard row A | Output, open drain - * PE1 | Keyboard row B | Output, open drain - * PE2 | Keyboard row C | Output, open drain - * PE3 | Keyboard row D | Output, open drain - * PE4 | Keyboard row E | Output, open drain - * PE5 | Keyboard row F | Output, open drain - * PE6 | Keyboard row G | Output, open drain - * PE7 | Keyboard row H | Output, open drain - * PE8 | Keyboard row I | Output, open drain + * 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 { diff --git a/ion/src/device/shared/drivers/keyboard.cpp b/ion/src/device/shared/drivers/keyboard.cpp index aa5e3d480..1b99fae23 100644 --- a/ion/src/device/shared/drivers/keyboard.cpp +++ b/ion/src/device/shared/drivers/keyboard.cpp @@ -3,12 +3,13 @@ * The job of this code is to implement the "ion_key_state" function. * * The keyboard is a matrix that is laid out as follow: + * (K_B2 and K_B3 are respectively specific to N0100 and N0101 * * | PC0 | PC1 | PC2 | PC3 | PC4 | PC5 | * -----+------+------+------+------+------+------+ * PE0 | K_A1 | K_A2 | K_A3 | K_A4 | K_A5 | K_A6 | * -----+------+------+------+------+------+------+ - * PE1 | K_B1 | K_B2 | | | | | + * PE1 | K_B1 |(K_B2)|(K_B3)| | | | * -----+------+------+------+------+------+------+ * PE2 | K_C1 | K_C2 | K_C3 | K_C4 | K_C5 | K_C6 | * -----+------+------+------+------+------+------+ From 7ce343296338ed21b35ca6047d424f1021fcc526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 8 Apr 2019 17:33:29 +0200 Subject: [PATCH 0249/1750] [ion] TEMPO: fix PowerKey (B2 for N0100 and B3 for N0101) --- apps/hardware_test/keyboard_view.cpp | 2 +- ion/include/ion/events.h | 2 +- ion/include/ion/keyboard.h | 9 ++++++++- ion/src/device/n0100/drivers/config/keyboard.h | 7 +++++-- ion/src/device/n0101/drivers/config/keyboard.h | 9 +++++---- ion/src/device/shared/drivers/keyboard.cpp | 9 ++------- ion/src/device/shared/drivers/power.cpp | 4 ++-- ion/src/device/shared/drivers/wakeup.cpp | 2 +- ion/src/shared/events.cpp | 3 ++- 9 files changed, 27 insertions(+), 20 deletions(-) diff --git a/apps/hardware_test/keyboard_view.cpp b/apps/hardware_test/keyboard_view.cpp index 8ffc042c0..41c2cb694 100644 --- a/apps/hardware_test/keyboard_view.cpp +++ b/apps/hardware_test/keyboard_view.cpp @@ -46,7 +46,7 @@ void KeyboardView::drawKey(int keyIndex, KDContext * ctx, KDRect rect) const { ctx->fillRect(KDRect(x, y, k_bigSquareSize, k_bigSquareSize), color); } /* the key is a "home" or "power" */ - if ((uint8_t)key >= 6 && (uint8_t)key < 8) { + if ((uint8_t)key >= 6 && (uint8_t)key <= 8) { KDCoordinate x = 3*k_margin + 3*k_smallSquareSize; KDCoordinate y = (uint8_t)key == 6 ? k_margin : 2*k_margin + k_bigRectHeight; ctx->fillRect(KDRect(x, y, k_bigRectWidth, k_bigRectHeight), color); diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 3ecbc5d2f..19c14f25e 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -67,7 +67,7 @@ constexpr Event OK = Event::PlainKey(Keyboard::Key::A5); constexpr Event Back = Event::PlainKey(Keyboard::Key::A6); constexpr Event Home = Event::PlainKey(Keyboard::Key::B1); -constexpr Event OnOff = Event::PlainKey(Keyboard::Key::B2); +constexpr Event OnOff = Event::PlainKey(Keyboard::PowerKey); constexpr Event Shift = Event::PlainKey(Keyboard::Key::C1); constexpr Event Alpha = Event::PlainKey(Keyboard::Key::C2); diff --git a/ion/include/ion/keyboard.h b/ion/include/ion/keyboard.h index 0bfa662bd..8e1d1cf74 100644 --- a/ion/include/ion/keyboard.h +++ b/ion/include/ion/keyboard.h @@ -22,8 +22,15 @@ enum class Key : uint8_t { None = 54 }; +#define MODEL_N0101 1 +#ifdef MODEL_N0101 +constexpr Key PowerKey = Key::B3; +#else +constexpr Key PowerKey = Key::B2; +#endif + constexpr Key ValidKeys[] = { - Key::A1, Key::A2, Key::A3, Key::A4, Key::A5, Key::A6, Key::B1, Key::B2, + Key::A1, Key::A2, Key::A3, Key::A4, Key::A5, Key::A6, Key::B1, PowerKey, Key::C1, Key::C2, Key::C3, Key::C4, Key::C5, Key::C6, Key::D1, Key::D2, Key::D3, Key::D4, Key::D5, Key::D6, Key::E1, Key::E2, Key::E3, Key::E4, Key::E5, Key::E6, Key::F1, Key::F2, Key::F3, Key::F4, Key::F5, Key::G1, diff --git a/ion/src/device/n0100/drivers/config/keyboard.h b/ion/src/device/n0100/drivers/config/keyboard.h index d51673dd5..e958696c0 100644 --- a/ion/src/device/n0100/drivers/config/keyboard.h +++ b/ion/src/device/n0100/drivers/config/keyboard.h @@ -61,8 +61,11 @@ constexpr GPIO ColumnGPIO = GPIOC; constexpr uint8_t numberOfColumns = 6; constexpr uint8_t ColumnPins[numberOfColumns] = {0, 1, 2, 3, 4, 5}; -inline uint64_t cheat(uint64_t state) { - return state; +/* Undefined keys numbers are: 8, 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 & 0x1F7DF7FFFFF0FF; } } diff --git a/ion/src/device/n0101/drivers/config/keyboard.h b/ion/src/device/n0101/drivers/config/keyboard.h index 099ef5a60..8037718b5 100644 --- a/ion/src/device/n0101/drivers/config/keyboard.h +++ b/ion/src/device/n0101/drivers/config/keyboard.h @@ -60,10 +60,11 @@ constexpr GPIO ColumnGPIO = GPIOC; constexpr uint8_t numberOfColumns = 6; constexpr uint8_t ColumnPins[numberOfColumns] = {0, 1, 2, 3, 4, 5}; -/* The key B2 is actually on B3 in hardware on model N0101. To avoid some extra - * work, we switch B2 and B3 bit in the bit array 'state'. */ -inline uint64_t cheat(uint64_t state) { - return state & Ion::Keyboard::State(Ion::Keyboard::Key::B3) ? state | Ion::Keyboard::State(Ion::Keyboard::Key::B2) : state; +/* 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; } } diff --git a/ion/src/device/shared/drivers/keyboard.cpp b/ion/src/device/shared/drivers/keyboard.cpp index 1b99fae23..b3751d0e1 100644 --- a/ion/src/device/shared/drivers/keyboard.cpp +++ b/ion/src/device/shared/drivers/keyboard.cpp @@ -63,13 +63,8 @@ State scan() { state = (state << 6) | (~columns & 0x3F); } - // On model N0101, the B2 key is located on B3. We cheat here to avoid extra re-work. - state = Config::cheat(state); - - /* Last but not least, keys number 8, 9, 10, 11, 35, 41, 47 and 53 are not - * defined. Therefore we want to make sure those bits are forced to zero in - * whatever value we return. */ - state = state & 0x1F7DF7FFFFF0FF; + // Make sure undefined key bits are forced to zero in the return value + state = Config::ValidKeys(state); return State(state); } diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index fbeb5848d..1849def1b 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -200,7 +200,7 @@ void suspend(bool checkIfPowerKeyReleased) { bool isPowerDown = true; while (isPowerDown) { Keyboard::State scan = Keyboard::scan(); - isPowerDown = scan.keyDown(Keyboard::Key::B2); + isPowerDown = scan.keyDown(Keyboard::PowerKey); } } @@ -236,7 +236,7 @@ void suspend(bool checkIfPowerKeyReleased) { Device::Keyboard::init(); Keyboard::State scan = Keyboard::scan(); - Ion::Keyboard::State OnlyPowerKeyDown = Keyboard::State(Keyboard::Key::B2); + Ion::Keyboard::State OnlyPowerKeyDown = Keyboard::State(Keyboard::PowerKey); if (scan == OnlyPowerKeyDown || (!isPlugged && USB::isPlugged())) { // Wake up break; diff --git a/ion/src/device/shared/drivers/wakeup.cpp b/ion/src/device/shared/drivers/wakeup.cpp index 536d19276..85d25c6fb 100644 --- a/ion/src/device/shared/drivers/wakeup.cpp +++ b/ion/src/device/shared/drivers/wakeup.cpp @@ -44,7 +44,7 @@ void onUSBPlugging() { void onPowerKeyDown() { - Keyboard::Key key = Keyboard::Key::B2; + Keyboard::Key key = Keyboard::PowerKey; uint8_t rowPin = Keyboard::Config::RowPins[Keyboard::rowForKey(key)]; Keyboard::Config::RowGPIO.MODER()->setMode(rowPin, GPIO::MODER::Mode::Output); Keyboard::Config::RowGPIO.OTYPER()->setType(rowPin, GPIO::OTYPER::Type::OpenDrain); diff --git a/ion/src/shared/events.cpp b/ion/src/shared/events.cpp index b5300ec37..d4b413ded 100644 --- a/ion/src/shared/events.cpp +++ b/ion/src/shared/events.cpp @@ -47,7 +47,8 @@ static constexpr const char k_arcTangent[8] = {'a', 't', 'a', 'n', '(', Ion::Cha static constexpr EventData s_dataForEvent[4*Event::PageSize] = { // Plain TL(), TL(), TL(), TL(), TL(), TL(), - TL(), TL(), U(), U(), U(), U(), + // Warning: B2 and B3 keys don't simultaneously exist in the same model but we define both here to avoid duplicating implementation of s_dataForEvent + TL(), TL(), TL(), U(), U(), U(), TL(), TL(), TL(), TL(), TL(), TL(), T(k_exponential), T(k_naperianLogarithm), T(k_logarithm), T(k_complexI), T(","), T("^"), T(k_sine), T(k_cosine), T(k_tangent), T(k_pi), T(k_root), T("^2"), From 488652f790d472dd0213674da8bea3483c8b9876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 9 Apr 2019 11:37:29 +0200 Subject: [PATCH 0250/1750] [ion] N0100: discard Bench for this model --- ion/src/device/n0100/drivers/board.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index 6d5f2652b..f397932ea 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -37,17 +37,7 @@ void init() { GPIO(g).PUPDR()->set(0x00000000); // All to "None" } -#if EPSILON_DEVICE_BENCH - bool consolePeerConnectedOnBoot = Console::peerConnected(); -#endif - initPeripherals(); - -#if EPSILON_DEVICE_BENCH - if (consolePeerConnectedOnBoot) { - Ion::Device::Bench::run(); - } -#endif } void initClocks() { @@ -135,9 +125,6 @@ void initClocks() { // We're using TIM3 for the LEDs RCC.APB1ENR()->setTIM3EN(true); RCC.APB1ENR()->setPWREN(true); -#if EPSILON_DEVICE_BENCH - RCC.APB1ENR()->setUSART3EN(true); -#endif // APB2 bus class RCC::APB2ENR apb2enr(0x00008000); // Reset value From 0d04733076b3b7ad56321977cf7ade4bb7f85e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 9 Apr 2019 11:38:46 +0200 Subject: [PATCH 0251/1750] [ion] Fix clocks for N0100 and N0101 --- ion/src/device/n0100/drivers/board.cpp | 3 ++- ion/src/device/n0100/drivers/config/clocks.h | 26 +++++++++++++------- ion/src/device/n0101/drivers/config/clocks.h | 19 ++++++++------ ion/src/device/shared/drivers/board.cpp | 6 ++--- ion/src/device/shared/drivers/power.cpp | 2 +- 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index f397932ea..52e778e0f 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -80,7 +81,7 @@ void initClocks() { RCC.PLLCFGR()->setPLLQ(Clocks::Config::PLL_Q); RCC.PLLCFGR()->setPLLSRC(RCC::PLLCFGR::PLLSRC::HSE); // 96 MHz is too fast for APB1. Divide it by two to reach 48 MHz - RCC.CFGR()->setPPRE1(Clocks::Config::APBP1rescaler); + RCC.CFGR()->setPPRE1(Clocks::Config::APB1PrescalerRegs); /* If you want to considerably slow down the whole machine uniformely, which * can be very useful to diagnose performance issues, just uncomment the line diff --git a/ion/src/device/n0100/drivers/config/clocks.h b/ion/src/device/n0100/drivers/config/clocks.h index acba1a15e..d883a9d22 100644 --- a/ion/src/device/n0100/drivers/config/clocks.h +++ b/ion/src/device/n0100/drivers/config/clocks.h @@ -20,17 +20,25 @@ namespace Config { * 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 = 25; +constexpr static int HSE = 25; constexpr static int PLL_M = 25; constexpr static int PLL_Q = 4; -//constexpr static int PLL_N = 192; -//constexpr static Regs::RCC::PLLCFGR::PLLP PLL_P_Reg = Regs::RCC::PLLCFGR::PLLP::PLLP2; -//constexpr static int PLL_P = 2*(int)PLL_P_Reg; -//constexpr static int SYSCLKFrequency = ((HSE/PLL_M)*PLL_N)/PLL_P; -//constexpr static int AHB_prescaler = 1; -//constexpr static int HCLKFrequency = SYSCLKFrequency/AHB_prescaler; -//constexpr static int AHBFrequency = HCLKFrequency; -constexpr static int APB1Prescaler = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy2; +constexpr static int PLL_N = 192; +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 SYSCLKFrequency = ((HSE/PLL_M)*PLL_N)/PLL_P; +constexpr static int AHB_prescaler = 1; +constexpr static int HCLKFrequency = SYSCLKFrequency/AHB_prescaler; +constexpr static int AHBFrequency = HCLKFrequency; +constexpr static Regs::RCC::CFGR::APBPrescaler APB1PrescalerRegs = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy2; +constexpr static int APB1Prescaler = 2; +constexpr static int APB1Frequency = HCLKFrequency/APB1Prescaler; +constexpr static int APB1TimerFrequency = 2*APB1Frequency; +/* 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 96MHz/14.2MHz~6.7. */ +constexpr static Regs::RCC::CFGR::AHBPrescaler AHBLowFrequencyPrescaler = Regs::RCC::CFGR::AHBPrescaler::SysClkDividedBy4; } } diff --git a/ion/src/device/n0101/drivers/config/clocks.h b/ion/src/device/n0101/drivers/config/clocks.h index 3474b3046..1317d3af7 100644 --- a/ion/src/device/n0101/drivers/config/clocks.h +++ b/ion/src/device/n0101/drivers/config/clocks.h @@ -27,15 +27,18 @@ constexpr static Regs::RCC::PLLCFGR::PLLP PLL_P_Reg = Regs::RCC::PLLCFGR::PLLP:: 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 AHB_prescaler = 1; -constexpr static int HCLKFrequency = SYSCLKFrequency/AHB_prescaler; +constexpr static int AHBPrescaler = 1; +constexpr static int HCLKFrequency = SYSCLKFrequency/AHBPrescaler; constexpr static int AHBFrequency = HCLKFrequency; -constexpr static int LoopsPerMillisecond = 4811; -constexpr static int LoopsPerMicrosecond = 38; -// CPU clock is 192 MHz, and systick clock source is divided by 8 -// To get 1 ms systick overflow we need to reset it to -// 192 000 000 (Hz) / 8 / 1 000 (ms/s) -constexpr static int SysTickPerMillisecond = 24000; +constexpr static int APB1Prescaler = 4; +constexpr static int APB1Frequency = HCLKFrequency/APB1Prescaler; +constexpr static int APB1TimerFrequency = 2*APB1Frequency; + +/* 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 AHBLowFrequencyPrescaler = Regs::RCC::CFGR::AHBPrescaler::SysClkDividedBy8; } } diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index d99616c62..2947aaef3 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace Ion { namespace Device { @@ -66,10 +67,7 @@ void setClockFrequency(Frequency f) { return; default: assert(f == Frequency::Low); - /* 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.5MHz. */ - RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy8); + RCC.CFGR()->setHPRE(Clocks::Config::AHBLowFrequencyPrescaler); return; } } diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 1849def1b..10cc286cf 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -52,7 +52,7 @@ void stopConfiguration() { void sleepConfiguration() { // Decrease HCLK frequency Device::Board::sNormalFrequency = Device::Board::Frequency::Low; - Device::Board::setClockFrequency(Device::Board::sNormalFrequency); + Device::Board::setClockFrequency(Device::Board::Frequency::Low); // Disable over-drive PWR.CR()->setODSWEN(false); From 67f59529022d8257756357face493400a2cc3d13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 9 Apr 2019 11:39:15 +0200 Subject: [PATCH 0252/1750] [ion] Fix rcc configuration for N0100 --- ion/src/device/n0100/regs/config/rcc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/n0100/regs/config/rcc.h b/ion/src/device/n0100/regs/config/rcc.h index 960becbb0..3d660699b 100644 --- a/ion/src/device/n0100/regs/config/rcc.h +++ b/ion/src/device/n0100/regs/config/rcc.h @@ -1,7 +1,7 @@ #ifndef ION_DEVICE_N0100_REGS_CONFIG_RCC_H #define ION_DEVICE_N0100_REGS_CONFIG_RCC_H -#if REGS_RCC_CONFIG_F730 0 -#if REGS_RCC_CONFIG_F412 1 +#define REGS_RCC_CONFIG_F730 0 +#define REGS_RCC_CONFIG_F412 1 #endif From 7a8238b82b3f095ab0dc37e5f6bc8aa05fbc0b16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 9 Apr 2019 11:39:39 +0200 Subject: [PATCH 0253/1750] [ion] Power: add macro to fix build of N0100 model --- ion/src/device/shared/drivers/power.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 10cc286cf..2fe03f9d1 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -54,6 +54,7 @@ void sleepConfiguration() { Device::Board::sNormalFrequency = Device::Board::Frequency::Low; Device::Board::setClockFrequency(Device::Board::Frequency::Low); +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS // Disable over-drive PWR.CR()->setODSWEN(false); while(!PWR.CSR1()->getODSWRDY()) { @@ -63,6 +64,7 @@ void sleepConfiguration() { // Choose Voltage scale 3 PWR.CR()->setVOS(PWR::CR::Voltage::Scale3); while (!PWR.CSR1()->getVOSRDY()) {} +#endif // AHB1 peripheral clock enable in low-power mode register #if REGS_RCC_CONFIG_F730 From a455df79f63bb6e4bf0b476ea4c13e3b02d9ab23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Tue, 9 Apr 2019 13:58:51 +0200 Subject: [PATCH 0254/1750] [ion] N0100: discard outdated comment --- ion/src/device/n0100/drivers/board.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index 52e778e0f..024d7a68e 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -83,12 +83,6 @@ void initClocks() { // 96 MHz is too fast for APB1. Divide it by two to reach 48 MHz RCC.CFGR()->setPPRE1(Clocks::Config::APB1PrescalerRegs); - /* If you want to considerably slow down the whole machine uniformely, which - * can be very useful to diagnose performance issues, just uncomment the line - * below. Note that even booting takes a few seconds, so don't be surprised - * if the screen is black for a short while upon booting. */ - // RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClkDividedBy128); - // Enable the PLL and wait for it to be ready RCC.CR()->setPLLON(true); while(!RCC.CR()->getPLLRDY()) { From deba6ca581ba567fb2824d3b386bbcda5aa10339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Mon, 8 Apr 2019 17:56:12 +0200 Subject: [PATCH 0255/1750] [scripts] Target to build an internal and an external .bin --- scripts/targets.device.mak | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/targets.device.mak b/scripts/targets.device.mak index e57c2d42a..38de0467b 100644 --- a/scripts/targets.device.mak +++ b/scripts/targets.device.mak @@ -73,5 +73,5 @@ endif .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 .data.external $< $(basename $<).external.bin - $(Q) $(OBJCOPY) -O binary -j .text.internal -j .rodata.internal -j .data.internal $< $(basename $<).internal.bin + $(Q) $(OBJCOPY) -O binary -j .text.external -j .rodata.external $< $(basename $<).external.bin + $(Q) $(OBJCOPY) -O binary -j .isr_vector_table -j .header -j .text.internal -j .rodata.internal -j .init_array -j .data $< $(basename $<).internal.bin From 9b6c6e9092e6b8eaeae0f2baa799675e9ddba47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 9 Apr 2019 15:25:51 +0200 Subject: [PATCH 0256/1750] [ion/bench] CRC command --- ion/src/device/bench/Makefile | 1 + ion/src/device/bench/bench.cpp | 1 + ion/src/device/bench/command/command.cpp | 11 +++++ ion/src/device/bench/command/command.h | 2 + ion/src/device/bench/command/crc.cpp | 57 ++++++++++++++++++++++++ 5 files changed, 72 insertions(+) create mode 100644 ion/src/device/bench/command/crc.cpp diff --git a/ion/src/device/bench/Makefile b/ion/src/device/bench/Makefile index b9c0a4e3e..9e9f1b7e6 100644 --- a/ion/src/device/bench/Makefile +++ b/ion/src/device/bench/Makefile @@ -9,6 +9,7 @@ bench_src += $(addprefix ion/src/device/bench/command/, \ adc.cpp \ backlight.cpp \ charge.cpp \ + crc.cpp \ command.cpp \ display.cpp \ exit.cpp \ diff --git a/ion/src/device/bench/bench.cpp b/ion/src/device/bench/bench.cpp index e2aa49542..20176777f 100644 --- a/ion/src/device/bench/bench.cpp +++ b/ion/src/device/bench/bench.cpp @@ -12,6 +12,7 @@ constexpr CommandHandler handles[] = { CommandHandler("ADC", Command::ADC), CommandHandler("BACKLIGHT", Command::Backlight), CommandHandler("CHARGE", Command::Charge), + CommandHandler("CRC", Command::CRC), CommandHandler("DISPLAY", Command::Display), CommandHandler("EXIT", Command::Exit), CommandHandler("KEYBOARD", Command::Keyboard), diff --git a/ion/src/device/bench/command/command.cpp b/ion/src/device/bench/command/command.cpp index 645b224d9..5f5f52a92 100644 --- a/ion/src/device/bench/command/command.cpp +++ b/ion/src/device/bench/command/command.cpp @@ -30,6 +30,17 @@ bool isHex(char c) { return hexChar(c) >= 0; } +uint32_t numberBase10(const char * s, int maxLength) { + uint32_t result = 0; + int index = 0; + while ((maxLength < 0 || index < maxLength) && s[index] != NULL) { + result = result * 10 + hexChar(s[index]); + index++; + } + return result; +} + + uint32_t hexNumber(const char * s, int maxLength) { uint32_t result = 0; int index = 0; diff --git a/ion/src/device/bench/command/command.h b/ion/src/device/bench/command/command.h index 4dad0476a..1a544c67a 100644 --- a/ion/src/device/bench/command/command.h +++ b/ion/src/device/bench/command/command.h @@ -13,6 +13,7 @@ typedef void (*Function)(const char * input); void ADC(const char * input); void Backlight(const char * input); void Charge(const char * input); +void CRC(const char * input); void Display(const char * input); void Exit(const char * input); void Keyboard(const char * input); @@ -36,6 +37,7 @@ extern const char * const sOFF; void reply(const char * s); int8_t hexChar(char c); bool isHex(char c); +uint32_t numberBase10(const char * s, int maxLength = -1); uint32_t hexNumber(const char * s, int maxLength = -1); } diff --git a/ion/src/device/bench/command/crc.cpp b/ion/src/device/bench/command/crc.cpp new file mode 100644 index 000000000..f5c3c5dfc --- /dev/null +++ b/ion/src/device/bench/command/crc.cpp @@ -0,0 +1,57 @@ +#include "command.h" +#include +#include + +namespace Ion { +namespace Device { +namespace Bench { +namespace Command { + +// Input must be either "INTERNAL_size" or "EXTERNAL_size" +void CRC(const char * input) { + bool internal = true; + int suffixSize = 9; + if (strncmp(input, "INTERNAL_", suffixSize) == 0) { + internal = true; + } else if (strncmp(input, "EXTERNAL_", suffixSize) == 0) { + internal = false; + } else { + reply(sSyntaxError); + return; + } + + int lengthStart = suffixSize; + int lengthEnd = lengthStart; + int lengthSizeLimit = 15; + + for (int i = 0; i < lengthSizeLimit; i++) { + char c = input[lengthStart+i] ; + lengthEnd++; + if (c == 0) { + break; + } + if (c < '0' || c > '9') { + reply(sSyntaxError); + return; + } + } + if (lengthEnd - lengthStart >= lengthSizeLimit) { + reply(sSyntaxError); + return; + } + + uint32_t length = numberBase10(input + lengthStart, lengthEnd - lengthStart); + uint32_t crc = Ion::crc32PaddedString(reinterpret_cast(internal ? 0x08000000 : 0x90000000), length); + + constexpr int bufferSize = 4+10+1; // crc is a uint32_t so 10 digits long. + char buffer[bufferSize] = {'C', 'R', 'C', '=', 0}; + constexpr int precision = 10; + Poincare::PrintFloat::convertFloatToText(crc, buffer+4, bufferSize - 4, precision, Poincare::Preferences::PrintFloatMode::Decimal); + + reply(buffer); +} + +} +} +} +} From f71241f16f13eefe20c2d4420e94e7045d14e31f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 9 Apr 2019 15:26:06 +0200 Subject: [PATCH 0257/1750] [ion/bench] Clean Led command --- ion/src/device/bench/command/led.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ion/src/device/bench/command/led.cpp b/ion/src/device/bench/command/led.cpp index 39157faeb..0620eff2f 100644 --- a/ion/src/device/bench/command/led.cpp +++ b/ion/src/device/bench/command/led.cpp @@ -11,22 +11,22 @@ namespace Command { void LED(const char * input) { if (strcmp(input, sON) == 0) { Ion::Device::LED::init(); - Ion::Console::writeLine(sOK); + reply(sOK); return; } if (strcmp(input, sOFF) == 0) { Ion::Device::LED::shutdown(); - Ion::Console::writeLine(sOK); + reply(sOK); return; } if (input == nullptr || input[0] != '0' || input[1] != 'x' || !isHex(input[2]) ||!isHex(input[3]) || !isHex(input[4]) || !isHex(input[5]) || !isHex(input[6]) || !isHex(input[7]) || input[8] != NULL) { - Ion::Console::writeLine(sSyntaxError); + reply(sSyntaxError); return; } uint32_t hexColor = hexNumber(input+2); KDColor ledColor = KDColor::RGB24(hexColor); Ion::LED::setColor(ledColor); - Ion::Console::writeLine(sOK); + reply(sOK); } } From c01f8ec27c8686a464f06d23e6e2978291ab44ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Tue, 9 Apr 2019 16:16:41 +0200 Subject: [PATCH 0258/1750] [scripts] Fix build for n0100 --- ion/src/device/n0100/drivers/cache.h | 1 + ion/src/device/shared/usb/Makefile | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/ion/src/device/n0100/drivers/cache.h b/ion/src/device/n0100/drivers/cache.h index dd9a8a3c6..276e6b93c 100644 --- a/ion/src/device/n0100/drivers/cache.h +++ b/ion/src/device/n0100/drivers/cache.h @@ -14,6 +14,7 @@ using namespace Regs; inline void dsb() {} inline void invalidateDCache() {} +inline void cleanDCache() {} inline void enableDCache() {} inline void disableDCache() {} diff --git a/ion/src/device/shared/usb/Makefile b/ion/src/device/shared/usb/Makefile index b61dbdb6b..dd826730d 100644 --- a/ion/src/device/shared/usb/Makefile +++ b/ion/src/device/shared/usb/Makefile @@ -47,17 +47,30 @@ dfu_src += liba/src/memset.c dfu_src += liba/src/memcpy.c dfu_src += libaxx/src/cxxabi/pure_virtual.cpp dfu_src += ion/src/device/shared/usb/boot.cpp +dfu_src += ion/src/device/$(MODEL)/drivers/board.cpp dfu_src += ion/src/device/$(MODEL)/drivers/cache.cpp dfu_src += ion/src/device/$(MODEL)/usb/stack/device.cpp dfu_src += $(addprefix ion/src/device/shared/drivers/, \ + backlight.cpp \ + battery.cpp \ base64.cpp \ + board.cpp \ + console.cpp \ + crc32.cpp \ + display.cpp \ + events_keyboard_platform.cpp \ external_flash.cpp \ flash.cpp \ keyboard.cpp \ + led.cpp \ + power.cpp\ + random.cpp\ reset.cpp \ serial_number.cpp \ + swd.cpp \ timing.cpp \ usb.cpp \ + wakeup.cpp \ ) $(BUILD_DIR)/ion/src/device/shared/usb/dfu.elf: LDSCRIPT = ion/src/device/shared/usb/dfu.ld From e53a37073f249961d6b337fb21afc671d3c457ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 10 Apr 2019 11:13:50 +0200 Subject: [PATCH 0259/1750] [ion/bench] Remove writeExternal test --- ion/src/device/bench/Makefile | 1 - ion/src/device/bench/bench.cpp | 1 - ion/src/device/bench/command/command.h | 1 - .../device/bench/command/write_external.cpp | 118 ------------------ 4 files changed, 121 deletions(-) delete mode 100644 ion/src/device/bench/command/write_external.cpp diff --git a/ion/src/device/bench/Makefile b/ion/src/device/bench/Makefile index 9e9f1b7e6..e34838a0e 100644 --- a/ion/src/device/bench/Makefile +++ b/ion/src/device/bench/Makefile @@ -23,5 +23,4 @@ bench_src += $(addprefix ion/src/device/bench/command/, \ stop.cpp \ standby.cpp \ vblank.cpp \ - write_external.cpp \ ) diff --git a/ion/src/device/bench/bench.cpp b/ion/src/device/bench/bench.cpp index 20176777f..56ae20f41 100644 --- a/ion/src/device/bench/bench.cpp +++ b/ion/src/device/bench/bench.cpp @@ -25,7 +25,6 @@ constexpr CommandHandler handles[] = { CommandHandler("STOP", Command::Stop), CommandHandler("STANDBY", Command::Standby), CommandHandler("VBLANK", Command::VBlank), - CommandHandler("WRITE_EXTERNAL", Command::WriteExternal), CommandHandler(nullptr, nullptr) }; diff --git a/ion/src/device/bench/command/command.h b/ion/src/device/bench/command/command.h index 1a544c67a..3eecd0ce2 100644 --- a/ion/src/device/bench/command/command.h +++ b/ion/src/device/bench/command/command.h @@ -26,7 +26,6 @@ void Sleep(const char * input); void Stop(const char * input); void Standby(const char * input); void VBlank(const char * input); -void WriteExternal(const char * input); extern const char * const sOK; extern const char * const sKO; diff --git a/ion/src/device/bench/command/write_external.cpp b/ion/src/device/bench/command/write_external.cpp deleted file mode 100644 index 81b554d58..000000000 --- a/ion/src/device/bench/command/write_external.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "command.h" -#include -#include -#include - -namespace Ion { -namespace Device { -namespace Bench { -namespace Command { - -/* Input must be of the form ??????:data, ?????? being the size of the data in - * hex */ - -#if 1 -void WriteExternal(const char * input) { - // Check the input format - if (input != nullptr) { - reply(sSyntaxError); - return; - } - - const char * data = "0987654321098764321098765432109876543210987654321"; - uint8_t * externalFlashStart = reinterpret_cast(0x90000000); - int dataSize = 10; - - // Copy the data in the external flash - Ion::Device::ExternalFlash::EraseSector(0); - Ion::Device::ExternalFlash::WriteMemory( - reinterpret_cast(0), - reinterpret_cast(data) , - dataSize); - - // Check it was correctly copied - for (int i = 0; i < dataSize; i++) { - if (*(externalFlashStart + i) != data[i]) { - reply(sKO); - return; - } - } - - Ion::Device::ExternalFlash::EraseSector(0); - Ion::Device::ExternalFlash::WriteMemory( - reinterpret_cast(0), - reinterpret_cast(data), - dataSize); - - // Check it was correctly copied - for (int i = 0; i < dataSize; i++) { - if (*(externalFlashStart + i) != data[i]) { - reply(sKO); - return; - } - } - reply(sOK); -} - - -#else -void WriteExternal(const char * input) { - // Check the input format - if (input == nullptr || input[0] != '0' || input[1] != 'x') { - reply(sSyntaxError); - return; - } - constexpr int sizeStart = 2; - constexpr int dataSizeLength = 6; - for (int i = sizeStart; i < sizeStart + dataSizeLength; i++) { - if (!isHex(input[i])) { - reply(sSyntaxError); - return; - } - } - if (input[sizeStart + dataSizeLength] != ':') { - reply(sSyntaxError); - return; - } - - // Get the data size - char dataSizeBuffer[dataSizeLength + 1]; - for (int i = 0; i < dataSizeLength; i++) { - dataSizeBuffer[i] = input[sizeStart + i]; - } - dataSizeBuffer[dataSizeLength] = 0; - uint32_t dataSize = hexNumber(dataSizeBuffer); - int dataIndex = sizeStart + dataSizeLength + 1; - - uint8_t * externalFlashStart = reinterpret_cast(0x90000000); // TODO reinterpret_cast(Ion::Device::ExternalFlash::QSPIBaseAddress) - - // Copy the data in the external flash - uint32_t writeOffset = 0x00000000; - // Erase the flash - int firstSector = Ion::Device::ExternalFlash::SectorAtAddress(writeOffset); - int lastSector = Ion::Device::ExternalFlash::SectorAtAddress(writeOffset + dataSize); - assert(firstSector <= lastSector); - for (int i = firstSector; i <= lastSector; i++) { - Ion::Device::ExternalFlash::EraseSector(i); - } - // Write - Ion::Device::ExternalFlash::WriteMemory( - reinterpret_cast(writeOffset), - reinterpret_cast(input + dataIndex), - dataSize); - - // Check it was correctly copied - for (uint32_t i = 0; i < dataSize; i++) { - if (*(externalFlashStart + writeOffset + i) != input[dataIndex+i]) { - reply(sKO); - return; - } - } - reply(sOK); -} -#endif - -} -} -} -} From b3d3cb5204a85370fd4a084a96ea76a1b4500a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 10 Apr 2019 11:28:37 +0200 Subject: [PATCH 0260/1750] [bench] Disblae the Cache before the CRC test --- ion/src/device/bench/command/crc.cpp | 7 +++++++ ion/src/device/n0100/drivers/cache.h | 3 +++ ion/src/device/n0101/drivers/board.cpp | 7 +------ ion/src/device/n0101/drivers/cache.cpp | 10 ++++++++++ ion/src/device/n0101/drivers/cache.h | 3 +++ 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ion/src/device/bench/command/crc.cpp b/ion/src/device/bench/command/crc.cpp index f5c3c5dfc..e136e5064 100644 --- a/ion/src/device/bench/command/crc.cpp +++ b/ion/src/device/bench/command/crc.cpp @@ -1,6 +1,7 @@ #include "command.h" #include #include +#include namespace Ion { namespace Device { @@ -41,8 +42,14 @@ void CRC(const char * input) { } uint32_t length = numberBase10(input + lengthStart, lengthEnd - lengthStart); + + // Disable the cache to make many cache accesses + Ion::Device::Cache::disable(); + uint32_t crc = Ion::crc32PaddedString(reinterpret_cast(internal ? 0x08000000 : 0x90000000), length); + Ion::Device::Cache::enable(); + constexpr int bufferSize = 4+10+1; // crc is a uint32_t so 10 digits long. char buffer[bufferSize] = {'C', 'R', 'C', '=', 0}; constexpr int precision = 10; diff --git a/ion/src/device/n0100/drivers/cache.h b/ion/src/device/n0100/drivers/cache.h index 276e6b93c..66d2a3200 100644 --- a/ion/src/device/n0100/drivers/cache.h +++ b/ion/src/device/n0100/drivers/cache.h @@ -13,6 +13,9 @@ using namespace Regs; inline void dsb() {} +inline void enable() {} +inline void disable() {} + inline void invalidateDCache() {} inline void cleanDCache() {} inline void enableDCache() {} diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 6469d91be..4efd95ee0 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -20,11 +20,6 @@ namespace Board { using namespace Regs; -void initL1Cache() { - Cache::enableICache(); - Cache::enableDCache(); -} - void initMPU() { // 1. Disable the MPU // 1.1 Memory barrier @@ -113,7 +108,7 @@ void init() { initPeripherals(); // Initiate L1 cache after initiating the external flash - initL1Cache(); + Cache::enable(); // TODO if EPSILON_DEVICE_BENCH, run bench? See n0100 } diff --git a/ion/src/device/n0101/drivers/cache.cpp b/ion/src/device/n0101/drivers/cache.cpp index e4be16935..1614d528b 100644 --- a/ion/src/device/n0101/drivers/cache.cpp +++ b/ion/src/device/n0101/drivers/cache.cpp @@ -48,6 +48,16 @@ void privateCleanInvalidateDisableDCache(bool clean, bool invalidate, bool disab isb(); } +void enable() { + enableICache(); + enableDCache(); +} + +void disable() { + disableICache(); + disableDCache(); +} + void invalidateDCache() { privateCleanInvalidateDisableDCache(false, true, false); } diff --git a/ion/src/device/n0101/drivers/cache.h b/ion/src/device/n0101/drivers/cache.h index cc074008a..5368f162d 100644 --- a/ion/src/device/n0101/drivers/cache.h +++ b/ion/src/device/n0101/drivers/cache.h @@ -27,6 +27,9 @@ inline void isb() { asm volatile("isb 0xF":::"memory"); } +void enable(); +void disable(); + void invalidateDCache(); void cleanDCache(); void enableDCache(); From 198b5c486decffd8caeebf5ea4977a3a34540476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 10:03:06 +0200 Subject: [PATCH 0261/1750] [ion] LED: fix TIM3 prescaler --- ion/src/device/shared/drivers/led.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ion/src/device/shared/drivers/led.cpp b/ion/src/device/shared/drivers/led.cpp index 7644040b7..c49ffda4f 100644 --- a/ion/src/device/shared/drivers/led.cpp +++ b/ion/src/device/shared/drivers/led.cpp @@ -105,10 +105,10 @@ void shutdownTimer() { void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, float dutyCycleBlue, uint16_t period) { switch (mode) { case Mode::PWM: - /* Let's set the prescaler to 1. Increasing the prescaler would slow down + /* Let's set the prescaler to 1 (PSC = 0). Increasing the prescaler would slow down * the modulation, which can be useful when debugging or when we want an * actual blinking. */ - TIM3.PSC()->set(1); + TIM3.PSC()->set(0); TIM3.ARR()->set(PWMPeriod); period = PWMPeriod; break; From fefa5beac4bdee5f0dce5f86651a95b9b7d4eb08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 10:24:59 +0200 Subject: [PATCH 0262/1750] [ion] LED: start fixing the blinking frequency --- ion/src/device/n0100/drivers/config/clocks.h | 21 ++++++++++++-------- ion/src/device/n0101/drivers/board.cpp | 4 ++-- ion/src/device/n0101/drivers/config/clocks.h | 20 ++++++++++++------- ion/src/device/shared/drivers/board.cpp | 3 ++- ion/src/device/shared/drivers/led.cpp | 11 +++++----- 5 files changed, 36 insertions(+), 23 deletions(-) diff --git a/ion/src/device/n0100/drivers/config/clocks.h b/ion/src/device/n0100/drivers/config/clocks.h index d883a9d22..552b7bb55 100644 --- a/ion/src/device/n0100/drivers/config/clocks.h +++ b/ion/src/device/n0100/drivers/config/clocks.h @@ -27,18 +27,23 @@ constexpr static int PLL_N = 192; 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 SYSCLKFrequency = ((HSE/PLL_M)*PLL_N)/PLL_P; -constexpr static int AHB_prescaler = 1; -constexpr static int HCLKFrequency = SYSCLKFrequency/AHB_prescaler; -constexpr static int AHBFrequency = HCLKFrequency; -constexpr static Regs::RCC::CFGR::APBPrescaler APB1PrescalerRegs = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy2; -constexpr static int APB1Prescaler = 2; -constexpr static int APB1Frequency = HCLKFrequency/APB1Prescaler; -constexpr static int APB1TimerFrequency = 2*APB1Frequency; +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 96MHz/14.2MHz~6.7. */ -constexpr static Regs::RCC::CFGR::AHBPrescaler AHBLowFrequencyPrescaler = Regs::RCC::CFGR::AHBPrescaler::SysClkDividedBy4; +constexpr static Regs::RCC::CFGR::AHBPrescaler AHBLowFrequencyPrescalerReg = Regs::RCC::CFGR::AHBPrescaler::SysClkDividedBy4; +constexpr static int AHBLowFrequencyPrescaler = 4; +constexpr static int HCLKFrequency = SYSCLKFrequency/AHBPrescaler; +constexpr static int HCLKLowFrequency = SYSCLKFrequency/AHBLowFrequencyPrescaler; +constexpr static int AHBFrequency = HCLKFrequency; +//constexpr static int AHBLowFrequency = HCLKLowFrequency; +constexpr static Regs::RCC::CFGR::APBPrescaler APB1PrescalerRegs = Regs::RCC::CFGR::APBPrescaler::AHBDividedBy2; +constexpr static int APB1Prescaler = 2; +//constexpr static int APB1Frequency = HCLKFrequency/APB1Prescaler; +constexpr static int APB1LowFrequency = HCLKLowFrequency/APB1Prescaler; +//constexpr static int APB1TimerFrequency = 2*APB1Frequency; +constexpr static int APB1TimerLowFrequency = 2*APB1LowFrequency; } } diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 4efd95ee0..df32de649 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -176,9 +176,9 @@ void initClocks() { FLASH.ACR()->setARTEN(true); // 192 MHz is too fast for APB1. Divide it by four to reach 48 MHz - RCC.CFGR()->setPPRE1(RCC::CFGR::APBPrescaler::AHBDividedBy4); + RCC.CFGR()->setPPRE1(Clocks::Config::APB1PrescalerReg); // 192 MHz is too fast for APB2. Divide it by two to reach 96 MHz - RCC.CFGR()->setPPRE2(RCC::CFGR::APBPrescaler::AHBDividedBy2); + RCC.CFGR()->setPPRE2(Clocks::Config::APB2PrescalerReg); while(!RCC.CR()->getPLLRDY()) { } diff --git a/ion/src/device/n0101/drivers/config/clocks.h b/ion/src/device/n0101/drivers/config/clocks.h index 1317d3af7..488f32a72 100644 --- a/ion/src/device/n0101/drivers/config/clocks.h +++ b/ion/src/device/n0101/drivers/config/clocks.h @@ -28,18 +28,24 @@ 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; -constexpr static int HCLKFrequency = SYSCLKFrequency/AHBPrescaler; -constexpr static int AHBFrequency = HCLKFrequency; -constexpr static int APB1Prescaler = 4; -constexpr static int APB1Frequency = HCLKFrequency/APB1Prescaler; -constexpr static int APB1TimerFrequency = 2*APB1Frequency; - /* 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 AHBLowFrequencyPrescaler = Regs::RCC::CFGR::AHBPrescaler::SysClkDividedBy8; +constexpr static Regs::RCC::CFGR::AHBPrescaler AHBLowFrequencyPrescalerReg = Regs::RCC::CFGR::AHBPrescaler::SysClkDividedBy8; +constexpr static int AHBLowFrequencyPrescaler = 8; +constexpr static int HCLKFrequency = SYSCLKFrequency/AHBPrescaler; +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; } } } diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index 2947aaef3..8ecb588d9 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -61,13 +61,14 @@ void shutdownPeripherals(bool keepLEDAwake) { } void setClockFrequency(Frequency f) { + // TODO: Update TIM3 prescaler or ARR to avoid irregular LED blinking switch (f) { case Frequency::High: RCC.CFGR()->setHPRE(RCC::CFGR::AHBPrescaler::SysClk); return; default: assert(f == Frequency::Low); - RCC.CFGR()->setHPRE(Clocks::Config::AHBLowFrequencyPrescaler); + RCC.CFGR()->setHPRE(Clocks::Config::AHBLowFrequencyPrescalerReg); return; } } diff --git a/ion/src/device/shared/drivers/led.cpp b/ion/src/device/shared/drivers/led.cpp index c49ffda4f..da867fd3c 100644 --- a/ion/src/device/shared/drivers/led.cpp +++ b/ion/src/device/shared/drivers/led.cpp @@ -1,6 +1,7 @@ #include "led.h" #include #include +#include static KDColor sLedColor = KDColorBlack; @@ -113,15 +114,15 @@ void setPeriodAndDutyCycles(Mode mode, float dutyCycleRed, float dutyCycleGreen, period = PWMPeriod; break; case Mode::Blink: - int systemClockFreq = 96; /* We still want to do PWM, but at a rate slow enough to blink. Ideally, * we want to pre-scale the period to be able to set it in milliseconds; * however, as the prescaler is cap by 2^16-1, we divide it by a factor * and correct the period consequently. */ - int factor = 2; - // TODO: explain the 2 ? - TIM3.PSC()->set(systemClockFreq*1000/factor); - period *= factor; + int prescalerFactor = 4; + /* Most of the time AP1TimerFrequency is the 'low' one as most of the time + * is spent in Timing::msleep. */ + TIM3.PSC()->set(Clocks::Config::APB1TimerLowFrequency*1000/prescalerFactor-1); + period *= prescalerFactor; TIM3.ARR()->set(period); break; } From 7cc8cc02c881de979e0e0fbd69639f042e5bc21c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 10:46:36 +0200 Subject: [PATCH 0263/1750] [ion] Add a keyboard platform event: Battery Charging --- ion/include/ion/events.h | 1 + .../device/shared/drivers/events_keyboard_platform.cpp | 10 ++++++++++ ion/src/shared/events.cpp | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ion/include/ion/events.h b/ion/include/ion/events.h index 19c14f25e..868360a41 100644 --- a/ion/include/ion/events.h +++ b/ion/include/ion/events.h @@ -218,6 +218,7 @@ constexpr Event Termination = Event::Special(1); constexpr Event TimerFire = Event::Special(2); constexpr Event USBEnumeration = Event::Special(3); constexpr Event USBPlug = Event::Special(4); +constexpr Event BatteryCharging = Event::Special(5); } } diff --git a/ion/src/device/shared/drivers/events_keyboard_platform.cpp b/ion/src/device/shared/drivers/events_keyboard_platform.cpp index 6e756fbae..86bdcfca8 100644 --- a/ion/src/device/shared/drivers/events_keyboard_platform.cpp +++ b/ion/src/device/shared/drivers/events_keyboard_platform.cpp @@ -1,5 +1,6 @@ #include #include +#include #include namespace Ion { @@ -7,12 +8,14 @@ namespace Events { bool sLastUSBPlugged = false; bool sLastUSBEnumerated = false; +bool sLastBatteryCharging = false; Event getPlatformEvent() { // First, check if the USB plugged status has changed bool usbPlugged = USB::isPlugged(); if (usbPlugged != sLastUSBPlugged) { sLastUSBPlugged = usbPlugged; + sLastBatteryCharging = Battery::isCharging(); return Events::USBPlug; } @@ -25,6 +28,13 @@ Event getPlatformEvent() { } } + // Third, check if the battery changed charging state + bool batteryCharging = Battery::isCharging(); + if (batteryCharging != sLastBatteryCharging) { + sLastBatteryCharging = batteryCharging; + return Events::BatteryCharging; + } + return Event(); } diff --git a/ion/src/shared/events.cpp b/ion/src/shared/events.cpp index d4b413ded..8ab51cc41 100644 --- a/ion/src/shared/events.cpp +++ b/ion/src/shared/events.cpp @@ -139,7 +139,7 @@ bool Event::isDefined() const { if (isKeyboardEvent()) { return s_dataForEvent[m_id].isDefined(); } else { - return (*this == None || *this == Termination || *this == USBEnumeration || *this == USBPlug); + return (*this == None || *this == Termination || *this == USBEnumeration || *this == USBPlug || *this == BatteryCharging); } } From 07f37ddac324bba7f6b7c9787de5d928ae37ed71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 12 Nov 2018 14:40:31 +0100 Subject: [PATCH 0264/1750] [apps] AppsContainer: update LED color with events: etnumeration, plug, charging --- apps/apps_container.cpp | 13 +++++++++++++ apps/apps_container.h | 1 + 2 files changed, 14 insertions(+) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index d5dd6ad98..382944af3 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -145,10 +145,21 @@ void AppsContainer::suspend(bool checkIfPowerKeyReleased) { window()->redraw(true); } +void AppsContainer::updateLED() { + if (Ion::USB::isPlugged()) { + Ion::LED::setColor(Ion::Battery::isCharging() ? KDColorYellow : KDColorGreen); + } else { + Ion::LED::setColor(KDColorBlack); + } +} + bool AppsContainer::dispatchEvent(Ion::Events::Event event) { bool alphaLockWantsRedraw = updateAlphaLock(); bool didProcessEvent = false; + if (event == Ion::Events::USBEnumeration || event == Ion::Events::USBPlug || event == Ion::Events::BatteryCharging) { + updateLED(); + } if (event == Ion::Events::USBEnumeration) { if (Ion::USB::isPlugged()) { App::Snapshot * activeSnapshot = (activeApp() == nullptr ? appSnapshotAtIndex(0) : activeApp()->snapshot()); @@ -158,6 +169,8 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { * event loop, we update the battery state "manually" here. */ updateBatteryState(); Ion::USB::DFU(); + // Update LED when exciting DFU mode + updateLED(); bool switched = switchTo(activeSnapshot); assert(switched); (void) switched; // Silence compilation warning about unused variable. diff --git a/apps/apps_container.h b/apps/apps_container.h index 5f23fbd1a..ca1d9c6d2 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -62,6 +62,7 @@ private: bool processEvent(Ion::Events::Event event); void resetShiftAlphaStatus(); bool updateAlphaLock(); + void updateLED(); AppsWindow m_window; EmptyBatteryWindow m_emptyBatteryWindow; From f89b46ed78a725ceaeae469c9df15b358ad1868d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 11:24:38 +0200 Subject: [PATCH 0265/1750] [apps] Redraw battery pictogram after a reset --- apps/apps_container.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 382944af3..fe103064b 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -163,11 +163,13 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { if (event == Ion::Events::USBEnumeration) { if (Ion::USB::isPlugged()) { App::Snapshot * activeSnapshot = (activeApp() == nullptr ? appSnapshotAtIndex(0) : activeApp()->snapshot()); + /* Just after a software update, the battery timer does not have time to + * fire before the calculator enters DFU mode. As the DFU mode blocks the + * event loop, we update the battery state "manually" here. + * We do it before switching to USB application to redraw the battery + * pictogram. */ + updateBatteryState(); if (switchTo(usbConnectedAppSnapshot())) { - /* Just after a software update, the battery timer does not have time to - * fire before the calculator enters DFU mode. As the DFU mode blocks the - * event loop, we update the battery state "manually" here. */ - updateBatteryState(); Ion::USB::DFU(); // Update LED when exciting DFU mode updateLED(); From 140460dff2158bb8fcb1ac7156890d8610d26bff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 11:25:03 +0200 Subject: [PATCH 0266/1750] [ion] Re-order platform events (plug, enumeration, charging) --- .../drivers/events_keyboard_platform.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ion/src/device/shared/drivers/events_keyboard_platform.cpp b/ion/src/device/shared/drivers/events_keyboard_platform.cpp index 86bdcfca8..2f8f7e26e 100644 --- a/ion/src/device/shared/drivers/events_keyboard_platform.cpp +++ b/ion/src/device/shared/drivers/events_keyboard_platform.cpp @@ -11,7 +11,18 @@ bool sLastUSBEnumerated = false; bool sLastBatteryCharging = false; Event getPlatformEvent() { - // First, check if the USB plugged status has changed + // First, check if the USB device has been connected to an USB host + bool usbEnumerated = USB::isEnumerated(); + if (usbEnumerated != sLastUSBEnumerated) { + sLastUSBPlugged = USB::isPlugged(); + sLastBatteryCharging = Battery::isCharging(); + sLastUSBEnumerated = usbEnumerated; + if (usbEnumerated) { + return Events::USBEnumeration; + } + } + + // Second, check if the USB plugged status has changed bool usbPlugged = USB::isPlugged(); if (usbPlugged != sLastUSBPlugged) { sLastUSBPlugged = usbPlugged; @@ -19,15 +30,6 @@ Event getPlatformEvent() { return Events::USBPlug; } - // Second, check if the USB device has been connected to an USB host - bool usbEnumerated = USB::isEnumerated(); - if (usbEnumerated != sLastUSBEnumerated) { - sLastUSBEnumerated = usbEnumerated; - if (usbEnumerated) { - return Events::USBEnumeration; - } - } - // Third, check if the battery changed charging state bool batteryCharging = Battery::isCharging(); if (batteryCharging != sLastBatteryCharging) { From 10698349090306b655b7d8d6a8b70b46d744d3f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 15:58:07 +0200 Subject: [PATCH 0267/1750] [ion] Link: KDColor can be used in Ion::LED::setColor, the KDColor symbols should be in the internal flash --- ion/src/device/n0101/flash.ld | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ion/src/device/n0101/flash.ld b/ion/src/device/n0101/flash.ld index f79ef1e06..22080eaa0 100644 --- a/ion/src/device/n0101/flash.ld +++ b/ion/src/device/n0101/flash.ld @@ -66,6 +66,10 @@ SECTIONS { .rodata.internal : { . = ALIGN(4); *(.rodata._ZN3Ion*) + *(.rodata._ZN3KDColor*) + *(.rodata._ZL11KDColor*) + *(.rodata._ZL12KDColor*) + *(.rodata._ZL13KDColor*) } >INTERNAL_FLASH .text.external : { From ba4d8e6a0626a8a1d95d82553d3d5830c7c65bed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 15:59:20 +0200 Subject: [PATCH 0268/1750] [ion] Power: While suspended, update LED with charging or plug --- ion/src/device/shared/drivers/power.cpp | 32 +++++++++++++++---------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 2fe03f9d1..8593a4638 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -1,8 +1,13 @@ +#include #include #include #include #include +#include +#include #include +#include +#include #include #include #include @@ -25,9 +30,7 @@ using namespace Device::Regs; void configWakeUp() { Device::WakeUp::onPowerKeyDown(); Device::WakeUp::onUSBPlugging(); -#if EPSILON_LED_WHILE_CHARGING Device::WakeUp::onChargingEvent(); -#endif } void stopConfiguration() { @@ -195,7 +198,7 @@ void sleepConfiguration() { void suspend(bool checkIfPowerKeyReleased) { bool isLEDActive = Ion::LED::getColor() != KDColorBlack; - bool isPlugged = USB::isPlugged(); + bool plugged = USB::isPlugged(); if (checkIfPowerKeyReleased) { /* Wait until power is released to avoid restarting just after suspending */ @@ -214,14 +217,6 @@ void suspend(bool checkIfPowerKeyReleased) { } else { stopConfiguration(); } -#if EPSILON_LED_WHILE_CHARGING - /* Update LEDS - * if the standby mode was stopped due to a "stop charging" event, we wait - * a while to be sure that the plug state of the USB is up-to-date. */ - msleep(200); - Ion::LED::setCharging(Ion::USB::isPlugged(), Ion::Battery::isCharging()); -#endif - /* To enter sleep, we need to issue a WFE instruction, which waits for the * event flag to be set and then clears it. However, the event flag might @@ -239,11 +234,22 @@ void suspend(bool checkIfPowerKeyReleased) { Keyboard::State scan = Keyboard::scan(); Ion::Keyboard::State OnlyPowerKeyDown = Keyboard::State(Keyboard::PowerKey); - if (scan == OnlyPowerKeyDown || (!isPlugged && USB::isPlugged())) { + + /* Update LEDS + * if the standby mode was stopped due to a "stop charging" event, we wait + * a while to be sure that the plug state of the USB is up-to-date. */ + Device::Battery::initGPIO(); + Device::USB::initGPIO(); + Device::LED::init(); + KDColor ledColor = USB::isPlugged() ? (Battery::isCharging() ? KDColorYellow : KDColorGreen) : KDColorBlack; + Ion::LED::setColor(ledColor); + isLEDActive = ledColor != KDColorBlack; + + if (scan == OnlyPowerKeyDown || (!plugged && USB::isPlugged())) { // Wake up break; } - isPlugged = USB::isPlugged(); + plugged = USB::isPlugged(); } Device::Board::sNormalFrequency = Device::Board::Frequency::High; From 26ea5718b5943ea46391890c010e13c7582b8cbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 16:04:32 +0200 Subject: [PATCH 0269/1750] [ion] ExternalFlash: record the operating mode to avoid sending Single SPI command when in Quad SPI mode and vice versa --- ion/src/device/shared/drivers/external_flash.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index bcb705c37..dd3cbdcdb 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -281,10 +281,12 @@ static void initQSPI() { QUADSPI.CR()->set(cr); } +static QUADSPI::CCR::OperatingMode sOperatingMode = QUADSPI::CCR::OperatingMode::Single; + static void initChip() { /* The chip initially expects commands in SPI mode. We need to use SPI to tell * it to switch to QPI. */ - if (DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { + if (sOperatingMode == QUADSPI::CCR::OperatingMode::Single && DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { send_command(Command::WriteEnable, QUADSPI::CCR::OperatingMode::Single); ExternalFlashStatusRegister::StatusRegister2 statusRegister2(0); statusRegister2.setQE(true); @@ -305,6 +307,7 @@ static void initChip() { readParameters.setP5(true); send_write_command(Command::SetReadParameters, reinterpret_cast(FlashAddressSpaceSize), reinterpret_cast(&readParameters), sizeof(readParameters)); } + sOperatingMode = QUADSPI::CCR::OperatingMode::Quad; } set_as_memory_mapped(); } @@ -331,6 +334,7 @@ static void shutdownChip() { // Reset send_command(Command::EnableReset); send_command(Command::Reset); + sOperatingMode = QUADSPI::CCR::OperatingMode::Single; Ion::Timing::usleep(30); // Sleep deep From 538172609a8abbb10ca3cd4975ae311ba8f76c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 19:09:39 +0200 Subject: [PATCH 0270/1750] [ion] Power: move clock configuration in sleep mode from Power to Board::initClock --- ion/src/device/n0100/drivers/board.cpp | 75 +++++++++++++++ ion/src/device/n0101/drivers/board.cpp | 88 ++++++++++++++++++ ion/src/device/shared/drivers/power.cpp | 118 ------------------------ 3 files changed, 163 insertions(+), 118 deletions(-) diff --git a/ion/src/device/n0100/drivers/board.cpp b/ion/src/device/n0100/drivers/board.cpp index 024d7a68e..68fea1cfd 100644 --- a/ion/src/device/n0100/drivers/board.cpp +++ b/ion/src/device/n0100/drivers/board.cpp @@ -129,6 +129,81 @@ void initClocks() { apb2enr.setSDIOEN(true); #endif RCC.APB2ENR()->set(apb2enr); + + // AHB1 peripheral clock enable in low-power mode register + class RCC::AHB1LPENR ahb1lpenr(0x006190FF); // 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); + RCC.AHB1LPENR()->set(ahb1lpenr); + + // AHB2 peripheral clock enable in low-power mode register + class RCC::AHB2LPENR ahb2lpenr(0x000000C0); // Reset value + ahb2lpenr.setOTGFSLPEN(false); + ahb2lpenr.setRNGLPEN(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(0x17E6CDFF); // 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.setI2CFMP1LPEN(false); + apb1lpenr.setCAN2LPEN(false); + RCC.APB1LPENR()->set(apb1lpenr); + + // APB2 peripheral clock enable in low-power mode register + class RCC::APB2LPENR apb2lpenr(0x0117F933); // 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.setSDIOLPEN(false); + apb2lpenr.setEXTITEN(false); + apb2lpenr.setDFSDM1LPEN(false); + RCC.APB2LPENR()->set(apb2lpenr); } void shutdownClocks(bool keepLEDAwake) { diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index df32de649..202af1705 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -221,6 +221,94 @@ void initClocks() { apb2enr.setSYSCFGEN(true); apb2enr.setUSART6EN(true); // TODO if EPSILON_DEVICE_BENCH? 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) { diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 8593a4638..05f5e14d6 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -69,124 +69,6 @@ void sleepConfiguration() { while (!PWR.CSR1()->getVOSRDY()) {} #endif - // AHB1 peripheral clock enable in low-power mode register -#if REGS_RCC_CONFIG_F730 - class RCC::AHB1LPENR ahb1lpenr(0x7EF7B7FF); // Reset value -#elif REGS_RCC_CONFIG_F412 - class RCC::AHB1LPENR ahb1lpenr(0x006190FF); // Reset value -#endif - 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); -#if REGS_RCC_CONFIG_F730 - ahb1lpenr.setAXILPEN(false); - ahb1lpenr.setSRAM2LPEN(false); - ahb1lpenr.setBKPSRAMLPEN(false); - ahb1lpenr.setDTCMLPEN(false); - ahb1lpenr.setOTGHSLPEN(false); - ahb1lpenr.setOTGHSULPILPEN(false); -#endif - RCC.AHB1LPENR()->set(ahb1lpenr); - - // AHB2 peripheral clock enable in low-power mode register -#if REGS_RCC_CONFIG_F730 - class RCC::AHB2LPENR ahb2lpenr(0x000000F1); // Reset value -#elif REGS_RCC_CONFIG_F412 - class RCC::AHB2LPENR ahb2lpenr(0x000000C0); // Reset value -#endif - ahb2lpenr.setOTGFSLPEN(false); - ahb2lpenr.setRNGLPEN(false); -#if REGS_RCC_CONFIG_F730 - ahb2lpenr.setAESLPEN(false); -#endif - 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 -#if REGS_RCC_CONFIG_F730 - class RCC::APB1LPENR apb1lpenr(0xFFFFCBFF); // Reset value -#elif REGS_RCC_CONFIG_F412 - class RCC::APB1LPENR apb1lpenr(0x17E6CDFF); // Reset value -#endif - 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); -#if REGS_RCC_CONFIG_F730 - apb1lpenr.setLPTIM1LPEN(false); - apb1lpenr.setUSART4LPEN(false); - apb1lpenr.setUSART5LPEN(false); - apb1lpenr.setOTGHSLPEN(false); - apb1lpenr.setOTGHSULPILPEN(false); -#elif REGS_RCC_CONFIG_F412 - apb1lpenr.setI2CFMP1LPEN(false); - apb1lpenr.setCAN2LPEN(false); -#endif - RCC.APB1LPENR()->set(apb1lpenr); - - // APB2 peripheral clock enable in low-power mode register -#if REGS_RCC_CONFIG_F730 - class RCC::APB2LPENR apb2lpenr(0x04F77F33); // Reset value -#elif REGS_RCC_CONFIG_F412 - class RCC::APB2LPENR apb2lpenr(0x0117F933); // Reset value -#endif - 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); -#if REGS_RCC_CONFIG_F730 - apb2lpenr.setSDMMC2LPEN(false); - apb2lpenr.setADC2LPEN(false); - apb2lpenr.setADC3LPEN(false); - apb2lpenr.setSAI1LPEN(false); - apb2lpenr.setSAI2LPEN(false); -#elif REGS_RCC_CONFIG_F412 - apb2lpenr.setSDIOLPEN(false); - apb2lpenr.setEXTITEN(false); - apb2lpenr.setDFSDM1LPEN(false); -#endif - RCC.APB2LPENR()->set(apb2lpenr); - CORTEX.SCR()->setSLEEPDEEP(false); Device::Board::shutdownPeripherals(true); From 326799abf753f2260441d1b6ba0f9a8e12c498a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 19:11:16 +0200 Subject: [PATCH 0271/1750] [ion] Discard useless comment --- ion/src/device/shared/drivers/wakeup.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/ion/src/device/shared/drivers/wakeup.cpp b/ion/src/device/shared/drivers/wakeup.cpp index 85d25c6fb..fb827baca 100644 --- a/ion/src/device/shared/drivers/wakeup.cpp +++ b/ion/src/device/shared/drivers/wakeup.cpp @@ -36,9 +36,7 @@ void onUSBPlugging() { SYSCFG.EXTICR3()->setEXTI(USB::Config::VbusPin.pin(), USB::Config::VbusPin.group()); EXTI.EMR()->set(USB::Config::VbusPin.pin(), true); -//#if EPSILON_LED_WHILE_CHARGING EXTI.FTSR()->set(USB::Config::VbusPin.pin(), true); -//#endif EXTI.RTSR()->set(USB::Config::VbusPin.pin(), true); } From f275b91c8cf040489122ee72b489f855289be95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Wed, 10 Apr 2019 19:11:34 +0200 Subject: [PATCH 0272/1750] [ion] WakeUp: fix comments --- ion/src/device/shared/drivers/wakeup.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ion/src/device/shared/drivers/wakeup.cpp b/ion/src/device/shared/drivers/wakeup.cpp index fb827baca..b8d045845 100644 --- a/ion/src/device/shared/drivers/wakeup.cpp +++ b/ion/src/device/shared/drivers/wakeup.cpp @@ -18,8 +18,10 @@ void onChargingEvent() { /* Warning: pins with the same number in different groups cannot be set as * source input for EXTI at the same time. Here, EXTICR1 register is filled - * between position 0-3 (charging pin = 0) with - * 0000 (ChargingGPIO = group A). */ + * between: + * - N0100: position 0-3 (charging pin = 0) with 0000 (ChargingGPIO = group A) + * - N0101: position 12-15 (charging pin = 3) with 0100 (ChargingGPIO = group E) + */ SYSCFG.EXTICR1()->setEXTI(Battery::Config::ChargingPin.pin(), Battery::Config::ChargingPin.group()); EXTI.EMR()->set(Battery::Config::ChargingPin.pin(), true); @@ -54,8 +56,9 @@ void onPowerKeyDown() { Keyboard::Config::ColumnGPIO.MODER()->setMode(columnPin, GPIO::MODER::Mode::Input); Keyboard::Config::ColumnGPIO.PUPDR()->setPull(columnPin, GPIO::PUPDR::Pull::Up); - /* Here, EXTICR1 register is filled between position 4-7 (column pin = 1) with - * 0010 (ColumnGPIO = group C). */ + /* Here, EXTICR1 register is filled between: + * - N0100: position 4-7 (column pin = 1) with 0010 (ColumnGPIO = group C) + * - N0101: position 8-11 (column pin = 2) with 0010 (ColumnGPIO = group C). */ SYSCFG.EXTICR1()->setEXTI(columnPin, Keyboard::Config::ColumnGPIO); From f123f1afaac010b07e84a7723398c676861ad655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 11 Apr 2019 10:02:15 +0200 Subject: [PATCH 0273/1750] [ion] Power: fix LED update when sleeping --- ion/src/device/shared/drivers/power.cpp | 57 +++++++++++++++---------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 05f5e14d6..ac2bdeb79 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -44,12 +44,6 @@ void stopConfiguration() { #endif CORTEX.SCR()->setSLEEPDEEP(true); - - Device::Board::shutdownPeripherals(); - - configWakeUp(); - - Device::Board::shutdownClocks(); } void sleepConfiguration() { @@ -70,12 +64,12 @@ void sleepConfiguration() { #endif CORTEX.SCR()->setSLEEPDEEP(false); +} - Device::Board::shutdownPeripherals(true); - - configWakeUp(); - - Device::Board::shutdownClocks(true); +KDColor updateLED() { + KDColor ledColor = USB::isPlugged() ? (Battery::isCharging() ? KDColorYellow : KDColorGreen) : KDColorBlack; + Ion::LED::setColor(ledColor); + return ledColor; } void suspend(bool checkIfPowerKeyReleased) { @@ -91,8 +85,17 @@ void suspend(bool checkIfPowerKeyReleased) { } } + /* First, shutdown all peripherals except LED. Indeed, the charging pin state + * might change when we shutdown peripherals that draw current. */ + Device::Board::shutdownPeripherals(true); while (1) { + // Update LED color according to plug and charge state + Device::Battery::initGPIO(); + Device::USB::initGPIO(); + Device::LED::init(); + isLEDActive = updateLED() != KDColorBlack; + // Configure low-power mode if (isLEDActive) { sleepConfiguration(); @@ -100,6 +103,18 @@ void suspend(bool checkIfPowerKeyReleased) { stopConfiguration(); } + // Shutdown all peripherals (except LED if active) + Device::Board::shutdownPeripherals(isLEDActive); + + /* Wake up on: + * - Power key + * - Plug/Unplug USB + * - Stop charging */ + configWakeUp(); + + // Shutdown all clocks (except the ones used by LED if active) + Device::Board::shutdownClocks(isLEDActive); + /* To enter sleep, we need to issue a WFE instruction, which waits for the * event flag to be set and then clears it. However, the event flag might * already be on. So the safest way to make sure we actually wait for a new @@ -110,23 +125,19 @@ void suspend(bool checkIfPowerKeyReleased) { asm("nop"); asm("wfe"); + /* A hardware event triggered a wake up, we determine if the device should + * wake up. We wake up when: + * - only the power key was down + * - the unplugged device was plugged */ Device::Board::initClocks(); + // Check power key Device::Keyboard::init(); Keyboard::State scan = Keyboard::scan(); - Ion::Keyboard::State OnlyPowerKeyDown = Keyboard::State(Keyboard::PowerKey); - /* Update LEDS - * if the standby mode was stopped due to a "stop charging" event, we wait - * a while to be sure that the plug state of the USB is up-to-date. */ - Device::Battery::initGPIO(); + // Check plugging state Device::USB::initGPIO(); - Device::LED::init(); - KDColor ledColor = USB::isPlugged() ? (Battery::isCharging() ? KDColorYellow : KDColorGreen) : KDColorBlack; - Ion::LED::setColor(ledColor); - isLEDActive = ledColor != KDColorBlack; - if (scan == OnlyPowerKeyDown || (!plugged && USB::isPlugged())) { // Wake up break; @@ -134,10 +145,12 @@ void suspend(bool checkIfPowerKeyReleased) { plugged = USB::isPlugged(); } + // Reset normal frequency Device::Board::sNormalFrequency = Device::Board::Frequency::High; Device::Board::initClocks(); - Device::Board::initPeripherals(); + // Update LED according to plug and charge state + updateLED(); } } From 3a57b2e7d29a822fa3b45f10e97654053cb34fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 11 Apr 2019 10:15:49 +0200 Subject: [PATCH 0274/1750] [ion] Change LED color when charging: yellow --> orange --- apps/apps_container.cpp | 2 +- ion/src/device/shared/drivers/power.cpp | 2 +- kandinsky/include/kandinsky/color.h | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index fe103064b..c22296354 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -147,7 +147,7 @@ void AppsContainer::suspend(bool checkIfPowerKeyReleased) { void AppsContainer::updateLED() { if (Ion::USB::isPlugged()) { - Ion::LED::setColor(Ion::Battery::isCharging() ? KDColorYellow : KDColorGreen); + Ion::LED::setColor(Ion::Battery::isCharging() ? KDColorOrange : KDColorGreen); } else { Ion::LED::setColor(KDColorBlack); } diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index ac2bdeb79..1f8e8fec5 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -67,7 +67,7 @@ void sleepConfiguration() { } KDColor updateLED() { - KDColor ledColor = USB::isPlugged() ? (Battery::isCharging() ? KDColorYellow : KDColorGreen) : KDColorBlack; + KDColor ledColor = USB::isPlugged() ? (Battery::isCharging() ? KDColorOrange : KDColorGreen) : KDColorBlack; Ion::LED::setColor(ledColor); return ledColor; } diff --git a/kandinsky/include/kandinsky/color.h b/kandinsky/include/kandinsky/color.h index b22d5e3b5..b4afe91fc 100644 --- a/kandinsky/include/kandinsky/color.h +++ b/kandinsky/include/kandinsky/color.h @@ -44,5 +44,6 @@ constexpr KDColor KDColorRed = KDColor::RGB24(0xFF0000); constexpr KDColor KDColorGreen = KDColor::RGB24(0x00FF00); constexpr KDColor KDColorBlue = KDColor::RGB24(0x0000FF); constexpr KDColor KDColorYellow = KDColor::RGB24(0xFFFF00); +constexpr KDColor KDColorOrange = KDColor::RGB24(0xFF2200); #endif From 98ddca8321a4519f8eab6ed062035ad96d264648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 11 Apr 2019 16:08:07 +0200 Subject: [PATCH 0275/1750] [ion/n0100] Add missing dummy file --- ion/src/device/n0100/drivers/cache.cpp | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 ion/src/device/n0100/drivers/cache.cpp diff --git a/ion/src/device/n0100/drivers/cache.cpp b/ion/src/device/n0100/drivers/cache.cpp new file mode 100644 index 000000000..fff40f118 --- /dev/null +++ b/ion/src/device/n0100/drivers/cache.cpp @@ -0,0 +1,2 @@ +/* This is a dummy file to use in ion/src/device/shared/usb/Makefile for the + * dfu.elf target. */ From 5dd009898136ae97b8a00b63af53563744778619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 11 Apr 2019 10:49:21 +0200 Subject: [PATCH 0276/1750] [apps][ion] Handle LED color concurrency between exam mode and charging state --- apps/apps_container.cpp | 12 ++---------- apps/apps_container.h | 1 - apps/exam_pop_up_controller.cpp | 1 + ion/Makefile | 1 + ion/include/ion/led.h | 2 ++ ion/src/device/shared/drivers/power.cpp | 10 ++-------- ion/src/shared/led.cpp | 22 ++++++++++++++++++++++ 7 files changed, 30 insertions(+), 19 deletions(-) create mode 100644 ion/src/shared/led.cpp diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index c22296354..6927e0574 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -145,20 +145,12 @@ void AppsContainer::suspend(bool checkIfPowerKeyReleased) { window()->redraw(true); } -void AppsContainer::updateLED() { - if (Ion::USB::isPlugged()) { - Ion::LED::setColor(Ion::Battery::isCharging() ? KDColorOrange : KDColorGreen); - } else { - Ion::LED::setColor(KDColorBlack); - } -} - bool AppsContainer::dispatchEvent(Ion::Events::Event event) { bool alphaLockWantsRedraw = updateAlphaLock(); bool didProcessEvent = false; if (event == Ion::Events::USBEnumeration || event == Ion::Events::USBPlug || event == Ion::Events::BatteryCharging) { - updateLED(); + Ion::LED::updateColorWithPlugAndCharge(); } if (event == Ion::Events::USBEnumeration) { if (Ion::USB::isPlugged()) { @@ -172,7 +164,7 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { if (switchTo(usbConnectedAppSnapshot())) { Ion::USB::DFU(); // Update LED when exciting DFU mode - updateLED(); + Ion::LED::updateColorWithPlugAndCharge(); bool switched = switchTo(activeSnapshot); assert(switched); (void) switched; // Silence compilation warning about unused variable. diff --git a/apps/apps_container.h b/apps/apps_container.h index ca1d9c6d2..5f23fbd1a 100644 --- a/apps/apps_container.h +++ b/apps/apps_container.h @@ -62,7 +62,6 @@ private: bool processEvent(Ion::Events::Event event); void resetShiftAlphaStatus(); bool updateAlphaLock(); - void updateLED(); AppsWindow m_window; EmptyBatteryWindow m_emptyBatteryWindow; diff --git a/apps/exam_pop_up_controller.cpp b/apps/exam_pop_up_controller.cpp index b72bca2b5..7b39164dd 100644 --- a/apps/exam_pop_up_controller.cpp +++ b/apps/exam_pop_up_controller.cpp @@ -63,6 +63,7 @@ ExamPopUpController::ContentView::ContentView(Responder * parentResponder) : Ion::LED::setBlinking(1000, 0.1f); } else { Ion::LED::setColor(KDColorBlack); + Ion::LED::updateColorWithPlugAndCharge(); } container->refreshPreferences(); container->activeApp()->dismissModalViewController(); diff --git a/ion/Makefile b/ion/Makefile index 61a09aaf9..a11229099 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -20,6 +20,7 @@ src += $(addprefix ion/src/shared/, \ crc32_padded.cpp \ decompress.cpp \ events.cpp \ + led.cpp \ platform_info.cpp \ storage.cpp \ ) diff --git a/ion/include/ion/led.h b/ion/include/ion/led.h index 2d4475eab..c6547bcb8 100644 --- a/ion/include/ion/led.h +++ b/ion/include/ion/led.h @@ -10,6 +10,8 @@ KDColor getColor(); void setColor(KDColor c); void setBlinking(uint16_t periodInMilliseconds, float dutyCycle); +KDColor updateColorWithPlugAndCharge(); + } } diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 1f8e8fec5..1f488e384 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -66,12 +66,6 @@ void sleepConfiguration() { CORTEX.SCR()->setSLEEPDEEP(false); } -KDColor updateLED() { - KDColor ledColor = USB::isPlugged() ? (Battery::isCharging() ? KDColorOrange : KDColorGreen) : KDColorBlack; - Ion::LED::setColor(ledColor); - return ledColor; -} - void suspend(bool checkIfPowerKeyReleased) { bool isLEDActive = Ion::LED::getColor() != KDColorBlack; bool plugged = USB::isPlugged(); @@ -94,7 +88,7 @@ void suspend(bool checkIfPowerKeyReleased) { Device::Battery::initGPIO(); Device::USB::initGPIO(); Device::LED::init(); - isLEDActive = updateLED() != KDColorBlack; + isLEDActive = LED::updateColorWithPlugAndCharge() != KDColorBlack; // Configure low-power mode if (isLEDActive) { @@ -150,7 +144,7 @@ void suspend(bool checkIfPowerKeyReleased) { Device::Board::initClocks(); Device::Board::initPeripherals(); // Update LED according to plug and charge state - updateLED(); + LED::updateColorWithPlugAndCharge(); } } diff --git a/ion/src/shared/led.cpp b/ion/src/shared/led.cpp new file mode 100644 index 000000000..d29cc8494 --- /dev/null +++ b/ion/src/shared/led.cpp @@ -0,0 +1,22 @@ +#include +#include +#include + +namespace Ion { +namespace LED { + +KDColor updateColorWithPlugAndCharge() { + KDColor ledColor = getColor(); + if (ledColor != KDColorRed) { // 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; +} + +} +} From 9eeccadd5409aca5b37c1c9f28b348355debd0e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 11 Apr 2019 15:25:45 +0200 Subject: [PATCH 0277/1750] [ion] Power: update sLastUSBPlugged & sLastBatteryCharging while asleep --- .../shared/drivers/events_keyboard_platform.h | 14 ++++++++++++++ ion/src/device/shared/drivers/power.cpp | 12 ++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 ion/src/device/shared/drivers/events_keyboard_platform.h diff --git a/ion/src/device/shared/drivers/events_keyboard_platform.h b/ion/src/device/shared/drivers/events_keyboard_platform.h new file mode 100644 index 000000000..5dc948f4a --- /dev/null +++ b/ion/src/device/shared/drivers/events_keyboard_platform.h @@ -0,0 +1,14 @@ +#ifndef ION_DEVICE_SHARED_EVENTS_KEYBOARD_PLATFORM_H +#define ION_DEVICE_SHARED_EVENTS_KEYBOARD_PLATFORM_H + +#include + +namespace Ion { +namespace Events { + +Event getPlatformEvent(); + +} +} + +#endif diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 1f488e384..af04c29d3 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -12,6 +12,7 @@ #include #include #include +#include "events_keyboard_platform.h" namespace Ion { @@ -135,6 +136,11 @@ void suspend(bool checkIfPowerKeyReleased) { if (scan == OnlyPowerKeyDown || (!plugged && USB::isPlugged())) { // Wake up break; + } else { + /* The wake up event can be an unplug event or a battery charging event. + * In both cases, we want to update static observed states like + * sLastUSBPlugged or sLastBatteryCharging. */ + Events::getPlatformEvent(); } plugged = USB::isPlugged(); } @@ -145,6 +151,12 @@ void suspend(bool checkIfPowerKeyReleased) { Device::Board::initPeripherals(); // Update LED according to plug and charge state LED::updateColorWithPlugAndCharge(); + /* If the USB has been unplugged while sleeping, the USB should have been + * soft disabled but as part of the USB peripheral was asleep, this could + * not be done before. */ + if (USB::isPlugged()) { + USB::disable(); + } } } From 73fcc71c7b99fda807825bda40871ca7f91bad95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 11 Apr 2019 15:44:59 +0200 Subject: [PATCH 0278/1750] [ion] Power: VOS (regulartor voltage scaling) can only be update when the PLL is off --- ion/src/device/shared/drivers/power.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index af04c29d3..a09c68f86 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -58,10 +58,6 @@ void sleepConfiguration() { while(!PWR.CSR1()->getODSWRDY()) { } PWR.CR()->setODEN(true); - - // Choose Voltage scale 3 - PWR.CR()->setVOS(PWR::CR::Voltage::Scale3); - while (!PWR.CSR1()->getVOSRDY()) {} #endif CORTEX.SCR()->setSLEEPDEEP(false); From 26e8e8b65729e3c2c36d293aeaf9224e993b96e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 11 Apr 2019 15:49:03 +0200 Subject: [PATCH 0279/1750] [ion] Fix PWR regs --- ion/src/device/n0101/drivers/board.cpp | 6 +++--- ion/src/device/shared/drivers/power.cpp | 2 +- ion/src/device/shared/regs/pwr.h | 12 +++++------- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/ion/src/device/n0101/drivers/board.cpp b/ion/src/device/n0101/drivers/board.cpp index 202af1705..941379285 100644 --- a/ion/src/device/n0101/drivers/board.cpp +++ b/ion/src/device/n0101/drivers/board.cpp @@ -150,16 +150,16 @@ void initClocks() { // Enable Over-drive PWR.CR()->setODEN(true); - while(!PWR.CSR1()->getODRDY()) { + while(!PWR.CSR()->getODRDY()) { } PWR.CR()->setODSWEN(true); - while(!PWR.CSR1()->getODSWRDY()) { + while(!PWR.CSR()->getODSWRDY()) { } // Choose Voltage scale 1 PWR.CR()->setVOS(PWR::CR::Voltage::Scale1); - while (!PWR.CSR1()->getVOSRDY()) {} + 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 diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index a09c68f86..43175b13d 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -55,7 +55,7 @@ void sleepConfiguration() { #if REGS_PWR_CONFIG_ADDITIONAL_FIELDS // Disable over-drive PWR.CR()->setODSWEN(false); - while(!PWR.CSR1()->getODSWRDY()) { + while(!PWR.CSR()->getODSWRDY()) { } PWR.CR()->setODEN(true); #endif diff --git a/ion/src/device/shared/regs/pwr.h b/ion/src/device/shared/regs/pwr.h index d441febe1..b6f1c4101 100644 --- a/ion/src/device/shared/regs/pwr.h +++ b/ion/src/device/shared/regs/pwr.h @@ -17,13 +17,13 @@ public: REGS_BOOL_FIELD(FPDS, 9); REGS_BOOL_FIELD(LPUDS, 10); // Called LPLVDS in N0100 REGS_BOOL_FIELD(MRUDS, 11); // Called MRLVDS in N100 -#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS enum class Voltage { Scale3 = 0x01, Scale2 = 0x10, Scale1 = 0x11 }; REGS_FIELD_W(VOS, Voltage, 15, 14); +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS REGS_BOOL_FIELD_W(ODEN, 16); REGS_BOOL_FIELD_W(ODSWEN, 17); enum class UnderDrive { @@ -34,20 +34,18 @@ public: #endif }; -#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS - class CSR1 : Register32 { + class CSR : Register32 { public: REGS_BOOL_FIELD_R(VOSRDY, 14); +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS REGS_BOOL_FIELD_R(ODRDY, 16); REGS_BOOL_FIELD_R(ODSWRDY, 17); - }; #endif + }; constexpr PWR() {}; REGS_REGISTER_AT(CR, 0x00); -#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS - REGS_REGISTER_AT(CSR1, 0x04); -#endif + REGS_REGISTER_AT(CSR, 0x04); private: constexpr uint32_t Base() const { return 0x40007000; From abfa71d06c5a39397611a9ec6e4e5018c55f868b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 11 Apr 2019 15:54:08 +0200 Subject: [PATCH 0280/1750] [ion] Add PWR regs --- ion/src/device/shared/regs/pwr.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ion/src/device/shared/regs/pwr.h b/ion/src/device/shared/regs/pwr.h index b6f1c4101..d65b6269e 100644 --- a/ion/src/device/shared/regs/pwr.h +++ b/ion/src/device/shared/regs/pwr.h @@ -14,6 +14,7 @@ public: public: REGS_BOOL_FIELD(LPDS, 0); REGS_BOOL_FIELD(PPDS, 1); + REGS_BOOL_FIELD(CSBF, 3); REGS_BOOL_FIELD(FPDS, 9); REGS_BOOL_FIELD(LPUDS, 10); // Called LPLVDS in N0100 REGS_BOOL_FIELD(MRUDS, 11); // Called MRLVDS in N100 @@ -36,6 +37,10 @@ public: class CSR : Register32 { public: + REGS_BOOL_FIELD_R(WUIF, 0); + REGS_BOOL_FIELD_R(SBF, 1); + REGS_BOOL_FIELD_W(EIWUP, 8); + REGS_BOOL_FIELD_W(BRE, 9); REGS_BOOL_FIELD_R(VOSRDY, 14); #if REGS_PWR_CONFIG_ADDITIONAL_FIELDS REGS_BOOL_FIELD_R(ODRDY, 16); @@ -43,9 +48,30 @@ public: #endif }; +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS + class CR2 : Register32 { + public: + REGS_BOOL_FIELD_W(WUPP3, 10); + REGS_BOOL_FIELD_W(WUPP2, 9); + REGS_BOOL_FIELD_W(WUPP1, 8); + REGS_BOOL_FIELD_W(CWUPF1, 0); + }; + + class CSR2 : Register32 { + public: + REGS_BOOL_FIELD_W(EWUP3, 10); + REGS_BOOL_FIELD_W(EWUP2, 9); + REGS_BOOL_FIELD_W(EWUP1, 8); + }; +#endif + constexpr PWR() {}; REGS_REGISTER_AT(CR, 0x00); REGS_REGISTER_AT(CSR, 0x04); +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS + REGS_REGISTER_AT(CR2, 0x08); + REGS_REGISTER_AT(CSR2, 0x0C); +#endif private: constexpr uint32_t Base() const { return 0x40007000; From a9c0f1a4ba937546c9a9bba206b36c29a98e95c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 11 Apr 2019 16:39:38 +0200 Subject: [PATCH 0281/1750] [ion] Board: clean standardFrequency --- ion/src/device/shared/drivers/board.cpp | 12 ++++++++++-- ion/src/device/shared/drivers/board.h | 2 ++ ion/src/device/shared/drivers/power.cpp | 14 +++----------- ion/src/device/shared/drivers/timing.cpp | 10 +--------- 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index 8ecb588d9..de6ef6442 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -18,8 +18,6 @@ namespace Board { using namespace Regs; -Frequency sNormalFrequency = Frequency::High; - void shutdown() { shutdownPeripherals(); shutdownClocks(); @@ -60,6 +58,16 @@ void shutdownPeripherals(bool keepLEDAwake) { Display::shutdown(); } +Frequency sStandardFrequency = Frequency::High; + +Frequency standardFrequency() { + return sStandardFrequency; +} + +void setStandardFrequency(Frequency f) { + sStandardFrequency = f; +} + void setClockFrequency(Frequency f) { // TODO: Update TIM3 prescaler or ARR to avoid irregular LED blinking switch (f) { diff --git a/ion/src/device/shared/drivers/board.h b/ion/src/device/shared/drivers/board.h index 372843077..96fb6eef5 100644 --- a/ion/src/device/shared/drivers/board.h +++ b/ion/src/device/shared/drivers/board.h @@ -20,6 +20,8 @@ enum class Frequency { High = 1 }; +Frequency standardFrequency(); +void setStandardFrequency(Frequency f); void setClockFrequency(Frequency f); } diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 43175b13d..26ba4d4f4 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -16,14 +16,6 @@ namespace Ion { -namespace Device { -namespace Board { - -extern Device::Board::Frequency sNormalFrequency; - -} -} - namespace Power { using namespace Device::Regs; @@ -49,8 +41,8 @@ void stopConfiguration() { void sleepConfiguration() { // Decrease HCLK frequency - Device::Board::sNormalFrequency = Device::Board::Frequency::Low; - Device::Board::setClockFrequency(Device::Board::Frequency::Low); + Device::Board::setStandardFrequency(Device::Board::Frequency::Low); + Device::Board::setClockFrequency(Device::Board::standardFrequency()); #if REGS_PWR_CONFIG_ADDITIONAL_FIELDS // Disable over-drive @@ -142,7 +134,7 @@ void suspend(bool checkIfPowerKeyReleased) { } // Reset normal frequency - Device::Board::sNormalFrequency = Device::Board::Frequency::High; + Device::Board::setStandardFrequency(Device::Board::Frequency::High); Device::Board::initClocks(); Device::Board::initPeripherals(); // Update LED according to plug and charge state diff --git a/ion/src/device/shared/drivers/timing.cpp b/ion/src/device/shared/drivers/timing.cpp index 399dc0f2d..63564d701 100644 --- a/ion/src/device/shared/drivers/timing.cpp +++ b/ion/src/device/shared/drivers/timing.cpp @@ -5,14 +5,6 @@ namespace Ion { -namespace Device { -namespace Board { - -extern Device::Board::Frequency sNormalFrequency; - -} -} - namespace Timing { using namespace Device::Timing; @@ -27,7 +19,7 @@ void msleep(uint32_t ms) { for (volatile uint32_t i=0; i Date: Thu, 11 Apr 2019 17:23:32 +0200 Subject: [PATCH 0282/1750] [ion] Make sStandardFrequency static --- ion/src/device/shared/drivers/board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/board.cpp b/ion/src/device/shared/drivers/board.cpp index de6ef6442..2b17cbd9b 100644 --- a/ion/src/device/shared/drivers/board.cpp +++ b/ion/src/device/shared/drivers/board.cpp @@ -58,7 +58,7 @@ void shutdownPeripherals(bool keepLEDAwake) { Display::shutdown(); } -Frequency sStandardFrequency = Frequency::High; +static Frequency sStandardFrequency = Frequency::High; Frequency standardFrequency() { return sStandardFrequency; From a4ef4bdd57f76d02eb88433ea02f278af5719bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 11 Apr 2019 17:24:45 +0200 Subject: [PATCH 0283/1750] [ion] In dfu.ld, keep sStandardFrequency symbol --- ion/src/device/shared/usb/dfu.ld | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ion/src/device/shared/usb/dfu.ld b/ion/src/device/shared/usb/dfu.ld index 66a250f74..a9f935286 100644 --- a/ion/src/device/shared/usb/dfu.ld +++ b/ion/src/device/shared/usb/dfu.ld @@ -42,6 +42,11 @@ SECTIONS { *(.rodata.*) } >RAM_BUFFER + .data : { + /* We need to keep this symbol. */ + *(.data._ZN3Ion6Device5BoardL18sStandardFrequencyE) + } >RAM_BUFFER + /DISCARD/ : { /* For now, we do not need .bss and .data sections. This allows us to simply * skip any rt0-style initialization and jump straight into the PollAndReset From 26bb33555bc297c4870108d7d4ee88bcbcf0d050 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 11 Apr 2019 17:25:43 +0200 Subject: [PATCH 0284/1750] [ion] Increase RAM_BUFFER space in dfu.ld --- ion/src/device/shared/usb/dfu.ld | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/shared/usb/dfu.ld b/ion/src/device/shared/usb/dfu.ld index a9f935286..0599dc581 100644 --- a/ion/src/device/shared/usb/dfu.ld +++ b/ion/src/device/shared/usb/dfu.ld @@ -22,7 +22,7 @@ EPSILON_STACK_END = 0x20000000 + 256K - 32K; MEMORY { - RAM_BUFFER (rw) : ORIGIN = EPSILON_STACK_END, LENGTH = 8K + RAM_BUFFER (rw) : ORIGIN = EPSILON_STACK_END, LENGTH = 9K } /* The DFU needs to know to which address it should jump to after a leave From 347057e9ac53acaf1142dc281742e2585a09ed69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 12 Apr 2019 10:16:26 +0200 Subject: [PATCH 0285/1750] [ion/n0100] Calibrate msleep --- ion/src/device/n0100/drivers/config/timing.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ion/src/device/n0100/drivers/config/timing.h b/ion/src/device/n0100/drivers/config/timing.h index 6856481b0..87f441fab 100644 --- a/ion/src/device/n0100/drivers/config/timing.h +++ b/ion/src/device/n0100/drivers/config/timing.h @@ -10,7 +10,7 @@ namespace Config { #warning N100-calibration //TODO: calibrate LoopsPerMillisecond now that we decrease AHB clock while msleeping -constexpr static int LoopsPerMillisecond = 8852; +constexpr static int LoopsPerMillisecond = 2200; constexpr static int LoopsPerMicrosecond = 9; // CPU clock is 96 MHz, and systick clock source is divided by 8 // To get 1 ms systick overflow we need to reset it to From 1ba7379021e322dcd3234a39fa3c7c88ad13fc4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 12 Apr 2019 10:20:46 +0200 Subject: [PATCH 0286/1750] [ion] External flash: fix deep sleep and release sleep deep --- ion/src/device/shared/drivers/external_flash.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ion/src/device/shared/drivers/external_flash.cpp b/ion/src/device/shared/drivers/external_flash.cpp index dd3cbdcdb..a556dc278 100644 --- a/ion/src/device/shared/drivers/external_flash.cpp +++ b/ion/src/device/shared/drivers/external_flash.cpp @@ -284,6 +284,10 @@ static void initQSPI() { static QUADSPI::CCR::OperatingMode sOperatingMode = QUADSPI::CCR::OperatingMode::Single; static void initChip() { + // Release sleep deep + send_command(Command::ReleaseDeepPowerDown, sOperatingMode); + Timing::usleep(3); + /* The chip initially expects commands in SPI mode. We need to use SPI to tell * it to switch to QPI. */ if (sOperatingMode == QUADSPI::CCR::OperatingMode::Single && DefaultOperatingMode == QUADSPI::CCR::OperatingMode::Quad) { @@ -338,7 +342,7 @@ static void shutdownChip() { Ion::Timing::usleep(30); // Sleep deep - send_command(Command::DeepPowerDown); + send_command(Command::DeepPowerDown, sOperatingMode); Timing::usleep(3); } From b13500699b400485f590a5507cae9fb03b592049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 12 Apr 2019 10:24:26 +0200 Subject: [PATCH 0287/1750] [apps] Typo --- apps/apps_container.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index 6927e0574..bbd8f1ca9 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -163,7 +163,7 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { updateBatteryState(); if (switchTo(usbConnectedAppSnapshot())) { Ion::USB::DFU(); - // Update LED when exciting DFU mode + // Update LED when exiting DFU mode Ion::LED::updateColorWithPlugAndCharge(); bool switched = switchTo(activeSnapshot); assert(switched); From 3fa902f8149f9ff162ccecbd99907c54f6bdb131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 12 Apr 2019 10:25:05 +0200 Subject: [PATCH 0288/1750] [ion] Power: implement standby --- ion/include/ion/power.h | 1 + ion/src/device/shared/drivers/keyboard.cpp | 5 ++ ion/src/device/shared/drivers/power.cpp | 59 ++++++++++++++++------ 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/ion/include/ion/power.h b/ion/include/ion/power.h index 937e9649b..ea540c229 100644 --- a/ion/include/ion/power.h +++ b/ion/include/ion/power.h @@ -5,6 +5,7 @@ namespace Ion { namespace Power { void suspend(bool checkIfPowerKeyReleased = false); +void standby(); } } diff --git a/ion/src/device/shared/drivers/keyboard.cpp b/ion/src/device/shared/drivers/keyboard.cpp index b3751d0e1..cde3fa09f 100644 --- a/ion/src/device/shared/drivers/keyboard.cpp +++ b/ion/src/device/shared/drivers/keyboard.cpp @@ -79,6 +79,11 @@ namespace Keyboard { using namespace Regs; void init() { + /* PA0 pin is also used as the wake up pin of the standby mode. It has to be + * unable to be used in output mode, open-drain for the keyboard. */ + PWR.CSR2()->setEWUP1(false); // Disable PA0 as wakeup pin + PWR.CR2()->setCWUPF1(true); // Clear wakeup pin flag for PA0 + for (uint8_t i=0; isetMode(pin, GPIO::MODER::Mode::Output); diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index 26ba4d4f4..acb387d71 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -55,17 +56,53 @@ void sleepConfiguration() { CORTEX.SCR()->setSLEEPDEEP(false); } +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) + + 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) + CORTEX.SCR()->setSLEEPDEEP(true); // Allow Cortex-M7 deepsleep state +} + +void waitUntilPowerKeyReleased() { + /* Wait until power is released to avoid restarting just after suspending */ + bool isPowerDown = true; + while (isPowerDown) { + Keyboard::State scan = Keyboard::scan(); + isPowerDown = scan.keyDown(Keyboard::PowerKey); + } +} + +void enterLowPowerMode() { + /* To enter sleep, we need to issue a WFE instruction, which waits for the + * event flag to be set and then clears it. However, the event flag might + * already be on. So the safest way to make sure we actually wait for a new + * event is to force the event flag to on (SEV instruction), use a first WFE + * to clear it, and then a second WFE to wait for a _new_ event. */ + asm("sev"); + asm("wfe"); + asm("nop"); + asm("wfe"); +} + +void standby() { + waitUntilPowerKeyReleased(); + standbyConfiguration(); + Device::Board::shutdown(); + enterLowPowerMode(); + Device::Reset::core(); +} + void suspend(bool checkIfPowerKeyReleased) { bool isLEDActive = Ion::LED::getColor() != KDColorBlack; bool plugged = USB::isPlugged(); if (checkIfPowerKeyReleased) { - /* Wait until power is released to avoid restarting just after suspending */ - bool isPowerDown = true; - while (isPowerDown) { - Keyboard::State scan = Keyboard::scan(); - isPowerDown = scan.keyDown(Keyboard::PowerKey); - } + waitUntilPowerKeyReleased(); } /* First, shutdown all peripherals except LED. Indeed, the charging pin state @@ -98,15 +135,7 @@ void suspend(bool checkIfPowerKeyReleased) { // Shutdown all clocks (except the ones used by LED if active) Device::Board::shutdownClocks(isLEDActive); - /* To enter sleep, we need to issue a WFE instruction, which waits for the - * event flag to be set and then clears it. However, the event flag might - * already be on. So the safest way to make sure we actually wait for a new - * event is to force the event flag to on (SEV instruction), use a first WFE - * to clear it, and then a second WFE to wait for a _new_ event. */ - asm("sev"); - asm("wfe"); - asm("nop"); - asm("wfe"); + enterLowPowerMode(); /* A hardware event triggered a wake up, we determine if the device should * wake up. We wake up when: From ea5653538472db28c18c6219a2a001bc2d512d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 12 Apr 2019 11:12:00 +0200 Subject: [PATCH 0289/1750] [apps/apps_container] Redraw the window after displaying exam pop up --- apps/apps_container.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/apps_container.cpp b/apps/apps_container.cpp index bbd8f1ca9..063406089 100644 --- a/apps/apps_container.cpp +++ b/apps/apps_container.cpp @@ -206,10 +206,12 @@ bool AppsContainer::dispatchEvent(Ion::Events::Event event) { } bool AppsContainer::processEvent(Ion::Events::Event event) { + // Warning: if the window is dirtied, you need to call window()->redraw() if (event == Ion::Events::USBPlug) { if (Ion::USB::isPlugged()) { if (GlobalPreferences::sharedGlobalPreferences()->examMode() == GlobalPreferences::ExamMode::Activate) { displayExamModePopUp(false); + window()->redraw(); } else { Ion::USB::enable(); } From 5df04affd6e159167dbeb01e8f40a6bb3cf726bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 12 Apr 2019 11:33:22 +0200 Subject: [PATCH 0290/1750] [ion/n0100] Fix DFU --- ion/src/device/shared/drivers/flash.cpp | 4 ++-- ion/src/device/shared/drivers/flash.h | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ion/src/device/shared/drivers/flash.cpp b/ion/src/device/shared/drivers/flash.cpp index dbd36e71f..2ae24eb97 100644 --- a/ion/src/device/shared/drivers/flash.cpp +++ b/ion/src/device/shared/drivers/flash.cpp @@ -213,7 +213,7 @@ static void flash_memcpy(uint8_t * destination, uint8_t * source, size_t length) } int SectorAtAddress(uint32_t address) { - for (int i=0; i= Config::SectorAddresses[i] && address < Config::SectorAddresses[i+1]) { return i; } @@ -231,7 +231,7 @@ void MassErase() { } void EraseSector(int i) { - assert(i >= 0 && i < NumberOfSectors); + assert(i >= 0 && i < Config::NumberOfSectors); open(); FLASH.CR()->setSNB(i); FLASH.CR()->setSER(true); diff --git a/ion/src/device/shared/drivers/flash.h b/ion/src/device/shared/drivers/flash.h index 25935c989..ebb3ffc4f 100644 --- a/ion/src/device/shared/drivers/flash.h +++ b/ion/src/device/shared/drivers/flash.h @@ -10,7 +10,6 @@ namespace Flash { void MassErase(); -constexpr int NumberOfSectors = 4; int SectorAtAddress(uint32_t address); void EraseSector(int i); From 25ba28a03e835d85886a6dfc214d274d9fbf9dff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 12 Apr 2019 11:34:00 +0200 Subject: [PATCH 0291/1750] [ion] Keyboard: add missing macro to build N0100 --- ion/src/device/shared/drivers/keyboard.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ion/src/device/shared/drivers/keyboard.cpp b/ion/src/device/shared/drivers/keyboard.cpp index cde3fa09f..e05094666 100644 --- a/ion/src/device/shared/drivers/keyboard.cpp +++ b/ion/src/device/shared/drivers/keyboard.cpp @@ -79,10 +79,12 @@ namespace Keyboard { using namespace Regs; void init() { +#if REGS_PWR_CONFIG_ADDITIONAL_FIELDS /* PA0 pin is also used as the wake up pin of the standby mode. It has to be * unable to be used in output mode, open-drain for the keyboard. */ PWR.CSR2()->setEWUP1(false); // Disable PA0 as wakeup pin PWR.CR2()->setCWUPF1(true); // Clear wakeup pin flag for PA0 +#endif for (uint8_t i=0; i Date: Fri, 12 Apr 2019 11:39:43 +0200 Subject: [PATCH 0292/1750] [ion] Power: add missing macro to build N0100 --- ion/src/device/shared/drivers/power.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ion/src/device/shared/drivers/power.cpp b/ion/src/device/shared/drivers/power.cpp index acb387d71..b4616e10c 100644 --- a/ion/src/device/shared/drivers/power.cpp +++ b/ion/src/device/shared/drivers/power.cpp @@ -62,9 +62,12 @@ void standbyConfiguration() { PWR.CSR()->setBRE(false); // Unable back up RAM (lower power consumption in standby) PWR.CSR()->setEIWUP(false); // Unable RTC (lower power consumption in standby) +#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 } From 43725bcac294cfbf5cb16c02e3c162f56b1072ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Fri, 12 Apr 2019 14:40:48 +0200 Subject: [PATCH 0293/1750] [ion] Keyboard: specify different keyboard layouts per platform/model --- ion/Makefile | 5 ++++ ion/include/ion/keyboard.h | 22 ++-------------- .../ion/keyboard/layout_B2/keyboard_layout.h | 25 +++++++++++++++++++ .../ion/keyboard/layout_B3/keyboard_layout.h | 25 +++++++++++++++++++ scripts/platform.blackbox.mak | 1 + scripts/platform.device.n0100.mak | 1 + scripts/platform.device.n0101.mak | 1 + scripts/platform.emscripten.mak | 1 + scripts/platform.sdl.mak | 1 + scripts/platform.simulator.mak | 1 + 10 files changed, 63 insertions(+), 20 deletions(-) create mode 100644 ion/include/ion/keyboard/layout_B2/keyboard_layout.h create mode 100644 ion/include/ion/keyboard/layout_B3/keyboard_layout.h diff --git a/ion/Makefile b/ion/Makefile index a11229099..363201c5f 100644 --- a/ion/Makefile +++ b/ion/Makefile @@ -6,6 +6,11 @@ endif SFLAGS += -Iion/include -DKD_CONFIG_H=1 +ifndef ION_KEYBOARD_LAYOUT + $(error platform.mak should define ION_KEYBOARD_LAYOUT) +endif +SFLAGS += -Iion/include/ion/keyboard/$(ION_KEYBOARD_LAYOUT) + include ion/src/$(PLATFORM)/Makefile include ion/src/shared/tools/Makefile diff --git a/ion/include/ion/keyboard.h b/ion/include/ion/keyboard.h index 8e1d1cf74..3db6045c9 100644 --- a/ion/include/ion/keyboard.h +++ b/ion/include/ion/keyboard.h @@ -6,29 +6,11 @@ extern "C" { #include } +#include + namespace Ion { namespace Keyboard { -enum class Key : uint8_t { - A1=0, A2=1, A3=2, A4=3, A5=4, A6=5, - B1=6, B2=7, B3=8, /*B4=9, B5=10, B6=11, */ - C1=12, C2=13, C3=14, C4=15, C5=16, C6=17, - D1=18, D2=19, D3=20, D4=21, D5=22, D6=23, - E1=24, E2=25, E3=26, E4=27, E5=28, E6=29, - F1=30, F2=31, F3=32, F4=33, F5=34, // F6=35, - G1=36, G2=37, G3=38, G4=39, G5=40, // G6=41, - H1=42, H2=43, H3=44, H4=45, H5=46, // H6=47, - I1=48, I2=49, I3=50, I4=51, I5=52, // I6=53, - None = 54 -}; - -#define MODEL_N0101 1 -#ifdef MODEL_N0101 -constexpr Key PowerKey = Key::B3; -#else -constexpr Key PowerKey = Key::B2; -#endif - constexpr Key ValidKeys[] = { Key::A1, Key::A2, Key::A3, Key::A4, Key::A5, Key::A6, Key::B1, PowerKey, Key::C1, Key::C2, Key::C3, Key::C4, Key::C5, Key::C6, Key::D1, Key::D2, diff --git a/ion/include/ion/keyboard/layout_B2/keyboard_layout.h b/ion/include/ion/keyboard/layout_B2/keyboard_layout.h new file mode 100644 index 000000000..42dc1e66d --- /dev/null +++ b/ion/include/ion/keyboard/layout_B2/keyboard_layout.h @@ -0,0 +1,25 @@ +#ifndef ION_KEYBOARD_LAYOUT_B2_H +#define ION_KEYBOARD_LAYOUT_B2_H + +namespace Ion { +namespace Keyboard { + +enum class Key : uint8_t { + A1=0, A2=1, A3=2, A4=3, A5=4, A6=5, + B1=6, B2=7, /*B3=8, B4=9, B5=10, B6=11, */ + C1=12, C2=13, C3=14, C4=15, C5=16, C6=17, + D1=18, D2=19, D3=20, D4=21, D5=22, D6=23, + E1=24, E2=25, E3=26, E4=27, E5=28, E6=29, + F1=30, F2=31, F3=32, F4=33, F5=34, // F6=35, + G1=36, G2=37, G3=38, G4=39, G5=40, // G6=41, + H1=42, H2=43, H3=44, H4=45, H5=46, // H6=47, + I1=48, I2=49, I3=50, I4=51, I5=52, // I6=53, + None = 54 +}; + +constexpr Key PowerKey = Key::B2; + +} +} + +#endif diff --git a/ion/include/ion/keyboard/layout_B3/keyboard_layout.h b/ion/include/ion/keyboard/layout_B3/keyboard_layout.h new file mode 100644 index 000000000..f519e35cd --- /dev/null +++ b/ion/include/ion/keyboard/layout_B3/keyboard_layout.h @@ -0,0 +1,25 @@ +#ifndef ION_KEYBOARD_LAYOUT_B3_H +#define ION_KEYBOARD_LAYOUT_B3_H + +namespace Ion { +namespace Keyboard { + +enum class Key : uint8_t { + A1=0, A2=1, A3=2, A4=3, A5=4, A6=5, + B1=6, /*B2=7,*/ B3=8, /*B4=9, B5=10, B6=11, */ + C1=12, C2=13, C3=14, C4=15, C5=16, C6=17, + D1=18, D2=19, D3=20, D4=21, D5=22, D6=23, + E1=24, E2=25, E3=26, E4=27, E5=28, E6=29, + F1=30, F2=31, F3=32, F4=33, F5=34, // F6=35, + G1=36, G2=37, G3=38, G4=39, G5=40, // G6=41, + H1=42, H2=43, H3=44, H4=45, H5=46, // H6=47, + I1=48, I2=49, I3=50, I4=51, I5=52, // I6=53, + None = 54 +}; + +constexpr Key PowerKey = Key::B3; + +} +} + +#endif diff --git a/scripts/platform.blackbox.mak b/scripts/platform.blackbox.mak index b12759f2b..d678f9501 100644 --- a/scripts/platform.blackbox.mak +++ b/scripts/platform.blackbox.mak @@ -1,5 +1,6 @@ TOOLCHAIN ?= host-gcc USE_LIBA ?= 0 +ION_KEYBOARD_LAYOUT = layout_B2 EXE = bin EPSILON_ONBOARDING_APP = 0 diff --git a/scripts/platform.device.n0100.mak b/scripts/platform.device.n0100.mak index 9c36368ed..8f41cc583 100644 --- a/scripts/platform.device.n0100.mak +++ b/scripts/platform.device.n0100.mak @@ -1 +1,2 @@ TOOLCHAIN ?= arm-gcc-m4f +ION_KEYBOARD_LAYOUT = layout_B2 diff --git a/scripts/platform.device.n0101.mak b/scripts/platform.device.n0101.mak index b9b31d0f1..8767597e6 100644 --- a/scripts/platform.device.n0101.mak +++ b/scripts/platform.device.n0101.mak @@ -1 +1,2 @@ TOOLCHAIN ?= arm-gcc-m7f +ION_KEYBOARD_LAYOUT = layout_B3 diff --git a/scripts/platform.emscripten.mak b/scripts/platform.emscripten.mak index dcbb6bff5..a19578a05 100644 --- a/scripts/platform.emscripten.mak +++ b/scripts/platform.emscripten.mak @@ -1,5 +1,6 @@ TOOLCHAIN = emscripten USE_LIBA = 0 +ION_KEYBOARD_LAYOUT = layout_B2 EXE = js EPSILON_ONBOARDING_APP = 0 EPSILON_GETOPT = 1 diff --git a/scripts/platform.sdl.mak b/scripts/platform.sdl.mak index 43e80e04e..0624babe6 100644 --- a/scripts/platform.sdl.mak +++ b/scripts/platform.sdl.mak @@ -1,4 +1,5 @@ USE_LIBA = 0 +ION_KEYBOARD_LAYOUT = layout_B2 EPSILON_ONBOARDING_APP = 0 EPSILON_GETOPT = 1 diff --git a/scripts/platform.simulator.mak b/scripts/platform.simulator.mak index fc42f9a21..4bc3f4186 100644 --- a/scripts/platform.simulator.mak +++ b/scripts/platform.simulator.mak @@ -3,6 +3,7 @@ ifeq ($(OS),Windows_NT) TOOLCHAIN = mingw endif USE_LIBA = 0 +ION_KEYBOARD_LAYOUT = layout_B2 EXE = elf EPSILON_ONBOARDING_APP = 0 SFLAGS += -fPIE From 0f9c1346f81d2f0709043c763e1e983b33515928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 12 Apr 2019 15:11:18 +0200 Subject: [PATCH 0294/1750] [hardware_test] VBlank test --- apps/hardware_test/Makefile | 1 + apps/hardware_test/app.cpp | 6 ++- apps/hardware_test/app.h | 2 + apps/hardware_test/vblank_test_controller.cpp | 53 +++++++++++++++++++ apps/hardware_test/vblank_test_controller.h | 42 +++++++++++++++ 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 apps/hardware_test/vblank_test_controller.cpp create mode 100644 apps/hardware_test/vblank_test_controller.h diff --git a/apps/hardware_test/Makefile b/apps/hardware_test/Makefile index f6a490b0d..de664ad2c 100644 --- a/apps/hardware_test/Makefile +++ b/apps/hardware_test/Makefile @@ -12,4 +12,5 @@ app_src += $(addprefix apps/hardware_test/,\ screen_test_controller.cpp \ screen_test_controller.cpp \ serial_number_controller.cpp \ + vblank_test_controller.cpp \ ) diff --git a/apps/hardware_test/app.cpp b/apps/hardware_test/app.cpp index ce18a656c..97452b2c2 100644 --- a/apps/hardware_test/app.cpp +++ b/apps/hardware_test/app.cpp @@ -28,16 +28,18 @@ App::WizardViewController::WizardViewController(Responder * parentResponder) : m_screenTestController(this), m_ledTestController(this), m_batteryTestController(this), - m_serialNumberController(this) + m_serialNumberController(this), + m_vBlankTestController(this) { } int App::WizardViewController::numberOfChildren() { - return 5; + return 6; } ViewController * App::WizardViewController::childAtIndex(int i) { ViewController * children[] = { + &m_vBlankTestController, &m_keyboardController, &m_screenTestController, &m_ledTestController, diff --git a/apps/hardware_test/app.h b/apps/hardware_test/app.h index 6d5047ffc..58a88cdd7 100644 --- a/apps/hardware_test/app.h +++ b/apps/hardware_test/app.h @@ -7,6 +7,7 @@ #include "led_test_controller.h" #include "battery_test_controller.h" #include "serial_number_controller.h" +#include "vblank_test_controller.h" class AppsContainer; @@ -32,6 +33,7 @@ private: LEDTestController m_ledTestController; BatteryTestController m_batteryTestController; SerialNumberController m_serialNumberController; + VBlankTestController m_vBlankTestController; }; App(Container * container, Snapshot * snapshot); diff --git a/apps/hardware_test/vblank_test_controller.cpp b/apps/hardware_test/vblank_test_controller.cpp new file mode 100644 index 000000000..94b38302d --- /dev/null +++ b/apps/hardware_test/vblank_test_controller.cpp @@ -0,0 +1,53 @@ +#include "vblank_test_controller.h" +#include "../apps_container.h" + +using namespace Poincare; + +namespace HardwareTest { + +bool VBlankTestController::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::OK) { + if (strcmp(m_view.vBlankStateTextView()->text(), k_vBlankOKText) == 0) { + // Handled in WizardViewController + return false; + } + assert(strcmp(m_view.vBlankStateTextView()->text(), k_vBlankLaunchTest) == 0); + m_view.setColor(KDColorRed); + m_view.vBlankStateTextView()->setText(k_vBlankFailTest); + static_cast(const_cast(app()->container()))->redrawWindow(); + /* We redraw the window with "VBLANK FAIL" before lauching the test. This + * test might end up in an infinite loop, in which case "VBLANK fail" keeps + * being displayed. If the test succeeds, the screen should change very + * quickly to "VBLANK OK". */ + for (int i=0; i<6; i++) { + Ion::Display::waitForVBlank(); + } + m_view.setColor(KDColorGreen); + m_view.vBlankStateTextView()->setText(k_vBlankOKText); + } + return true; +} + +void VBlankTestController::viewWillAppear() { + for (int i=0; i<6; i++) { + Ion::Display::waitForVBlank(); + } + m_view.vBlankStateTextView()->setText(k_vBlankLaunchTest); +} + +VBlankTestController::ContentView::ContentView() : + SolidColorView(KDColorWhite), + m_vBlankStateView(KDFont::LargeFont) +{ +} + +void VBlankTestController::ContentView::setColor(KDColor color) { + SolidColorView::setColor(color); + m_vBlankStateView.setBackgroundColor(color); +} + +void VBlankTestController::ContentView::layoutSubviews() { + m_vBlankStateView.setFrame(KDRect(0, 0, Ion::Display::Width, Ion::Display::Height)); +} + +} diff --git a/apps/hardware_test/vblank_test_controller.h b/apps/hardware_test/vblank_test_controller.h new file mode 100644 index 000000000..b52fdc95b --- /dev/null +++ b/apps/hardware_test/vblank_test_controller.h @@ -0,0 +1,42 @@ +#ifndef HARDWARE_TEST_VBLANK_TEST_CONTROLLER_H +#define HARDWARE_TEST_VBLANK_TEST_CONTROLLER_H + +#include +#include + +namespace HardwareTest { + +class VBlankTestController : public ViewController { +public: + VBlankTestController(Responder * parentResponder) : + ViewController(parentResponder), + m_view() + {} + View * view() override { return &m_view; } + bool handleEvent(Ion::Events::Event event) override; + void viewWillAppear() override; +private: + class ContentView : public SolidColorView { + public: + ContentView(); + BufferTextView * vBlankStateTextView() { return &m_vBlankStateView; } + void setColor(KDColor color) override; + private: + void layoutSubviews() override; + int numberOfSubviews() const override { return 1; } + View * subviewAtIndex(int index) override { + assert(index == 0); + return &m_vBlankStateView; + } + BufferTextView m_vBlankStateView; + }; + constexpr static const char * k_vBlankOKText = "VBLANK: OK"; + constexpr static const char * k_vBlankFailTest = "VBLANK: FAIL"; + constexpr static const char * k_vBlankLaunchTest = "Launch VBLANK test"; + ContentView m_view; +}; + +} + +#endif + From 8106ba6371ddbabeb7f189264f10daee678833dd Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 31 Oct 2018 10:28:57 +0100 Subject: [PATCH 0295/1750] [kandinsky] Assume UTF-8 encoding for strings --- kandinsky/include/kandinsky/font.h | 67 +++++---- .../include/kandinsky/unicode/codepoint.h | 23 ++++ .../include/kandinsky/unicode/utf8decoder.h | 14 ++ kandinsky/src/context_text.cpp | 35 +++-- kandinsky/src/font.cpp | 130 +++++++++++++++--- kandinsky/src/unicode/utf8decoder.cpp | 26 ++++ 6 files changed, 237 insertions(+), 58 deletions(-) create mode 100644 kandinsky/include/kandinsky/unicode/codepoint.h create mode 100644 kandinsky/include/kandinsky/unicode/utf8decoder.h create mode 100644 kandinsky/src/unicode/utf8decoder.cpp diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index a4ae9c786..5d2a571e9 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -2,13 +2,15 @@ #define KANDINSKY_FONT_H #include +#include #include #include +#include #include "palette.h" class KDFont { private: - static constexpr int k_bitsPerPixel = 4; + static constexpr int k_bitsPerPixel = 4; // TODO: Should be generated by the rasterizer static const KDFont privateLargeFont; static const KDFont privateSmallFont; public: @@ -17,38 +19,57 @@ public: KDSize stringSize(const char * text) const; + union GlyphBuffer { + public: + GlyphBuffer() {} // Don't initialize either buffer + KDColor * colorBuffer() { return m_colors; } + uint8_t * greyscaleBuffer() { return m_greyscales; } + uint8_t * secondaryGreyscaleBuffer() { return m_greyscales + k_maxGlyphPixelCount; } + private: + private: + static constexpr int k_maxGlyphPixelCount = 180; //TODO: Should be generated by the rasterizer + uint8_t m_greyscales[2*k_maxGlyphPixelCount]; + KDColor m_colors[k_maxGlyphPixelCount]; + }; + + using GlyphIndex = uint8_t; + class CodepointIndexPair { + public: + constexpr CodepointIndexPair(Codepoint c, GlyphIndex i) : m_codepoint(c), m_glyphIndex(i) {} + Codepoint codepoint() const { return m_codepoint; } + GlyphIndex glyphIndex() const { return m_glyphIndex; } + private: + Codepoint m_codepoint; + GlyphIndex m_glyphIndex; + }; + GlyphIndex indexForCodepoint(Codepoint c) const; + + void setGlyphGreyscalesForCodepoint(Codepoint codepoint, GlyphBuffer * glyphBuffer) const; + void accumulateGlyphGreyscalesForCodepoint(Codepoint codepoint, GlyphBuffer * glyphBuffer) const; + using RenderPalette = KDPalette<(1<; - void fetchGlyphForChar(char c, const RenderPalette * renderPalette, KDColor * pixelBuffer) const; + void colorizeGlyphBuffer(const RenderPalette * renderPalette, GlyphBuffer * glyphBuffer) const; + RenderPalette renderPalette(KDColor textColor, KDColor backgroundColor) const { return RenderPalette::Gradient(textColor, backgroundColor); } KDSize glyphSize() const { return m_glyphSize; } - constexpr KDFont(KDCoordinate glyphWidth, KDCoordinate glyphHeight, const uint16_t * glyphDataOffset, const uint8_t * data) : - m_glyphSize(glyphWidth, glyphHeight), m_glyphDataOffset(glyphDataOffset), m_data(data) { } + constexpr KDFont(size_t tableLength, const CodepointIndexPair * table, KDCoordinate glyphWidth, KDCoordinate glyphHeight, const uint16_t * glyphDataOffset, const uint8_t * data) : + m_tableLength(tableLength), m_table(table), m_glyphSize(glyphWidth, glyphHeight), m_glyphDataOffset(glyphDataOffset), m_data(data) { } private: - void fetchGreyscaleGlyphForChar(char c, uint8_t * greyscaleBuffer) const; + void fetchGreyscaleGlyphAtIndex(GlyphIndex index, uint8_t * greyscaleBuffer) const; - const uint8_t * compressedGlyphData(char c) const { - return m_data + m_glyphDataOffset[charAsIndex(c)]; - } - uint16_t compressedGlyphDataSize(char c) const { - return m_glyphDataOffset[charAsIndex(c)+1] - m_glyphDataOffset[charAsIndex(c)]; - } - uint8_t charAsIndex(char c) const { - // FIXME: This is most likely false for chars greater than 127 - return static_cast(c) - k_magicCharOffsetValue; - } - int signedCharAsIndex(char c) const { - int cInt = c; - if (cInt < 0) { - /* A char casted as int takes its value between -127 and +128, but we want - * a positive value. -127 is thus 129, -126 is 130, etc. */ - cInt=128+(cInt-(-127)+1); - } - return cInt - k_magicCharOffsetValue; + const uint8_t * compressedGlyphData(GlyphIndex index) const { + return m_data + m_glyphDataOffset[index]; } + uint16_t compressedGlyphDataSize(GlyphIndex index) const { + return m_glyphDataOffset[index+1] - m_glyphDataOffset[index]; + } + + size_t m_tableLength; + const CodepointIndexPair * m_table; KDSize m_glyphSize; const uint16_t * m_glyphDataOffset; const uint8_t * m_data; diff --git a/kandinsky/include/kandinsky/unicode/codepoint.h b/kandinsky/include/kandinsky/unicode/codepoint.h new file mode 100644 index 000000000..323647cfe --- /dev/null +++ b/kandinsky/include/kandinsky/unicode/codepoint.h @@ -0,0 +1,23 @@ +#ifndef KANDINSKY_UNICODE_CODEPOINT_H +#define KANDINSKY_UNICODE_CODEPOINT_H + +#include + +class Codepoint { +public: + constexpr Codepoint(uint32_t c) : m_code(c) {} + operator uint16_t() const { return m_code; } + + + bool isCombining() const { + return (m_code >= 0x300 && m_code <= 0x036F); + } +private: + uint32_t m_code; +}; + +static constexpr Codepoint Null = 0x0; +static constexpr Codepoint Tabulation = 0x9; +static constexpr Codepoint LineFeed = 0xA; + +#endif diff --git a/kandinsky/include/kandinsky/unicode/utf8decoder.h b/kandinsky/include/kandinsky/unicode/utf8decoder.h new file mode 100644 index 000000000..f9badc77f --- /dev/null +++ b/kandinsky/include/kandinsky/unicode/utf8decoder.h @@ -0,0 +1,14 @@ +#ifndef KANDINSKY_UNICODE_UTF8DECODER_H +#define KANDINSKY_UNICODE_UTF8DECODER_H + +#include "codepoint.h" + +class UTF8Decoder { +public: + UTF8Decoder(const char * string) : m_string(string) {} + Codepoint nextCodepoint(); +private: + const char * m_string; +}; + +#endif diff --git a/kandinsky/src/context_text.cpp b/kandinsky/src/context_text.cpp index 8d401d773..6a4282f90 100644 --- a/kandinsky/src/context_text.cpp +++ b/kandinsky/src/context_text.cpp @@ -1,35 +1,44 @@ #include #include #include +#include -constexpr int maxGlyphPixelCount = 180; constexpr static int k_tabCharacterWidth = 4; KDPoint KDContext::drawString(const char * text, KDPoint p, const KDFont * font, KDColor textColor, KDColor backgroundColor, int maxLength) { - KDPoint position = p; KDSize glyphSize = font->glyphSize(); KDFont::RenderPalette palette = font->renderPalette(textColor, backgroundColor); - KDColor glyph[maxGlyphPixelCount]; + KDFont::GlyphBuffer glyphBuffer; - const char * end = text + maxLength; - while(*text != 0 && text != end) { - if (*text == '\n') { + UTF8Decoder decoder(text); + Codepoint codepoint = decoder.nextCodepoint(); + while (codepoint != Null) { + if (codepoint == LineFeed) { position = KDPoint(0, position.y() + glyphSize.height()); - } else if (*text == '\t') { + codepoint = decoder.nextCodepoint(); + } else if (codepoint == Tabulation) { position = position.translatedBy(KDPoint(k_tabCharacterWidth * glyphSize.width(), 0)); + codepoint = decoder.nextCodepoint(); } else { - // Fetch and draw glyph for current char - font->fetchGlyphForChar(*text, &palette, glyph); + assert(!codepoint.isCombining()); + font->setGlyphGreyscalesForCodepoint(codepoint, &glyphBuffer); + codepoint = decoder.nextCodepoint(); + while (codepoint.isCombining()) { + font->accumulateGlyphGreyscalesForCodepoint(codepoint, &glyphBuffer); + codepoint = decoder.nextCodepoint(); + } + font->colorizeGlyphBuffer(&palette, &glyphBuffer); + // Flush accumulated content fillRectWithPixels( - KDRect(position, glyphSize.width(), glyphSize.height()), - glyph, - glyph // It's OK to trash the content of glyph since we'll re-fetch it for the next char anyway + KDRect(position, glyphSize), + glyphBuffer.colorBuffer(), + glyphBuffer.colorBuffer() // It's OK to trash the content of the color buffer since we'll re-fetch it for the next char anyway ); position = position.translatedBy(KDPoint(glyphSize.width(), 0)); } - text++; } + return position; } diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index 38f947d7e..2e5a88321 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -1,6 +1,7 @@ #include #include #include +#include constexpr static int k_tabCharacterWidth = 4; @@ -9,39 +10,48 @@ KDSize KDFont::stringSize(const char * text) const { return KDSizeZero; } KDSize stringSize = KDSize(0, m_glyphSize.height()); - while (*text != 0) { + + UTF8Decoder decoder(text); + Codepoint codepoint = decoder.nextCodepoint(); + while (codepoint != Null) { KDSize cSize = KDSize(m_glyphSize.width(), 0); - if (*text == '\t') { - cSize = KDSize(k_tabCharacterWidth*m_glyphSize.width(), 0); - } - if (*text == '\n') { + if (codepoint == LineFeed) { cSize = KDSize(0, m_glyphSize.height()); + codepoint = decoder.nextCodepoint(); + } else if (codepoint == Tabulation) { + cSize = KDSize(k_tabCharacterWidth*m_glyphSize.width(), 0); + } else if (codepoint.isCombining()) { + cSize = KDSizeZero; } stringSize = KDSize(stringSize.width()+cSize.width(), stringSize.height()+cSize.height()); - text++; + codepoint = decoder.nextCodepoint(); } return stringSize; } -void KDFont::fetchGreyscaleGlyphForChar(char c, uint8_t * greyscaleBuffer) const { +void KDFont::setGlyphGreyscalesForCodepoint(Codepoint codepoint, GlyphBuffer * glyphBuffer) const { + fetchGreyscaleGlyphAtIndex(indexForCodepoint(codepoint), glyphBuffer->greyscaleBuffer()); +} + +void KDFont::accumulateGlyphGreyscalesForCodepoint(Codepoint codepoint, GlyphBuffer * glyphBuffer) const { + uint8_t * greyscaleBuffer = glyphBuffer->greyscaleBuffer(); + uint8_t * accumulationGreyscaleBuffer = glyphBuffer->secondaryGreyscaleBuffer(); + fetchGreyscaleGlyphAtIndex(indexForCodepoint(codepoint), accumulationGreyscaleBuffer); + for (int i=0; i= k_numberOfGlyphs) { - // There is no data for this glyph - for (int i = 0; i < pixelCount; i++) { - pixelBuffer[i] = KDColorBlack; - } - return; - } +void KDFont::colorizeGlyphBuffer(const RenderPalette * renderPalette, GlyphBuffer * glyphBuffer) const { /* Since a greyscale value is smaller than a color value (see assertion), we * can store the temporary greyscale values in the output pixel buffer. * What's great is that now, if we fill the pixel buffer right-to-left with @@ -49,8 +59,9 @@ void KDFont::fetchGlyphForChar(char c, const KDFont::RenderPalette * renderPalet * the remaining grayscale values since those are smaller. So we can avoid a * separate buffer for the temporary greyscale values. */ assert(k_bitsPerPixel < 8*sizeof(KDColor)); - uint8_t * greyscaleBuffer = reinterpret_cast(pixelBuffer); - fetchGreyscaleGlyphForChar(c, greyscaleBuffer); + + uint8_t * greyscaleBuffer = glyphBuffer->greyscaleBuffer(); + KDColor * colorBuffer = glyphBuffer->colorBuffer(); uint8_t mask = (0xFF >> (8-k_bitsPerPixel)); int pixelIndex = pixelCount; // Let's start at the final pixel @@ -62,7 +73,82 @@ void KDFont::fetchGlyphForChar(char c, const KDFont::RenderPalette * renderPalet uint8_t greyscale = greyscaleByte & mask; greyscaleByte = greyscaleByte >> k_bitsPerPixel; assert(pixelIndex >= 0); - pixelBuffer[pixelIndex--] = renderPalette->colorAtIndex(greyscale); + colorBuffer[pixelIndex--] = renderPalette->colorAtIndex(greyscale); } } } + +KDFont::GlyphIndex KDFont::indexForCodepoint(Codepoint c) const { +#define USE_BINARY_SEARCH 0 +#if USE_BINARY_SEARCH + int lowerBound = 0; + int upperBound = m_tableLength; + while (true) { + int currentIndex = (lowerBound+upperBound)/2; + // printf("Considering %d in [%d,%d]\n", currentIndex, lowerBound, upperBound); + const CodepointIndexPair * currentPair = m_table + currentIndex; + const CodepointIndexPair * nextPair = currentIndex + 1 < m_tableLength ? currentPair + 1 : nullptr; + // printf("At this point, currentPair->codepoint() = %d and c = %d\n", currentPair->codepoint(), c); + if (currentPair->codepoint() == c) { + return currentPair->glyphIndex(); + } else if (currentPair->codepoint() > c) { + // We need to look below + if (upperBound == currentIndex) { + // There's nothing below. Error out. + return 0; + } + upperBound = currentIndex; + continue; + } else if (nextPair == nullptr) { + return 0; + } else if (nextPair->codepoint() == c) { + return nextPair->glyphIndex(); + } else if (nextPair->codepoint() < c) { + // We need to look above + if (lowerBound == currentIndex) { + // There's nothing above. Error out. + return 0; + } + lowerBound = currentIndex; + continue; + } else { + // At this point, + // currentPair->codepoint < c && nextPair != nullptr && nextPair->codepoint > c + // Yay, it's over! + // There can be an empty space between the currentPair and the nextPair + // e.g. currentPair(3,1) and nextPair(9, 4) + // means value at codepoints 3, 4, 5, 6, 7, 8, 9 + // are glyph identifiers 1, ?, ?, ?, ?, ?, 4 + // solved as 1, 2, 3, 0, 0, 0, 4 + + // Let's hunt down the zeroes + Codepoint lastCodepointOfCurrentPair = currentPair->codepoint() + (nextPair->glyphIndex() - currentPair->glyphIndex() - 1); + if (c > lastCodepointOfCurrentPair) { + return 0; + } + return currentPair->glyphIndex() + (c - currentPair->codepoint()); + } + } +#else + const CodepointIndexPair * currentPair = m_table; + if (c < currentPair->codepoint()) { + return 0; + } + const CodepointIndexPair * endPair = m_table + m_tableLength - 1; + while (currentPair < endPair) { + const CodepointIndexPair * nextPair = currentPair + 1; + if (c < nextPair->codepoint()) { + Codepoint lastCodepointOfCurrentPair = currentPair->codepoint() + (nextPair->glyphIndex() - currentPair->glyphIndex() - 1); + if (c > lastCodepointOfCurrentPair) { + return 0; + } + return currentPair->glyphIndex() + (c - currentPair->codepoint()); + } + currentPair = nextPair; + } + if (endPair->codepoint() == c) { + return endPair->glyphIndex(); + } + return 0; +#endif +} diff --git a/kandinsky/src/unicode/utf8decoder.cpp b/kandinsky/src/unicode/utf8decoder.cpp new file mode 100644 index 000000000..e8585e9f8 --- /dev/null +++ b/kandinsky/src/unicode/utf8decoder.cpp @@ -0,0 +1,26 @@ +#include +#include + +static inline int leading_ones(uint8_t value) { + for (int i=0; i<8; i++) { + if (!(value & 0x80)) { + return i; + } + value = value << 1; + } + assert(false); +} + +static inline uint8_t last_k_bits(uint8_t value, uint8_t bits) { + return (value & ((1< Date: Wed, 31 Oct 2018 10:30:34 +0100 Subject: [PATCH 0296/1750] [kandinsky] The rasterizer encodes Unicode codepoints --- kandinsky/fonts/codepoints.h | 149 +++++++++++++++++++++++++++++++++++ kandinsky/fonts/rasterizer.c | 118 +++++++++++++-------------- 2 files changed, 204 insertions(+), 63 deletions(-) create mode 100644 kandinsky/fonts/codepoints.h diff --git a/kandinsky/fonts/codepoints.h b/kandinsky/fonts/codepoints.h new file mode 100644 index 000000000..f039df1d8 --- /dev/null +++ b/kandinsky/fonts/codepoints.h @@ -0,0 +1,149 @@ +// [0x30a].map{|i| "0x" + i.to_s(16) +", // " + [i].pack("U") + " // " + Unicode::Name.of([i].pack("U"))}.join("|") +#include + +uint32_t Codepoints[] = { + 0x20, // // SPACE + 0x21, // ! // EXCLAMATION MARK + 0x22, // " // QUOTATION MARK + 0x23, // # // NUMBER SIGN + 0x24, // $ // DOLLAR SIGN + 0x25, // % // PERCENT SIGN + 0x26, // & // AMPERSAND + 0x27, // ' // APOSTROPHE + 0x28, // ( // LEFT PARENTHESIS + 0x29, // ) // RIGHT PARENTHESIS + 0x2a, // * // ASTERISK + 0x2b, // + // PLUS SIGN + 0x2c, // , // COMMA + 0x2d, // - // HYPHEN-MINUS + 0x2e, // . // FULL STOP + 0x2f, // / // SOLIDUS + 0x30, // 0 // DIGIT ZERO + 0x31, // 1 // DIGIT ONE + 0x32, // 2 // DIGIT TWO + 0x33, // 3 // DIGIT THREE + 0x34, // 4 // DIGIT FOUR + 0x35, // 5 // DIGIT FIVE + 0x36, // 6 // DIGIT SIX + 0x37, // 7 // DIGIT SEVEN + 0x38, // 8 // DIGIT EIGHT + 0x39, // 9 // DIGIT NINE + 0x3a, // : // COLON + 0x3b, // ; // SEMICOLON + 0x3c, // < // LESS-THAN SIGN + 0x3d, // = // EQUALS SIGN + 0x3e, // > // GREATER-THAN SIGN + 0x3f, // ? // QUESTION MARK + 0x40, // @ // COMMERCIAL AT + 0x41, // A // LATIN CAPITAL LETTER A + 0x42, // B // LATIN CAPITAL LETTER B + 0x43, // C // LATIN CAPITAL LETTER C + 0x44, // D // LATIN CAPITAL LETTER D + 0x45, // E // LATIN CAPITAL LETTER E + 0x46, // F // LATIN CAPITAL LETTER F + 0x47, // G // LATIN CAPITAL LETTER G + 0x48, // H // LATIN CAPITAL LETTER H + 0x49, // I // LATIN CAPITAL LETTER I + 0x4a, // J // LATIN CAPITAL LETTER J + 0x4b, // K // LATIN CAPITAL LETTER K + 0x4c, // L // LATIN CAPITAL LETTER L + 0x4d, // M // LATIN CAPITAL LETTER M + 0x4e, // N // LATIN CAPITAL LETTER N + 0x4f, // O // LATIN CAPITAL LETTER O + 0x50, // P // LATIN CAPITAL LETTER P + 0x51, // Q // LATIN CAPITAL LETTER Q + 0x52, // R // LATIN CAPITAL LETTER R + 0x53, // S // LATIN CAPITAL LETTER S + 0x54, // T // LATIN CAPITAL LETTER T + 0x55, // U // LATIN CAPITAL LETTER U + 0x56, // V // LATIN CAPITAL LETTER V + 0x57, // W // LATIN CAPITAL LETTER W + 0x58, // X // LATIN CAPITAL LETTER X + 0x59, // Y // LATIN CAPITAL LETTER Y + 0x5a, // Z // LATIN CAPITAL LETTER Z + 0x5b, // [ // LEFT SQUARE BRACKET + 0x5c, // \ // REVERSE SOLIDUS + 0x5d, // ] // RIGHT SQUARE BRACKET + 0x5e, // ^ // CIRCUMFLEX ACCENT + 0x5f, // _ // LOW LINE + 0x60, // ` // GRAVE ACCENT + 0x61, // a // LATIN SMALL LETTER A + 0x62, // b // LATIN SMALL LETTER B + 0x63, // c // LATIN SMALL LETTER C + 0x64, // d // LATIN SMALL LETTER D + 0x65, // e // LATIN SMALL LETTER E + 0x66, // f // LATIN SMALL LETTER F + 0x67, // g // LATIN SMALL LETTER G + 0x68, // h // LATIN SMALL LETTER H + 0x69, // i // LATIN SMALL LETTER I + 0x6a, // j // LATIN SMALL LETTER J + 0x6b, // k // LATIN SMALL LETTER K + 0x6c, // l // LATIN SMALL LETTER L + 0x6d, // m // LATIN SMALL LETTER M + 0x6e, // n // LATIN SMALL LETTER N + 0x6f, // o // LATIN SMALL LETTER O + 0x70, // p // LATIN SMALL LETTER P + 0x71, // q // LATIN SMALL LETTER Q + 0x72, // r // LATIN SMALL LETTER R + 0x73, // s // LATIN SMALL LETTER S + 0x74, // t // LATIN SMALL LETTER T + 0x75, // u // LATIN SMALL LETTER U + 0x76, // v // LATIN SMALL LETTER V + 0x77, // w // LATIN SMALL LETTER W + 0x78, // x // LATIN SMALL LETTER X + 0x79, // y // LATIN SMALL LETTER Y + 0x7a, // z // LATIN SMALL LETTER Z + 0x7b, // { // LEFT CURLY BRACKET + 0x7c, // | // VERTICAL LINE + 0x7d, // } // RIGHT CURLY BRACKET + 0x7e, // ~ // TILDE + + 0xb0, // ° // DEGREE SIGN + 0xb5, // µ // MICRO SIGN + 0xb7, // · // MIDDLE DOT + + 0xc6, // Æ // LATIN CAPITAL LETTER AE + 0xd0, // à // LATIN CAPITAL LETTER ETH + 0xd7, // × // MULTIPLICATION SIGN + 0xd8, // Ø // LATIN CAPITAL LETTER O WITH STROKE + 0xde, // Þ // LATIN CAPITAL LETTER THORN + 0xdf, // ß // LATIN SMALL LETTER SHARP S + 0xe6, // æ // LATIN SMALL LETTER AE + 0xf0, // ð // LATIN SMALL LETTER ETH + 0xf7, // ÷ // DIVISION SIGN + 0xf8, // ø // LATIN SMALL LETTER O WITH STROKE + 0xfe, // þ // LATIN SMALL LETTER THORN + + 0x300, // Ì€ // COMBINING GRAVE ACCENT + 0x301, // Ì // COMBINING ACUTE ACCENT + 0x302, // Ì‚ // COMBINING CIRCUMFLEX ACCENT + 0x303, // ̃ // COMBINING TILDE + 0x304, // Ì„ // COMBINING MACRON + 0x308, // ̈ // COMBINING DIAERESIS + 0x30a, // ÌŠ // COMBINING RING ABOVE + 0x327, // ̧ // COMBINING CEDILLA + + 0x393, // Γ // GREEK CAPITAL LETTER GAMMA + 0x394, // Δ // GREEK CAPITAL LETTER DELTA + 0x3b8, // θ // GREEK SMALL LETTER THETA + 0x3bb, // λ // GREEK SMALL LETTER LAMDA + 0x3c0, // Ï€ // GREEK SMALL LETTER PI + 0x3c3, // σ // GREEK SMALL LETTER SIGMA + 0x1d07, // á´‡ // LATIN LETTER SMALL CAPITAL E + 0x212f, // ℯ // SCRIPT SMALL E + 0x2192, // → // RIGHTWARDS ARROW + 0x2211, // ∑ // N-ARY SUMMATION + 0x221a, // √ // SQUARE ROOT + 0x222b, // ∫ // INTEGRAL + 0x2248, // ≈ // ALMOST EQUAL TO + 0x2264, // ≤ // LESS-THAN OR EQUAL TO + 0x2265, // ≥ // GREATER-THAN OR EQUAL TO + 0x1d422, // ð¢ // MATHEMATICAL BOLD SMALL I" + + // Apostrophe + // Xbar + // Ybar + +}; + +int NumberOfCodepoints = sizeof(Codepoints)/sizeof(Codepoints[0]); diff --git a/kandinsky/fonts/rasterizer.c b/kandinsky/fonts/rasterizer.c index baafd3b4e..5903adfdf 100644 --- a/kandinsky/fonts/rasterizer.c +++ b/kandinsky/fonts/rasterizer.c @@ -16,6 +16,7 @@ #include FT_FREETYPE_H #include "unicode_for_symbol.h" +#include "codepoints.h" #include "../../ion/src/external/lz4/lz4hc.h" @@ -40,18 +41,6 @@ typedef struct { void writeImageToPNGFile(image_t * image, char * filename); #endif -#define CHARACTER_RANGE_START 0x20 -#define CHARACTER_RANGE_END 0x7E -#define CHARACTER_COUNT (CHARACTER_RANGE_END-CHARACTER_RANGE_START+1) -#define GLYPH_COUNT (CHARACTER_COUNT + NUMBER_OF_SYMBOLS) - -#define GRID_WIDTH 19 -#define GRID_HEIGHT 8 - -#if (GRID_WIDTH*GRID_HEIGHT < GLYPH_COUNT) -#error Grid too small. Consider increasing GRID_WIDTH or GRID_HEIGHT -#endif - void drawGlyphInImage(FT_Bitmap * glyphBitmap, image_t * image, int x, int y); static void prettyPrintArray(FILE * stream, int maxWidth, int typeSize, void * array, int numberOfElements); @@ -105,24 +94,9 @@ int main(int argc, char * argv[]) { int maxWidth = 0; int maxAboveBaseline = 0; int maxBelowBaseline = 0; - for (unsigned char character = CHARACTER_RANGE_START; character <= CHARACTER_RANGE_END; character++) { - ENSURE(!FT_Load_Char(face, character, FT_LOAD_RENDER), "Loading character 0x%02x", character); - int aboveBaseline = face->glyph->bitmap_top; - int belowBaseline = face->glyph->bitmap.rows - face->glyph->bitmap_top; - int width = face->glyph->bitmap_left + face->glyph->bitmap.width; - if (width > maxWidth) { - maxWidth = width; - } - if (aboveBaseline > maxAboveBaseline) { - maxAboveBaseline = aboveBaseline; - } - if (belowBaseline > maxBelowBaseline) { - maxBelowBaseline = belowBaseline; - } - } - for (int charIndex = 0; charIndex < NUMBER_OF_SYMBOLS; charIndex++) { - wchar_t wideChar = codePointForSymbol[charIndex]; - ENSURE(!FT_Load_Char(face, wideChar, FT_LOAD_RENDER), "Loading character 0x%02x", wideChar); + for (int i=0; i < NumberOfCodepoints; i++) { + wchar_t codepoint = Codepoints[i]; + ENSURE(!FT_Load_Char(face, codepoint, FT_LOAD_RENDER), "Loading character 0x%02x", codepoint); int aboveBaseline = face->glyph->bitmap_top; int belowBaseline = face->glyph->bitmap.rows - face->glyph->bitmap_top; int width = face->glyph->bitmap_left + face->glyph->bitmap.width; @@ -142,9 +116,11 @@ int main(int argc, char * argv[]) { //printf("Actual glyph size = %dx%d\n", glyph_width, glyph_height); int grid_size = 1; + int grid_width = 20; + int grid_height = ((NumberOfCodepoints-1)/grid_width)+1; - bitmap_image.width = GRID_WIDTH*glyph_width+(GRID_WIDTH-1)*grid_size; - bitmap_image.height = GRID_HEIGHT*glyph_height+(GRID_HEIGHT-1)*grid_size; + bitmap_image.width = grid_width*glyph_width+(grid_width-1)*grid_size; + bitmap_image.height = grid_height*glyph_height+(grid_height-1)*grid_size; bitmap_image.pixels = malloc(sizeof(pixel_t)*bitmap_image.width*bitmap_image.height); ENSURE(bitmap_image.pixels != NULL, "Allocating bitmap image of size %dx%d at %ld bytes per pixel", bitmap_image.width, bitmap_image.height, sizeof(pixel_t)); @@ -159,30 +135,23 @@ int main(int argc, char * argv[]) { } } - // We're doing the ASCII table, so characters from 0 to 255 inclusive - for (unsigned char character = CHARACTER_RANGE_START; character <= CHARACTER_RANGE_END; character++) { - int x = (character-CHARACTER_RANGE_START)%(GRID_WIDTH); - int y = (character-CHARACTER_RANGE_START)/(GRID_WIDTH); + for (int i=0; iglyph->bitmap_left, face->glyph->bitmap_top); + while (face->glyph->bitmap_left < 0) { + // This is a workaround for combining glyphs. + // For some reason, FreeType does a fun hack and yields a negative bitmap_left + // This way, the glyph automagically combines with the previous one. That's neat, + // but we don't want to do that. + face->glyph->bitmap_left += glyph_width; + } drawGlyphInImage(&face->glyph->bitmap, &bitmap_image, - x*(glyph_width+grid_size) + face->glyph->bitmap_left, - y*(glyph_height+grid_size) + maxAboveBaseline - face->glyph->bitmap_top - ); - } - // We are now using unicode to access non-ASCII characters - for (int charIndex = 0; charIndex < NUMBER_OF_SYMBOLS; charIndex++) { - wchar_t wideChar = codePointForSymbol[charIndex]; - int x = (charIndex+1+CHARACTER_RANGE_END-CHARACTER_RANGE_START)%(GRID_WIDTH); - int y = (charIndex+1+CHARACTER_RANGE_END-CHARACTER_RANGE_START)/(GRID_WIDTH); - // FT_LOAD_RENDER: Render the glyph upon load - ENSURE(!FT_Load_Char(face, wideChar, FT_LOAD_RENDER), "Loading character 0x%02x", wideChar); - //printf("Advances = %dx%d\n", face->glyph->bitmap_left, face->glyph->bitmap_top); - drawGlyphInImage(&face->glyph->bitmap, - &bitmap_image, - x*(glyph_width+grid_size) + face->glyph->bitmap_left, + x*(glyph_width+grid_size) + face->glyph->bitmap_left, y*(glyph_height+grid_size) + maxAboveBaseline - face->glyph->bitmap_top ); } @@ -195,6 +164,29 @@ int main(int argc, char * argv[]) { fprintf(sourceFile, "/* This file is auto-generated by the rasterizer */\n\n"); fprintf(sourceFile, "#include \n\n"); + + // Step 1 - Build the GlyphIndex <-> UnicodeCodepoint correspondance table + + int previousIndex = -1; + uint32_t previousCodepoint = 0; + int numberOfPairs = 0; + + fprintf(sourceFile, "static constexpr KDFont::CodepointIndexPair table[] = {\n"); + for (int i=0; i Date: Wed, 31 Oct 2018 10:37:41 +0100 Subject: [PATCH 0297/1750] [i18n] Encode strings as NFKD-normalized UTF-8 strings --- apps/i18n.py | 62 +++++++++++++++++++--------------------------------- 1 file changed, 23 insertions(+), 39 deletions(-) diff --git a/apps/i18n.py b/apps/i18n.py index 93ec034c9..dd307b8ba 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -1,4 +1,6 @@ -#coding=utf-8 +# This script gather all .i18n files and aggregates them as a pair of .h/.cpp file +# In practice, it enforces a NFKD normalization +# It works with Python 2 and Python 3 import sys import re @@ -6,42 +8,8 @@ import unicodedata import argparse import io -ion_special_characters = { - u'Δ': "Ion::Charset::CapitalDelta", - u'Σ': "Ion::Charset::CapitalSigma", - u'λ': "Ion::Charset::SmallLambda", - u'μ': "Ion::Charset::SmallMu", - u'σ': "Ion::Charset::SmallSigma", - u'≤': "Ion::Charset::LessEqual", - u'≈': "Ion::Charset::AlmostEqual", - u'ø': "Ion::Charset::Empty", - u'•': "Ion::Charset::MiddleDot" -} - -def ion_char(i18n_letter): - if i18n_letter == '\'': - return "'\\\''" - if ord(i18n_letter) < 128: - return "'" + i18n_letter + "'" - if i18n_letter in ion_special_characters: - return ion_special_characters[i18n_letter] - normalized = unicodedata.normalize("NFD", i18n_letter).encode('ascii', 'ignore') - #sys.stderr.write("Warning: Normalizing unicode character \"" + i18n_letter + "\" -> \"" + normalized + "\"\n") - return "'" + normalized.decode() + "'" - def source_definition(i18n_string): - ion_characters = [] - i = 0 - while i < len(i18n_string): - if i18n_string[i] == '\\': - i = i+1 - newChar = "'\\"+i18n_string[i]+"'" - ion_characters.append(newChar) - else: - ion_characters.append(ion_char(i18n_string[i])) - i = i+1 - ion_characters.append("0") - return "{" + ", ".join(ion_characters) + "}" + return (u"\"" + unicodedata.normalize("NFKD", i18n_string) + u"\"").encode("utf-8") def split_line(line): match = re.match(r"^(\w+)\s*=\s*\"(.*)\"$", line) @@ -78,7 +46,7 @@ def parse_files(files): return {"messages": sorted(messages), "universal_messages": sorted(universal_messages), "data": data} def print_header(data, path, locales): - f = open(path, 'w') + f = open(path, "w") f.write("#ifndef APPS_I18N_H\n") f.write("#define APPS_I18N_H\n\n") f.write("// This file is auto-generated by i18n.py\n\n") @@ -114,7 +82,7 @@ def print_header(data, path, locales): f.close() def print_implementation(data, path, locales): - f = open(path, 'w') + f = open(path, "w") f.write("#include \"i18n.h\"\n") f.write("#include \n") f.write("#include \n\n"); @@ -141,7 +109,11 @@ def print_implementation(data, path, locales): if not message in data["data"][locale]: sys.stderr.write("Error: Undefined key \"" + message + "\" for locale \"" + locale + "\"\n") sys.exit(-1) - f.write("constexpr static char " + locale + message + "[] = " + data["data"][locale][message] + ";\n") + f.write("constexpr static char " + locale + message + "[] = ") + f = open(path, "ab") # Re-open the file as binary to output raw UTF-8 bytes + f.write(data["data"][locale][message]) + f = open(path, "a") # Re-open the file as text + f.write(";\n") f.write("\n") f.write("constexpr static const char * messages[%d][%d] = {\n" % (len(data["messages"]), len(locales))) for message in data["messages"]: @@ -152,6 +124,18 @@ def print_implementation(data, path, locales): f.write("};\n\n") # Write the translate method + for message in data["universal_messages"]: + f.write("constexpr static char universal" + message + "[] = ") + f = open(path, "ab") # Re-open the file as binary to output raw UTF-8 bytes + f.write(data["data"]["universal"][message]) + f = open(path, "a") # Re-open the file as text + f.write(";\n") + f.write("\n") + f.write("constexpr static const char * universalMessages[%d] = {\n" % len(data["universal_messages"])) + for message in data["universal_messages"]: + f.write(" universal" + message + ",\n") + f.write("};\n") + f.write("\n") f.write("const char * translate(Message m, Language l) {\n") f.write(" assert(m != Message::LocalizedMessageMarker);\n") f.write(" int localizedMessageOffset = (int)Message::LocalizedMessageMarker+1;\n") From 865e7cb39e02843baa5997f9c05970d9f92abb8b Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 31 Oct 2018 10:38:25 +0100 Subject: [PATCH 0298/1750] [kandinsky] Add unit tests for UTF-8 decoding and CodepointToIndex --- kandinsky/Makefile | 3 +++ kandinsky/test/font.cpp | 24 ++++++++++++++++++++++++ kandinsky/test/utf8decoder.cpp | 15 +++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 kandinsky/test/font.cpp create mode 100644 kandinsky/test/utf8decoder.cpp diff --git a/kandinsky/Makefile b/kandinsky/Makefile index 9705de6fe..af4804052 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -13,6 +13,7 @@ src += $(addprefix kandinsky/src/,\ ion_context.cpp \ point.cpp \ rect.cpp \ + unicode/utf8decoder.cpp\ ) src += $(addprefix kandinsky/fonts/, \ @@ -22,7 +23,9 @@ src += $(addprefix kandinsky/fonts/, \ tests += $(addprefix kandinsky/test/,\ color.cpp\ + font.cpp\ rect.cpp\ + utf8decoder.cpp\ ) RASTERIZER_CFLAGS := -std=c99 `pkg-config freetype2 --cflags` diff --git a/kandinsky/test/font.cpp b/kandinsky/test/font.cpp new file mode 100644 index 000000000..96a2acfed --- /dev/null +++ b/kandinsky/test/font.cpp @@ -0,0 +1,24 @@ +#include +#include +#include + +static constexpr KDFont::CodepointIndexPair table[] = { + KDFont::CodepointIndexPair(3, 1), // Codepoint, identifier + KDFont::CodepointIndexPair(9, 4), + KDFont::CodepointIndexPair(12, 5), + KDFont::CodepointIndexPair(14, 7) +}; + +constexpr KDFont testFont(4, table, 10, 10, nullptr, nullptr); + +const KDFont::GlyphIndex index_for_codepoint[] = { +/* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 */ + 0, 0, 0, 1, 2, 3, 0, 0, 0, 4, 0, 0, 5, 6, 7, 0 +}; + +QUIZ_CASE(kandinsky_font_index_for_codepoint) { + for (int i=0; i<16; i++) { + KDFont::GlyphIndex result = testFont.indexForCodepoint(i); + quiz_assert(result == index_for_codepoint[i]); + } +} diff --git a/kandinsky/test/utf8decoder.cpp b/kandinsky/test/utf8decoder.cpp new file mode 100644 index 000000000..3e858f526 --- /dev/null +++ b/kandinsky/test/utf8decoder.cpp @@ -0,0 +1,15 @@ +#include +#include + +void assert_decodes_to(const char * string, Codepoint c) { + UTF8Decoder d(string); + quiz_assert(d.nextCodepoint() == c); + quiz_assert(d.nextCodepoint() == 0); +} + +QUIZ_CASE(kandinsky_utf8_decoder) { + assert_decodes_to("\x20", 0x20); + assert_decodes_to("\xC2\xA2", 0xA2); + assert_decodes_to("\xED\x9F\xBF", 0xD7FF); + assert_decodes_to("\xCC\x81", 0x301); +} From 3156f92baf5dd612494a43a3421b195da4c845f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 10 Jan 2019 14:07:36 +0100 Subject: [PATCH 0299/1750] [kandinsky/font] Fix rebase on master --- apps/i18n.py | 23 +++++++++-------------- kandinsky/src/font.cpp | 2 +- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/apps/i18n.py b/apps/i18n.py index dd307b8ba..8aee99be6 100644 --- a/apps/i18n.py +++ b/apps/i18n.py @@ -85,14 +85,20 @@ def print_implementation(data, path, locales): f = open(path, "w") f.write("#include \"i18n.h\"\n") f.write("#include \n") - f.write("#include \n\n"); + f.write("#include \n\n") f.write("namespace I18n {\n\n") + # Write the default message f.write("constexpr static char universalDefault[] = {0};\n") + # Write the universal messages for message in data["universal_messages"]: - f.write("constexpr static char universal" + message + "[] = " + data["data"]["universal"][message] + ";\n") + f.write("constexpr static char universal" + message + "[] = ") + f = open(path, "ab") # Re-open the file as binary to output raw UTF-8 bytes + f.write(data["data"]["universal"][message]) + f = open(path, "a") # Re-open the file as text + f.write(";\n") f.write("\n") f.write("constexpr static const char * universalMessages[%d] = {\n" % (len(data["universal_messages"])+1)) f.write(" universalDefault,\n") @@ -123,19 +129,8 @@ def print_implementation(data, path, locales): f.write("},\n") f.write("};\n\n") + # Write the translate method - for message in data["universal_messages"]: - f.write("constexpr static char universal" + message + "[] = ") - f = open(path, "ab") # Re-open the file as binary to output raw UTF-8 bytes - f.write(data["data"]["universal"][message]) - f = open(path, "a") # Re-open the file as text - f.write(";\n") - f.write("\n") - f.write("constexpr static const char * universalMessages[%d] = {\n" % len(data["universal_messages"])) - for message in data["universal_messages"]: - f.write(" universal" + message + ",\n") - f.write("};\n") - f.write("\n") f.write("const char * translate(Message m, Language l) {\n") f.write(" assert(m != Message::LocalizedMessageMarker);\n") f.write(" int localizedMessageOffset = (int)Message::LocalizedMessageMarker+1;\n") diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index 2e5a88321..b6c43151d 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -64,7 +64,7 @@ void KDFont::colorizeGlyphBuffer(const RenderPalette * renderPalette, GlyphBuffe KDColor * colorBuffer = glyphBuffer->colorBuffer(); uint8_t mask = (0xFF >> (8-k_bitsPerPixel)); - int pixelIndex = pixelCount; // Let's start at the final pixel + int pixelIndex = m_glyphSize.width() * m_glyphSize.height() - 1; // Let's start at the final pixel int greyscaleByteIndex = pixelIndex * k_bitsPerPixel / 8; while (pixelIndex >= 0) { assert(greyscaleByteIndex == pixelIndex * k_bitsPerPixel / 8); From f184f1ff367f1e63c46f2490099d82f1d8fd0fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 10 Jan 2019 14:36:06 +0100 Subject: [PATCH 0300/1750] [kandinsky] Coding style --- kandinsky/include/kandinsky/font.h | 1 - kandinsky/src/font.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index 5d2a571e9..a45a1e283 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -25,7 +25,6 @@ public: KDColor * colorBuffer() { return m_colors; } uint8_t * greyscaleBuffer() { return m_greyscales; } uint8_t * secondaryGreyscaleBuffer() { return m_greyscales + k_maxGlyphPixelCount; } - private: private: static constexpr int k_maxGlyphPixelCount = 180; //TODO: Should be generated by the rasterizer uint8_t m_greyscales[2*k_maxGlyphPixelCount]; diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index b6c43151d..40fbebd00 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -87,7 +87,7 @@ KDFont::GlyphIndex KDFont::indexForCodepoint(Codepoint c) const { int currentIndex = (lowerBound+upperBound)/2; // printf("Considering %d in [%d,%d]\n", currentIndex, lowerBound, upperBound); const CodepointIndexPair * currentPair = m_table + currentIndex; - const CodepointIndexPair * nextPair = currentIndex + 1 < m_tableLength ? currentPair + 1 : nullptr; + const CodepointIndexPair * nextPair = (currentIndex + 1) < m_tableLength ? currentPair + 1 : nullptr; // printf("At this point, currentPair->codepoint() = %d and c = %d\n", currentPair->codepoint(), c); if (currentPair->codepoint() == c) { return currentPair->glyphIndex(); From 834ed75995cb0376599a8b2726bba985a07cdf87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 10 Jan 2019 14:40:45 +0100 Subject: [PATCH 0301/1750] [poincare/tokenizer] Coding style --- poincare/src/parsing/tokenizer.cpp | 19 ++++++++++--------- poincare/src/parsing/tokenizer.h | 13 ++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index 1ad7dfd99..00698188a 100644 --- a/poincare/src/parsing/tokenizer.cpp +++ b/poincare/src/parsing/tokenizer.cpp @@ -16,7 +16,7 @@ const char Tokenizer::popChar() { const char nextChar = *m_nextCharP; m_nextCharP++; return nextChar; - // Note that, after returning, m_nextCharP points to the character after nextChar. + // After returning, m_nextCharP points to the character after nextChar. } bool Tokenizer::canPopChar (const char c) { @@ -28,8 +28,8 @@ bool Tokenizer::canPopChar (const char c) { } size_t Tokenizer::popIdentifier() { - // Since this method is only called by popToken, - // currentChar is necessary a letter. + /* Since this method is only called by popToken, currentChar is necessarily a + * letter. */ size_t length = 1; char nextChar = *m_nextCharP; while (isLetter(nextChar) || isDigit(nextChar) || nextChar == '_') { @@ -50,7 +50,7 @@ size_t Tokenizer::popDigits() { Token Tokenizer::popNumber() { /* This method is only called by popToken, after popping a dot or a digit. - * Hence one needs to get one character back. */ + * Hence the need to get one character back. */ m_nextCharP--; const char * integralPartText = m_nextCharP; @@ -88,7 +88,8 @@ Token Tokenizer::popToken() { // Skip whitespaces while (canPopChar(' ')) {} - // Save for later use (since m_nextCharP is altered by popChar, popNumber, popIdentifier). + /* Save for later use (since m_nextCharP is altered by popChar, popNumber, + * popIdentifier). */ const char * start = m_nextCharP; const char currentChar = popChar(); @@ -102,8 +103,8 @@ Token Tokenizer::popToken() { return result; } if ('(' <= currentChar && currentChar <= '/') { - // Those characters form a contiguous range in the ascii character set, - // so one can make searching faster with this lookup table. + /* Those characters form a contiguous range in the ascii character set, we + * make searching faster with this lookup table. */ constexpr Token::Type typeForChar[] = { Token::LeftParenthesis, Token::RightParenthesis, @@ -114,8 +115,8 @@ Token Tokenizer::popToken() { Token::Undefined, Token::Slash }; - // The dot character is the second last of that range, - // but it is matched before (with popNumber). + /* The dot character is the second last of that range, but it is matched + * before (with popNumber). */ assert(currentChar != '.'); return Token(typeForChar[currentChar - '(']); } diff --git a/poincare/src/parsing/tokenizer.h b/poincare/src/parsing/tokenizer.h index 7e3cd452e..05e2017d9 100644 --- a/poincare/src/parsing/tokenizer.h +++ b/poincare/src/parsing/tokenizer.h @@ -1,12 +1,11 @@ #ifndef POINCARE_PARSING_TOKENIZER_H #define POINCARE_PARSING_TOKENIZER_H -/* In order to parse a text input into an Expression, - * (an instance of) the Tokenizer reads the successive - * characters of the input, pops the Tokens it recognizes, - * which are then consumed by the Parser. - * For each Token, the Tokenizer determines a Type and - * may save other relevant data intended for the Parser. */ +/* In order to parse a text input into an Expression, (an instance of) the + * Tokenizer reads the successive characters of the input, pops the Tokens it + * recognizes, which are then consumed by the Parser. For each Token, the + * Tokenizer determines a Type and may save other relevant data intended for the + * Parser. */ #include "token.h" @@ -14,7 +13,7 @@ namespace Poincare { class Tokenizer { public: - Tokenizer(const char * text) : m_nextCharP(text) {}; + Tokenizer(const char * text) : m_nextCharP(text) {} Token popToken(); private: const char popChar(); From 4cd28dc0248c840b445fafbddc56215f25dfaac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 10 Jan 2019 15:38:36 +0100 Subject: [PATCH 0302/1750] [poincare/parser] Use the UTF8Decoder in the parser --- poincare/src/parsing/tokenizer.cpp | 95 ++++++++++++++++++------------ poincare/src/parsing/tokenizer.h | 7 ++- 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index 00698188a..bd2c8e823 100644 --- a/poincare/src/parsing/tokenizer.cpp +++ b/poincare/src/parsing/tokenizer.cpp @@ -1,6 +1,7 @@ #include "tokenizer.h" #include #include +#include namespace Poincare { @@ -12,54 +13,70 @@ static inline bool isDigit(const char c) { return '0' <= c && c <= '9'; } +const char Tokenizer::nextChar(PopTest popTest, char context, bool * testResult) { + // Beware of chars spaning over more than one byte: use the UTF8Decoder. + UTF8Decoder decoder(m_text); + Codepoint firstCodepoint = decoder.nextCodepoint(); + int numberOfBytesForChar = 1; + if (firstCodepoint != Null) { + Codepoint codepoint = decoder.nextCodepoint(); + while (codepoint.isCombining()) { + numberOfBytesForChar++; + codepoint = decoder.nextCodepoint(); + } + } + char c = *m_text; // TODO handle combined chars? + bool shouldPop = popTest(c, context); + if (testResult != nullptr) { + *testResult = shouldPop; + } + if (shouldPop) { + m_text+= numberOfBytesForChar; + } + return c; +} + const char Tokenizer::popChar() { - const char nextChar = *m_nextCharP; - m_nextCharP++; - return nextChar; - // After returning, m_nextCharP points to the character after nextChar. + return nextChar([](char c, char context) { return true; }); + // m_text now points to the start of the character after the returned char. } bool Tokenizer::canPopChar (const char c) { - if (*m_nextCharP == c) { - m_nextCharP++; - return true; + bool didPop = false; + nextChar([](char nextC, char context) { return nextC == context; }, c, &didPop); + return didPop; +} + +size_t Tokenizer::popWhile(PopTest popTest, char context) { + size_t length = 0; + bool didPop = true; + while (didPop) { + nextChar(popTest, context, &didPop); + if (didPop) { + length++; + } } - return false; + return length; } size_t Tokenizer::popIdentifier() { - /* Since this method is only called by popToken, currentChar is necessarily a - * letter. */ - size_t length = 1; - char nextChar = *m_nextCharP; - while (isLetter(nextChar) || isDigit(nextChar) || nextChar == '_') { - length++; - nextChar = *++m_nextCharP; - } - return length; + return popWhile([](char c, char context) { return isLetter(c) || isDigit(c) || c == context; }, '_'); } size_t Tokenizer::popDigits() { - size_t length = 0; - while (isDigit(*m_nextCharP)) { - length++; - m_nextCharP++; - } - return length; + return popWhile([](char c, char context) { return isDigit(c); }); } Token Tokenizer::popNumber() { - /* This method is only called by popToken, after popping a dot or a digit. - * Hence the need to get one character back. */ - m_nextCharP--; - - const char * integralPartText = m_nextCharP; + const char * integralPartText = m_text; size_t integralPartLength = popDigits(); - const char * fractionalPartText = m_nextCharP; + const char * fractionalPartText = m_text; size_t fractionalPartLength = 0; + + assert(integralPartLength > 0 || *m_text == '.'); if (canPopChar('.')) { - fractionalPartText = m_nextCharP; + fractionalPartText = m_text; fractionalPartLength = popDigits(); } @@ -67,12 +84,12 @@ Token Tokenizer::popNumber() { return Token(Token::Undefined); } - const char * exponentPartText = m_nextCharP; + const char * exponentPartText = m_text; size_t exponentPartLength = 0; bool exponentIsNegative = false; if (canPopChar(Ion::Charset::Exponent)) { exponentIsNegative = canPopChar('-'); - exponentPartText = m_nextCharP; + exponentPartText = m_text; exponentPartLength = popDigits(); if (exponentPartLength == 0) { return Token(Token::Undefined); @@ -88,18 +105,22 @@ Token Tokenizer::popToken() { // Skip whitespaces while (canPopChar(' ')) {} - /* Save for later use (since m_nextCharP is altered by popChar, popNumber, + /* Save for later use (since m_text is altered by popChar, popNumber, * popIdentifier). */ - const char * start = m_nextCharP; + const char * start = m_text; + + /* If the next char is the start of a number, we do not want to pop it because + * popNumber needs this char. */ + bool nextCharIsNeitherDotNorDigit = true; + const char currentChar = nextChar([](char c, char context) { return c != context && !isDigit(c); }, '.', &nextCharIsNeitherDotNorDigit); - const char currentChar = popChar(); // According to currentChar, recognize the Token::Type. - if (currentChar == '.' || isDigit(currentChar)) { + if (!nextCharIsNeitherDotNorDigit) { return popNumber(); } if (isLetter(currentChar)) { Token result(Token::Identifier); - result.setString(start, popIdentifier()); + result.setString(start, 1 + popIdentifier()); // We already popped 1 char return result; } if ('(' <= currentChar && currentChar <= '/') { diff --git a/poincare/src/parsing/tokenizer.h b/poincare/src/parsing/tokenizer.h index 05e2017d9..b75d6e863 100644 --- a/poincare/src/parsing/tokenizer.h +++ b/poincare/src/parsing/tokenizer.h @@ -13,16 +13,19 @@ namespace Poincare { class Tokenizer { public: - Tokenizer(const char * text) : m_nextCharP(text) {} + Tokenizer(const char * text) : m_text(text) {} Token popToken(); private: + typedef bool (*PopTest)(char c, char context); + const char nextChar(PopTest popTest, char context = 0, bool * testResult = nullptr); const char popChar(); bool canPopChar(const char c); + size_t popWhile(PopTest popTest, char context = 0); size_t popDigits(); size_t popIdentifier(); Token popNumber(); - const char * m_nextCharP; + const char * m_text; }; } From 42ecd20f12346c73c65f2a3b43b5232a2aae322f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 11 Jan 2019 11:09:07 +0100 Subject: [PATCH 0303/1750] [kandinsky/font] Add comment about UTF-8 --- kandinsky/include/kandinsky/font.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index a45a1e283..7f716ba70 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -8,6 +8,21 @@ #include #include "palette.h" +/* We use UTF-8 encoding. This means that a character is encoded as a code point + * that uses between 1 and 4 bytes. Code points can be "combining", in which + * case their glyph should be superimposed to the glyph of the previous code + * point in the string. This is for instance used to print accents: the string + * for the glyph 'è' is composed of the code point for 'e' followed by the + * combining code point for '`'. + * ASCII characters have the same encoding in ASCII and in UTF-8. + * + * We do not provide a glyph for each of the 1,112,064 valid UTF-8 code points. + * We thus have a table of the glyphs we can draw (uint32_t Codepoints[] in + * kandinsky/fonts/codepoints.h). To easily compute the index of a code point in + * the Codepoints table, we use the m_table matching table: it contains the + * CodepointIndexPairs of the first code point of each series of consecutive + * code points in the Codepoints table. */ + class KDFont { private: static constexpr int k_bitsPerPixel = 4; // TODO: Should be generated by the rasterizer From d5df3b74ea3ba76b15efce93187ced47e75dcbb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 11 Jan 2019 11:16:14 +0100 Subject: [PATCH 0304/1750] Rename Codepoint Code point --- .../fonts/{codepoints.h => code_points.h} | 4 +- kandinsky/fonts/rasterizer.c | 48 +++++++------- kandinsky/include/kandinsky/font.h | 30 ++++----- .../include/kandinsky/unicode/code_point.h | 23 +++++++ .../include/kandinsky/unicode/codepoint.h | 23 ------- .../include/kandinsky/unicode/utf8decoder.h | 4 +- kandinsky/src/context_text.cpp | 24 +++---- kandinsky/src/font.cpp | 66 +++++++++---------- kandinsky/src/unicode/utf8decoder.cpp | 4 +- kandinsky/test/font.cpp | 18 ++--- kandinsky/test/utf8decoder.cpp | 6 +- poincare/src/parsing/tokenizer.cpp | 10 +-- 12 files changed, 130 insertions(+), 130 deletions(-) rename kandinsky/fonts/{codepoints.h => code_points.h} (98%) create mode 100644 kandinsky/include/kandinsky/unicode/code_point.h delete mode 100644 kandinsky/include/kandinsky/unicode/codepoint.h diff --git a/kandinsky/fonts/codepoints.h b/kandinsky/fonts/code_points.h similarity index 98% rename from kandinsky/fonts/codepoints.h rename to kandinsky/fonts/code_points.h index f039df1d8..44195cf96 100644 --- a/kandinsky/fonts/codepoints.h +++ b/kandinsky/fonts/code_points.h @@ -1,7 +1,7 @@ // [0x30a].map{|i| "0x" + i.to_s(16) +", // " + [i].pack("U") + " // " + Unicode::Name.of([i].pack("U"))}.join("|") #include -uint32_t Codepoints[] = { +uint32_t CodePoints[] = { 0x20, // // SPACE 0x21, // ! // EXCLAMATION MARK 0x22, // " // QUOTATION MARK @@ -146,4 +146,4 @@ uint32_t Codepoints[] = { }; -int NumberOfCodepoints = sizeof(Codepoints)/sizeof(Codepoints[0]); +int NumberOfCodePoints = sizeof(CodePoints)/sizeof(CodePoints[0]); diff --git a/kandinsky/fonts/rasterizer.c b/kandinsky/fonts/rasterizer.c index 5903adfdf..1990ba56c 100644 --- a/kandinsky/fonts/rasterizer.c +++ b/kandinsky/fonts/rasterizer.c @@ -16,7 +16,7 @@ #include FT_FREETYPE_H #include "unicode_for_symbol.h" -#include "codepoints.h" +#include "code_points.h" #include "../../ion/src/external/lz4/lz4hc.h" @@ -94,9 +94,9 @@ int main(int argc, char * argv[]) { int maxWidth = 0; int maxAboveBaseline = 0; int maxBelowBaseline = 0; - for (int i=0; i < NumberOfCodepoints; i++) { - wchar_t codepoint = Codepoints[i]; - ENSURE(!FT_Load_Char(face, codepoint, FT_LOAD_RENDER), "Loading character 0x%02x", codepoint); + for (int i=0; i < NumberOfCodePoints; i++) { + wchar_t codePoint = CodePoints[i]; + ENSURE(!FT_Load_Char(face, codePoint, FT_LOAD_RENDER), "Loading character 0x%02x", codePoint); int aboveBaseline = face->glyph->bitmap_top; int belowBaseline = face->glyph->bitmap.rows - face->glyph->bitmap_top; int width = face->glyph->bitmap_left + face->glyph->bitmap.width; @@ -117,7 +117,7 @@ int main(int argc, char * argv[]) { int grid_size = 1; int grid_width = 20; - int grid_height = ((NumberOfCodepoints-1)/grid_width)+1; + int grid_height = ((NumberOfCodePoints-1)/grid_width)+1; bitmap_image.width = grid_width*glyph_width+(grid_width-1)*grid_size; bitmap_image.height = grid_height*glyph_height+(grid_height-1)*grid_size; @@ -135,12 +135,12 @@ int main(int argc, char * argv[]) { } } - for (int i=0; iglyph->bitmap_left, face->glyph->bitmap_top); while (face->glyph->bitmap_left < 0) { // This is a workaround for combining glyphs. @@ -165,18 +165,18 @@ int main(int argc, char * argv[]) { fprintf(sourceFile, "/* This file is auto-generated by the rasterizer */\n\n"); fprintf(sourceFile, "#include \n\n"); - // Step 1 - Build the GlyphIndex <-> UnicodeCodepoint correspondance table + // Step 1 - Build the GlyphIndex <-> UnicodeCodePoint correspondance table int previousIndex = -1; - uint32_t previousCodepoint = 0; + uint32_t previousCodePoint = 0; int numberOfPairs = 0; - fprintf(sourceFile, "static constexpr KDFont::CodepointIndexPair table[] = {\n"); - for (int i=0; i #include #include -#include +#include #include "palette.h" /* We use UTF-8 encoding. This means that a character is encoded as a code point @@ -17,11 +17,11 @@ * ASCII characters have the same encoding in ASCII and in UTF-8. * * We do not provide a glyph for each of the 1,112,064 valid UTF-8 code points. - * We thus have a table of the glyphs we can draw (uint32_t Codepoints[] in - * kandinsky/fonts/codepoints.h). To easily compute the index of a code point in - * the Codepoints table, we use the m_table matching table: it contains the - * CodepointIndexPairs of the first code point of each series of consecutive - * code points in the Codepoints table. */ + * We thus have a table of the glyphs we can draw (uint32_t CodePoints[] in + * kandinsky/fonts/code_points.h). To easily compute the index of a code point in + * the CodePoints table, we use the m_table matching table: it contains the + * CodePointIndexPairs of the first code point of each series of consecutive + * code points in the CodePoints table. */ class KDFont { private: @@ -47,19 +47,19 @@ public: }; using GlyphIndex = uint8_t; - class CodepointIndexPair { + class CodePointIndexPair { public: - constexpr CodepointIndexPair(Codepoint c, GlyphIndex i) : m_codepoint(c), m_glyphIndex(i) {} - Codepoint codepoint() const { return m_codepoint; } + constexpr CodePointIndexPair(CodePoint c, GlyphIndex i) : m_codePoint(c), m_glyphIndex(i) {} + CodePoint codePoint() const { return m_codePoint; } GlyphIndex glyphIndex() const { return m_glyphIndex; } private: - Codepoint m_codepoint; + CodePoint m_codePoint; GlyphIndex m_glyphIndex; }; - GlyphIndex indexForCodepoint(Codepoint c) const; + GlyphIndex indexForCodePoint(CodePoint c) const; - void setGlyphGreyscalesForCodepoint(Codepoint codepoint, GlyphBuffer * glyphBuffer) const; - void accumulateGlyphGreyscalesForCodepoint(Codepoint codepoint, GlyphBuffer * glyphBuffer) const; + void setGlyphGreyscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const; + void accumulateGlyphGreyscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const; using RenderPalette = KDPalette<(1<; void colorizeGlyphBuffer(const RenderPalette * renderPalette, GlyphBuffer * glyphBuffer) const; @@ -69,7 +69,7 @@ public: } KDSize glyphSize() const { return m_glyphSize; } - constexpr KDFont(size_t tableLength, const CodepointIndexPair * table, KDCoordinate glyphWidth, KDCoordinate glyphHeight, const uint16_t * glyphDataOffset, const uint8_t * data) : + constexpr KDFont(size_t tableLength, const CodePointIndexPair * table, KDCoordinate glyphWidth, KDCoordinate glyphHeight, const uint16_t * glyphDataOffset, const uint8_t * data) : m_tableLength(tableLength), m_table(table), m_glyphSize(glyphWidth, glyphHeight), m_glyphDataOffset(glyphDataOffset), m_data(data) { } private: void fetchGreyscaleGlyphAtIndex(GlyphIndex index, uint8_t * greyscaleBuffer) const; @@ -83,7 +83,7 @@ private: } size_t m_tableLength; - const CodepointIndexPair * m_table; + const CodePointIndexPair * m_table; KDSize m_glyphSize; const uint16_t * m_glyphDataOffset; const uint8_t * m_data; diff --git a/kandinsky/include/kandinsky/unicode/code_point.h b/kandinsky/include/kandinsky/unicode/code_point.h new file mode 100644 index 000000000..0487efb97 --- /dev/null +++ b/kandinsky/include/kandinsky/unicode/code_point.h @@ -0,0 +1,23 @@ +#ifndef KANDINSKY_UNICODE_CODE_POINT_H +#define KANDINSKY_UNICODE_CODE_POINT_H + +#include + +class CodePoint { +public: + constexpr CodePoint(uint32_t c) : m_code(c) {} + operator uint16_t() const { return m_code; } + + + bool isCombining() const { + return (m_code >= 0x300 && m_code <= 0x036F); + } +private: + uint32_t m_code; +}; + +static constexpr CodePoint Null = 0x0; +static constexpr CodePoint Tabulation = 0x9; +static constexpr CodePoint LineFeed = 0xA; + +#endif diff --git a/kandinsky/include/kandinsky/unicode/codepoint.h b/kandinsky/include/kandinsky/unicode/codepoint.h deleted file mode 100644 index 323647cfe..000000000 --- a/kandinsky/include/kandinsky/unicode/codepoint.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef KANDINSKY_UNICODE_CODEPOINT_H -#define KANDINSKY_UNICODE_CODEPOINT_H - -#include - -class Codepoint { -public: - constexpr Codepoint(uint32_t c) : m_code(c) {} - operator uint16_t() const { return m_code; } - - - bool isCombining() const { - return (m_code >= 0x300 && m_code <= 0x036F); - } -private: - uint32_t m_code; -}; - -static constexpr Codepoint Null = 0x0; -static constexpr Codepoint Tabulation = 0x9; -static constexpr Codepoint LineFeed = 0xA; - -#endif diff --git a/kandinsky/include/kandinsky/unicode/utf8decoder.h b/kandinsky/include/kandinsky/unicode/utf8decoder.h index f9badc77f..36aed2c48 100644 --- a/kandinsky/include/kandinsky/unicode/utf8decoder.h +++ b/kandinsky/include/kandinsky/unicode/utf8decoder.h @@ -1,12 +1,12 @@ #ifndef KANDINSKY_UNICODE_UTF8DECODER_H #define KANDINSKY_UNICODE_UTF8DECODER_H -#include "codepoint.h" +#include "code_point.h" class UTF8Decoder { public: UTF8Decoder(const char * string) : m_string(string) {} - Codepoint nextCodepoint(); + CodePoint nextCodePoint(); private: const char * m_string; }; diff --git a/kandinsky/src/context_text.cpp b/kandinsky/src/context_text.cpp index 6a4282f90..d4654a6d0 100644 --- a/kandinsky/src/context_text.cpp +++ b/kandinsky/src/context_text.cpp @@ -13,21 +13,21 @@ KDPoint KDContext::drawString(const char * text, KDPoint p, const KDFont * font, KDFont::GlyphBuffer glyphBuffer; UTF8Decoder decoder(text); - Codepoint codepoint = decoder.nextCodepoint(); - while (codepoint != Null) { - if (codepoint == LineFeed) { + CodePoint codePoint = decoder.nextCodePoint(); + while (codePoint != Null) { + if (codePoint == LineFeed) { position = KDPoint(0, position.y() + glyphSize.height()); - codepoint = decoder.nextCodepoint(); - } else if (codepoint == Tabulation) { + codePoint = decoder.nextCodePoint(); + } else if (codePoint == Tabulation) { position = position.translatedBy(KDPoint(k_tabCharacterWidth * glyphSize.width(), 0)); - codepoint = decoder.nextCodepoint(); + codePoint = decoder.nextCodePoint(); } else { - assert(!codepoint.isCombining()); - font->setGlyphGreyscalesForCodepoint(codepoint, &glyphBuffer); - codepoint = decoder.nextCodepoint(); - while (codepoint.isCombining()) { - font->accumulateGlyphGreyscalesForCodepoint(codepoint, &glyphBuffer); - codepoint = decoder.nextCodepoint(); + assert(!codePoint.isCombining()); + font->setGlyphGreyscalesForCodePoint(codePoint, &glyphBuffer); + codePoint = decoder.nextCodePoint(); + while (codePoint.isCombining()) { + font->accumulateGlyphGreyscalesForCodePoint(codePoint, &glyphBuffer); + codePoint = decoder.nextCodePoint(); } font->colorizeGlyphBuffer(&palette, &glyphBuffer); // Flush accumulated content diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index 40fbebd00..15173836b 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -12,31 +12,31 @@ KDSize KDFont::stringSize(const char * text) const { KDSize stringSize = KDSize(0, m_glyphSize.height()); UTF8Decoder decoder(text); - Codepoint codepoint = decoder.nextCodepoint(); - while (codepoint != Null) { + CodePoint codePoint = decoder.nextCodePoint(); + while (codePoint != Null) { KDSize cSize = KDSize(m_glyphSize.width(), 0); - if (codepoint == LineFeed) { + if (codePoint == LineFeed) { cSize = KDSize(0, m_glyphSize.height()); - codepoint = decoder.nextCodepoint(); - } else if (codepoint == Tabulation) { + codePoint = decoder.nextCodePoint(); + } else if (codePoint == Tabulation) { cSize = KDSize(k_tabCharacterWidth*m_glyphSize.width(), 0); - } else if (codepoint.isCombining()) { + } else if (codePoint.isCombining()) { cSize = KDSizeZero; } stringSize = KDSize(stringSize.width()+cSize.width(), stringSize.height()+cSize.height()); - codepoint = decoder.nextCodepoint(); + codePoint = decoder.nextCodePoint(); } return stringSize; } -void KDFont::setGlyphGreyscalesForCodepoint(Codepoint codepoint, GlyphBuffer * glyphBuffer) const { - fetchGreyscaleGlyphAtIndex(indexForCodepoint(codepoint), glyphBuffer->greyscaleBuffer()); +void KDFont::setGlyphGreyscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const { + fetchGreyscaleGlyphAtIndex(indexForCodePoint(codePoint), glyphBuffer->greyscaleBuffer()); } -void KDFont::accumulateGlyphGreyscalesForCodepoint(Codepoint codepoint, GlyphBuffer * glyphBuffer) const { +void KDFont::accumulateGlyphGreyscalesForCodePoint(CodePoint codePoint, GlyphBuffer * glyphBuffer) const { uint8_t * greyscaleBuffer = glyphBuffer->greyscaleBuffer(); uint8_t * accumulationGreyscaleBuffer = glyphBuffer->secondaryGreyscaleBuffer(); - fetchGreyscaleGlyphAtIndex(indexForCodepoint(codepoint), accumulationGreyscaleBuffer); + fetchGreyscaleGlyphAtIndex(indexForCodePoint(codePoint), accumulationGreyscaleBuffer); for (int i=0; icodepoint() = %d and c = %d\n", currentPair->codepoint(), c); - if (currentPair->codepoint() == c) { + const CodePointIndexPair * currentPair = m_table + currentIndex; + const CodePointIndexPair * nextPair = (currentIndex + 1) < m_tableLength ? currentPair + 1 : nullptr; + // printf("At this point, currentPair->codePoint() = %d and c = %d\n", currentPair->codePoint(), c); + if (currentPair->codePoint() == c) { return currentPair->glyphIndex(); - } else if (currentPair->codepoint() > c) { + } else if (currentPair->codePoint() > c) { // We need to look below if (upperBound == currentIndex) { // There's nothing below. Error out. @@ -101,9 +101,9 @@ KDFont::GlyphIndex KDFont::indexForCodepoint(Codepoint c) const { continue; } else if (nextPair == nullptr) { return 0; - } else if (nextPair->codepoint() == c) { + } else if (nextPair->codePoint() == c) { return nextPair->glyphIndex(); - } else if (nextPair->codepoint() < c) { + } else if (nextPair->codePoint() < c) { // We need to look above if (lowerBound == currentIndex) { // There's nothing above. Error out. @@ -113,40 +113,40 @@ KDFont::GlyphIndex KDFont::indexForCodepoint(Codepoint c) const { continue; } else { // At this point, - // currentPair->codepoint < c && nextPair != nullptr && nextPair->codepoint > c + // currentPair->codePoint < c && nextPair != nullptr && nextPair->codePoint > c // Yay, it's over! // There can be an empty space between the currentPair and the nextPair // e.g. currentPair(3,1) and nextPair(9, 4) - // means value at codepoints 3, 4, 5, 6, 7, 8, 9 + // means value at codePoints 3, 4, 5, 6, 7, 8, 9 // are glyph identifiers 1, ?, ?, ?, ?, ?, 4 // solved as 1, 2, 3, 0, 0, 0, 4 // Let's hunt down the zeroes - Codepoint lastCodepointOfCurrentPair = currentPair->codepoint() + (nextPair->glyphIndex() - currentPair->glyphIndex() - 1); - if (c > lastCodepointOfCurrentPair) { + CodePoint lastCodePointOfCurrentPair = currentPair->codePoint() + (nextPair->glyphIndex() - currentPair->glyphIndex() - 1); + if (c > lastCodePointOfCurrentPair) { return 0; } - return currentPair->glyphIndex() + (c - currentPair->codepoint()); + return currentPair->glyphIndex() + (c - currentPair->codePoint()); } } #else - const CodepointIndexPair * currentPair = m_table; - if (c < currentPair->codepoint()) { + const CodePointIndexPair * currentPair = m_table; + if (c < currentPair->codePoint()) { return 0; } - const CodepointIndexPair * endPair = m_table + m_tableLength - 1; + const CodePointIndexPair * endPair = m_table + m_tableLength - 1; while (currentPair < endPair) { - const CodepointIndexPair * nextPair = currentPair + 1; - if (c < nextPair->codepoint()) { - Codepoint lastCodepointOfCurrentPair = currentPair->codepoint() + (nextPair->glyphIndex() - currentPair->glyphIndex() - 1); - if (c > lastCodepointOfCurrentPair) { + const CodePointIndexPair * nextPair = currentPair + 1; + if (c < nextPair->codePoint()) { + CodePoint lastCodePointOfCurrentPair = currentPair->codePoint() + (nextPair->glyphIndex() - currentPair->glyphIndex() - 1); + if (c > lastCodePointOfCurrentPair) { return 0; } - return currentPair->glyphIndex() + (c - currentPair->codepoint()); + return currentPair->glyphIndex() + (c - currentPair->codePoint()); } currentPair = nextPair; } - if (endPair->codepoint() == c) { + if (endPair->codePoint() == c) { return endPair->glyphIndex(); } return 0; diff --git a/kandinsky/src/unicode/utf8decoder.cpp b/kandinsky/src/unicode/utf8decoder.cpp index e8585e9f8..7990c28bb 100644 --- a/kandinsky/src/unicode/utf8decoder.cpp +++ b/kandinsky/src/unicode/utf8decoder.cpp @@ -15,12 +15,12 @@ static inline uint8_t last_k_bits(uint8_t value, uint8_t bits) { return (value & ((1< #include -static constexpr KDFont::CodepointIndexPair table[] = { - KDFont::CodepointIndexPair(3, 1), // Codepoint, identifier - KDFont::CodepointIndexPair(9, 4), - KDFont::CodepointIndexPair(12, 5), - KDFont::CodepointIndexPair(14, 7) +static constexpr KDFont::CodePointIndexPair table[] = { + KDFont::CodePointIndexPair(3, 1), // CodePoint, identifier + KDFont::CodePointIndexPair(9, 4), + KDFont::CodePointIndexPair(12, 5), + KDFont::CodePointIndexPair(14, 7) }; constexpr KDFont testFont(4, table, 10, 10, nullptr, nullptr); -const KDFont::GlyphIndex index_for_codepoint[] = { +const KDFont::GlyphIndex index_for_code_point[] = { /* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 */ 0, 0, 0, 1, 2, 3, 0, 0, 0, 4, 0, 0, 5, 6, 7, 0 }; -QUIZ_CASE(kandinsky_font_index_for_codepoint) { +QUIZ_CASE(kandinsky_font_index_for_code_point) { for (int i=0; i<16; i++) { - KDFont::GlyphIndex result = testFont.indexForCodepoint(i); - quiz_assert(result == index_for_codepoint[i]); + KDFont::GlyphIndex result = testFont.indexForCodePoint(i); + quiz_assert(result == index_for_code_point[i]); } } diff --git a/kandinsky/test/utf8decoder.cpp b/kandinsky/test/utf8decoder.cpp index 3e858f526..1198166a6 100644 --- a/kandinsky/test/utf8decoder.cpp +++ b/kandinsky/test/utf8decoder.cpp @@ -1,10 +1,10 @@ #include #include -void assert_decodes_to(const char * string, Codepoint c) { +void assert_decodes_to(const char * string, CodePoint c) { UTF8Decoder d(string); - quiz_assert(d.nextCodepoint() == c); - quiz_assert(d.nextCodepoint() == 0); + quiz_assert(d.nextCodePoint() == c); + quiz_assert(d.nextCodePoint() == 0); } QUIZ_CASE(kandinsky_utf8_decoder) { diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index bd2c8e823..84c3a2d7e 100644 --- a/poincare/src/parsing/tokenizer.cpp +++ b/poincare/src/parsing/tokenizer.cpp @@ -16,13 +16,13 @@ static inline bool isDigit(const char c) { const char Tokenizer::nextChar(PopTest popTest, char context, bool * testResult) { // Beware of chars spaning over more than one byte: use the UTF8Decoder. UTF8Decoder decoder(m_text); - Codepoint firstCodepoint = decoder.nextCodepoint(); + CodePoint firstCodePoint = decoder.nextCodePoint(); int numberOfBytesForChar = 1; - if (firstCodepoint != Null) { - Codepoint codepoint = decoder.nextCodepoint(); - while (codepoint.isCombining()) { + if (firstCodePoint != Null) { + CodePoint codePoint = decoder.nextCodePoint(); + while (codePoint.isCombining()) { numberOfBytesForChar++; - codepoint = decoder.nextCodepoint(); + codePoint = decoder.nextCodePoint(); } } char c = *m_text; // TODO handle combined chars? From 02a5d6cd237711c688efd63642c41c1fb4133dc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 11 Jan 2019 11:40:32 +0100 Subject: [PATCH 0305/1750] [kandinsky] Add KDCodePoint prefix static code points --- kandinsky/include/kandinsky/unicode/code_point.h | 6 +++--- kandinsky/src/context_text.cpp | 6 +++--- kandinsky/src/font.cpp | 6 +++--- poincare/src/parsing/tokenizer.cpp | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/kandinsky/include/kandinsky/unicode/code_point.h b/kandinsky/include/kandinsky/unicode/code_point.h index 0487efb97..033e96407 100644 --- a/kandinsky/include/kandinsky/unicode/code_point.h +++ b/kandinsky/include/kandinsky/unicode/code_point.h @@ -16,8 +16,8 @@ private: uint32_t m_code; }; -static constexpr CodePoint Null = 0x0; -static constexpr CodePoint Tabulation = 0x9; -static constexpr CodePoint LineFeed = 0xA; +static constexpr CodePoint KDCodePointNull = 0x0; +static constexpr CodePoint KDCodePointTabulation = 0x9; +static constexpr CodePoint KDCodePointLineFeed = 0xA; #endif diff --git a/kandinsky/src/context_text.cpp b/kandinsky/src/context_text.cpp index d4654a6d0..05728dbba 100644 --- a/kandinsky/src/context_text.cpp +++ b/kandinsky/src/context_text.cpp @@ -14,11 +14,11 @@ KDPoint KDContext::drawString(const char * text, KDPoint p, const KDFont * font, UTF8Decoder decoder(text); CodePoint codePoint = decoder.nextCodePoint(); - while (codePoint != Null) { - if (codePoint == LineFeed) { + while (codePoint != KDCodePointNull) { + if (codePoint == KDCodePointLineFeed) { position = KDPoint(0, position.y() + glyphSize.height()); codePoint = decoder.nextCodePoint(); - } else if (codePoint == Tabulation) { + } else if (codePoint == KDCodePointTabulation) { position = position.translatedBy(KDPoint(k_tabCharacterWidth * glyphSize.width(), 0)); codePoint = decoder.nextCodePoint(); } else { diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index 15173836b..6f48d7814 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -13,12 +13,12 @@ KDSize KDFont::stringSize(const char * text) const { UTF8Decoder decoder(text); CodePoint codePoint = decoder.nextCodePoint(); - while (codePoint != Null) { + while (codePoint != KDCodePointNull) { KDSize cSize = KDSize(m_glyphSize.width(), 0); - if (codePoint == LineFeed) { + if (codePoint == KDCodePointLineFeed) { cSize = KDSize(0, m_glyphSize.height()); codePoint = decoder.nextCodePoint(); - } else if (codePoint == Tabulation) { + } else if (codePoint == KDCodePointTabulation) { cSize = KDSize(k_tabCharacterWidth*m_glyphSize.width(), 0); } else if (codePoint.isCombining()) { cSize = KDSizeZero; diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index 84c3a2d7e..0dfa5e0fa 100644 --- a/poincare/src/parsing/tokenizer.cpp +++ b/poincare/src/parsing/tokenizer.cpp @@ -18,7 +18,7 @@ const char Tokenizer::nextChar(PopTest popTest, char context, bool * testResult) UTF8Decoder decoder(m_text); CodePoint firstCodePoint = decoder.nextCodePoint(); int numberOfBytesForChar = 1; - if (firstCodePoint != Null) { + if (firstCodePoint != KDCodePointNull) { CodePoint codePoint = decoder.nextCodePoint(); while (codePoint.isCombining()) { numberOfBytesForChar++; From cf79b26ceb3fbfb97d6014140ec0badd159e4eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 11 Jan 2019 11:52:20 +0100 Subject: [PATCH 0306/1750] [kandinsky] UTF8Decoder::CodePointToChars --- .../include/kandinsky/unicode/utf8decoder.h | 13 ++++++++++++ kandinsky/src/unicode/utf8decoder.cpp | 21 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/kandinsky/include/kandinsky/unicode/utf8decoder.h b/kandinsky/include/kandinsky/unicode/utf8decoder.h index 36aed2c48..fcabe859a 100644 --- a/kandinsky/include/kandinsky/unicode/utf8decoder.h +++ b/kandinsky/include/kandinsky/unicode/utf8decoder.h @@ -1,12 +1,25 @@ #ifndef KANDINSKY_UNICODE_UTF8DECODER_H #define KANDINSKY_UNICODE_UTF8DECODER_H +#include #include "code_point.h" +/* UTF-8 encodes all valid code points using at most 4 bytes (= 28 bits), the + * lowest codes being equal to ASCII codes. There are less than 2^21 different + * UTF-8 valid code points. + * + * The encoding is the following: + * For code points between ... -> The corresponding bits are ... + * 0 and 7F -> 0xxxxxxx + * 80 and 7FF -> 110xxxxx 10xxxxxx + * 800 and FFFF -> 1110xxxx 10xxxxxx 10xxxxxx + * 10000 and 10FFFF -> 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + class UTF8Decoder { public: UTF8Decoder(const char * string) : m_string(string) {} CodePoint nextCodePoint(); + static size_t CodePointToChars(CodePoint c, char * buffer, int bufferSize); private: const char * m_string; }; diff --git a/kandinsky/src/unicode/utf8decoder.cpp b/kandinsky/src/unicode/utf8decoder.cpp index 7990c28bb..5648df4df 100644 --- a/kandinsky/src/unicode/utf8decoder.cpp +++ b/kandinsky/src/unicode/utf8decoder.cpp @@ -24,3 +24,24 @@ CodePoint UTF8Decoder::nextCodePoint() { } return CodePoint(result); } + +size_t UTF8Decoder::CodePointToChars(CodePoint c, char * buffer, int bufferSize) { + assert(bufferSize >= sizeof(CodePoint)/sizeof(char)); + size_t i = 0; + if (c <= 0x7F) { + buffer[i++] = c; + } else if (c <= 0x7FF) { + buffer[i++] = 0b11000000 | (c >> 6); + buffer[i++] = 0b10000000 | (c & 0b111111); + } else if (c <= 0xFFFF) { + buffer[i++] = 0b11100000 | (c >> 12); + buffer[i++] = 0b10000000 | ((c >> 6) & 0b111111); + buffer[i++] = 0b10000000 | (c & 0b111111); + } else { + buffer[i++] = 0b11110000 | (c >> 18); + buffer[i++] = 0b10000000 | ((c >> 12) & 0b111111); + buffer[i++] = 0b10000000 | ((c >> 6) & 0b111111); + buffer[i++] = 0b10000000 | (c & 0b111111); + } + return i; +} From 8d584cf37b75a9349f859dd37c5108fab571e490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 11 Jan 2019 12:13:22 +0100 Subject: [PATCH 0307/1750] [kandinsly] Rename utf8decoder as utf8_decoder --- kandinsky/Makefile | 4 ++-- .../kandinsky/unicode/{utf8decoder.h => utf8_decoder.h} | 4 ++-- kandinsky/src/context_text.cpp | 2 +- kandinsky/src/font.cpp | 2 +- kandinsky/src/unicode/{utf8decoder.cpp => utf8_decoder.cpp} | 2 +- kandinsky/test/utf8decoder.cpp | 2 +- poincare/src/parsing/tokenizer.cpp | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) rename kandinsky/include/kandinsky/unicode/{utf8decoder.h => utf8_decoder.h} (90%) rename kandinsky/src/unicode/{utf8decoder.cpp => utf8_decoder.cpp} (96%) diff --git a/kandinsky/Makefile b/kandinsky/Makefile index af4804052..368948437 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -13,7 +13,7 @@ src += $(addprefix kandinsky/src/,\ ion_context.cpp \ point.cpp \ rect.cpp \ - unicode/utf8decoder.cpp\ + unicode/utf8_decoder.cpp\ ) src += $(addprefix kandinsky/fonts/, \ @@ -25,7 +25,7 @@ tests += $(addprefix kandinsky/test/,\ color.cpp\ font.cpp\ rect.cpp\ - utf8decoder.cpp\ + utf8_decoder.cpp\ ) RASTERIZER_CFLAGS := -std=c99 `pkg-config freetype2 --cflags` diff --git a/kandinsky/include/kandinsky/unicode/utf8decoder.h b/kandinsky/include/kandinsky/unicode/utf8_decoder.h similarity index 90% rename from kandinsky/include/kandinsky/unicode/utf8decoder.h rename to kandinsky/include/kandinsky/unicode/utf8_decoder.h index fcabe859a..f4dab5269 100644 --- a/kandinsky/include/kandinsky/unicode/utf8decoder.h +++ b/kandinsky/include/kandinsky/unicode/utf8_decoder.h @@ -1,5 +1,5 @@ -#ifndef KANDINSKY_UNICODE_UTF8DECODER_H -#define KANDINSKY_UNICODE_UTF8DECODER_H +#ifndef KANDINSKY_UNICODE_UTF8_DECODER_H +#define KANDINSKY_UNICODE_UTF8_DECODER_H #include #include "code_point.h" diff --git a/kandinsky/src/context_text.cpp b/kandinsky/src/context_text.cpp index 05728dbba..860d9da65 100644 --- a/kandinsky/src/context_text.cpp +++ b/kandinsky/src/context_text.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include constexpr static int k_tabCharacterWidth = 4; diff --git a/kandinsky/src/font.cpp b/kandinsky/src/font.cpp index 6f48d7814..a921b4a4b 100644 --- a/kandinsky/src/font.cpp +++ b/kandinsky/src/font.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include constexpr static int k_tabCharacterWidth = 4; diff --git a/kandinsky/src/unicode/utf8decoder.cpp b/kandinsky/src/unicode/utf8_decoder.cpp similarity index 96% rename from kandinsky/src/unicode/utf8decoder.cpp rename to kandinsky/src/unicode/utf8_decoder.cpp index 5648df4df..bdb6ed370 100644 --- a/kandinsky/src/unicode/utf8decoder.cpp +++ b/kandinsky/src/unicode/utf8_decoder.cpp @@ -1,4 +1,4 @@ -#include +#include #include static inline int leading_ones(uint8_t value) { diff --git a/kandinsky/test/utf8decoder.cpp b/kandinsky/test/utf8decoder.cpp index 1198166a6..3c649f3b7 100644 --- a/kandinsky/test/utf8decoder.cpp +++ b/kandinsky/test/utf8decoder.cpp @@ -1,5 +1,5 @@ #include -#include +#include void assert_decodes_to(const char * string, CodePoint c) { UTF8Decoder d(string); diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index 0dfa5e0fa..9b7d440f7 100644 --- a/poincare/src/parsing/tokenizer.cpp +++ b/poincare/src/parsing/tokenizer.cpp @@ -1,7 +1,7 @@ #include "tokenizer.h" #include #include -#include +#include namespace Poincare { From 956c9fe300e8912f0229964b30375449aca39ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 11 Jan 2019 14:02:50 +0100 Subject: [PATCH 0308/1750] [poincare] CodePointLayout --- .../include/kandinsky/unicode/code_point.h | 10 +- poincare/Makefile | 1 + poincare/include/poincare/code_point_layout.h | 76 ++++++++++++ poincare/include/poincare/layout.h | 1 + poincare/include/poincare/layout_node.h | 1 + .../include/poincare/serialization_helper.h | 9 +- poincare/include/poincare_layouts.h | 1 + poincare/src/code_point_layout.cpp | 108 ++++++++++++++++++ poincare/src/serialization_helper.cpp | 18 +++ 9 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 poincare/include/poincare/code_point_layout.h create mode 100644 poincare/src/code_point_layout.cpp diff --git a/kandinsky/include/kandinsky/unicode/code_point.h b/kandinsky/include/kandinsky/unicode/code_point.h index 033e96407..fc32a7b1a 100644 --- a/kandinsky/include/kandinsky/unicode/code_point.h +++ b/kandinsky/include/kandinsky/unicode/code_point.h @@ -16,8 +16,12 @@ private: uint32_t m_code; }; -static constexpr CodePoint KDCodePointNull = 0x0; -static constexpr CodePoint KDCodePointTabulation = 0x9; -static constexpr CodePoint KDCodePointLineFeed = 0xA; +static constexpr CodePoint KDCodePointNull = 0x0; +static constexpr CodePoint KDCodePointTabulation = 0x9; +static constexpr CodePoint KDCodePointLineFeed = 0xA; +static constexpr CodePoint KDCodePointMiddleDot = 0xB7; +static constexpr CodePoint KDCodePointMultiplicationSign = 0xD7; +static constexpr CodePoint KDCodePointLatinLetterSmallCapitalE = 0x1d07; +static constexpr CodePoint KDCodePointRightwardsArrow = 0x2192; #endif diff --git a/poincare/Makefile b/poincare/Makefile index 4a41680f9..cc7f7e2df 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -5,6 +5,7 @@ src += $(addprefix poincare/src/,\ bracket_layout.cpp \ bracket_pair_layout.cpp \ char_layout.cpp \ + code_point_layout.cpp\ condensed_sum_layout.cpp \ conjugate_layout.cpp \ empty_layout.cpp \ diff --git a/poincare/include/poincare/code_point_layout.h b/poincare/include/poincare/code_point_layout.h new file mode 100644 index 000000000..06f5adb91 --- /dev/null +++ b/poincare/include/poincare/code_point_layout.h @@ -0,0 +1,76 @@ +#ifndef POINCARE_CODEPOINT_LAYOUT_NODE_H +#define POINCARE_CODEPOINT_LAYOUT_NODE_H + +#include +#include +#include + +namespace Poincare { + +/* TODO: Make several code point classes depending on codepoint size? + * (m_codePoint sometimes fits in a char, no need for a whole CodePoint */ + +class CodePointLayoutNode final : public LayoutNode { +public: + static constexpr const KDFont * k_defaultFont = KDFont::LargeFont; + CodePointLayoutNode(CodePoint c = KDCodePointNull, const KDFont * font = k_defaultFont) : + LayoutNode(), + m_codePoint(c), + m_font(font) + {} + + // CodePointLayout + CodePoint codePoint() const { return m_codePoint; } + const KDFont * font() const { return m_font; } + + // LayoutNode + void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; + void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; + int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; + bool isCodePoint() const override { return true; } + bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; + bool canBeOmittedMultiplicationLeftFactor() const override; + bool canBeOmittedMultiplicationRightFactor() const override; + + // TreeNode + size_t size() const override { return sizeof(CodePointLayoutNode); } + int numberOfChildren() const override { return 0; } +#if POINCARE_TREE_LOG + virtual void logNodeName(std::ostream & stream) const override { + stream << "CodePointLayout"; + } + virtual void logAttributes(std::ostream & stream) const override { + stream << " CodePoint=\"" << m_codePoint << "\""; + } +#endif + +protected: + // LayoutNode + KDSize computeSize() override; + KDCoordinate computeBaseline() override; + KDPoint positionOfChild(LayoutNode * child) override { + assert(false); + return KDPointZero; + } + +private: + void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; + bool isMultiplicationCodePoint() const; + CodePoint m_codePoint; + const KDFont * m_font; +}; + +class CodePointLayout final : public Layout { +public: + CodePointLayout(const CodePointLayoutNode * n) : Layout(n) {} + static CharLayout Builder(CodePoint c, const KDFont * font = KDFont::LargeFont); + const KDFont * font() const { return const_cast(this)->node()->font(); } + CodePoint codePoint() const { return const_cast(this)->node()->codePoint(); } +private: + using Layout::node; + CodePointLayoutNode * node() { return static_cast(Layout::node()); } +}; + +} + +#endif diff --git a/poincare/include/poincare/layout.h b/poincare/include/poincare/layout.h index 2dfffdcd9..bf70fba41 100644 --- a/poincare/include/poincare/layout.h +++ b/poincare/include/poincare/layout.h @@ -50,6 +50,7 @@ public: bool isVerticalOffset() const { return const_cast(this)->node()->isVerticalOffset(); } bool isLeftParenthesis() const { return const_cast(this)->node()->isLeftParenthesis(); } bool isChar() const { return const_cast(this)->node()->isChar(); } + bool isCodePoint() const { return const_cast(this)->node()->isCodePoint(); } bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { return const_cast(this)->node()->isCollapsable(numberOfOpenParenthesis, goingLeft); } int leftCollapsingAbsorbingChildIndex() const { return const_cast(this)->node()->leftCollapsingAbsorbingChildIndex(); } int rightCollapsingAbsorbingChildIndex() const { return const_cast(this)->node()->rightCollapsingAbsorbingChildIndex(); } diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index 40a2afe41..c10120091 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -105,6 +105,7 @@ public: virtual bool isEmpty() const { return false; } virtual bool isMatrix() const { return false; } virtual bool isChar() const { return false; } + virtual bool isCodePoint() const { return false; } virtual bool hasUpperLeftIndex() const { return false; } virtual char XNTChar() const { LayoutNode * p = parent(); diff --git a/poincare/include/poincare/serialization_helper.h b/poincare/include/poincare/serialization_helper.h index 2135d0aea..c2406302d 100644 --- a/poincare/include/poincare/serialization_helper.h +++ b/poincare/include/poincare/serialization_helper.h @@ -2,11 +2,12 @@ #define POINCARE_SERIALIZATION_HELPER_H #include +#include namespace Poincare { namespace SerializationHelper { - /* SerializableReference to Text */ + // SerializableReference to text int Infix( const TreeNode * node, char * buffer, @@ -26,8 +27,10 @@ namespace SerializationHelper { const char * operatorName, bool writeFirstChild = true); - /* Write one char in buffer */ - int Char(char * buffer, int bufferSize, char charToWrite); + // Write one char in buffer + int Char(char * buffer, int bufferSize, char charToWrite); // TODO REMOVE + // Write one code point in a buffer + int CodePoint(char * buffer, int bufferSize, CodePoint c); }; } diff --git a/poincare/include/poincare_layouts.h b/poincare/include/poincare_layouts.h index 743d99bac..3cf5ee953 100644 --- a/poincare/include/poincare_layouts.h +++ b/poincare/include/poincare_layouts.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include diff --git a/poincare/src/code_point_layout.cpp b/poincare/src/code_point_layout.cpp new file mode 100644 index 000000000..2e052164c --- /dev/null +++ b/poincare/src/code_point_layout.cpp @@ -0,0 +1,108 @@ +#include +#include +#include + +namespace Poincare { + +// LayoutNode +void CodePointLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) { + if (cursor->position() == LayoutCursor::Position::Right) { + cursor->setPosition(LayoutCursor::Position::Left); + return; + } + LayoutNode * parentNode = parent(); + if (parentNode != nullptr) { + parentNode->moveCursorLeft(cursor, shouldRecomputeLayout); + } +} + +void CodePointLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) { + if (cursor->position() == LayoutCursor::Position::Left) { + cursor->setPosition(LayoutCursor::Position::Right); + return; + } + LayoutNode * parentNode = parent(); + if (parentNode != nullptr) { + parentNode->moveCursorRight(cursor, shouldRecomputeLayout); + } +} + +int CodePointLayoutNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { + return SerializationHelper::CodePoint(buffer, bufferSize, m_codePoint); +} + +bool CodePointLayoutNode::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { + if (*numberOfOpenParenthesis <= 0) { + if (m_codePoint == '+' + || m_codePoint == KDCodePointRightwardsArrow + || m_codePoint == '=' + || m_codePoint == ',') + { + return false; + } + if (m_codePoint == '-') { + /* If the expression is like 3á´‡-200, we want '-' to be collapsable. + * Otherwise, '-' is not collapsable. */ + Layout thisRef = CodePointLayout(this); + Layout parent = thisRef.parent(); + if (!parent.isUninitialized()) { + int indexOfThis = parent.indexOfChild(thisRef); + if (indexOfThis > 0) { + Layout leftBrother = parent.childAtIndex(indexOfThis-1); + if (leftBrother.isCodePoint() + && static_cast(leftBrother).codePoint() == KDCodePointLatinLetterSmallCapitalE) + { + return true; + } + } + } + return false; + } + } + return true; +} + +bool CodePointLayoutNode::canBeOmittedMultiplicationLeftFactor() const { + if (isMultiplicationCodePoint()) { + return false; + } + return LayoutNode::canBeOmittedMultiplicationRightFactor(); +} + +bool CodePointLayoutNode::canBeOmittedMultiplicationRightFactor() const { + if (m_codePoint == '!' || isMultiplicationCodePoint()) { + return false; + } + return LayoutNode::canBeOmittedMultiplicationRightFactor(); +} + +// Sizing and positioning +KDSize CodePointLayoutNode::computeSize() { + return m_font->glyphSize(); +} + +KDCoordinate CodePointLayoutNode::computeBaseline() { + return m_font->glyphSize().height()/2; +} + +void CodePointLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { + constexpr int bufferSize = sizeof(CodePoint)/sizeof(char) + 1; // Null-terminating char + char buffer[bufferSize]; + SerializationHelper::CodePoint(buffer, bufferSize, m_codePoint); + ctx->drawString(buffer, p, m_font, expressionColor, backgroundColor); +} + +bool CodePointLayoutNode::isMultiplicationCodePoint() const { + return m_codePoint == '*' + || m_codePoint == KDCodePointMultiplicationSign + || m_codePoint == KDCodePointMiddleDot; +} + +CodePointLayout CodePointLayout::Builder(CodePoint c, const KDFont * font) { + void * bufferNode = TreePool::sharedPool()->alloc(sizeof(CodePointLayoutNode)); + CodePointLayoutNode * node = new (bufferNode) CodePointLayoutNode(c, font); + TreeHandle h = TreeHandle::BuildWithGhostChildren(node); + return static_cast(h); +} + +} diff --git a/poincare/src/serialization_helper.cpp b/poincare/src/serialization_helper.cpp index 3375d3b6f..c255bd160 100644 --- a/poincare/src/serialization_helper.cpp +++ b/poincare/src/serialization_helper.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -149,4 +150,21 @@ int SerializationHelper::Char(char * buffer, int bufferSize, char charToWrite) { return 1; } +int SerializationHelper::CodePoint(char * buffer, int bufferSize, class CodePoint c) { + if (bufferSize == 0) { + return -1; + } + if (bufferSize == 1) { + buffer[0] = 0; + return 0; + } + constexpr int maxCodePointSize = sizeof(class CodePoint)/sizeof(char) + 1; // Null-terminating char + char helpBuffer[maxCodePointSize]; + size_t size = UTF8Decoder::CodePointToChars(c, helpBuffer, maxCodePointSize); + assert(size < maxCodePointSize); + helpBuffer[size] = 0; + strlcpy(buffer, helpBuffer, bufferSize); + return strlen(buffer); +} + } From db54c5bd27396819a1281b250ca1b65be756d98f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 11 Jan 2019 14:09:45 +0100 Subject: [PATCH 0309/1750] [apps/poincare] Replace CharLayouts with CodePointLayouts --- apps/regression/calculation_controller.cpp | 4 +- apps/regression/model/cubic_model.cpp | 32 ++--- apps/regression/model/exponential_model.cpp | 14 +-- apps/regression/model/linear_model.cpp | 12 +- apps/regression/model/logarithmic_model.cpp | 20 ++-- apps/regression/model/logistic_model.cpp | 22 ++-- apps/regression/model/power_model.cpp | 10 +- apps/regression/model/quadratic_model.cpp | 22 ++-- apps/regression/model/quartic_model.cpp | 42 +++---- apps/regression/model/trigonometric_model.cpp | 30 ++--- apps/sequence/graph/term_sum_controller.cpp | 6 +- apps/sequence/list/sequence_toolbox.cpp | 8 +- .../list/type_parameter_controller.cpp | 4 +- apps/sequence/sequence.cpp | 16 +-- apps/sequence/sequence_title_cell.h | 2 +- .../sub_menu/preferences_controller.cpp | 4 +- apps/solver/list_controller.cpp | 4 +- apps/solver/solutions_controller.cpp | 4 +- poincare/Makefile | 1 - poincare/include/poincare/char_layout.h | 73 ------------ poincare/include/poincare/layout.h | 1 - poincare/include/poincare/layout_cursor.h | 2 +- poincare/include/poincare/layout_node.h | 5 +- .../include/poincare/serialization_helper.h | 4 +- poincare/include/poincare_layouts.h | 1 - poincare/src/char_layout.cpp | 106 ----------------- poincare/src/constant.cpp | 2 +- poincare/src/equal.cpp | 4 +- poincare/src/factorial.cpp | 4 +- poincare/src/integral_layout.cpp | 2 +- poincare/src/layout.cpp | 2 +- poincare/src/layout_cursor.cpp | 20 ++-- poincare/src/layout_helper.cpp | 6 +- poincare/src/opposite.cpp | 4 +- poincare/src/product_layout.cpp | 2 +- poincare/src/rational.cpp | 2 +- poincare/src/sequence_layout.cpp | 2 +- poincare/src/serialization_helper.cpp | 6 +- poincare/src/store.cpp | 4 +- poincare/src/sum_layout.cpp | 2 +- poincare/src/symbol.cpp | 16 +-- poincare/test/fraction_layout.cpp | 8 +- poincare/test/layouts.cpp | 110 +++++++++--------- poincare/test/parentheses_layout.cpp | 12 +- poincare/test/vertical_offset_layout.cpp | 2 +- 45 files changed, 238 insertions(+), 421 deletions(-) delete mode 100644 poincare/include/poincare/char_layout.h delete mode 100644 poincare/src/char_layout.cpp diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index 895256377..02446c0f3 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -1,7 +1,7 @@ #include "calculation_controller.h" #include "../apps_container.h" #include "../shared/poincare_helpers.h" -#include +#include #include #include @@ -25,7 +25,7 @@ CalculationController::CalculationController(Responder * parentResponder, Button m_hideableCell(), m_store(store) { - m_r2Layout = HorizontalLayout::Builder(CharLayout::Builder('r', KDFont::SmallFont), VerticalOffsetLayout::Builder(CharLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript)); + m_r2Layout = HorizontalLayout::Builder(CodePointLayout::Builder('r', KDFont::SmallFont), VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript)); m_selectableTableView.setVerticalCellOverlap(0); m_selectableTableView.setBackgroundColor(Palette::WallScreenDark); m_selectableTableView.setMargins(k_margin, k_scrollBarMargin, k_scrollBarMargin, k_margin); diff --git a/apps/regression/model/cubic_model.cpp b/apps/regression/model/cubic_model.cpp index 5147ff5f5..39cf2139b 100644 --- a/apps/regression/model/cubic_model.cpp +++ b/apps/regression/model/cubic_model.cpp @@ -2,7 +2,7 @@ #include "../../shared/poincare_helpers.h" #include #include -#include +#include #include #include #include @@ -20,27 +20,27 @@ namespace Regression { Layout CubicModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { - CharLayout::Builder('a', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('a', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( - CharLayout::Builder('3', KDFont::SmallFont), + CodePointLayout::Builder('3', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript ), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('b', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('b', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( - CharLayout::Builder('2', KDFont::SmallFont), + CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript ), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('c', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('d', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('c', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('d', KDFont::SmallFont), }; m_layout = HorizontalLayout::Builder(layoutChildren, 15); } diff --git a/apps/regression/model/exponential_model.cpp b/apps/regression/model/exponential_model.cpp index 320d99b45..4b2c3fa5d 100644 --- a/apps/regression/model/exponential_model.cpp +++ b/apps/regression/model/exponential_model.cpp @@ -1,7 +1,7 @@ #include "exponential_model.h" #include #include -#include +#include #include #include @@ -12,14 +12,14 @@ namespace Regression { Layout ExponentialModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { - CharLayout::Builder('a', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('e', KDFont::SmallFont), + CodePointLayout::Builder('a', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('e', KDFont::SmallFont), VerticalOffsetLayout::Builder( HorizontalLayout::Builder( - CharLayout::Builder('b', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont) + CodePointLayout::Builder('b', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont) ), VerticalOffsetLayoutNode::Type::Superscript ) diff --git a/apps/regression/model/linear_model.cpp b/apps/regression/model/linear_model.cpp index 92e03af93..116126fc5 100644 --- a/apps/regression/model/linear_model.cpp +++ b/apps/regression/model/linear_model.cpp @@ -2,7 +2,7 @@ #include "../store.h" #include #include -#include +#include #include using namespace Poincare; @@ -12,11 +12,11 @@ namespace Regression { Layout LinearModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { - CharLayout::Builder('a', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('b', KDFont::SmallFont), + CodePointLayout::Builder('a', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('b', KDFont::SmallFont), }; m_layout = HorizontalLayout::Builder(layoutChildren, 5); } diff --git a/apps/regression/model/logarithmic_model.cpp b/apps/regression/model/logarithmic_model.cpp index fb7563928..255ef5268 100644 --- a/apps/regression/model/logarithmic_model.cpp +++ b/apps/regression/model/logarithmic_model.cpp @@ -2,7 +2,7 @@ #include "../store.h" #include #include -#include +#include #include using namespace Poincare; @@ -12,15 +12,15 @@ namespace Regression { Layout LogarithmicModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { - CharLayout::Builder('a', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('l', KDFont::SmallFont), - CharLayout::Builder('n', KDFont::SmallFont), - CharLayout::Builder('(', KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), - CharLayout::Builder(')', KDFont::SmallFont), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('b', KDFont::SmallFont) + CodePointLayout::Builder('a', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('l', KDFont::SmallFont), + CodePointLayout::Builder('n', KDFont::SmallFont), + CodePointLayout::Builder('(', KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder(')', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('b', KDFont::SmallFont) }; m_layout = HorizontalLayout::Builder(layoutChildren, 9); } diff --git a/apps/regression/model/logistic_model.cpp b/apps/regression/model/logistic_model.cpp index ad0ea7503..5f637e798 100644 --- a/apps/regression/model/logistic_model.cpp +++ b/apps/regression/model/logistic_model.cpp @@ -1,7 +1,7 @@ #include "logistic_model.h" #include #include -#include +#include #include #include #include @@ -13,24 +13,24 @@ namespace Regression { Layout LogisticModel::layout() { if (m_layout.isUninitialized()) { Layout exponentLayoutChildren[] = { - CharLayout::Builder('-', KDFont::SmallFont), - CharLayout::Builder('b', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont) + CodePointLayout::Builder('-', KDFont::SmallFont), + CodePointLayout::Builder('b', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont) }; Layout layoutChildren[] = { - CharLayout::Builder('1', KDFont::SmallFont), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('a', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('e', KDFont::SmallFont), + CodePointLayout::Builder('1', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('a', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('e', KDFont::SmallFont), VerticalOffsetLayout::Builder( HorizontalLayout::Builder(exponentLayoutChildren, 4), VerticalOffsetLayoutNode::Type::Superscript ) }; m_layout = FractionLayout::Builder( - CharLayout::Builder('c', KDFont::SmallFont), + CodePointLayout::Builder('c', KDFont::SmallFont), HorizontalLayout::Builder(layoutChildren, 6) ); } diff --git a/apps/regression/model/power_model.cpp b/apps/regression/model/power_model.cpp index bd790d815..7a5dcc992 100644 --- a/apps/regression/model/power_model.cpp +++ b/apps/regression/model/power_model.cpp @@ -2,7 +2,7 @@ #include "../store.h" #include #include -#include +#include #include #include @@ -13,11 +13,11 @@ namespace Regression { Layout PowerModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { - CharLayout::Builder('a', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('a', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( - CharLayout::Builder('b', KDFont::SmallFont), + CodePointLayout::Builder('b', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript ), }; diff --git a/apps/regression/model/quadratic_model.cpp b/apps/regression/model/quadratic_model.cpp index 0ee0b1764..16d9fd6c9 100644 --- a/apps/regression/model/quadratic_model.cpp +++ b/apps/regression/model/quadratic_model.cpp @@ -2,7 +2,7 @@ #include "../../shared/poincare_helpers.h" #include #include -#include +#include #include #include #include @@ -20,19 +20,19 @@ namespace Regression { Layout QuadraticModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { - CharLayout::Builder('a', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('a', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( - CharLayout::Builder('2', KDFont::SmallFont), + CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript ), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('b', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('c', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('b', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('c', KDFont::SmallFont), }; m_layout = HorizontalLayout::Builder(layoutChildren, 10); } diff --git a/apps/regression/model/quartic_model.cpp b/apps/regression/model/quartic_model.cpp index 3f7a4395a..163880fae 100644 --- a/apps/regression/model/quartic_model.cpp +++ b/apps/regression/model/quartic_model.cpp @@ -2,7 +2,7 @@ #include "../../shared/poincare_helpers.h" #include #include -#include +#include #include #include #include @@ -20,35 +20,35 @@ namespace Regression { Layout QuarticModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { - CharLayout::Builder('a', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('a', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( - CharLayout::Builder('4', KDFont::SmallFont), + CodePointLayout::Builder('4', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript ), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('b', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('b', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( - CharLayout::Builder('3', KDFont::SmallFont), + CodePointLayout::Builder('3', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript ), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('c', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('c', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( - CharLayout::Builder('2', KDFont::SmallFont), + CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript ), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('d', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('e', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('d', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('e', KDFont::SmallFont), }; m_layout = HorizontalLayout::Builder(layoutChildren, 20); } diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index 2a605cdde..8fceb306f 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include #include @@ -21,20 +21,20 @@ namespace Regression { Layout TrigonometricModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { - CharLayout::Builder('a', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('s', KDFont::SmallFont), - CharLayout::Builder('i', KDFont::SmallFont), - CharLayout::Builder('n', KDFont::SmallFont), - CharLayout::Builder('(', KDFont::SmallFont), - CharLayout::Builder('b', KDFont::SmallFont), - CharLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), - CharLayout::Builder('X', KDFont::SmallFont), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('c', KDFont::SmallFont), - CharLayout::Builder(')', KDFont::SmallFont), - CharLayout::Builder('+', KDFont::SmallFont), - CharLayout::Builder('d', KDFont::SmallFont) + CodePointLayout::Builder('a', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('s', KDFont::SmallFont), + CodePointLayout::Builder('i', KDFont::SmallFont), + CodePointLayout::Builder('n', KDFont::SmallFont), + CodePointLayout::Builder('(', KDFont::SmallFont), + CodePointLayout::Builder('b', KDFont::SmallFont), + CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder('X', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('c', KDFont::SmallFont), + CodePointLayout::Builder(')', KDFont::SmallFont), + CodePointLayout::Builder('+', KDFont::SmallFont), + CodePointLayout::Builder('d', KDFont::SmallFont) }; m_layout = HorizontalLayout::Builder(layoutChildren, 14); } diff --git a/apps/sequence/graph/term_sum_controller.cpp b/apps/sequence/graph/term_sum_controller.cpp index 35fcfb49f..17b59c943 100644 --- a/apps/sequence/graph/term_sum_controller.cpp +++ b/apps/sequence/graph/term_sum_controller.cpp @@ -1,7 +1,7 @@ #include "term_sum_controller.h" #include "../../shared/text_field_delegate.h" #include "../app.h" -#include +#include #include #include @@ -51,9 +51,9 @@ double TermSumController::cursorNextStep(double x, int direction) { Layout TermSumController::createFunctionLayout(const char * functionName) { return HorizontalLayout::Builder( - CharLayout::Builder(functionName[0], KDFont::SmallFont), + CodePointLayout::Builder(functionName[0], KDFont::SmallFont), VerticalOffsetLayout::Builder( - CharLayout::Builder('n', KDFont::SmallFont), + CodePointLayout::Builder('n', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Subscript ) ); diff --git a/apps/sequence/list/sequence_toolbox.cpp b/apps/sequence/list/sequence_toolbox.cpp index 5dbcd3380..0742fc4bd 100644 --- a/apps/sequence/list/sequence_toolbox.cpp +++ b/apps/sequence/list/sequence_toolbox.cpp @@ -2,7 +2,7 @@ #include "../sequence_store.h" #include #include -#include +#include #include using namespace Poincare; @@ -77,18 +77,18 @@ void SequenceToolbox::buildExtraCellsLayouts(const char * sequenceName, int recu for (int j = 0; j < recurrenceDepth; j++) { const char * indice = j == 0 ? "n" : "n+1"; m_addedCellLayout[j] = HorizontalLayout::Builder( - CharLayout::Builder(sequenceName[0], KDFont::LargeFont), + CodePointLayout::Builder(sequenceName[0], KDFont::LargeFont), VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Type::Subscript) ); m_addedCellLayout[j+recurrenceDepth] = HorizontalLayout::Builder( - CharLayout::Builder(otherSequenceName[0], KDFont::LargeFont), + CodePointLayout::Builder(otherSequenceName[0], KDFont::LargeFont), VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Type::Subscript) ); } if (recurrenceDepth < 2) { const char * indice = recurrenceDepth == 0 ? "n" : (recurrenceDepth == 1 ? "n+1" : "n+2"); m_addedCellLayout[2*recurrenceDepth] = HorizontalLayout::Builder( - CharLayout::Builder(otherSequenceName[0], KDFont::LargeFont), + CodePointLayout::Builder(otherSequenceName[0], KDFont::LargeFont), VerticalOffsetLayout::Builder(LayoutHelper::String(indice, strlen(indice), KDFont::LargeFont), VerticalOffsetLayoutNode::Type::Subscript) ); } diff --git a/apps/sequence/list/type_parameter_controller.cpp b/apps/sequence/list/type_parameter_controller.cpp index 71bace67e..75bbd12e0 100644 --- a/apps/sequence/list/type_parameter_controller.cpp +++ b/apps/sequence/list/type_parameter_controller.cpp @@ -3,7 +3,7 @@ #include "../app.h" #include #include -#include +#include #include using namespace Poincare; @@ -116,7 +116,7 @@ void TypeParameterController::willDisplayCellAtLocation(HighlightCell * cell, in } const char * subscripts[3] = {"n", "n+1", "n+2"}; m_layouts[j] = HorizontalLayout::Builder( - CharLayout::Builder(nextName[0], font), + CodePointLayout::Builder(nextName[0], font), VerticalOffsetLayout::Builder(LayoutHelper::String(subscripts[j], strlen(subscripts[j]), font), VerticalOffsetLayoutNode::Type::Subscript) ); ExpressionTableCellWithPointer * myCell = (ExpressionTableCellWithPointer *)cell; diff --git a/apps/sequence/sequence.cpp b/apps/sequence/sequence.cpp index d413343b0..e108f1b25 100644 --- a/apps/sequence/sequence.cpp +++ b/apps/sequence/sequence.cpp @@ -2,7 +2,7 @@ #include "sequence_store.h" #include "cache_context.h" #include -#include +#include #include #include #include "../shared/poincare_helpers.h" @@ -147,8 +147,8 @@ int Sequence::numberOfElements() { Poincare::Layout Sequence::nameLayout() { if (m_nameLayout.isUninitialized()) { m_nameLayout = HorizontalLayout::Builder( - CharLayout::Builder(name()[0], KDFont::SmallFont), - VerticalOffsetLayout::Builder(CharLayout::Builder('n', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Subscript) + CodePointLayout::Builder(name()[0], KDFont::SmallFont), + VerticalOffsetLayout::Builder(CodePointLayout::Builder('n', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Subscript) ); } return m_nameLayout; @@ -158,16 +158,16 @@ Poincare::Layout Sequence::definitionName() { if (m_definitionName.isUninitialized()) { if (m_type == Type::Explicit) { m_definitionName = HorizontalLayout::Builder( - CharLayout::Builder(name()[0], k_layoutFont), + CodePointLayout::Builder(name()[0], k_layoutFont), VerticalOffsetLayout::Builder(LayoutHelper::String("n", 1, k_layoutFont), VerticalOffsetLayoutNode::Type::Subscript)); } else if (m_type == Type::SingleRecurrence) { m_definitionName = HorizontalLayout::Builder( - CharLayout::Builder(name()[0], k_layoutFont), + CodePointLayout::Builder(name()[0], k_layoutFont), VerticalOffsetLayout::Builder(LayoutHelper::String("n+1", 3, k_layoutFont), VerticalOffsetLayoutNode::Type::Subscript)); } else { assert(m_type == Type::DoubleRecurrence); m_definitionName = HorizontalLayout::Builder( - CharLayout::Builder(name()[0], k_layoutFont), + CodePointLayout::Builder(name()[0], k_layoutFont), VerticalOffsetLayout::Builder(LayoutHelper::String("n+2", 3, k_layoutFont), VerticalOffsetLayoutNode::Type::Subscript)); } } @@ -183,7 +183,7 @@ Poincare::Layout Sequence::firstInitialConditionName() { { Layout indexLayout = LayoutHelper::String(buffer, strlen(buffer), k_layoutFont); m_firstInitialConditionName = HorizontalLayout::Builder( - CharLayout::Builder(name()[0], k_layoutFont), + CodePointLayout::Builder(name()[0], k_layoutFont), VerticalOffsetLayout::Builder(indexLayout, VerticalOffsetLayoutNode::Type::Subscript)); } return m_firstInitialConditionName; @@ -196,7 +196,7 @@ Poincare::Layout Sequence::secondInitialConditionName() { if (m_type == Type::DoubleRecurrence) { Layout indexLayout = LayoutHelper::String(buffer, strlen(buffer), k_layoutFont); m_secondInitialConditionName = HorizontalLayout::Builder( - CharLayout::Builder(name()[0], k_layoutFont), + CodePointLayout::Builder(name()[0], k_layoutFont), VerticalOffsetLayout::Builder(indexLayout, VerticalOffsetLayoutNode::Type::Subscript)); } } diff --git a/apps/sequence/sequence_title_cell.h b/apps/sequence/sequence_title_cell.h index 1f6b92c67..bcebbd4a9 100644 --- a/apps/sequence/sequence_title_cell.h +++ b/apps/sequence/sequence_title_cell.h @@ -15,7 +15,7 @@ public: void setColor(KDColor color) override; void setOrientation(Orientation orientation) override; const KDFont * font() const override { - return Poincare::CharLayoutNode::k_defaultFont; + return Poincare::CodePointLayoutNode::k_defaultFont; } Poincare::Layout layout() const override { return m_titleTextView.layout(); diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index bb96f02cf..65baa91eb 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -84,7 +84,7 @@ Layout layoutForPreferences(I18n::Message message) { // Complex format case I18n::Message::Real: { - return CharLayout::Builder('x', KDFont::SmallFont); + return CodePointLayout::Builder('x', KDFont::SmallFont); } case I18n::Message::Cartesian: { diff --git a/apps/solver/list_controller.cpp b/apps/solver/list_controller.cpp index d46095f21..1f8fc7434 100644 --- a/apps/solver/list_controller.cpp +++ b/apps/solver/list_controller.cpp @@ -1,6 +1,6 @@ #include "list_controller.h" #include "app.h" -#include +#include #include using namespace Shared; @@ -124,7 +124,7 @@ bool textRepresentsAnEquality(const char * text) { bool layoutRepresentsAnEquality(Poincare::Layout l) { Poincare::Layout match = l.recursivelyMatches( [](Poincare::Layout layout) { - return layout.isChar() && static_cast(layout).character() == '='; }); + return layout.isCodePoint() && static_cast(layout).codePoint() == '='; }); return !match.isUninitialized(); } diff --git a/apps/solver/solutions_controller.cpp b/apps/solver/solutions_controller.cpp index f9b97405b..1f01562a7 100644 --- a/apps/solver/solutions_controller.cpp +++ b/apps/solver/solutions_controller.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include #include @@ -75,7 +75,7 @@ SolutionsController::SolutionsController(Responder * parentResponder, EquationSt m_delta2Layout(), m_contentView(this) { - m_delta2Layout = HorizontalLayout::Builder(VerticalOffsetLayout::Builder(CharLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript), LayoutHelper::String("-4ac", 4, KDFont::SmallFont)); + m_delta2Layout = HorizontalLayout::Builder(VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript), LayoutHelper::String("-4ac", 4, KDFont::SmallFont)); char deltaB[] = {Ion::Charset::CapitalDelta, '=', 'b'}; static_cast(m_delta2Layout).addOrMergeChildAtIndex(LayoutHelper::String(deltaB, 3, KDFont::SmallFont), 0, false); for (int i = 0; i < EquationStore::k_maxNumberOfExactSolutions; i++) { diff --git a/poincare/Makefile b/poincare/Makefile index cc7f7e2df..d6a13b974 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -4,7 +4,6 @@ src += $(addprefix poincare/src/,\ binomial_coefficient_layout.cpp \ bracket_layout.cpp \ bracket_pair_layout.cpp \ - char_layout.cpp \ code_point_layout.cpp\ condensed_sum_layout.cpp \ conjugate_layout.cpp \ diff --git a/poincare/include/poincare/char_layout.h b/poincare/include/poincare/char_layout.h deleted file mode 100644 index c206d39b8..000000000 --- a/poincare/include/poincare/char_layout.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef POINCARE_CHAR_LAYOUT_NODE_H -#define POINCARE_CHAR_LAYOUT_NODE_H - -#include -#include -#include - -namespace Poincare { - -class CharLayoutNode final : public LayoutNode { -public: - static constexpr const KDFont * k_defaultFont = KDFont::LargeFont; - CharLayoutNode(char c = Ion::Charset::Empty, const KDFont * font = k_defaultFont) : - LayoutNode(), - m_char(c), - m_font(font) - {} - - // CharLayout - char character() const { return m_char; } - const KDFont * font() const { return m_font; } - - // LayoutNode - void moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; - void moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) override; - int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; - bool isChar() const override { return true; } - bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const override; - bool canBeOmittedMultiplicationLeftFactor() const override; - bool canBeOmittedMultiplicationRightFactor() const override; - - // TreeNode - size_t size() const override { return sizeof(CharLayoutNode); } - int numberOfChildren() const override { return 0; } -#if POINCARE_TREE_LOG - virtual void logNodeName(std::ostream & stream) const override { - stream << "CharLayout"; - } - virtual void logAttributes(std::ostream & stream) const override { - stream << " char=\"" << m_char << "\""; - } -#endif - -protected: - // LayoutNode - KDSize computeSize() override; - KDCoordinate computeBaseline() override; - KDPoint positionOfChild(LayoutNode * child) override { - assert(false); - return KDPointZero; - } - -private: - void render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) override; - bool isMultiplicationChar() const; - char m_char; - const KDFont * m_font; -}; - -class CharLayout final : public Layout { -public: - CharLayout(const CharLayoutNode * n) : Layout(n) {} - static CharLayout Builder(char c, const KDFont * font = KDFont::LargeFont); - const KDFont * font() const { return const_cast(this)->node()->font(); } - char character() const {return const_cast(this)->node()->character();} -private: - using Layout::node; - CharLayoutNode * node() { return static_cast(Layout::node());} -}; - -} - -#endif diff --git a/poincare/include/poincare/layout.h b/poincare/include/poincare/layout.h index bf70fba41..306b93355 100644 --- a/poincare/include/poincare/layout.h +++ b/poincare/include/poincare/layout.h @@ -49,7 +49,6 @@ public: bool isMatrix() const { return const_cast(this)->node()->isMatrix(); } bool isVerticalOffset() const { return const_cast(this)->node()->isVerticalOffset(); } bool isLeftParenthesis() const { return const_cast(this)->node()->isLeftParenthesis(); } - bool isChar() const { return const_cast(this)->node()->isChar(); } bool isCodePoint() const { return const_cast(this)->node()->isCodePoint(); } bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { return const_cast(this)->node()->isCollapsable(numberOfOpenParenthesis, goingLeft); } int leftCollapsingAbsorbingChildIndex() const { return const_cast(this)->node()->leftCollapsingAbsorbingChildIndex(); } diff --git a/poincare/include/poincare/layout_cursor.h b/poincare/include/poincare/layout_cursor.h index 17f99143c..764023e04 100644 --- a/poincare/include/poincare/layout_cursor.h +++ b/poincare/include/poincare/layout_cursor.h @@ -109,7 +109,7 @@ public: void addEmptySquarePowerLayout(); void addEmptyTenPowerLayout(); void addFractionLayoutAndCollapseSiblings(); - void addXNTCharLayout(); + void addXNTCodePointLayout(); void insertText(const char * text); void addLayoutAndMoveCursor(Layout l); bool showEmptyLayoutIfNeeded() { return privateShowHideEmptyLayoutIfNeeded(true); } diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index c10120091..37c8899f9 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -84,14 +84,14 @@ public: virtual bool isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { return true; } /* isCollapsable is used when adding a sibling fraction: should the layout be * inserted in the numerator (or denominator)? For instance, 1+2|3-4 should - * become 1+ 2/3 - 4 when pressing "Divide": a CharLayout is collapsable if + * become 1+ 2/3 - 4 when pressing "Divide": a CodePointLayout is collapsable if * its char is not +, -, or *. */ virtual bool canBeOmittedMultiplicationLeftFactor() const; virtual bool canBeOmittedMultiplicationRightFactor() const; /* canBeOmittedMultiplicationLeftFactor and RightFactor return true if the * layout, next to another layout, might be the factor of a multiplication * with an omitted multiplication sign. For instance, an absolute value layout - * returns true, because |3|2 means |3|*2. A '+' CharLayout returns false, + * returns true, because |3|2 means |3|*2. A '+' CodePointLayout returns false, * because +'something' nevers means +*'something'. */ virtual bool mustHaveLeftSibling() const { return false; } virtual bool isVerticalOffset() const { return false; } @@ -104,7 +104,6 @@ public: virtual bool isRightBracket() const { return false; } virtual bool isEmpty() const { return false; } virtual bool isMatrix() const { return false; } - virtual bool isChar() const { return false; } virtual bool isCodePoint() const { return false; } virtual bool hasUpperLeftIndex() const { return false; } virtual char XNTChar() const { diff --git a/poincare/include/poincare/serialization_helper.h b/poincare/include/poincare/serialization_helper.h index c2406302d..6d2a0de39 100644 --- a/poincare/include/poincare/serialization_helper.h +++ b/poincare/include/poincare/serialization_helper.h @@ -27,8 +27,8 @@ namespace SerializationHelper { const char * operatorName, bool writeFirstChild = true); - // Write one char in buffer - int Char(char * buffer, int bufferSize, char charToWrite); // TODO REMOVE + // Write one char in a buffer + int Char(char * buffer, int bufferSize, char c); // Write one code point in a buffer int CodePoint(char * buffer, int bufferSize, CodePoint c); }; diff --git a/poincare/include/poincare_layouts.h b/poincare/include/poincare_layouts.h index 3cf5ee953..ef9d39212 100644 --- a/poincare/include/poincare_layouts.h +++ b/poincare/include/poincare_layouts.h @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include diff --git a/poincare/src/char_layout.cpp b/poincare/src/char_layout.cpp deleted file mode 100644 index c938c06ff..000000000 --- a/poincare/src/char_layout.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include -#include -#include - -namespace Poincare { - -// LayoutNode -void CharLayoutNode::moveCursorLeft(LayoutCursor * cursor, bool * shouldRecomputeLayout) { - if (cursor->position() == LayoutCursor::Position::Right) { - cursor->setPosition(LayoutCursor::Position::Left); - return; - } - LayoutNode * parentNode = parent(); - if (parentNode != nullptr) { - parentNode->moveCursorLeft(cursor, shouldRecomputeLayout); - } -} - -void CharLayoutNode::moveCursorRight(LayoutCursor * cursor, bool * shouldRecomputeLayout) { - if (cursor->position() == LayoutCursor::Position::Left) { - cursor->setPosition(LayoutCursor::Position::Right); - return; - } - LayoutNode * parentNode = parent(); - if (parentNode != nullptr) { - parentNode->moveCursorRight(cursor, shouldRecomputeLayout); - } -} - -int CharLayoutNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - return SerializationHelper::Char(buffer, bufferSize, m_char); -} - -bool CharLayoutNode::isCollapsable(int * numberOfOpenParenthesis, bool goingLeft) const { - if (*numberOfOpenParenthesis <= 0) { - if (m_char == '+' - || m_char == Ion::Charset::Sto - || m_char == '=' - || m_char == ',') - { - return false; - } - if (m_char == '-') { - /* If the expression is like 3E-200, we want '-' to be collapsable. - * Otherwise, '-' is not collapsable. */ - Layout thisRef = CharLayout(this); - Layout parent = thisRef.parent(); - if (!parent.isUninitialized()) { - int indexOfThis = parent.indexOfChild(thisRef); - if (indexOfThis > 0) { - Layout leftBrother = parent.childAtIndex(indexOfThis-1); - if (leftBrother.isChar() - && static_cast(leftBrother).character() == Ion::Charset::Exponent) - { - return true; - } - } - } - return false; - } - } - return true; -} - -bool CharLayoutNode::canBeOmittedMultiplicationLeftFactor() const { - if (isMultiplicationChar()) { - return false; - } - return LayoutNode::canBeOmittedMultiplicationRightFactor(); -} - -bool CharLayoutNode::canBeOmittedMultiplicationRightFactor() const { - if (m_char == '!' || isMultiplicationChar()) { - return false; - } - return LayoutNode::canBeOmittedMultiplicationRightFactor(); -} - -// Sizing and positioning -KDSize CharLayoutNode::computeSize() { - return m_font->glyphSize(); -} - -KDCoordinate CharLayoutNode::computeBaseline() { - return m_font->glyphSize().height()/2; -} - -void CharLayoutNode::render(KDContext * ctx, KDPoint p, KDColor expressionColor, KDColor backgroundColor) { - char string[2] = {m_char, 0}; - ctx->drawString(string, p, m_font, expressionColor, backgroundColor); -} - -bool CharLayoutNode::isMultiplicationChar() const { - return m_char == '*' - || m_char == Ion::Charset::MultiplicationSign - || m_char == Ion::Charset::MiddleDot; -} - -CharLayout CharLayout::Builder(char c, const KDFont * font) { - void * bufferNode = TreePool::sharedPool()->alloc(sizeof(CharLayoutNode)); - CharLayoutNode * node = new (bufferNode) CharLayoutNode(c, font); - TreeHandle h = TreeHandle::BuildWithGhostChildren(node); - return static_cast(h); -} - -} diff --git a/poincare/src/constant.cpp b/poincare/src/constant.cpp index 6c4039c03..2f971598c 100644 --- a/poincare/src/constant.cpp +++ b/poincare/src/constant.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/poincare/src/equal.cpp b/poincare/src/equal.cpp index 0f8c758fe..338ac935f 100644 --- a/poincare/src/equal.cpp +++ b/poincare/src/equal.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include @@ -27,7 +27,7 @@ Expression EqualNode::shallowReduce(Context & context, Preferences::ComplexForma Layout EqualNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { HorizontalLayout result = HorizontalLayout::Builder(); result.addOrMergeChildAtIndex(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits), 0, false); - result.addChildAtIndex(CharLayout::Builder('='), result.numberOfChildren(), result.numberOfChildren(), nullptr); + result.addChildAtIndex(CodePointLayout::Builder('='), result.numberOfChildren(), result.numberOfChildren(), nullptr); result.addOrMergeChildAtIndex(childAtIndex(1)->createLayout(floatDisplayMode, numberOfSignificantDigits), result.numberOfChildren(), false); return result; } diff --git a/poincare/src/factorial.cpp b/poincare/src/factorial.cpp index 643d24001..9c777a44a 100644 --- a/poincare/src/factorial.cpp +++ b/poincare/src/factorial.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -62,7 +62,7 @@ Layout FactorialNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, HorizontalLayout result = HorizontalLayout::Builder(); result.addOrMergeChildAtIndex(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits), 0, false); int childrenCount = result.numberOfChildren(); - result.addChildAtIndex(CharLayout::Builder('!'), childrenCount, childrenCount, nullptr); + result.addChildAtIndex(CodePointLayout::Builder('!'), childrenCount, childrenCount, nullptr); return result; } diff --git a/poincare/src/integral_layout.cpp b/poincare/src/integral_layout.cpp index b53035fa6..907ee5c01 100644 --- a/poincare/src/integral_layout.cpp +++ b/poincare/src/integral_layout.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/poincare/src/layout.cpp b/poincare/src/layout.cpp index 103f5fa6d..d55906954 100644 --- a/poincare/src/layout.cpp +++ b/poincare/src/layout.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index 07b205b4c..a5f9346e5 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -75,7 +75,7 @@ void LayoutCursor::move(MoveDirection direction, bool * shouldRecomputeLayout) { void LayoutCursor::addEmptyExponentialLayout() { EmptyLayout emptyLayout = EmptyLayout::Builder(); HorizontalLayout sibling = HorizontalLayout::Builder( - CharLayout::Builder(Ion::Charset::Exponential), + CodePointLayout::Builder(Ion::Charset::Exponential), VerticalOffsetLayout::Builder(emptyLayout, VerticalOffsetLayoutNode::Type::Superscript)); m_layout.addSibling(this, sibling, false); m_layout = emptyLayout; @@ -107,16 +107,16 @@ void LayoutCursor::addEmptyPowerLayout() { } void LayoutCursor::addEmptySquarePowerLayout() { - VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(CharLayout::Builder('2'), VerticalOffsetLayoutNode::Type::Superscript); + VerticalOffsetLayout offsetLayout = VerticalOffsetLayout::Builder(CodePointLayout::Builder('2'), VerticalOffsetLayoutNode::Type::Superscript); privateAddEmptyPowerLayout(offsetLayout); } void LayoutCursor::addEmptyTenPowerLayout() { EmptyLayout emptyLayout = EmptyLayout::Builder(); HorizontalLayout sibling = HorizontalLayout::Builder( - CharLayout::Builder(Ion::Charset::MiddleDot), - CharLayout::Builder('1'), - CharLayout::Builder('0'), + CodePointLayout::Builder(Ion::Charset::MiddleDot), + CodePointLayout::Builder('1'), + CodePointLayout::Builder('0'), VerticalOffsetLayout::Builder( emptyLayout, VerticalOffsetLayoutNode::Type::Superscript)); @@ -132,8 +132,8 @@ void LayoutCursor::addFractionLayoutAndCollapseSiblings() { Layout(newChild.node()).collapseSiblings(this); } -void LayoutCursor::addXNTCharLayout() { - m_layout.addSibling(this, CharLayout::Builder(m_layout.XNTChar()), true); +void LayoutCursor::addXNTCodePointLayout() { + m_layout.addSibling(this, CodePointLayout::Builder(m_layout.XNTChar()), true); } void LayoutCursor::insertText(const char * text) { @@ -148,7 +148,7 @@ void LayoutCursor::insertText(const char * text) { continue; } if (text[i] == Ion::Charset::MultiplicationSign) { - newChild = CharLayout::Builder(Ion::Charset::MiddleDot); + newChild = CodePointLayout::Builder(Ion::Charset::MiddleDot); } else if (text[i] == '(') { newChild = LeftParenthesisLayout::Builder(); if (pointedChild.isUninitialized()) { @@ -167,7 +167,7 @@ void LayoutCursor::insertText(const char * text) { } #endif else { - newChild = CharLayout::Builder(text[i]); + newChild = CodePointLayout::Builder(text[i]); } m_layout.addSibling(this, newChild, true); } diff --git a/poincare/src/layout_helper.cpp b/poincare/src/layout_helper.cpp index 7a61da0b8..d713ec1eb 100644 --- a/poincare/src/layout_helper.cpp +++ b/poincare/src/layout_helper.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -34,7 +34,7 @@ Layout LayoutHelper::Prefix(const Expression & expression, Preferences::PrintFlo if (numberOfChildren > 0) { args.addOrMergeChildAtIndex(expression.childAtIndex(0).createLayout(floatDisplayMode, numberOfSignificantDigits), 0, true); for (int i = 1; i < numberOfChildren; i++) { - args.addChildAtIndex(CharLayout::Builder(','), args.numberOfChildren(), args.numberOfChildren(), nullptr); + args.addChildAtIndex(CodePointLayout::Builder(','), args.numberOfChildren(), args.numberOfChildren(), nullptr); args.addOrMergeChildAtIndex(expression.childAtIndex(i).createLayout(floatDisplayMode, numberOfSignificantDigits), args.numberOfChildren(), true); } } @@ -57,7 +57,7 @@ HorizontalLayout LayoutHelper::String(const char * buffer, int bufferLen, const assert(bufferLen > 0); HorizontalLayout resultLayout = HorizontalLayout::Builder(); for (int i = 0; i < bufferLen; i++) { - resultLayout.addChildAtIndex(CharLayout::Builder(buffer[i], font), i, i, nullptr); + resultLayout.addChildAtIndex(CodePointLayout::Builder(buffer[i], font), i, i, nullptr); } return resultLayout; } diff --git a/poincare/src/opposite.cpp b/poincare/src/opposite.cpp index ec4665c05..071f7b8c8 100644 --- a/poincare/src/opposite.cpp +++ b/poincare/src/opposite.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -41,7 +41,7 @@ bool OppositeNode::childNeedsParenthesis(const TreeNode * child) const { } Layout OppositeNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - HorizontalLayout result = HorizontalLayout::Builder(CharLayout::Builder('-')); + HorizontalLayout result = HorizontalLayout::Builder(CodePointLayout::Builder('-')); if (childAtIndex(0)->type() == Type::Opposite) { result.addOrMergeChildAtIndex(LayoutHelper::Parentheses(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits), false), 1, false); } else { diff --git a/poincare/src/product_layout.cpp b/poincare/src/product_layout.cpp index 8be05d651..70abbfb8f 100644 --- a/poincare/src/product_layout.cpp +++ b/poincare/src/product_layout.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include namespace Poincare { diff --git a/poincare/src/rational.cpp b/poincare/src/rational.cpp index fa86aba96..3379dec01 100644 --- a/poincare/src/rational.cpp +++ b/poincare/src/rational.cpp @@ -9,7 +9,7 @@ extern "C" { #include #include #include -#include +#include namespace Poincare { diff --git a/poincare/src/sequence_layout.cpp b/poincare/src/sequence_layout.cpp index b1c0e96eb..ed209b147 100644 --- a/poincare/src/sequence_layout.cpp +++ b/poincare/src/sequence_layout.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include diff --git a/poincare/src/serialization_helper.cpp b/poincare/src/serialization_helper.cpp index c255bd160..f66c02ec6 100644 --- a/poincare/src/serialization_helper.cpp +++ b/poincare/src/serialization_helper.cpp @@ -137,15 +137,15 @@ int SerializationHelper::Prefix( return numberOfChar; } -int SerializationHelper::Char(char * buffer, int bufferSize, char charToWrite) { +int SerializationHelper::Char(char * buffer, int bufferSize, char c) { if (bufferSize == 0) { return -1; } - buffer[bufferSize-1] = 0; if (bufferSize == 1) { + buffer[0] = 0; return 0; } - buffer[0] = charToWrite; + buffer[0] = c; buffer[1] = 0; return 1; } diff --git a/poincare/src/store.cpp b/poincare/src/store.cpp index 0c085733d..76e646653 100644 --- a/poincare/src/store.cpp +++ b/poincare/src/store.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -34,7 +34,7 @@ int StoreNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatM Layout StoreNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { HorizontalLayout result = HorizontalLayout::Builder(); result.addOrMergeChildAtIndex(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits), 0, false); - result.addChildAtIndex(CharLayout::Builder(Ion::Charset::Sto), result.numberOfChildren(), result.numberOfChildren(), nullptr); + result.addChildAtIndex(CodePointLayout::Builder(Ion::Charset::Sto), result.numberOfChildren(), result.numberOfChildren(), nullptr); result.addOrMergeChildAtIndex(childAtIndex(1)->createLayout(floatDisplayMode, numberOfSignificantDigits), result.numberOfChildren(), false); return result; } diff --git a/poincare/src/sum_layout.cpp b/poincare/src/sum_layout.cpp index 9c9a079e8..1d7a60d74 100644 --- a/poincare/src/sum_layout.cpp +++ b/poincare/src/sum_layout.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include namespace Poincare { diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index 0428218eb..de844615a 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -81,32 +81,32 @@ bool SymbolNode::isReal(Context & context) const { Layout SymbolNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { if (m_name[0] == Symbol::SpecialSymbols::UnknownX) { assert(m_name[1] == 0); - return CharLayout::Builder(Symbol::k_unknownXReadableChar); + return CodePointLayout::Builder(Symbol::k_unknownXReadableChar); } if (strcmp(m_name, "u(n)") == 0) { return HorizontalLayout::Builder( - CharLayout::Builder('u'), + CodePointLayout::Builder('u'), VerticalOffsetLayout::Builder( - CharLayout::Builder('n'), + CodePointLayout::Builder('n'), VerticalOffsetLayoutNode::Type::Subscript)); } if (strcmp(m_name, "u(n+1)") == 0) { return HorizontalLayout::Builder( - CharLayout::Builder('u'), + CodePointLayout::Builder('u'), VerticalOffsetLayout::Builder( LayoutHelper::String("n+1", 3), VerticalOffsetLayoutNode::Type::Subscript)); } if (strcmp(m_name, "v(n)") == 0) { return HorizontalLayout::Builder( - CharLayout::Builder('v'), + CodePointLayout::Builder('v'), VerticalOffsetLayout::Builder( - CharLayout::Builder('n'), + CodePointLayout::Builder('n'), VerticalOffsetLayoutNode::Type::Subscript)); } if (strcmp(m_name, "v(n+1)") == 0) { return HorizontalLayout::Builder( - CharLayout::Builder('v'), + CodePointLayout::Builder('v'), VerticalOffsetLayout::Builder( LayoutHelper::String("n+1", 3), VerticalOffsetLayoutNode::Type::Subscript)); diff --git a/poincare/test/fraction_layout.cpp b/poincare/test/fraction_layout.cpp index 51417ed3f..e17ec7cf6 100644 --- a/poincare/test/fraction_layout.cpp +++ b/poincare/test/fraction_layout.cpp @@ -41,11 +41,11 @@ QUIZ_CASE(poincare_fraction_layout_delete) { * |3 * */ HorizontalLayout layout2 = HorizontalLayout::Builder( - CharLayout::Builder('1'), - CharLayout::Builder('+'), + CodePointLayout::Builder('1'), + CodePointLayout::Builder('+'), FractionLayout::Builder( EmptyLayout::Builder(), - CharLayout::Builder('3') + CodePointLayout::Builder('3') ) ); LayoutCursor cursor2(layout2.childAtIndex(2).childAtIndex(1), LayoutCursor::Position::Left); @@ -56,7 +56,7 @@ QUIZ_CASE(poincare_fraction_layout_delete) { QUIZ_CASE(poincare_fraction_layout_serialize) { FractionLayout layout = FractionLayout::Builder( - CharLayout::Builder('1'), + CodePointLayout::Builder('1'), LayoutHelper::String("2+3", 3) ); assert_expression_layout_serialize_to(layout, "(1)/(2+3)"); diff --git a/poincare/test/layouts.cpp b/poincare/test/layouts.cpp index c8ee32a97..7e7172748 100644 --- a/poincare/test/layouts.cpp +++ b/poincare/test/layouts.cpp @@ -44,7 +44,7 @@ void assert_parsed_layout_is(Layout l, Poincare::Expression r) { QUIZ_CASE(poincare_create_all_layouts) { EmptyLayout e0 = EmptyLayout::Builder(); AbsoluteValueLayout e1 = AbsoluteValueLayout::Builder(e0); - CharLayout e2 = CharLayout::Builder('a'); + CodePointLayout e2 = CodePointLayout::Builder('a'); BinomialCoefficientLayout e3 = BinomialCoefficientLayout::Builder(e1, e2); CeilingLayout e4 = CeilingLayout::Builder(e3); RightParenthesisLayout e5 = RightParenthesisLayout::Builder(); @@ -82,20 +82,20 @@ QUIZ_CASE(poincare_parse_layouts) { // 1+2 l = HorizontalLayout::Builder( - CharLayout::Builder('1'), - CharLayout::Builder('+'), - CharLayout::Builder('2')); + CodePointLayout::Builder('1'), + CodePointLayout::Builder('+'), + CodePointLayout::Builder('2')); e = Addition::Builder(Rational::Builder(1), Rational::Builder(2)); assert_parsed_layout_is(l, e); // |3+3/6| l = AbsoluteValueLayout:: Builder( HorizontalLayout::Builder( - CharLayout::Builder('3'), - CharLayout::Builder('+'), + CodePointLayout::Builder('3'), + CodePointLayout::Builder('+'), FractionLayout::Builder( - CharLayout::Builder('3'), - CharLayout::Builder('6')))); + CodePointLayout::Builder('3'), + CodePointLayout::Builder('6')))); e = AbsoluteValue::Builder( Addition::Builder( Rational::Builder(3), @@ -106,8 +106,8 @@ QUIZ_CASE(poincare_parse_layouts) { // binCoef(4,5) l = BinomialCoefficientLayout::Builder( - CharLayout::Builder('4'), - CharLayout::Builder('5')); + CodePointLayout::Builder('4'), + CodePointLayout::Builder('5')); e = BinomialCoefficient::Builder( Rational::Builder(4), Rational::Builder(5)); @@ -116,9 +116,9 @@ QUIZ_CASE(poincare_parse_layouts) { // ceil(4.6) l = CeilingLayout::Builder( HorizontalLayout::Builder( - CharLayout::Builder('4'), - CharLayout::Builder('.'), - CharLayout::Builder('6'))); + CodePointLayout::Builder('4'), + CodePointLayout::Builder('.'), + CodePointLayout::Builder('6'))); e = Ceiling::Builder( Decimal::Builder(4.6)); assert_parsed_layout_is(l, e); @@ -126,21 +126,21 @@ QUIZ_CASE(poincare_parse_layouts) { // floor(7.2) l = FloorLayout::Builder( HorizontalLayout::Builder( - CharLayout::Builder('7'), - CharLayout::Builder('.'), - CharLayout::Builder('2'))); + CodePointLayout::Builder('7'), + CodePointLayout::Builder('.'), + CodePointLayout::Builder('2'))); e = Floor::Builder( Decimal::Builder(7.2)); assert_parsed_layout_is(l, e); // 2^(3+4) l = HorizontalLayout::Builder( - CharLayout::Builder('2'), + CodePointLayout::Builder('2'), VerticalOffsetLayout::Builder( HorizontalLayout::Builder( - CharLayout::Builder('3'), - CharLayout::Builder('+'), - CharLayout::Builder('4')), + CodePointLayout::Builder('3'), + CodePointLayout::Builder('+'), + CodePointLayout::Builder('4')), VerticalOffsetLayoutNode::Type::Superscript)); e = Power::Builder( Rational::Builder(2), @@ -151,12 +151,12 @@ QUIZ_CASE(poincare_parse_layouts) { // log_3(2) HorizontalLayout l1 = HorizontalLayout::Builder(); - l1.addChildAtIndex(CharLayout::Builder('l'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); - l1.addChildAtIndex(CharLayout::Builder('o'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); - l1.addChildAtIndex(CharLayout::Builder('g'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); - l1.addChildAtIndex(VerticalOffsetLayout::Builder(CharLayout::Builder('3'), VerticalOffsetLayoutNode::Type::Subscript), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); + l1.addChildAtIndex(CodePointLayout::Builder('l'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); + l1.addChildAtIndex(CodePointLayout::Builder('o'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); + l1.addChildAtIndex(CodePointLayout::Builder('g'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); + l1.addChildAtIndex(VerticalOffsetLayout::Builder(CodePointLayout::Builder('3'), VerticalOffsetLayoutNode::Type::Subscript), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(LeftParenthesisLayout::Builder(), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); - l1.addChildAtIndex(CharLayout::Builder('2'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); + l1.addChildAtIndex(CodePointLayout::Builder('2'), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l1.addChildAtIndex(RightParenthesisLayout::Builder(), l1.numberOfChildren(), l1.numberOfChildren(), nullptr); l = l1; e = Logarithm::Builder( @@ -166,17 +166,17 @@ QUIZ_CASE(poincare_parse_layouts) { // root(5,3) l = NthRootLayout::Builder( - CharLayout::Builder('5'), - CharLayout::Builder('3')); + CodePointLayout::Builder('5'), + CodePointLayout::Builder('3')); e = NthRoot::Builder(Rational::Builder(5), Rational::Builder(3)); assert_parsed_layout_is(l, e); // int(7, x, 4, 5) l = IntegralLayout::Builder( - CharLayout::Builder('7'), - CharLayout::Builder('x'), - CharLayout::Builder('4'), - CharLayout::Builder('5')); + CodePointLayout::Builder('7'), + CodePointLayout::Builder('x'), + CodePointLayout::Builder('4'), + CodePointLayout::Builder('5')); e = Integral::Builder( Rational::Builder(7), Symbol::Builder('x'), @@ -186,11 +186,11 @@ QUIZ_CASE(poincare_parse_layouts) { // 2^2 ! l = HorizontalLayout::Builder( - CharLayout::Builder('2'), + CodePointLayout::Builder('2'), VerticalOffsetLayout::Builder( - CharLayout::Builder('2'), + CodePointLayout::Builder('2'), VerticalOffsetLayoutNode::Type::Superscript), - CharLayout::Builder('!')); + CodePointLayout::Builder('!')); e = Factorial::Builder( Power::Builder( Rational::Builder(2), @@ -199,14 +199,14 @@ QUIZ_CASE(poincare_parse_layouts) { // 5* 6/(7+5) *3 l = HorizontalLayout::Builder( - CharLayout::Builder('5'), + CodePointLayout::Builder('5'), FractionLayout::Builder( - CharLayout::Builder('6'), + CodePointLayout::Builder('6'), HorizontalLayout::Builder( - CharLayout::Builder('7'), - CharLayout::Builder('+'), - CharLayout::Builder('5'))), - CharLayout::Builder('3')); + CodePointLayout::Builder('7'), + CodePointLayout::Builder('+'), + CodePointLayout::Builder('5'))), + CodePointLayout::Builder('3')); e = Multiplication::Builder( Rational::Builder(5), Division::Builder( @@ -220,14 +220,14 @@ QUIZ_CASE(poincare_parse_layouts) { // [[3^2!, 7][4,5] l = MatrixLayout::Builder( HorizontalLayout::Builder( - CharLayout::Builder('3'), + CodePointLayout::Builder('3'), VerticalOffsetLayout::Builder( - CharLayout::Builder('2'), + CodePointLayout::Builder('2'), VerticalOffsetLayoutNode::Type::Superscript), - CharLayout::Builder('!')), - CharLayout::Builder('7'), - CharLayout::Builder('4'), - CharLayout::Builder('5')); + CodePointLayout::Builder('!')), + CodePointLayout::Builder('7'), + CodePointLayout::Builder('4'), + CodePointLayout::Builder('5')); Matrix m = BuildOneChildMatrix( Factorial::Builder( Power::Builder( @@ -242,15 +242,15 @@ QUIZ_CASE(poincare_parse_layouts) { // 2^det([[3!, 7][4,5]) l = HorizontalLayout::Builder( - CharLayout::Builder('2'), + CodePointLayout::Builder('2'), VerticalOffsetLayout::Builder( MatrixLayout::Builder( HorizontalLayout::Builder( - CharLayout::Builder('3'), - CharLayout::Builder('!')), - CharLayout::Builder('7'), - CharLayout::Builder('4'), - CharLayout::Builder('5')), + CodePointLayout::Builder('3'), + CodePointLayout::Builder('!')), + CodePointLayout::Builder('7'), + CodePointLayout::Builder('4'), + CodePointLayout::Builder('5')), VerticalOffsetLayoutNode::Type::Superscript)); m = BuildOneChildMatrix( Factorial::Builder( @@ -264,10 +264,10 @@ QUIZ_CASE(poincare_parse_layouts) { // 2e^3 l = HorizontalLayout::Builder( - CharLayout::Builder('2'), - CharLayout::Builder(Ion::Charset::Exponential), + CodePointLayout::Builder('2'), + CodePointLayout::Builder(Ion::Charset::Exponential), VerticalOffsetLayout::Builder( - CharLayout::Builder('3'), + CodePointLayout::Builder('3'), VerticalOffsetLayoutNode::Type::Superscript)); e = Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(Ion::Charset::Exponential),Parenthesis::Builder(Rational::Builder(3)))); assert_parsed_expression_is("2X^(3)", Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(Ion::Charset::Exponential),Parenthesis::Builder(Rational::Builder(3))))); diff --git a/poincare/test/parentheses_layout.cpp b/poincare/test/parentheses_layout.cpp index 1cb82c0d3..241ac6f02 100644 --- a/poincare/test/parentheses_layout.cpp +++ b/poincare/test/parentheses_layout.cpp @@ -16,16 +16,16 @@ QUIZ_CASE(poincare_parenthesis_layout_size) { LeftParenthesisLayout leftPar = LeftParenthesisLayout::Builder(); RightParenthesisLayout rightPar = RightParenthesisLayout::Builder(); layout.addChildAtIndex(leftPar, 0, 0, nullptr); - layout.addChildAtIndex(CharLayout::Builder('2'), 1, 1, nullptr); - layout.addChildAtIndex(CharLayout::Builder('+'), 2, 2, nullptr); + layout.addChildAtIndex(CodePointLayout::Builder('2'), 1, 1, nullptr); + layout.addChildAtIndex(CodePointLayout::Builder('+'), 2, 2, nullptr); layout.addChildAtIndex(LeftParenthesisLayout::Builder(), 3, 3, nullptr); layout.addChildAtIndex(FractionLayout::Builder( - CharLayout::Builder('3'), - CharLayout::Builder('4')), + CodePointLayout::Builder('3'), + CodePointLayout::Builder('4')), 4, 4, nullptr); layout.addChildAtIndex(RightParenthesisLayout::Builder(), 4, 4, nullptr); - layout.addChildAtIndex(CharLayout::Builder('6'), 5, 5, nullptr); + layout.addChildAtIndex(CodePointLayout::Builder('6'), 5, 5, nullptr); layout.addChildAtIndex(rightPar, 7, 7, nullptr); - layout.addChildAtIndex(CharLayout::Builder('1'), 8, 8, nullptr); + layout.addChildAtIndex(CodePointLayout::Builder('1'), 8, 8, nullptr); quiz_assert(leftPar.layoutSize().height() == rightPar.layoutSize().height()); } diff --git a/poincare/test/vertical_offset_layout.cpp b/poincare/test/vertical_offset_layout.cpp index 4c4e71750..72bc3ae09 100644 --- a/poincare/test/vertical_offset_layout.cpp +++ b/poincare/test/vertical_offset_layout.cpp @@ -8,7 +8,7 @@ using namespace Poincare; QUIZ_CASE(poincare_vertical_offset_layout_serialize) { HorizontalLayout layout = HorizontalLayout::Builder( - CharLayout::Builder('2'), + CodePointLayout::Builder('2'), VerticalOffsetLayout::Builder( LayoutHelper::String("x+5", 3), VerticalOffsetLayoutNode::Type::Superscript From 63d71553766e57411016de8943c963419a0e2e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 11 Jan 2019 17:37:23 +0100 Subject: [PATCH 0310/1750] Remove Ion::Charset --- apps/calculation/test/calculation_store.cpp | 13 +- apps/code/helpers.cpp | 15 +-- apps/code/script_node_cell.h | 5 +- .../graph/graph/integral_graph_controller.cpp | 2 +- apps/regression/graph_controller.cpp | 4 +- apps/regression/model/cubic_model.cpp | 6 +- apps/regression/model/exponential_model.cpp | 4 +- apps/regression/model/linear_model.cpp | 2 +- apps/regression/model/logarithmic_model.cpp | 2 +- apps/regression/model/logistic_model.cpp | 4 +- apps/regression/model/power_model.cpp | 2 +- apps/regression/model/quadratic_model.cpp | 4 +- apps/regression/model/quartic_model.cpp | 8 +- apps/regression/model/trigonometric_model.cpp | 4 +- apps/sequence/graph/term_sum_controller.cpp | 2 +- .../sub_menu/preferences_controller.cpp | 22 ++-- apps/shared/storage_sum_graph_controller.cpp | 15 +-- apps/shared/storage_sum_graph_controller.h | 6 +- apps/shared/sum_graph_controller.cpp | 15 +-- apps/shared/sum_graph_controller.h | 6 +- apps/shared/text_field_delegate_app.cpp | 10 +- apps/shared/toolbox_helpers.cpp | 4 +- apps/solver/equation.cpp | 2 +- apps/solver/equation_store.cpp | 2 +- apps/solver/solutions_controller.cpp | 2 +- apps/solver/test/equation_store.cpp | 14 +-- apps/variable_box_controller.cpp | 2 +- apps/variable_box_empty_controller.cpp | 8 +- escher/include/escher/editable_field.h | 3 +- escher/include/escher/layout_field.h | 2 +- escher/include/escher/text_field.h | 2 +- escher/src/layout_field.cpp | 8 +- escher/src/text_area.cpp | 3 +- escher/src/text_field.cpp | 12 +- escher/src/text_input_helpers.cpp | 6 +- ion/include/ion.h | 1 - ion/include/ion/charset.h | 37 ------ ion/src/shared/events.cpp | 34 ++---- .../include/kandinsky/unicode/code_point.h | 44 +++++-- .../include/kandinsky/unicode/utf8_decoder.h | 1 + kandinsky/src/unicode/utf8_decoder.cpp | 8 +- poincare/include/poincare/code_point_layout.h | 2 +- poincare/include/poincare/constant.h | 14 ++- poincare/include/poincare/integral_layout.h | 2 +- poincare/include/poincare/layout.h | 2 +- poincare/include/poincare/layout_helper.h | 1 + poincare/include/poincare/layout_node.h | 5 +- poincare/include/poincare/print_float.h | 2 +- poincare/include/poincare/sequence_layout.h | 2 +- .../include/poincare/serialization_helper.h | 1 + poincare/include/poincare/square_root.h | 8 +- poincare/include/poincare/symbol.h | 2 +- .../poincare/trigonometry_cheat_table.h | 2 +- poincare/src/complex_argument.cpp | 2 +- poincare/src/complex_cartesian.cpp | 6 +- poincare/src/constant.cpp | 40 +++++-- poincare/src/decimal.cpp | 4 +- poincare/src/empty_expression.cpp | 3 +- poincare/src/expression.cpp | 17 +-- poincare/src/expression_debug.cpp | 10 +- poincare/src/fraction_layout.cpp | 5 +- poincare/src/layout_cursor.cpp | 18 +-- poincare/src/layout_helper.cpp | 10 ++ poincare/src/logarithm.cpp | 2 +- poincare/src/multiplication.cpp | 12 +- poincare/src/naperian_logarithm.cpp | 2 +- poincare/src/nth_root_layout.cpp | 3 +- poincare/src/parsing/tokenizer.cpp | 112 +++++++++--------- poincare/src/parsing/tokenizer.h | 10 +- poincare/src/power.cpp | 12 +- poincare/src/print_float.cpp | 36 +++--- poincare/src/serialization_helper.cpp | 9 +- poincare/src/store.cpp | 7 +- poincare/src/trigonometry.cpp | 46 +++---- poincare/src/trigonometry_cheat_table.cpp | 74 ++++++------ poincare/src/vertical_offset_layout.cpp | 7 +- poincare/test/convert_expression_to_text.cpp | 2 - poincare/test/expression_order.cpp | 14 +-- poincare/test/float.cpp | 1 - poincare/test/helper.cpp | 37 ++---- poincare/test/helper.h | 2 - poincare/test/layouts.cpp | 6 +- poincare/test/parser.cpp | 25 ++-- 83 files changed, 453 insertions(+), 470 deletions(-) delete mode 100644 ion/include/ion/charset.h diff --git a/apps/calculation/test/calculation_store.cpp b/apps/calculation/test/calculation_store.cpp index 151d6254a..c64fdd9ac 100644 --- a/apps/calculation/test/calculation_store.cpp +++ b/apps/calculation/test/calculation_store.cpp @@ -70,10 +70,7 @@ QUIZ_CASE(calculation_ans) { } void assertCalculationDisplay(const char * input, bool displayExactOutput, bool displayApproximateOutput, ::Calculation::Calculation::EqualSign sign, const char * exactOutput, const char * approximateOutput, Context * context, CalculationStore * store) { - char buffer[500]; - strlcpy(buffer, input, sizeof(buffer)); - translate_in_special_chars(buffer); - store->push(buffer, context); + store->push(input, context); ::Calculation::Calculation * lastCalculation = store->calculationAtIndex(1); quiz_assert(lastCalculation->shouldOnlyDisplayExactOutput() == displayExactOutput); quiz_assert(lastCalculation->shouldOnlyDisplayApproximateOutput(context) == displayApproximateOutput); @@ -81,14 +78,10 @@ void assertCalculationDisplay(const char * input, bool displayExactOutput, bool quiz_assert(lastCalculation->exactAndApproximateDisplayedOutputsAreEqual(context) == sign); } if (exactOutput) { - strlcpy(buffer, exactOutput, sizeof(buffer)); - translate_in_special_chars(buffer); - quiz_assert(strcmp(lastCalculation->exactOutputText(), buffer) == 0); + quiz_assert(strcmp(lastCalculation->exactOutputText(), exactOutput) == 0); } if (approximateOutput) { - strlcpy(buffer, approximateOutput, sizeof(buffer)); - translate_in_special_chars(buffer); - quiz_assert(strcmp(lastCalculation->approximateOutputText(),buffer) == 0); + quiz_assert(strcmp(lastCalculation->approximateOutputText(), approximateOutput) == 0); } store->deleteAll(); } diff --git a/apps/code/helpers.cpp b/apps/code/helpers.cpp index 12582729c..8a9735de4 100644 --- a/apps/code/helpers.cpp +++ b/apps/code/helpers.cpp @@ -1,5 +1,6 @@ #include "helpers.h" #include +#include #include namespace Code { @@ -15,20 +16,16 @@ private: const char * m_text; }; -static constexpr const char k_exponential[7] = {'e', 'x', 'p', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_logarithm[7] = {'l', 'o', 'g', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_logarithm10[9] = {'l', 'o', 'g', '1', '0', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_sqrt[8] = {'s', 'q', 'r', 't', '(', Ion::Charset::Empty, ')', 0}; - +static_assert('\x11' == KDCodePointEmpty, "Unicode error"); static constexpr EventTextPair sEventTextMap[] = { EventTextPair(Ion::Events::XNT, "x"), - EventTextPair(Ion::Events::Exp, k_exponential), - EventTextPair(Ion::Events::Ln, k_logarithm), - EventTextPair(Ion::Events::Log, k_logarithm10), + EventTextPair(Ion::Events::Exp, "exp(\x11)"), + EventTextPair(Ion::Events::Ln, "log(\x11)"), + EventTextPair(Ion::Events::Log, "log10(\x11)"), EventTextPair(Ion::Events::Imaginary, "1j"), EventTextPair(Ion::Events::Power, "**"), EventTextPair(Ion::Events::Pi, "pi"), - EventTextPair(Ion::Events::Sqrt, k_sqrt), + EventTextPair(Ion::Events::Sqrt, "sqrt(\x11)"), EventTextPair(Ion::Events::Square, "**2"), EventTextPair(Ion::Events::Multiplication, "*"), EventTextPair(Ion::Events::EE, "e"), diff --git a/apps/code/script_node_cell.h b/apps/code/script_node_cell.h index ae1c252ba..29e9f8bbf 100644 --- a/apps/code/script_node_cell.h +++ b/apps/code/script_node_cell.h @@ -4,7 +4,6 @@ #include "script_node.h" #include "script_store.h" #include -#include #include namespace Code { @@ -23,8 +22,10 @@ public: void reloadCell() override; const char * text() const override { return m_scriptNodeView.text(); } + static_assert('\x11' == KDCodePointEmpty, "Unicode error"); constexpr static char k_parentheses[] = "()"; - constexpr static char k_parenthesesWithEmpty[] = {'(', Ion::Charset::Empty, ')', 0}; + constexpr static char k_parenthesesWithEmpty[] = "(\x11)"; + protected: class ScriptNodeView : public HighlightCell { public: diff --git a/apps/graph/graph/integral_graph_controller.cpp b/apps/graph/graph/integral_graph_controller.cpp index a2ff51402..6dc3fe6bd 100644 --- a/apps/graph/graph/integral_graph_controller.cpp +++ b/apps/graph/graph/integral_graph_controller.cpp @@ -13,7 +13,7 @@ using namespace Poincare; namespace Graph { IntegralGraphController::IntegralGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, GraphView * graphView, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor) : - StorageSumGraphController(parentResponder, inputEventHandlerDelegate, graphView, graphRange, cursor, Ion::Charset::Integral) + StorageSumGraphController(parentResponder, inputEventHandlerDelegate, graphView, graphRange, cursor, KDCodePointIntegral) { } diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index be3298f72..3d8a8a807 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -121,7 +121,7 @@ void GraphController::reloadBannerView() { double x = m_cursor->x(); // Display a specific legend if the mean dot is selected if (*m_selectedDotIndex == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { - constexpr static char legX[] = {Ion::Charset::XBar, '=', 0}; + constexpr static char legX[] = {'X'/*TODO LEA Ion::Charset::XBar*/, '=', 0}; legend = legX; x = m_store->meanOfColumn(*m_selectedSeriesIndex, 0); } @@ -138,7 +138,7 @@ void GraphController::reloadBannerView() { legend = "y="; double y = m_cursor->y(); if (*m_selectedDotIndex == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { - constexpr static char legY[] = {Ion::Charset::YBar, '=', 0}; + constexpr static char legY[] = {'Y' /*TODO LEA Ion::Charset::YBar*/, '=', 0}; legend = legY; y = m_store->meanOfColumn(*m_selectedSeriesIndex, 1); } diff --git a/apps/regression/model/cubic_model.cpp b/apps/regression/model/cubic_model.cpp index 39cf2139b..e8bbc5734 100644 --- a/apps/regression/model/cubic_model.cpp +++ b/apps/regression/model/cubic_model.cpp @@ -21,7 +21,7 @@ Layout CubicModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { CodePointLayout::Builder('a', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('3', KDFont::SmallFont), @@ -29,7 +29,7 @@ Layout CubicModel::layout() { ), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('b', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2', KDFont::SmallFont), @@ -37,7 +37,7 @@ Layout CubicModel::layout() { ), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('c', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('d', KDFont::SmallFont), diff --git a/apps/regression/model/exponential_model.cpp b/apps/regression/model/exponential_model.cpp index 4b2c3fa5d..a5f5b937f 100644 --- a/apps/regression/model/exponential_model.cpp +++ b/apps/regression/model/exponential_model.cpp @@ -13,12 +13,12 @@ Layout ExponentialModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { CodePointLayout::Builder('a', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('e', KDFont::SmallFont), VerticalOffsetLayout::Builder( HorizontalLayout::Builder( CodePointLayout::Builder('b', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont) ), VerticalOffsetLayoutNode::Type::Superscript diff --git a/apps/regression/model/linear_model.cpp b/apps/regression/model/linear_model.cpp index 116126fc5..15b811181 100644 --- a/apps/regression/model/linear_model.cpp +++ b/apps/regression/model/linear_model.cpp @@ -13,7 +13,7 @@ Layout LinearModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { CodePointLayout::Builder('a', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('b', KDFont::SmallFont), diff --git a/apps/regression/model/logarithmic_model.cpp b/apps/regression/model/logarithmic_model.cpp index 255ef5268..c0402bac9 100644 --- a/apps/regression/model/logarithmic_model.cpp +++ b/apps/regression/model/logarithmic_model.cpp @@ -13,7 +13,7 @@ Layout LogarithmicModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { CodePointLayout::Builder('a', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('l', KDFont::SmallFont), CodePointLayout::Builder('n', KDFont::SmallFont), CodePointLayout::Builder('(', KDFont::SmallFont), diff --git a/apps/regression/model/logistic_model.cpp b/apps/regression/model/logistic_model.cpp index 5f637e798..f7744f916 100644 --- a/apps/regression/model/logistic_model.cpp +++ b/apps/regression/model/logistic_model.cpp @@ -15,14 +15,14 @@ Layout LogisticModel::layout() { Layout exponentLayoutChildren[] = { CodePointLayout::Builder('-', KDFont::SmallFont), CodePointLayout::Builder('b', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont) }; Layout layoutChildren[] = { CodePointLayout::Builder('1', KDFont::SmallFont), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('a', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('e', KDFont::SmallFont), VerticalOffsetLayout::Builder( HorizontalLayout::Builder(exponentLayoutChildren, 4), diff --git a/apps/regression/model/power_model.cpp b/apps/regression/model/power_model.cpp index 7a5dcc992..2241eb4fa 100644 --- a/apps/regression/model/power_model.cpp +++ b/apps/regression/model/power_model.cpp @@ -14,7 +14,7 @@ Layout PowerModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { CodePointLayout::Builder('a', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('b', KDFont::SmallFont), diff --git a/apps/regression/model/quadratic_model.cpp b/apps/regression/model/quadratic_model.cpp index 16d9fd6c9..feaf176e6 100644 --- a/apps/regression/model/quadratic_model.cpp +++ b/apps/regression/model/quadratic_model.cpp @@ -21,7 +21,7 @@ Layout QuadraticModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { CodePointLayout::Builder('a', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2', KDFont::SmallFont), @@ -29,7 +29,7 @@ Layout QuadraticModel::layout() { ), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('b', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('c', KDFont::SmallFont), diff --git a/apps/regression/model/quartic_model.cpp b/apps/regression/model/quartic_model.cpp index 163880fae..710bb970a 100644 --- a/apps/regression/model/quartic_model.cpp +++ b/apps/regression/model/quartic_model.cpp @@ -21,7 +21,7 @@ Layout QuarticModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { CodePointLayout::Builder('a', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('4', KDFont::SmallFont), @@ -29,7 +29,7 @@ Layout QuarticModel::layout() { ), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('b', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('3', KDFont::SmallFont), @@ -37,7 +37,7 @@ Layout QuarticModel::layout() { ), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('c', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), VerticalOffsetLayout::Builder( CodePointLayout::Builder('2', KDFont::SmallFont), @@ -45,7 +45,7 @@ Layout QuarticModel::layout() { ), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('d', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('e', KDFont::SmallFont), diff --git a/apps/regression/model/trigonometric_model.cpp b/apps/regression/model/trigonometric_model.cpp index 8fceb306f..46ba81386 100644 --- a/apps/regression/model/trigonometric_model.cpp +++ b/apps/regression/model/trigonometric_model.cpp @@ -22,13 +22,13 @@ Layout TrigonometricModel::layout() { if (m_layout.isUninitialized()) { Layout layoutChildren[] = { CodePointLayout::Builder('a', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('s', KDFont::SmallFont), CodePointLayout::Builder('i', KDFont::SmallFont), CodePointLayout::Builder('n', KDFont::SmallFont), CodePointLayout::Builder('(', KDFont::SmallFont), CodePointLayout::Builder('b', KDFont::SmallFont), - CodePointLayout::Builder(Ion::Charset::MiddleDot, KDFont::SmallFont), + CodePointLayout::Builder(KDCodePointMiddleDot, KDFont::SmallFont), CodePointLayout::Builder('X', KDFont::SmallFont), CodePointLayout::Builder('+', KDFont::SmallFont), CodePointLayout::Builder('c', KDFont::SmallFont), diff --git a/apps/sequence/graph/term_sum_controller.cpp b/apps/sequence/graph/term_sum_controller.cpp index 17b59c943..aa636e422 100644 --- a/apps/sequence/graph/term_sum_controller.cpp +++ b/apps/sequence/graph/term_sum_controller.cpp @@ -18,7 +18,7 @@ using namespace Poincare; namespace Sequence { TermSumController::TermSumController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, GraphView * graphView, CurveViewRange * graphRange, CurveViewCursor * cursor) : - SumGraphController(parentResponder, inputEventHandlerDelegate, graphView, graphRange, cursor, Ion::Charset::CapitalSigma) + SumGraphController(parentResponder, inputEventHandlerDelegate, graphView, graphRange, cursor, KDCodePointNArySummation) { } diff --git a/apps/settings/sub_menu/preferences_controller.cpp b/apps/settings/sub_menu/preferences_controller.cpp index 65baa91eb..a20c3b45f 100644 --- a/apps/settings/sub_menu/preferences_controller.cpp +++ b/apps/settings/sub_menu/preferences_controller.cpp @@ -54,12 +54,12 @@ Layout layoutForPreferences(I18n::Message message) { // Angle Unit case I18n::Message::Degres: { - const char degEx[] = {'9', '0', Ion::Charset::Degree}; - return LayoutHelper::String(degEx, sizeof(degEx), KDFont::SmallFont); + const char * degEx = "90°"; + return LayoutHelper::String("90°", strlen(degEx), KDFont::SmallFont); } case I18n::Message::Radian: { - const char pi[] = {Ion::Charset::SmallPi}; + const char * pi = "Ï€"; return FractionLayout::Builder( LayoutHelper::String(pi, sizeof(pi), KDFont::SmallFont), LayoutHelper::String("2", 1, KDFont::SmallFont) @@ -70,8 +70,8 @@ Layout layoutForPreferences(I18n::Message message) { return LayoutHelper::String("12.34", 5, KDFont::SmallFont); case I18n::Message::Scientific: { - const char text[] = {'1','.', '2', '3', '4', Ion::Charset::Exponent, '1'}; - return LayoutHelper::String(text, sizeof(text), KDFont::SmallFont); + const char * text = "1.234á´‡1"; + return LayoutHelper::String(text, strlen(text), KDFont::SmallFont); } // Edition mode case I18n::Message::Edition2D: @@ -88,16 +88,16 @@ Layout layoutForPreferences(I18n::Message message) { } case I18n::Message::Cartesian: { - const char text[] = {'a','+', Ion::Charset::IComplex, 'b'}; - return LayoutHelper::String(text, sizeof(text), KDFont::SmallFont); + const char * text = "a+ð¢b"; + return LayoutHelper::String(text, strlen(text), KDFont::SmallFont); } case I18n::Message::Polar: { - const char base[] = {'r', Ion::Charset::Exponential}; - const char superscript[] = {Ion::Charset::IComplex, Ion::Charset::SmallTheta}; + const char * base = "rℯ"; + const char * superscript = "ð¢Î¸"; return HorizontalLayout::Builder( - LayoutHelper::String(base, sizeof(base), KDFont::SmallFont), - VerticalOffsetLayout::Builder(LayoutHelper::String(superscript, sizeof(superscript), KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript) + LayoutHelper::String(base, strlen(base), KDFont::SmallFont), + VerticalOffsetLayout::Builder(LayoutHelper::String(superscript, strlen(superscript), KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript) ); } default: diff --git a/apps/shared/storage_sum_graph_controller.cpp b/apps/shared/storage_sum_graph_controller.cpp index bc164fb5a..7582995f6 100644 --- a/apps/shared/storage_sum_graph_controller.cpp +++ b/apps/shared/storage_sum_graph_controller.cpp @@ -13,7 +13,7 @@ using namespace Poincare; namespace Shared { -StorageSumGraphController::StorageSumGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, StorageFunctionGraphView * graphView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, char sumSymbol) : +StorageSumGraphController::StorageSumGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, StorageFunctionGraphView * graphView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, CodePoint sumSymbol) : SimpleInteractiveCurveViewController(parentResponder, range, graphView, cursor), m_step(Step::FirstParameter), m_startSum(NAN), @@ -203,7 +203,7 @@ bool StorageSumGraphController::handleEnter() { /* Legend View */ -StorageSumGraphController::LegendView::LegendView(StorageSumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, char sumSymbol) : +StorageSumGraphController::LegendView::LegendView(StorageSumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, CodePoint sumSymbol) : m_sum(0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), m_sumLayout(), m_legend(k_font, I18n::Message::Default, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), @@ -234,25 +234,26 @@ void StorageSumGraphController::LegendView::setEditableZone(double d) { void StorageSumGraphController::LegendView::setSumSymbol(Step step, double start, double end, double result, Layout functionLayout) { assert(step == Step::Result || functionLayout.isUninitialized()); - const char sigma[] = {' ', m_sumSymbol}; + constexpr int sigmaLength = 2; + const CodePoint sigma[sigmaLength] = {KDCodePointSpace, m_sumSymbol}; if (step == Step::FirstParameter) { - m_sumLayout = LayoutHelper::String(sigma, sizeof(sigma)); + m_sumLayout = LayoutHelper::CodePointString(sigma, sigmaLength); } else if (step == Step::SecondParameter) { char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits)]; PrintFloat::convertFloatToText(start, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); m_sumLayout = CondensedSumLayout::Builder( - LayoutHelper::String(sigma, sizeof(sigma)), + LayoutHelper::CodePointString(sigma, sizeof(sigma)), LayoutHelper::String(buffer, strlen(buffer), k_font), EmptyLayout::Builder(EmptyLayoutNode::Color::Yellow, false, k_font, false)); } else { - m_sumLayout = LayoutHelper::String(sigma, sizeof(sigma)); + m_sumLayout = LayoutHelper::CodePointString(sigma, sizeof(sigma)); char buffer[2+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)]; PrintFloat::convertFloatToText(start, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); Layout start = LayoutHelper::String(buffer, strlen(buffer), KDFont::SmallFont); PrintFloat::convertFloatToText(end, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); Layout end = LayoutHelper::String(buffer, strlen(buffer), k_font); m_sumLayout = CondensedSumLayout::Builder( - LayoutHelper::String(sigma, sizeof(sigma)), + LayoutHelper::CodePointString(sigma, sizeof(sigma)), start, end); strlcpy(buffer, "= ", 3); diff --git a/apps/shared/storage_sum_graph_controller.h b/apps/shared/storage_sum_graph_controller.h index 0f5189133..7b9aceecc 100644 --- a/apps/shared/storage_sum_graph_controller.h +++ b/apps/shared/storage_sum_graph_controller.h @@ -15,7 +15,7 @@ namespace Shared { class StorageSumGraphController : public SimpleInteractiveCurveViewController, public TextFieldDelegate { public: - StorageSumGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, StorageFunctionGraphView * curveView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, char sumSymbol); + StorageSumGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, StorageFunctionGraphView * curveView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, CodePoint sumSymbol); void viewWillAppear() override; void didEnterResponderChain(Responder * previousFirstResponder) override; bool handleEvent(Ion::Events::Event event) override; @@ -49,7 +49,7 @@ private: bool handleEnter() override; class LegendView : public View { public: - LegendView(StorageSumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, char sumSymbol); + LegendView(StorageSumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, CodePoint sumSymbol); LegendView(const LegendView& other) = delete; LegendView(LegendView&& other) = delete; LegendView& operator=(const LegendView& other) = delete; @@ -76,7 +76,7 @@ private: MessageTextView m_legend; TextField m_editableZone; char m_draftText[TextField::maxBufferSize()]; - char m_sumSymbol; + CodePoint m_sumSymbol; }; StorageFunctionGraphView * m_graphView; LegendView m_legendView; diff --git a/apps/shared/sum_graph_controller.cpp b/apps/shared/sum_graph_controller.cpp index ee03dbc2b..e45984d5b 100644 --- a/apps/shared/sum_graph_controller.cpp +++ b/apps/shared/sum_graph_controller.cpp @@ -13,7 +13,7 @@ using namespace Poincare; namespace Shared { -SumGraphController::SumGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, FunctionGraphView * graphView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, char sumSymbol) : +SumGraphController::SumGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, FunctionGraphView * graphView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, CodePoint sumSymbol) : SimpleInteractiveCurveViewController(parentResponder, range, graphView, cursor), m_step(Step::FirstParameter), m_startSum(NAN), @@ -202,7 +202,7 @@ bool SumGraphController::handleEnter() { /* Legend View */ -SumGraphController::LegendView::LegendView(SumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, char sumSymbol) : +SumGraphController::LegendView::LegendView(SumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, CodePoint sumSymbol) : m_sum(0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), m_sumLayout(), m_legend(KDFont::SmallFont, I18n::Message::Default, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), @@ -233,18 +233,19 @@ void SumGraphController::LegendView::setEditableZone(double d) { void SumGraphController::LegendView::setSumSymbol(Step step, double start, double end, double result, Layout functionLayout) { assert(step == Step::Result || functionLayout.isUninitialized()); - const char sigma[] = {' ', m_sumSymbol}; + constexpr int sigmaSize = 2; + const CodePoint sigma[sigmaSize] = {KDCodePointSpace, m_sumSymbol}; if (step == Step::FirstParameter) { - m_sumLayout = LayoutHelper::String(sigma, sizeof(sigma)); + m_sumLayout = LayoutHelper::CodePointString(sigma, sigmaSize); } else if (step == Step::SecondParameter) { char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits)]; PrintFloat::convertFloatToText(start, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::MediumNumberOfSignificantDigits), Constant::MediumNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); m_sumLayout = CondensedSumLayout::Builder( - LayoutHelper::String(sigma, sizeof(sigma)), + LayoutHelper::CodePointString(sigma, sizeof(sigma)), LayoutHelper::String(buffer, strlen(buffer), KDFont::SmallFont), EmptyLayout::Builder(EmptyLayoutNode::Color::Yellow, false, KDFont::SmallFont, false)); } else { - m_sumLayout = LayoutHelper::String(sigma, sizeof(sigma)); + m_sumLayout = LayoutHelper::CodePointString(sigma, sigmaSize); constexpr size_t bufferSize = 2+PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits); char buffer[bufferSize]; PrintFloat::convertFloatToText(start, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); @@ -252,7 +253,7 @@ void SumGraphController::LegendView::setSumSymbol(Step step, double start, doubl PrintFloat::convertFloatToText(end, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits, Preferences::PrintFloatMode::Decimal); Layout end = LayoutHelper::String(buffer, strlen(buffer), KDFont::SmallFont); m_sumLayout = CondensedSumLayout::Builder( - LayoutHelper::String(sigma, sizeof(sigma)), + LayoutHelper::CodePointString(sigma, sizeof(sigma)), start, end); strlcpy(buffer, "= ", bufferSize); diff --git a/apps/shared/sum_graph_controller.h b/apps/shared/sum_graph_controller.h index 9def6e084..595f9f4bc 100644 --- a/apps/shared/sum_graph_controller.h +++ b/apps/shared/sum_graph_controller.h @@ -14,7 +14,7 @@ namespace Shared { class SumGraphController : public SimpleInteractiveCurveViewController, public TextFieldDelegate { public: - SumGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, FunctionGraphView * curveView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, char sumSymbol); + SumGraphController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, FunctionGraphView * curveView, InteractiveCurveViewRange * range, CurveViewCursor * cursor, CodePoint sumSymbol); void viewWillAppear() override; void didEnterResponderChain(Responder * previousFirstResponder) override; bool handleEvent(Ion::Events::Event event) override; @@ -48,7 +48,7 @@ private: bool handleEnter() override; class LegendView : public View { public: - LegendView(SumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, char sumSymbol); + LegendView(SumGraphController * controller, InputEventHandlerDelegate * inputEventHandlerDelegate, CodePoint sumSymbol); LegendView(const LegendView& other) = delete; LegendView(LegendView&& other) = delete; LegendView& operator=(const LegendView& other) = delete; @@ -72,7 +72,7 @@ private: MessageTextView m_legend; TextField m_editableZone; char m_draftText[TextField::maxBufferSize()]; - char m_sumSymbol; + CodePoint m_sumSymbol; }; FunctionGraphView * m_graphView; LegendView m_legendView; diff --git a/apps/shared/text_field_delegate_app.cpp b/apps/shared/text_field_delegate_app.cpp index b9062c352..94aacf698 100644 --- a/apps/shared/text_field_delegate_app.cpp +++ b/apps/shared/text_field_delegate_app.cpp @@ -1,4 +1,5 @@ #include "text_field_delegate_app.h" +#include #include #include #include @@ -53,8 +54,13 @@ bool TextFieldDelegateApp::fieldDidReceiveEvent(EditableField * field, Responder if (!field->isEditing()) { field->setEditing(true); } - const char xnt[2] = {field->XNTChar(XNT()), 0}; - return field->handleEventWithText(xnt); + /* TODO decode here to encode again in handleEventWithText? */ + constexpr int bufferSize = CodePoint::MaxCodePointCharLength+1; + char buffer[bufferSize]; + size_t length = UTF8Decoder::CodePointToChars(XNT(), buffer, bufferSize); + assert(length < bufferSize - 1); + buffer[length] = 0; + return field->handleEventWithText(buffer); } return false; } diff --git a/apps/shared/toolbox_helpers.cpp b/apps/shared/toolbox_helpers.cpp index 0a0e50035..99c89cd3b 100644 --- a/apps/shared/toolbox_helpers.cpp +++ b/apps/shared/toolbox_helpers.cpp @@ -1,5 +1,4 @@ #include "toolbox_helpers.h" -#include #include #include #include @@ -8,6 +7,7 @@ namespace Shared { namespace ToolboxHelpers { int CursorIndexInCommandText(const char * text) { + // TODO LEA size_t textLength = strlen(text); for (size_t i = 0; i < textLength; i++) { if (text[i] == '(' || text[i] == '\'') { @@ -49,7 +49,7 @@ void TextToInsertForCommandText(const char * command, char * buffer, int bufferS buffer[currentNewTextIndex++] = command[i]; } else { if (replaceArgsWithEmptyChar && !argumentAlreadyReplaced) { - buffer[currentNewTextIndex++] = Ion::Charset::Empty; + // TODO LEA buffer[currentNewTextIndex++] = Ion::Charset::Empty; argumentAlreadyReplaced = true; } } diff --git a/apps/solver/equation.cpp b/apps/solver/equation.cpp index 15c9e1578..04edc3eb8 100644 --- a/apps/solver/equation.cpp +++ b/apps/solver/equation.cpp @@ -50,7 +50,7 @@ Expression Equation::standardForm(Context * context) const { } bool Equation::containsIComplex() const { - return strchr(text(), Ion::Charset::IComplex) != nullptr; + return false; //TODO LEA strchr(text(), KDCodePointMathematicalBoldSmallI) != nullptr; } void Equation::tidyStandardForm() { diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index b81de3552..c629302a3 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -325,7 +325,7 @@ EquationStore::Error EquationStore::oneDimensialPolynomialSolve(Expression exact // C = Root((delta1+sqrt(-27a^2*delta))/2, 3) Expression * mult11Operands[3] = {new Rational::Builder(-27), new Power::Builder(a->clone(), new Rational::Builder(2), false), (*delta)->clone()}; Expression * c = new Power::Builder(new Division::Builder(new Addition(delta1, new SquareRoot(new Multiplication::Builder(mult11Operands, 3, false), false), false), new Rational::Builder(2), false), new Rational::Builder(1,3), false); - Expression * unary3roots[2] = {new Addition(new Rational::Builder(-1,2), new Division::Builder(new Multiplication::Builder(new SquareRoot(new Rational::Builder(3), false), new Constant::Builder(Ion::Charset::IComplex), false), new Rational::Builder(2), false), false), new Subtraction::Builder(new Rational::Builder(-1,2), new Division::Builder(new Multiplication::Builder(new SquareRoot(new Rational::Builder(3), false), new Constant::Builder(Ion::Charset::IComplex), false), new Rational::Builder(2), false), false)}; + Expression * unary3roots[2] = {new Addition(new Rational::Builder(-1,2), new Division::Builder(new Multiplication::Builder(new SquareRoot(new Rational::Builder(3), false), new Constant::Builder(KDCodePointMathematicalBoldSmallI), false), new Rational::Builder(2), false), false), new Subtraction::Builder(new Rational::Builder(-1,2), new Division::Builder(new Multiplication::Builder(new SquareRoot(new Rational::Builder(3), false), new Constant::Builder(KDCodePointMathematicalBoldSmallI), false), new Rational::Builder(2), false), false)}; // x_k = -1/(3a)*(b+C*z+delta0/(zC)) with z = unary cube root for (int k = 0; k < 3; k++) { Expression * ccopy = c; diff --git a/apps/solver/solutions_controller.cpp b/apps/solver/solutions_controller.cpp index 1f01562a7..c3b9ae56a 100644 --- a/apps/solver/solutions_controller.cpp +++ b/apps/solver/solutions_controller.cpp @@ -76,7 +76,7 @@ SolutionsController::SolutionsController(Responder * parentResponder, EquationSt m_contentView(this) { m_delta2Layout = HorizontalLayout::Builder(VerticalOffsetLayout::Builder(CodePointLayout::Builder('2', KDFont::SmallFont), VerticalOffsetLayoutNode::Type::Superscript), LayoutHelper::String("-4ac", 4, KDFont::SmallFont)); - char deltaB[] = {Ion::Charset::CapitalDelta, '=', 'b'}; + const char * deltaB = "Δ=b"; static_cast(m_delta2Layout).addOrMergeChildAtIndex(LayoutHelper::String(deltaB, 3, KDFont::SmallFont), 0, false); for (int i = 0; i < EquationStore::k_maxNumberOfExactSolutions; i++) { m_exactValueCells[i].setParentResponder(m_contentView.selectableTableView()); diff --git a/apps/solver/test/equation_store.cpp b/apps/solver/test/equation_store.cpp index fd2e3e9c5..2053defb6 100644 --- a/apps/solver/test/equation_store.cpp +++ b/apps/solver/test/equation_store.cpp @@ -12,15 +12,12 @@ using namespace Poincare; namespace Solver { void assert_equation_system_exact_solve_to(const char * equations[], EquationStore::Error error, EquationStore::Type type, const char * variables[], const char * solutions[], int numberOfSolutions) { - char buffer[200]; Shared::GlobalContext globalContext; EquationStore equationStore; int index = 0; while (equations[index] != 0) { Shared::ExpressionModel * e = equationStore.addEmptyModel(); - strlcpy(buffer, equations[index++], 200); - translate_in_special_chars(buffer); - e->setContent(buffer); + e->setContent(equations[index++]); } EquationStore::Error err = equationStore.exactSolve(&globalContext); quiz_assert(err == error); @@ -40,20 +37,15 @@ void assert_equation_system_exact_solve_to(const char * equations[], EquationSto quiz_assert(strcmp(equationStore.variableAtIndex(0), variables[0]) == 0); } for (int i = 0; i < numberOfSolutions; i++) { - equationStore.exactSolutionLayoutAtIndex(i, true).serializeForParsing(buffer, 200); - translate_in_ASCII_chars(buffer); - quiz_assert(strcmp(buffer, solutions[i]) == 0); + quiz_assert(strcmp(equationStore.exactSolutionLayoutAtIndex(i, true), solutions[i]) == 0); } } void assert_equation_approximate_solve_to(const char * equations, double xMin, double xMax, const char * variable, double solutions[], int numberOfSolutions, bool hasMoreSolutions) { - char buffer[200]; Shared::GlobalContext globalContext; EquationStore equationStore; Shared::ExpressionModel * e = equationStore.addEmptyModel(); - strlcpy(buffer, equations, 200); - translate_in_special_chars(buffer); - e->setContent(buffer); + e->setContent(equations); EquationStore::Error err = equationStore.exactSolve(&globalContext); quiz_assert(err == EquationStore::Error::RequireApproximateSolution); equationStore.setIntervalBound(0, xMin); diff --git a/apps/variable_box_controller.cpp b/apps/variable_box_controller.cpp index adc6f61bd..54a6367c4 100644 --- a/apps/variable_box_controller.cpp +++ b/apps/variable_box_controller.cpp @@ -199,7 +199,7 @@ bool VariableBoxController::selectLeaf(int selectedRow) { assert(nameLength < nameToHandleMaxSize); nameToHandle[nameLength++] = '('; assert(nameLength < nameToHandleMaxSize); - nameToHandle[nameLength++] = Ion::Charset::Empty; + // TODO LEA nameToHandle[nameLength++] = Ion::Charset::Empty; assert(nameLength < nameToHandleMaxSize); nameToHandle[nameLength++] = ')'; assert(nameLength < nameToHandleMaxSize); diff --git a/apps/variable_box_empty_controller.cpp b/apps/variable_box_empty_controller.cpp index 0c810bdd8..1f0d4fd7e 100644 --- a/apps/variable_box_empty_controller.cpp +++ b/apps/variable_box_empty_controller.cpp @@ -84,8 +84,8 @@ void VariableBoxEmptyController::setType(Type type) { messages[0] = I18n::Message::EmptyExpressionBox0; messages[1] = I18n::Message::EmptyExpressionBox1; messages[2] = I18n::Message::EmptyExpressionBox2; - char storeExpression[] = {'3', Ion::Charset::Sto, 'A'}; - layout = LayoutHelper::String(storeExpression, sizeof(storeExpression), VariableBoxEmptyView::k_font); + const char * storeExpression = "3→A"; + layout = LayoutHelper::String(storeExpression, strlen(storeExpression), VariableBoxEmptyView::k_font); break; } case Type::Functions: @@ -93,8 +93,8 @@ void VariableBoxEmptyController::setType(Type type) { messages[0] = I18n::Message::EmptyFunctionBox0; messages[1] = I18n::Message::EmptyFunctionBox1; messages[2] = I18n::Message::EmptyFunctionBox2; - char storeFunction[] = {'3', '+', Graph::StorageCartesianFunctionStore::Symbol(), Ion::Charset::Sto, 'f', '(', Graph::StorageCartesianFunctionStore::Symbol(), ')'}; - layout = LayoutHelper::String(storeFunction, sizeof(storeFunction), VariableBoxEmptyView::k_font); + const char * storeFunction = "3+x→f(x)"; + layout = LayoutHelper::String(storeFunction, strlen(storeFunction), VariableBoxEmptyView::k_font); break; } default: diff --git a/escher/include/escher/editable_field.h b/escher/include/escher/editable_field.h index 0343d183e..53671bd62 100644 --- a/escher/include/escher/editable_field.h +++ b/escher/include/escher/editable_field.h @@ -3,13 +3,14 @@ #include #include +#include class EditableField : public InputEventHandler { public: using InputEventHandler::InputEventHandler; virtual bool isEditing() const = 0; virtual void setEditing(bool isEditing, bool reinitDraftBuffer = true) = 0; - virtual char XNTChar(char defaultXNTChar) = 0; + virtual CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) = 0; virtual bool shouldFinishEditing(Ion::Events::Event event) = 0; }; diff --git a/escher/include/escher/layout_field.h b/escher/include/escher/layout_field.h index bb8a38927..f04ed133b 100644 --- a/escher/include/escher/layout_field.h +++ b/escher/include/escher/layout_field.h @@ -28,7 +28,7 @@ public: } bool hasText() const { return layout().hasText(); } Poincare::Layout layout() const { return m_contentView.expressionView()->layout(); } - char XNTChar(char defaultXNTChar) override; + CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override; // ScrollableView void setBackgroundColor(KDColor c) override { diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index 49b488747..46a9c73f2 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -20,7 +20,7 @@ public: void setText(const char * text); void setAlignment(float horizontalAlignment, float verticalAlignment); virtual void setEditing(bool isEditing, bool reinitDraftBuffer = true) override; - char XNTChar(char defaultXNTChar) override; + CodePoint XNTCodePoint(CodePoint defaultXNTCodePoint) override; bool handleEventWithText(const char * text, bool indentation = false, bool forceCursorRightOfText = false) override; bool handleEvent(Ion::Events::Event event) override; constexpr static int maxBufferSize() { diff --git a/escher/src/layout_field.cpp b/escher/src/layout_field.cpp index b31123263..ecac2dc63 100644 --- a/escher/src/layout_field.cpp +++ b/escher/src/layout_field.cpp @@ -68,12 +68,12 @@ void LayoutField::ContentView::layoutCursorSubview() { m_cursorView.setFrame(KDRect(cursorTopLeftPosition, LayoutCursor::k_cursorWidth, m_cursor.cursorHeight())); } -char LayoutField::XNTChar(char defaultXNTChar) { - char xnt = m_contentView.cursor()->layoutReference().XNTChar(); - if (xnt != Ion::Charset::Empty) { +CodePoint LayoutField::XNTCodePoint(CodePoint defaultXNTCodePoint) { + CodePoint xnt = m_contentView.cursor()->layoutReference().XNTCodePoint(); + if (xnt != KDCodePointNull) { return xnt; } - return defaultXNTChar; + return defaultXNTCodePoint; } void LayoutField::reload(KDSize previousSize) { diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index e9084892c..e7bccb24d 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -30,11 +30,12 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for // Remove EmptyChars for (size_t i = bufferIndex; i < eventTextSize; i++) { + /* TODO LEA if (text[i] != Ion::Charset::Empty) { buffer[bufferIndex++] = text[i]; } else if (i < cursorIndexInCommand) { cursorIndexInCommand--; - } + } */ } if ((indentation && insertTextWithIndentation(buffer, cursorLocation())) || insertTextAtLocation(buffer, cursorLocation())) { diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index 7f21bcfc6..1a2190a42 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include /* TextField::ContentView */ @@ -312,13 +311,14 @@ bool TextField::privateHandleEvent(Ion::Events::Event event) { return false; } -char TextField::XNTChar(char defaultXNTChar) { +CodePoint TextField::XNTCodePoint(CodePoint defaultXNTCodePoint) { static constexpr struct { const char *name; char xnt; } sFunctions[] = { { "diff", 'x' }, { "int", 'x' }, { "product", 'n' }, { "sum", 'n' } }; // Let's assume everything before the cursor is nested correctly, which is reasonable if the expression is being entered left-to-right. const char * text = this->text(); + /* TODO LEA size_t location = cursorLocation(); unsigned level = 0; while (location >= 1) { @@ -355,8 +355,9 @@ char TextField::XNTChar(char defaultXNTChar) { break; } } + */ // Fallback to the default - return defaultXNTChar; + return defaultXNTCodePoint; } bool TextField::handleEvent(Ion::Events::Event event) { @@ -404,6 +405,7 @@ bool TextField::privateHandleMoveEvent(Ion::Events::Event event) { } bool TextField::handleEventWithText(const char * eventText, bool indentation, bool forceCursorRightOfText) { +//TODO LEA size_t previousTextLength = strlen(text()); size_t eventTextLength = strlen(eventText); @@ -421,11 +423,11 @@ bool TextField::handleEventWithText(const char * eventText, bool indentation, bo int newBufferIndex = 0; // Remove EmptyChars - for (size_t i = 0; i < eventTextSize; i++) { + /* TODO for (size_t i = 0; i < eventTextSize; i++) { if (eventText[i] != Ion::Charset::Empty) { buffer[newBufferIndex++] = eventText[i]; } - } + }*/ int nextCursorLocation = draftTextLength(); if (insertTextAtLocation(buffer, cursorLocation())) { diff --git a/escher/src/text_input_helpers.cpp b/escher/src/text_input_helpers.cpp index a9600b62b..c210c33a0 100644 --- a/escher/src/text_input_helpers.cpp +++ b/escher/src/text_input_helpers.cpp @@ -1,17 +1,17 @@ #include -#include #include namespace TextInputHelpers { size_t CursorIndexInCommand(const char * text) { + // TODO LEA size_t index = 0; while (text[index] != 0) { if (text[index] == '\'' && text[index+1] == '\'') { return index + 1; - } else if (text[index] == Ion::Charset::Empty) { + } /* TODO else if (text[index] == Ion::Charset::Empty) { return index; - } + }*/ index++; } return index; diff --git a/ion/include/ion.h b/ion/include/ion.h index 8c66af6bf..74e62514b 100644 --- a/ion/include/ion.h +++ b/ion/include/ion.h @@ -3,7 +3,6 @@ #include #include -#include #include #include #include diff --git a/ion/include/ion/charset.h b/ion/include/ion/charset.h deleted file mode 100644 index 1a18bbfec..000000000 --- a/ion/include/ion/charset.h +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef ION_CHARSET_H -#define ION_CHARSET_H - -namespace Ion { - -enum Charset : char { - Integral = (char)127, - XBar = (char)128, - YBar = (char)130, - CapitalGamma = (char)132, - CapitalDelta = (char)133, - CapitalSigma = (char)134, - SmallTheta = (char)135, - SmallLambda = (char)136, - SmallMu = (char)137, - SmallPi = (char)138, - SmallSigma = (char)139, - IComplex = (char)140, - Exponent = (char)141, - Prime = (char)142, - Exponential = (char)143, - Sto = (char)144, - Root = (char)145, - LessEqual = (char)146, - GreaterEqual = (char)147, - MultiplicationSign = (char)148, - MiddleDot = (char)149, - AlmostEqual = (char)150, - Degree = (char)151, - Empty = (char)152, // This char is used to be parsed into EmptyExpression - LeftSuperscript = (char)153, // This char is used to parse Power - RightSuperscript = (char)154 // This char is used to parse Power -}; - -} - -#endif diff --git a/ion/src/shared/events.cpp b/ion/src/shared/events.cpp index b5300ec37..89fb1a736 100644 --- a/ion/src/shared/events.cpp +++ b/ion/src/shared/events.cpp @@ -1,5 +1,5 @@ #include -#include +#include extern "C" { #include @@ -25,42 +25,24 @@ private: #define U() EventData::Undefined() #define T(x) EventData::Text(x) -static constexpr const char k_exponential[6] = {Ion::Charset::Exponential, '^', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_naperianLogarithm[6] = {'l', 'n', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_logarithm[7] = {'l', 'o', 'g', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_complexI[2] = {Ion::Charset::IComplex, 0}; - -static constexpr const char k_sine[7] = {'s', 'i', 'n', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_cosine[7] = {'c', 'o', 's', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_tangent[7] = {'t', 'a', 'n', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_pi[2] = {Ion::Charset::SmallPi, 0}; -static constexpr const char k_root[5] = {Ion::Charset::Root, '(', Ion::Charset::Empty, ')', 0}; - -static constexpr const char k_multiplicationSign[2] = {Ion::Charset::MultiplicationSign, 0}; -static constexpr const char k_exponent[2] = {Ion::Charset::Exponent, 0}; -static constexpr const char k_sto[2] = {Ion::Charset::Sto, 0}; - -static constexpr const char k_arcSine[8] = {'a', 's', 'i', 'n', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_arcCosine[8] = {'a', 'c', 'o', 's', '(', Ion::Charset::Empty, ')', 0}; -static constexpr const char k_arcTangent[8] = {'a', 't', 'a', 'n', '(', Ion::Charset::Empty, ')', 0}; - +static_assert('\x11' == KDCodePointEmpty, "Unicode error"); static constexpr EventData s_dataForEvent[4*Event::PageSize] = { // Plain TL(), TL(), TL(), TL(), TL(), TL(), TL(), TL(), U(), U(), U(), U(), TL(), TL(), TL(), TL(), TL(), TL(), - T(k_exponential), T(k_naperianLogarithm), T(k_logarithm), T(k_complexI), T(","), T("^"), - T(k_sine), T(k_cosine), T(k_tangent), T(k_pi), T(k_root), T("^2"), + T("ℯ^(\x11)"), T("ln(\x11)"), T("log(\x11)"), T("ð¢"), T(","), T("^"), + T("sin(\x11)"), T("cos(\x11)"), T("tan(\x11)"), T("Ï€"), T("√(\x11)"), T("^2"), T("7"), T("8"), T("9"), T("("), T(")"), U(), - T("4"), T("5"), T("6"), T(k_multiplicationSign), T("/"), U(), + T("4"), T("5"), T("6"), T("×"), T("/"), U(), T("1"), T("2"), T("3"), T("+"), T("-"), U(), - T("0"), T("."), T(k_exponent), TL(), TL(), U(), + T("0"), T("."), T("ℯ"), TL(), TL(), U(), // Shift TL(), U(), U(), TL(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), TL(), TL(), TL(), TL(), - T("["), T("]"), T("{"), T("}"), T("_"), T(k_sto), - T(k_arcSine), T(k_arcCosine), T(k_arcTangent), T("="), T("<"), T(">"), + T("["), T("]"), T("{"), T("}"), T("_"), T("→"), + T("arcsin(\x11)"), T("arccos(\x11)"), T("arctan(\x11)"), T("="), T("<"), T(">"), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), U(), diff --git a/kandinsky/include/kandinsky/unicode/code_point.h b/kandinsky/include/kandinsky/unicode/code_point.h index fc32a7b1a..e3648384d 100644 --- a/kandinsky/include/kandinsky/unicode/code_point.h +++ b/kandinsky/include/kandinsky/unicode/code_point.h @@ -5,9 +5,9 @@ class CodePoint { public: + constexpr static int MaxCodePointCharLength = sizeof(uint32_t) / sizeof(char); constexpr CodePoint(uint32_t c) : m_code(c) {} - operator uint16_t() const { return m_code; } - + constexpr operator uint16_t() const { return m_code; } bool isCombining() const { return (m_code >= 0x300 && m_code <= 0x036F); @@ -16,12 +16,38 @@ private: uint32_t m_code; }; -static constexpr CodePoint KDCodePointNull = 0x0; -static constexpr CodePoint KDCodePointTabulation = 0x9; -static constexpr CodePoint KDCodePointLineFeed = 0xA; -static constexpr CodePoint KDCodePointMiddleDot = 0xB7; -static constexpr CodePoint KDCodePointMultiplicationSign = 0xD7; -static constexpr CodePoint KDCodePointLatinLetterSmallCapitalE = 0x1d07; -static constexpr CodePoint KDCodePointRightwardsArrow = 0x2192; + +// TODO LEA Remove unneeded values +static constexpr CodePoint KDCodePointNull = 0x0; +static constexpr CodePoint KDCodePointTabulation = 0x9; +static constexpr CodePoint KDCodePointLineFeed = 0xa; + +/* 0x11, 0x12, 0x13, 0x14 represent DEVICE CONTROL ONE TO FOUR. They are not + * used, so we can use them for another purpose */ +static constexpr CodePoint KDCodePointEmpty = 0x11; // Used to be parsed into EmptyExpression +static constexpr CodePoint KDCodePointLeftSuperscript = 0x12; // Used to parse Power +static constexpr CodePoint KDCodePointRightSuperscript = 0x13; // Used to parse Power +static constexpr CodePoint KDCodePointUnknownX = 0x14; // Used to store expressions + +static constexpr CodePoint KDCodePointSpace = 0x20; // +static constexpr CodePoint KDCodePointDegree = 0xb0; // ° +static constexpr CodePoint KDCodePointMiddleDot = 0xb7; // · +static constexpr CodePoint KDCodePointMultiplicationSign = 0xd7; // × +static constexpr CodePoint KDCodePointGreekCapitalLetterGamma = 0x393; // Γ +static constexpr CodePoint KDCodePointGreekCapitalLetterDelta = 0x394; // Δ +static constexpr CodePoint KDCodePointGreekSmallLetterTheta = 0x3b8; // θ +static constexpr CodePoint KDCodePointGreekSmallLetterLambda = 0x3bb; // λ +static constexpr CodePoint KDCodePointGreekSmallLetterPi = 0x3c0; // Ï€ +static constexpr CodePoint KDCodePointGreekSmallLetterSigma = 0x3c3; // σ +static constexpr CodePoint KDCodePointLatinLetterSmallCapitalE = 0x1d07; // á´‡ +static constexpr CodePoint KDCodePointScriptSmallE = 0x212f; // ℯ +static constexpr CodePoint KDCodePointRightwardsArrow = 0x2192; // → +static constexpr CodePoint KDCodePointNArySummation = 0x2211; // ∑ +static constexpr CodePoint KDCodePointSquareRoot = 0x221a; // √ +static constexpr CodePoint KDCodePointIntegral = 0x222b; // ∫ +static constexpr CodePoint KDCodePointAlmostEqualTo = 0x2248; // ≈ +static constexpr CodePoint KDCodePointLessThanOrEqualTo = 0x2264; // ≤ +static constexpr CodePoint KDCodePointGreaterThanOrEqualTo = 0x2265; // ≥ +static constexpr CodePoint KDCodePointMathematicalBoldSmallI = 0x1d422; // ð¢ #endif diff --git a/kandinsky/include/kandinsky/unicode/utf8_decoder.h b/kandinsky/include/kandinsky/unicode/utf8_decoder.h index f4dab5269..abb768d7f 100644 --- a/kandinsky/include/kandinsky/unicode/utf8_decoder.h +++ b/kandinsky/include/kandinsky/unicode/utf8_decoder.h @@ -19,6 +19,7 @@ class UTF8Decoder { public: UTF8Decoder(const char * string) : m_string(string) {} CodePoint nextCodePoint(); + static size_t CharSizeOfCodePoint(CodePoint c); static size_t CodePointToChars(CodePoint c, char * buffer, int bufferSize); private: const char * m_string; diff --git a/kandinsky/src/unicode/utf8_decoder.cpp b/kandinsky/src/unicode/utf8_decoder.cpp index bdb6ed370..cb6f449d3 100644 --- a/kandinsky/src/unicode/utf8_decoder.cpp +++ b/kandinsky/src/unicode/utf8_decoder.cpp @@ -25,8 +25,14 @@ CodePoint UTF8Decoder::nextCodePoint() { return CodePoint(result); } +size_t UTF8Decoder::CharSizeOfCodePoint(CodePoint c) { + constexpr int bufferSize = CodePoint::MaxCodePointCharLength; + char buffer[bufferSize]; + return CodePointToChars(c, buffer, bufferSize); +} + size_t UTF8Decoder::CodePointToChars(CodePoint c, char * buffer, int bufferSize) { - assert(bufferSize >= sizeof(CodePoint)/sizeof(char)); + assert(bufferSize >= CodePoint::MaxCodePointCharLength); size_t i = 0; if (c <= 0x7F) { buffer[i++] = c; diff --git a/poincare/include/poincare/code_point_layout.h b/poincare/include/poincare/code_point_layout.h index 06f5adb91..28d3970e7 100644 --- a/poincare/include/poincare/code_point_layout.h +++ b/poincare/include/poincare/code_point_layout.h @@ -63,7 +63,7 @@ private: class CodePointLayout final : public Layout { public: CodePointLayout(const CodePointLayoutNode * n) : Layout(n) {} - static CharLayout Builder(CodePoint c, const KDFont * font = KDFont::LargeFont); + static CodePointLayout Builder(CodePoint c, const KDFont * font = KDFont::LargeFont); const KDFont * font() const { return const_cast(this)->node()->font(); } CodePoint codePoint() const { return const_cast(this)->node()->codePoint(); } private: diff --git a/poincare/include/poincare/constant.h b/poincare/include/poincare/constant.h index 9f09fbbe1..e0a9c5626 100644 --- a/poincare/include/poincare/constant.h +++ b/poincare/include/poincare/constant.h @@ -5,6 +5,9 @@ namespace Poincare { +/* TODO: Also keep a m_codePoint ? Redundant with m_name, but faster constants + * comparison */ + class ConstantNode final : public SymbolAbstractNode { public: ConstantNode(const char * newName, int length); @@ -35,9 +38,10 @@ public: Evaluation approximate(DoublePrecision p, Context& context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const override { return templatedApproximate(context, complexFormat, angleUnit); } /* Symbol properties */ - bool isPi() const { return isConstantChar(Ion::Charset::SmallPi); } - bool isExponential() const { return isConstantChar(Ion::Charset::Exponential); } - bool isIComplex() const { return isConstantChar(Ion::Charset::IComplex); } + bool isPi() const { return isConstantCodePoint(KDCodePointGreekSmallLetterPi); } + bool isExponential() const { return isConstantCodePoint(KDCodePointScriptSmallE); } + bool isIComplex() const { return isConstantCodePoint(KDCodePointMathematicalBoldSmallI); } + CodePoint codePoint() const; // Comparison int simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted) const override; @@ -49,13 +53,13 @@ private: size_t nodeSize() const override { return sizeof(ConstantNode); } template Evaluation templatedApproximate(Context& context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const; - bool isConstantChar(char c) const { const char constantName[2] = {c, 0}; return strcmp(m_name, constantName) == 0; } + bool isConstantCodePoint(CodePoint c) const; }; class Constant final : public SymbolAbstract { public: Constant(const ConstantNode * node) : SymbolAbstract(node) {} - static Constant Builder(char name) { return SymbolAbstract::Builder(&name, 1); } + static Constant Builder(CodePoint c); // Constant properties bool isPi() const { return node()->isPi(); } diff --git a/poincare/include/poincare/integral_layout.h b/poincare/include/poincare/integral_layout.h index 30b8901cf..9b33b2c40 100644 --- a/poincare/include/poincare/integral_layout.h +++ b/poincare/include/poincare/integral_layout.h @@ -22,7 +22,7 @@ public: void deleteBeforeCursor(LayoutCursor * cursor) override; int serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const override; LayoutNode * layoutToPointWhenInserting() override { return lowerBoundLayout(); } - char XNTChar() const override { return 'x'; } + CodePoint XNTCodePoint() const override { return 'x'; } // TreeNode size_t size() const override { return sizeof(IntegralLayoutNode); } diff --git a/poincare/include/poincare/layout.h b/poincare/include/poincare/layout.h index 306b93355..7cffc3ded 100644 --- a/poincare/include/poincare/layout.h +++ b/poincare/include/poincare/layout.h @@ -54,7 +54,7 @@ public: int leftCollapsingAbsorbingChildIndex() const { return const_cast(this)->node()->leftCollapsingAbsorbingChildIndex(); } int rightCollapsingAbsorbingChildIndex() const { return const_cast(this)->node()->rightCollapsingAbsorbingChildIndex(); } bool hasText() { return node()->hasText(); } - char XNTChar() const { return const_cast(this)->node()->XNTChar(); } + CodePoint XNTCodePoint() const { return const_cast(this)->node()->XNTCodePoint(); } // Layout modification void deleteBeforeCursor(LayoutCursor * cursor) { return node()->deleteBeforeCursor(cursor); } diff --git a/poincare/include/poincare/layout_helper.h b/poincare/include/poincare/layout_helper.h index d9b3a37c8..92a2036f8 100644 --- a/poincare/include/poincare/layout_helper.h +++ b/poincare/include/poincare/layout_helper.h @@ -15,6 +15,7 @@ namespace LayoutHelper { /* Create special layouts */ Layout Parentheses(Layout layout, bool cloneLayout); HorizontalLayout String(const char * buffer, int bufferLen, const KDFont * font = KDFont::LargeFont); + HorizontalLayout CodePointString(const CodePoint * buffer, int bufferLen, const KDFont * font = KDFont::LargeFont); Layout Logarithm(Layout argument, Layout index); }; diff --git a/poincare/include/poincare/layout_node.h b/poincare/include/poincare/layout_node.h index 37c8899f9..d73188a7d 100644 --- a/poincare/include/poincare/layout_node.h +++ b/poincare/include/poincare/layout_node.h @@ -3,7 +3,6 @@ #include #include -#include namespace Poincare { @@ -106,9 +105,9 @@ public: virtual bool isMatrix() const { return false; } virtual bool isCodePoint() const { return false; } virtual bool hasUpperLeftIndex() const { return false; } - virtual char XNTChar() const { + virtual CodePoint XNTCodePoint() const { LayoutNode * p = parent(); - return p == nullptr ? Ion::Charset::Empty : p->XNTChar(); + return p == nullptr ? KDCodePointNull : p->XNTCodePoint(); } virtual bool willAddChildAtIndex(LayoutNode * l, int * index, int * currentNumberOfChildren, LayoutCursor * cursor) { return true; } diff --git a/poincare/include/poincare/print_float.h b/poincare/include/poincare/print_float.h index 7509878b0..26dfb0a2d 100644 --- a/poincare/include/poincare/print_float.h +++ b/poincare/include/poincare/print_float.h @@ -47,7 +47,7 @@ namespace PrintFloat { template int convertFloatToText(T d, char * buffer, int bufferSize, int numberOfSignificantDigits, Preferences::PrintFloatMode mode, bool allowRounding = true); template - static int convertFloatToTextPrivate(T f, char * buffer, int numberOfSignificantDigits, Preferences::PrintFloatMode mode, int * numberOfRemovedZeros); + static int convertFloatToTextPrivate(T f, char * buffer, int bufferSize, int numberOfSignificantDigits, Preferences::PrintFloatMode mode, int * numberOfRemovedZeros); } } diff --git a/poincare/include/poincare/sequence_layout.h b/poincare/include/poincare/sequence_layout.h index 59ef8d34d..28d2a48d5 100644 --- a/poincare/include/poincare/sequence_layout.h +++ b/poincare/include/poincare/sequence_layout.h @@ -20,7 +20,7 @@ public: void moveCursorDown(LayoutCursor * cursor, bool * shouldRecomputeLayout, bool equivalentPositionVisited = false) override; void deleteBeforeCursor(LayoutCursor * cursor) override; LayoutNode * layoutToPointWhenInserting() override { return lowerBoundLayout(); } - char XNTChar() const override { return 'n'; } + CodePoint XNTCodePoint() const override { return 'n'; } // TreeNode int numberOfChildren() const override { return 4; } diff --git a/poincare/include/poincare/serialization_helper.h b/poincare/include/poincare/serialization_helper.h index 6d2a0de39..e0ed71193 100644 --- a/poincare/include/poincare/serialization_helper.h +++ b/poincare/include/poincare/serialization_helper.h @@ -30,6 +30,7 @@ namespace SerializationHelper { // Write one char in a buffer int Char(char * buffer, int bufferSize, char c); // Write one code point in a buffer + constexpr int MaxSerializedCodePointSize = CodePoint::MaxCodePointCharLength + 1; // Null-terminating char int CodePoint(char * buffer, int bufferSize, CodePoint c); }; diff --git a/poincare/include/poincare/square_root.h b/poincare/include/poincare/square_root.h index 4ea8737d1..0cc02f5d0 100644 --- a/poincare/include/poincare/square_root.h +++ b/poincare/include/poincare/square_root.h @@ -4,7 +4,6 @@ #include #include #include -#include namespace Poincare { @@ -42,13 +41,8 @@ class SquareRoot final : public Expression { public: SquareRoot(const SquareRootNode * n) : Expression(n) {} static SquareRoot Builder(Expression child) { return TreeHandle::FixedArityBuilder(&child, 1); } - - static_assert('\x91' == Ion::Charset::Root, "Charset error"); - static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("\x91", 1, &UntypedBuilderOneChild); - + static constexpr Expression::FunctionHelper s_functionHelper = Expression::FunctionHelper("√", 1, &UntypedBuilderOneChild); Expression shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target); -private: - static const char k_name[2]; }; } diff --git a/poincare/include/poincare/symbol.h b/poincare/include/poincare/symbol.h index 3c4c93ec4..e7715e65e 100644 --- a/poincare/include/poincare/symbol.h +++ b/poincare/include/poincare/symbol.h @@ -61,7 +61,7 @@ public: enum SpecialSymbols : char { /* We can use characters from 1 to 31 as they do not correspond to usual * characters but events as 'end of text', 'backspace'... */ - UnknownX = 1, + UnknownX = 1 //TODO LEA make sure there is no collision with the code points }; Symbol(const SymbolNode * node) : SymbolAbstract(node) {} static Symbol Builder(const char * name, int length) { return SymbolAbstract::Builder(name, length); } diff --git a/poincare/include/poincare/trigonometry_cheat_table.h b/poincare/include/poincare/trigonometry_cheat_table.h index eb81172d5..0ae4f678a 100644 --- a/poincare/include/poincare/trigonometry_cheat_table.h +++ b/poincare/include/poincare/trigonometry_cheat_table.h @@ -7,7 +7,7 @@ namespace Poincare { /* We use the cheat table to look for known simplifications (e.g. cos(0)=1, - * cos(Pi/2)=1...). For each entry of the table, we store its expression and + * cos(Ï€/2)=1...). For each entry of the table, we store its expression and * its float approximation in order to quickly scan the table looking for our * input approximation. If one entry matches the float approximation, we then * check that the actual expression of our input is equivalent to the table diff --git a/poincare/src/complex_argument.cpp b/poincare/src/complex_argument.cpp index eb6983ec3..b803c66eb 100644 --- a/poincare/src/complex_argument.cpp +++ b/poincare/src/complex_argument.cpp @@ -57,7 +57,7 @@ Expression ComplexArgument::shallowReduce(Context & context, Preferences::Comple return result; } else if (!std::isnan(app) && app <= -Expression::Epsilon()) { // arg(x) = Pi if x < 0 - Expression result = Constant::Builder(Ion::Charset::SmallPi); + Expression result = Constant::Builder(KDCodePointGreekSmallLetterPi); replaceWithInPlace(result); return result; } diff --git a/poincare/src/complex_cartesian.cpp b/poincare/src/complex_cartesian.cpp index ce0416d42..70b027169 100644 --- a/poincare/src/complex_cartesian.cpp +++ b/poincare/src/complex_cartesian.cpp @@ -152,7 +152,7 @@ Expression ComplexCartesian::argument(Context & context, Preferences::ComplexFor } // Then, compute sign(b) * Pi/2 - arctan(a/b) Expression signb = SignFunction::Builder(b); - Expression signbPi2 = Multiplication::Builder(Rational::Builder(1,2), signb, Constant::Builder(Ion::Charset::SmallPi)); + Expression signbPi2 = Multiplication::Builder(Rational::Builder(1,2), signb, Constant::Builder(KDCodePointGreekSmallLetterPi)); signb.shallowReduce(context, complexFormat, angleUnit, target); Expression sub = Subtraction::Builder(signbPi2, arcTangent); signbPi2.shallowReduce(context, complexFormat, angleUnit, target); @@ -163,7 +163,7 @@ Expression ComplexCartesian::argument(Context & context, Preferences::ComplexFor Expression signa = SignFunction::Builder(a).shallowReduce(context, complexFormat, angleUnit, target); Subtraction sub = Subtraction::Builder(Rational::Builder(1), signa); signa.shallowReduce(context, complexFormat, angleUnit, target); - Multiplication mul = Multiplication::Builder(Rational::Builder(1,2), Constant::Builder(Ion::Charset::SmallPi), sub); + Multiplication mul = Multiplication::Builder(Rational::Builder(1,2), Constant::Builder(KDCodePointGreekSmallLetterPi), sub); sub.shallowReduce(context, complexFormat, angleUnit, target); return mul; } @@ -330,7 +330,7 @@ ComplexCartesian ComplexCartesian::power(ComplexCartesian & other, Context & con rclone.shallowReduce(context, complexFormat, angleUnit, target); Expression thmuld = Multiplication::Builder(Rational::Builder(-1), thclone, d.clone()); thclone.shallowReduce(context, complexFormat, angleUnit, target); - Expression exp = Power::Builder(Constant::Builder(Ion::Charset::Exponential), thmuld); + Expression exp = Power::Builder(Constant::Builder(KDCodePointScriptSmallE), thmuld); thmuld.shallowReduce(context, complexFormat, angleUnit, target); Multiplication norm = Multiplication::Builder(rpowc, exp); rpowc.shallowReduce(context, complexFormat, angleUnit, target); diff --git a/poincare/src/constant.cpp b/poincare/src/constant.cpp index 2f971598c..788cf5135 100644 --- a/poincare/src/constant.cpp +++ b/poincare/src/constant.cpp @@ -4,10 +4,13 @@ #include #include #include +#include +#include #include #include #include #include +#include namespace Poincare { @@ -26,13 +29,13 @@ bool ConstantNode::isReal(Context & context) const { return !isIComplex(); } -int rankOfConstant(char c) { +int rankOfConstant(CodePoint c) { switch (c) { - case Ion::Charset::IComplex: + case KDCodePointMathematicalBoldSmallI : return 0; - case Ion::Charset::SmallPi: + case KDCodePointGreekSmallLetterPi : return 1; - case Ion::Charset::Exponential: + case KDCodePointScriptSmallE : return 2; default: assert(false); @@ -40,12 +43,19 @@ int rankOfConstant(char c) { } } +CodePoint ConstantNode::codePoint() const { + UTF8Decoder decoder = UTF8Decoder(m_name); + CodePoint result = decoder.nextCodePoint(); + assert(decoder.nextCodePoint() == KDCodePointNull); + return result; +} + int ConstantNode::simplificationOrderSameType(const ExpressionNode * e, bool ascending, bool canBeInterrupted) const { if (!ascending) { return e->simplificationOrderSameType(this, true, canBeInterrupted); } assert(type() == e->type()); - return (rankOfConstant(name()[0]) - rankOfConstant(static_cast(e)->name()[0])); + return rankOfConstant(codePoint()) - rankOfConstant(static_cast(e)->codePoint()); } Layout ConstantNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { @@ -62,7 +72,6 @@ int ConstantNode::serialize(char * buffer, int bufferSize, Preferences::PrintFlo template Evaluation ConstantNode::templatedApproximate(Context& context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) const { if (isIComplex()) { - assert(m_name[1] == 0); return Complex::Builder(0.0, 1.0); } if (isPi()) { @@ -76,11 +85,26 @@ Expression ConstantNode::shallowReduce(Context & context, Preferences::ComplexFo return Constant(this).shallowReduce(context, complexFormat, angleUnit, target); } +bool ConstantNode::isConstantCodePoint(CodePoint c) const { + UTF8Decoder decoder(m_name); + bool result = (decoder.nextCodePoint() == c); + assert(decoder.nextCodePoint() == KDCodePointNull); + return result; +} + +Constant Constant::Builder(CodePoint c) { + constexpr int bufferSize = SerializationHelper::MaxSerializedCodePointSize; + char buffer[bufferSize]; + size_t codePointSize = SerializationHelper::CodePoint(buffer, bufferSize, c); + return SymbolAbstract::Builder(buffer, codePointSize); +} + Expression Constant::shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target) { Expression result; - if (complexFormat == Preferences::ComplexFormat::Real && isIComplex()) { + bool isI = isIComplex(); + if (complexFormat == Preferences::ComplexFormat::Real && isI) { result = Unreal::Builder(); - } else if (target == ExpressionNode::ReductionTarget::User && isIComplex()) { + } else if (target == ExpressionNode::ReductionTarget::User && isI) { result = ComplexCartesian::Builder(Rational::Builder(0), Rational::Builder(1)); } if (!result.isUninitialized()) { diff --git a/poincare/src/decimal.cpp b/poincare/src/decimal.cpp index 1e88397e9..fcad08c9a 100644 --- a/poincare/src/decimal.cpp +++ b/poincare/src/decimal.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -176,7 +177,8 @@ int DecimalNode::convertToText(char * buffer, int bufferSize, Preferences::Print return currentChar; } if (currentChar >= bufferSize-1) { return bufferSize-1; } - buffer[currentChar++] = Ion::Charset::Exponent; + currentChar += SerializationHelper::CodePoint(buffer + currentChar, bufferSize - currentChar, KDCodePointScriptSmallE); + if (currentChar >= bufferSize-1) { return bufferSize-1; } currentChar += Integer(exponent).serialize(buffer+currentChar, bufferSize-currentChar); return currentChar; } diff --git a/poincare/src/empty_expression.cpp b/poincare/src/empty_expression.cpp index 882d6311f..88bd12e1f 100644 --- a/poincare/src/empty_expression.cpp +++ b/poincare/src/empty_expression.cpp @@ -2,12 +2,11 @@ #include #include #include -#include namespace Poincare { int EmptyExpressionNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - return SerializationHelper::Char(buffer, bufferSize, Ion::Charset::Empty); + return SerializationHelper::CodePoint(buffer, bufferSize, KDCodePointEmpty); } Layout EmptyExpressionNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index ec19edd2f..b08f8e3ff 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -342,9 +342,10 @@ void Expression::SetEncounteredComplex(bool encounterComplex) { } Preferences::ComplexFormat Expression::UpdatedComplexFormatWithTextInput(Preferences::ComplexFormat complexFormat, const char * textInput) { - if (complexFormat == Preferences::ComplexFormat::Real && strchr(textInput, Ion::Charset::IComplex) != nullptr) { + /* TODO LEA if (complexFormat == Preferences::ComplexFormat::Real && strchr(textInput, KDCodePointMathematicalBoldSmallI) != nullptr) { return Preferences::ComplexFormat::Cartesian; } + */ return complexFormat; } @@ -519,12 +520,12 @@ Expression Expression::ExpressionWithoutSymbols(Expression e, Context & context) Expression Expression::radianToDegree() { // e*180/Pi - return Multiplication::Builder(*this, Rational::Builder(180), Power::Builder(Constant::Builder(Ion::Charset::SmallPi), Rational::Builder(-1))); + return Multiplication::Builder(*this, Rational::Builder(180), Power::Builder(Constant::Builder(KDCodePointGreekSmallLetterPi), Rational::Builder(-1))); } Expression Expression::degreeToRadian() { // e*Pi/180 - return Multiplication::Builder(*this, Rational::Builder(1, 180), Constant::Builder(Ion::Charset::SmallPi)); + return Multiplication::Builder(*this, Rational::Builder(1, 180), Constant::Builder(KDCodePointGreekSmallLetterPi)); } Expression Expression::reduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit) { @@ -626,9 +627,9 @@ Expression Expression::CreateComplexExpression(Expression ra, Expression tb, Pre } if (!isZeroTb) { if (isOneTb) { - imag = Constant::Builder(Ion::Charset::IComplex); + imag = Constant::Builder(KDCodePointMathematicalBoldSmallI); } else { - imag = Multiplication::Builder(tb , Constant::Builder(Ion::Charset::IComplex)); + imag = Multiplication::Builder(tb , Constant::Builder(KDCodePointMathematicalBoldSmallI)); } } if (imag.isUninitialized()) { @@ -657,14 +658,14 @@ Expression Expression::CreateComplexExpression(Expression ra, Expression tb, Pre if (!isZeroRa && !isZeroTb) { Expression arg; if (isOneTb) { - arg = Constant::Builder(Ion::Charset::IComplex); + arg = Constant::Builder(KDCodePointMathematicalBoldSmallI); } else { - arg = Multiplication::Builder(tb, Constant::Builder(Ion::Charset::IComplex)); + arg = Multiplication::Builder(tb, Constant::Builder(KDCodePointMathematicalBoldSmallI)); } if (isNegativeTb) { arg = Opposite::Builder(arg); } - exp = Power::Builder(Constant::Builder(Ion::Charset::Exponential), arg); + exp = Power::Builder(Constant::Builder(KDCodePointScriptSmallE), arg); } if (exp.isUninitialized()) { return norm; diff --git a/poincare/src/expression_debug.cpp b/poincare/src/expression_debug.cpp index f99d9523f..34eaa839d 100644 --- a/poincare/src/expression_debug.cpp +++ b/poincare/src/expression_debug.cpp @@ -213,14 +213,16 @@ void print_expression(const Expression e, int indentationLevel) { break; case ExpressionNode::Type::Symbol: std::cout << "Symbol("; - switch (static_cast(e).name()) { - case Ion::Charset::SmallPi: + UTF8Decoder decoder(static_cast(e).name()); + CodePoint firstCodePoint = decoder.nextCodePoint(); + switch (firstCodePoint) { + case KDCodePointGreekSmallLetterPi: std::cout << "PI"; break; - case Ion::Charset::IComplex: + case KDCodePointMathematicalBoldSmallI: std::cout << "i"; break; - case Ion::Charset::Exponential: + case KDCodePointScriptSmallE: std::cout << "e"; break; default: diff --git a/poincare/src/fraction_layout.cpp b/poincare/src/fraction_layout.cpp index 2b615d11a..4443c13fd 100644 --- a/poincare/src/fraction_layout.cpp +++ b/poincare/src/fraction_layout.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -135,7 +134,7 @@ int FractionLayoutNode::serialize(char * buffer, int bufferSize, Preferences::Pr // Add a multiplication if omitted. if (idxInParent > 0 && p->isHorizontal() && p->childAtIndex(idxInParent - 1)->canBeOmittedMultiplicationLeftFactor()) { - buffer[numberOfChar++] = Ion::Charset::MiddleDot; + numberOfChar+= SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, KDCodePointMiddleDot); if (numberOfChar >= bufferSize-1) { return bufferSize-1;} } @@ -159,7 +158,7 @@ int FractionLayoutNode::serialize(char * buffer, int bufferSize, Preferences::Pr // Add a multiplication if omitted. if (idxInParent >= 0 && idxInParent < (p->numberOfChildren() - 1) && p->isHorizontal() && p->childAtIndex(idxInParent + 1)->canBeOmittedMultiplicationRightFactor()) { - buffer[numberOfChar++] = Ion::Charset::MiddleDot; + numberOfChar+= SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, KDCodePointMiddleDot); if (numberOfChar >= bufferSize-1) { return bufferSize-1;} } diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index a5f9346e5..9c29bd146 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include namespace Poincare { @@ -75,7 +74,7 @@ void LayoutCursor::move(MoveDirection direction, bool * shouldRecomputeLayout) { void LayoutCursor::addEmptyExponentialLayout() { EmptyLayout emptyLayout = EmptyLayout::Builder(); HorizontalLayout sibling = HorizontalLayout::Builder( - CodePointLayout::Builder(Ion::Charset::Exponential), + CodePointLayout::Builder(KDCodePointScriptSmallE), VerticalOffsetLayout::Builder(emptyLayout, VerticalOffsetLayoutNode::Type::Superscript)); m_layout.addSibling(this, sibling, false); m_layout = emptyLayout; @@ -114,7 +113,7 @@ void LayoutCursor::addEmptySquarePowerLayout() { void LayoutCursor::addEmptyTenPowerLayout() { EmptyLayout emptyLayout = EmptyLayout::Builder(); HorizontalLayout sibling = HorizontalLayout::Builder( - CodePointLayout::Builder(Ion::Charset::MiddleDot), + CodePointLayout::Builder(KDCodePointMiddleDot), CodePointLayout::Builder('1'), CodePointLayout::Builder('0'), VerticalOffsetLayout::Builder( @@ -133,10 +132,12 @@ void LayoutCursor::addFractionLayoutAndCollapseSiblings() { } void LayoutCursor::addXNTCodePointLayout() { - m_layout.addSibling(this, CodePointLayout::Builder(m_layout.XNTChar()), true); + m_layout.addSibling(this, CodePointLayout::Builder(m_layout.XNTCodePoint()), true); } void LayoutCursor::insertText(const char * text) { +// TODO LEA +#if 0 int textLength = strlen(text); if (textLength <= 0) { return; @@ -144,12 +145,12 @@ void LayoutCursor::insertText(const char * text) { Layout newChild; Layout pointedChild; for (int i = 0; i < textLength; i++) { - if (text[i] == Ion::Charset::Empty) { + if (text[i] == //TODO Ion::Charset::Empty) { continue; } - if (text[i] == Ion::Charset::MultiplicationSign) { - newChild = CodePointLayout::Builder(Ion::Charset::MiddleDot); - } else if (text[i] == '(') { + if (text[i] == //TODO Ion::Charset::MultiplicationSign) { + newChild = CodePointLayout::Builder(KDCodePointMiddleDot); + } else*/ if (text[i] == '(') { newChild = LeftParenthesisLayout::Builder(); if (pointedChild.isUninitialized()) { pointedChild = newChild; @@ -175,6 +176,7 @@ void LayoutCursor::insertText(const char * text) { m_layout = pointedChild; m_position = Position::Right; } +#endif } void LayoutCursor::addLayoutAndMoveCursor(Layout l) { diff --git a/poincare/src/layout_helper.cpp b/poincare/src/layout_helper.cpp index d713ec1eb..9555eab3e 100644 --- a/poincare/src/layout_helper.cpp +++ b/poincare/src/layout_helper.cpp @@ -54,6 +54,16 @@ Layout LayoutHelper::Parentheses(Layout layout, bool cloneLayout) { } HorizontalLayout LayoutHelper::String(const char * buffer, int bufferLen, const KDFont * font) { + assert(bufferLen > 0); + HorizontalLayout resultLayout = HorizontalLayout::Builder(); + /* TODO LEA */ + for (int i = 0; i < bufferLen; i++) { + resultLayout.addChildAtIndex(CodePointLayout::Builder(buffer[i], font), i, i, nullptr); + } + return resultLayout; +} + +HorizontalLayout LayoutHelper::CodePointString(const CodePoint * buffer, int bufferLen, const KDFont * font) { assert(bufferLen > 0); HorizontalLayout resultLayout = HorizontalLayout::Builder(); for (int i = 0; i < bufferLen; i++) { diff --git a/poincare/src/logarithm.cpp b/poincare/src/logarithm.cpp index 4301d3c07..1e911c6e3 100644 --- a/poincare/src/logarithm.cpp +++ b/poincare/src/logarithm.cpp @@ -341,7 +341,7 @@ Expression Logarithm::splitLogarithmInteger(Integer i, bool isDenominator, Conte Expression Logarithm::shallowBeautify() { assert(numberOfChildren() == 2); - Constant e = Constant::Builder(Ion::Charset::Exponential); + Constant e = Constant::Builder(KDCodePointScriptSmallE); if (childAtIndex(1).isIdenticalTo(e)) { NaperianLogarithm np = NaperianLogarithm::Builder(childAtIndex(0)); replaceWithInPlace(np); diff --git a/poincare/src/multiplication.cpp b/poincare/src/multiplication.cpp index 29aceb262..edc28d20e 100644 --- a/poincare/src/multiplication.cpp +++ b/poincare/src/multiplication.cpp @@ -85,13 +85,17 @@ bool MultiplicationNode::childNeedsParenthesis(const TreeNode * child) const { } Layout MultiplicationNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - const char middleDotString[] = {Ion::Charset::MiddleDot, 0}; - return LayoutHelper::Infix(Multiplication(this), floatDisplayMode, numberOfSignificantDigits, middleDotString); + constexpr int stringMaxSize = CodePoint::MaxCodePointCharLength + 1; + char string[stringMaxSize]; + SerializationHelper::CodePoint(string, stringMaxSize, KDCodePointMiddleDot); + return LayoutHelper::Infix(Multiplication(this), floatDisplayMode, numberOfSignificantDigits, string); } int MultiplicationNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - const char multiplicationString[] = {Ion::Charset::MultiplicationSign, 0}; - return SerializationHelper::Infix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, multiplicationString); + constexpr int stringMaxSize = CodePoint::MaxCodePointCharLength + 1; + char string[stringMaxSize]; + SerializationHelper::CodePoint(string, stringMaxSize, KDCodePointMultiplicationSign); + return SerializationHelper::Infix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, string); } Expression MultiplicationNode::shallowReduce(Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ReductionTarget target) { diff --git a/poincare/src/naperian_logarithm.cpp b/poincare/src/naperian_logarithm.cpp index 29ea6cb9b..56fb53063 100644 --- a/poincare/src/naperian_logarithm.cpp +++ b/poincare/src/naperian_logarithm.cpp @@ -35,7 +35,7 @@ Expression NaperianLogarithm::shallowReduce(Context & context, Preferences::Comp return SimplificationHelper::Map(*this, context, angleUnit); } #endif - Logarithm l = Logarithm::Builder(childAtIndex(0), Constant::Builder(Ion::Charset::Exponential)); + Logarithm l = Logarithm::Builder(childAtIndex(0), Constant::Builder(KDCodePointScriptSmallE)); replaceWithInPlace(l); return l.shallowReduce(context, complexFormat, angleUnit, target); } diff --git a/poincare/src/nth_root_layout.cpp b/poincare/src/nth_root_layout.cpp index 4e7dd4573..898641318 100644 --- a/poincare/src/nth_root_layout.cpp +++ b/poincare/src/nth_root_layout.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include namespace Poincare { @@ -169,7 +168,7 @@ int NthRootLayoutNode::serialize(char * buffer, int bufferSize, Preferences::Pri buffer[bufferSize-1] = 0; int numberOfChar = 0; - buffer[numberOfChar++] = Ion::Charset::Root; + numberOfChar += SerializationHelper::CodePoint(buffer + numberOfChar, bufferSize - numberOfChar, KDCodePointSquareRoot); if (numberOfChar >= bufferSize-1) { return bufferSize-1; } diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index 9b7d440f7..f5143f235 100644 --- a/poincare/src/parsing/tokenizer.cpp +++ b/poincare/src/parsing/tokenizer.cpp @@ -1,57 +1,56 @@ #include "tokenizer.h" -#include #include #include namespace Poincare { -static inline bool isLetter(const char c) { +static inline bool isLetter(const CodePoint c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'); } -static inline bool isDigit(const char c) { +static inline bool isDigit(const CodePoint c) { return '0' <= c && c <= '9'; } -const char Tokenizer::nextChar(PopTest popTest, char context, bool * testResult) { - // Beware of chars spaning over more than one byte: use the UTF8Decoder. +const CodePoint Tokenizer::nextCodePoint(PopTest popTest, CodePoint context, bool * testResult) { UTF8Decoder decoder(m_text); CodePoint firstCodePoint = decoder.nextCodePoint(); - int numberOfBytesForChar = 1; + size_t numberOfBytesForCodePoint = UTF8Decoder::CharSizeOfCodePoint(firstCodePoint); if (firstCodePoint != KDCodePointNull) { CodePoint codePoint = decoder.nextCodePoint(); while (codePoint.isCombining()) { - numberOfBytesForChar++; + numberOfBytesForCodePoint = UTF8Decoder::CharSizeOfCodePoint(codePoint); codePoint = decoder.nextCodePoint(); } } - char c = *m_text; // TODO handle combined chars? - bool shouldPop = popTest(c, context); + // TODO handle combined code points? + bool shouldPop = popTest(firstCodePoint, context); if (testResult != nullptr) { *testResult = shouldPop; } if (shouldPop) { - m_text+= numberOfBytesForChar; + m_text+= numberOfBytesForCodePoint; } - return c; + return firstCodePoint; } -const char Tokenizer::popChar() { - return nextChar([](char c, char context) { return true; }); - // m_text now points to the start of the character after the returned char. +const CodePoint Tokenizer::popCodePoint() { + return nextCodePoint([](CodePoint c, CodePoint context) { return true; }); + /* m_text now points to the start of the first non combining code point after + * the returned code point. */ } -bool Tokenizer::canPopChar (const char c) { +bool Tokenizer::canPopCodePoint(const CodePoint c) { bool didPop = false; - nextChar([](char nextC, char context) { return nextC == context; }, c, &didPop); + nextCodePoint([](CodePoint nextC, CodePoint context) { return nextC == context; }, c, &didPop); return didPop; } -size_t Tokenizer::popWhile(PopTest popTest, char context) { +size_t Tokenizer::popWhile(PopTest popTest, CodePoint context) { size_t length = 0; bool didPop = true; while (didPop) { - nextChar(popTest, context, &didPop); + nextCodePoint(popTest, context, &didPop); if (didPop) { length++; } @@ -60,11 +59,11 @@ size_t Tokenizer::popWhile(PopTest popTest, char context) { } size_t Tokenizer::popIdentifier() { - return popWhile([](char c, char context) { return isLetter(c) || isDigit(c) || c == context; }, '_'); + return popWhile([](CodePoint c, CodePoint context) { return isLetter(c) || isDigit(c) || c == context; }, '_'); } size_t Tokenizer::popDigits() { - return popWhile([](char c, char context) { return isDigit(c); }); + return popWhile([](CodePoint c, CodePoint context) { return isDigit(c); }); } Token Tokenizer::popNumber() { @@ -75,7 +74,7 @@ Token Tokenizer::popNumber() { size_t fractionalPartLength = 0; assert(integralPartLength > 0 || *m_text == '.'); - if (canPopChar('.')) { + if (canPopCodePoint('.')) { fractionalPartText = m_text; fractionalPartLength = popDigits(); } @@ -87,8 +86,8 @@ Token Tokenizer::popNumber() { const char * exponentPartText = m_text; size_t exponentPartLength = 0; bool exponentIsNegative = false; - if (canPopChar(Ion::Charset::Exponent)) { - exponentIsNegative = canPopChar('-'); + if (canPopCodePoint(KDCodePointLatinLetterSmallCapitalE)) { + exponentIsNegative = canPopCodePoint('-'); exponentPartText = m_text; exponentPartLength = popDigits(); if (exponentPartLength == 0) { @@ -103,30 +102,30 @@ Token Tokenizer::popNumber() { Token Tokenizer::popToken() { // Skip whitespaces - while (canPopChar(' ')) {} + while (canPopCodePoint(' ')) {} - /* Save for later use (since m_text is altered by popChar, popNumber, + /* Save for later use (since m_text is altered by popCodePoint, popNumber, * popIdentifier). */ const char * start = m_text; - /* If the next char is the start of a number, we do not want to pop it because - * popNumber needs this char. */ - bool nextCharIsNeitherDotNorDigit = true; - const char currentChar = nextChar([](char c, char context) { return c != context && !isDigit(c); }, '.', &nextCharIsNeitherDotNorDigit); + /* If the next code point is the start of a number, we do not want to pop it + * because popNumber needs this code point. */ + bool nextCodePointIsNeitherDotNorDigit = true; + const CodePoint c = nextCodePoint([](CodePoint cp, CodePoint context) { return cp != context && !isDigit(cp); }, '.', &nextCodePointIsNeitherDotNorDigit); - // According to currentChar, recognize the Token::Type. - if (!nextCharIsNeitherDotNorDigit) { + // According to c, recognize the Token::Type. + if (!nextCodePointIsNeitherDotNorDigit) { return popNumber(); } - if (isLetter(currentChar)) { + if (isLetter(c)) { Token result(Token::Identifier); - result.setString(start, 1 + popIdentifier()); // We already popped 1 char + result.setString(start, 1 + popIdentifier()); // We already popped 1 code point return result; } - if ('(' <= currentChar && currentChar <= '/') { - /* Those characters form a contiguous range in the ascii character set, we - * make searching faster with this lookup table. */ - constexpr Token::Type typeForChar[] = { + if ('(' <= c && c <= '/') { + /* Those code points form a contiguous range in the utf-8 code points set, + * we can thus search faster with this lookup table. */ + constexpr Token::Type typeForCodePoint[] = { Token::LeftParenthesis, Token::RightParenthesis, Token::Times, @@ -136,58 +135,61 @@ Token Tokenizer::popToken() { Token::Undefined, Token::Slash }; - /* The dot character is the second last of that range, but it is matched + /* The dot code point is the second last of that range, but it is matched * before (with popNumber). */ - assert(currentChar != '.'); - return Token(typeForChar[currentChar - '(']); + assert(c != '.'); + return Token(typeForCodePoint[c - '(']); } - if (currentChar == Ion::Charset::MultiplicationSign || currentChar == Ion::Charset::MiddleDot) { + if (c == KDCodePointMultiplicationSign || c == KDCodePointMiddleDot) { return Token(Token::Times); } - if (currentChar == '^') { + if (c == '^') { return Token(Token::Caret); } - if (currentChar == Ion::Charset::LeftSuperscript) { + if (c == KDCodePointLeftSuperscript) { return Token(Token::LeftSuperscript); } - if (currentChar == Ion::Charset::RightSuperscript) { + if (c == KDCodePointRightSuperscript) { return Token(Token::RightSuperscript); } - if (currentChar == '!') { + if (c == '!') { return Token(Token::Bang); } - if (currentChar == '=') { + if (c == '=') { return Token(Token::Equal); } - if (currentChar == '[') { + if (c == '[') { return Token(Token::LeftBracket); } - if (currentChar == ']') { + if (c == ']') { return Token(Token::RightBracket); } - if (currentChar == '{') { + if (c == '{') { return Token(Token::LeftBrace); } - if (currentChar == '}') { + if (c == '}') { return Token(Token::RightBrace); } - if (currentChar == Ion::Charset::SmallPi || currentChar == Ion::Charset::IComplex || currentChar == Ion::Charset::Exponential) { + if (c == KDCodePointGreekSmallLetterPi + || c == KDCodePointMathematicalBoldSmallI + || c == KDCodePointScriptSmallE) + { Token result(Token::Constant); result.setString(start, 1); return result; } - if (currentChar == Ion::Charset::Root) { + if (c == KDCodePointSquareRoot) { Token result(Token::Identifier); result.setString(start, 1); return result; } - if (currentChar == Ion::Charset::Empty) { + if (c == KDCodePointEmpty) { return Token(Token::Empty); } - if (currentChar == Ion::Charset::Sto) { + if (c == KDCodePointRightwardsArrow) { return Token(Token::Store); } - if (currentChar == 0) { + if (c == 0) { return Token(Token::EndOfStream); } return Token(Token::Undefined); diff --git a/poincare/src/parsing/tokenizer.h b/poincare/src/parsing/tokenizer.h index b75d6e863..ef475f6b0 100644 --- a/poincare/src/parsing/tokenizer.h +++ b/poincare/src/parsing/tokenizer.h @@ -16,11 +16,11 @@ public: Tokenizer(const char * text) : m_text(text) {} Token popToken(); private: - typedef bool (*PopTest)(char c, char context); - const char nextChar(PopTest popTest, char context = 0, bool * testResult = nullptr); - const char popChar(); - bool canPopChar(const char c); - size_t popWhile(PopTest popTest, char context = 0); + typedef bool (*PopTest)(CodePoint c, CodePoint context); + const CodePoint nextCodePoint(PopTest popTest, CodePoint context = KDCodePointNull, bool * testResult = nullptr); + const CodePoint popCodePoint(); + bool canPopCodePoint(const CodePoint c); + size_t popWhile(PopTest popTest, CodePoint context = KDCodePointNull); size_t popDigits(); size_t popIdentifier(); Token popNumber(); diff --git a/poincare/src/power.cpp b/poincare/src/power.cpp index 91302c26b..f19b4954b 100644 --- a/poincare/src/power.cpp +++ b/poincare/src/power.cpp @@ -521,7 +521,7 @@ Expression Power::shallowReduce(Context & context, Preferences::ComplexFormat co Multiplication m1 = Multiplication::Builder(); replaceWithInPlace(m1); // Multiply m1 by i complex - Constant i = Constant::Builder(Ion::Charset::IComplex); + Constant i = Constant::Builder(KDCodePointMathematicalBoldSmallI); m1.addChildAtIndexInPlace(i, 0, 0); i.shallowReduce(context, complexFormat, angleUnit, target); m1.addChildAtIndexInPlace(*this, 1, 1); @@ -1136,17 +1136,17 @@ Expression Power::equivalentExpressionUsingStandardExpression() const { Expression Power::CreateComplexExponent(const Expression & r, Context & context, Preferences::ComplexFormat complexFormat, Preferences::AngleUnit angleUnit, ExpressionNode::ReductionTarget target) { // Returns e^(i*pi*r) - const Constant exp = Constant::Builder(Ion::Charset::Exponential); - Constant iComplex = Constant::Builder(Ion::Charset::IComplex); - const Constant pi = Constant::Builder(Ion::Charset::SmallPi); + const Constant exp = Constant::Builder(KDCodePointScriptSmallE); + Constant iComplex = Constant::Builder(KDCodePointMathematicalBoldSmallI); + const Constant pi = Constant::Builder(KDCodePointGreekSmallLetterPi); Multiplication mExp = Multiplication::Builder(iComplex, pi, r.clone()); iComplex.shallowReduce(context, complexFormat, angleUnit, target); Power p = Power::Builder(exp, mExp); mExp.shallowReduce(context, complexFormat, angleUnit, target); return p; #if 0 - const Constant iComplex = Constant::Builder(Ion::Charset::IComplex); - const Constant pi = Constant::Builder(Ion::Charset::SmallPi); + const Constant iComplex = Constant::Builder(KDCodePointMathematicalBoldSmallI); + const Constant pi = Constant::Builder(KDCodePointGreekSmallLetterPi); Expression op = Multiplication::Builder(pi, r).shallowReduce(context, complexFormat, angleUnit, false); Cosine cos = Cosine(op).shallowReduce(context, complexFormat, angleUnit, false);; Sine sin = Sine(op).shallowReduce(context, complexFormat, angleUnit, false); diff --git a/poincare/src/print_float.cpp b/poincare/src/print_float.cpp index 10171fe93..af65a5d59 100644 --- a/poincare/src/print_float.cpp +++ b/poincare/src/print_float.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include extern "C" { #include @@ -20,8 +21,9 @@ void PrintFloat::printBase10IntegerWithDecimalMarker(char * buffer, int bufferLe * in first position. When called by convertFloatToText, the buffer length is * always > 0 as we asserted a minimal number of available chars. */ assert(bufferLength > 0 && decimalMarkerPosition != 0); - char tempBuffer[PrintFloat::k_maxFloatBufferLength]; - int intLength = i.serialize(tempBuffer, PrintFloat::k_maxFloatBufferLength); + constexpr int tempBufferSize = PrintFloat::k_maxFloatBufferLength; + char tempBuffer[tempBufferSize]; + int intLength = i.serialize(tempBuffer, tempBufferSize); int firstDigitChar = tempBuffer[0] == '-' ? 1 : 0; for (int k = bufferLength-1; k >= firstDigitChar; k--) { if (k == decimalMarkerPosition) { @@ -46,14 +48,15 @@ int PrintFloat::convertFloatToText(T f, char * buffer, int bufferSize, assert(numberOfSignificantDigits > 0); assert(bufferSize > 0); - char tempBuffer[PrintFloat::k_maxFloatBufferLength]; + constexpr int tempBufferSize = PrintFloat::k_maxFloatBufferLength; + char tempBuffer[tempBufferSize]; int numberOfZerosRemoved = 0; - int requiredLength = convertFloatToTextPrivate(f, tempBuffer, numberOfSignificantDigits, mode, &numberOfZerosRemoved); + int requiredLength = convertFloatToTextPrivate(f, tempBuffer, tempBufferSize, numberOfSignificantDigits, mode, &numberOfZerosRemoved); /* If the required buffer size overflows the buffer size, we first force the * display mode to scientific and decrease the number of significant digits to * fit the buffer size. */ if (mode == Preferences::PrintFloatMode::Decimal && requiredLength >= bufferSize) { - requiredLength = convertFloatToTextPrivate(f, tempBuffer, numberOfSignificantDigits, Preferences::PrintFloatMode::Scientific, &numberOfZerosRemoved); + requiredLength = convertFloatToTextPrivate(f, tempBuffer, tempBufferSize, numberOfSignificantDigits, Preferences::PrintFloatMode::Scientific, &numberOfZerosRemoved); } if (requiredLength >= bufferSize) { /* If the buffer size is still too small and rounding is allowed, we only @@ -65,7 +68,7 @@ int PrintFloat::convertFloatToText(T f, char * buffer, int bufferSize, } int adjustedNumberOfSignificantDigits = numberOfSignificantDigits - numberOfZerosRemoved - requiredLength + bufferSize - 1; adjustedNumberOfSignificantDigits = adjustedNumberOfSignificantDigits < 1 ? 1 : adjustedNumberOfSignificantDigits; - requiredLength = convertFloatToTextPrivate(f, tempBuffer, adjustedNumberOfSignificantDigits, Preferences::PrintFloatMode::Scientific, &numberOfZerosRemoved); + requiredLength = convertFloatToTextPrivate(f, tempBuffer, tempBufferSize, adjustedNumberOfSignificantDigits, Preferences::PrintFloatMode::Scientific, &numberOfZerosRemoved); } requiredLength = requiredLength < bufferSize ? requiredLength : bufferSize-1; strlcpy(buffer, tempBuffer, bufferSize); @@ -73,21 +76,21 @@ int PrintFloat::convertFloatToText(T f, char * buffer, int bufferSize, } template -int PrintFloat::convertFloatToTextPrivate(T f, char * buffer, int numberOfSignificantDigits, Preferences::PrintFloatMode mode, int * numberOfRemovedZeros) { +int PrintFloat::convertFloatToTextPrivate(T f, char * buffer, int bufferSize, int numberOfSignificantDigits, Preferences::PrintFloatMode mode, int * numberOfRemovedZeros) { assert(numberOfSignificantDigits > 0); if (std::isinf(f)) { - assert(Infinity::NameSize()+1 < PrintFloat::k_maxFloatBufferLength); + assert(Infinity::NameSize()+1 < bufferSize); int currentChar = 0; if (f < 0) { buffer[currentChar++] = '-'; } - strlcpy(&buffer[currentChar], Infinity::Name(), PrintFloat::k_maxFloatBufferLength-1); + strlcpy(&buffer[currentChar], Infinity::Name(), bufferSize-1); return currentChar + Infinity::NameSize() - 1; } if (std::isnan(f)) { - assert(Undefined::NameSize() < PrintFloat::k_maxFloatBufferLength); - strlcpy(buffer, Undefined::Name(), PrintFloat::k_maxFloatBufferLength); + assert(Undefined::NameSize() < bufferSize); + strlcpy(buffer, Undefined::Name(), bufferSize); return Undefined::NameSize() - 1; } @@ -180,23 +183,24 @@ int PrintFloat::convertFloatToTextPrivate(T f, char * buffer, int numberOfSignif int numberOfCharsForMantissaWithSign = f >= 0 ? numberOfCharsForMantissaWithoutSign : numberOfCharsForMantissaWithoutSign + 1; // Print mantissa assert(!dividend.isOverflow()); - if (numberOfCharsForMantissaWithSign >= PrintFloat::k_maxFloatBufferLength) { + if (numberOfCharsForMantissaWithSign >= bufferSize) { /* Exception 3: if we are about to overflow the buffer, we escape by * returning a big int. This will be caught by 'convertFloatToText' which * will force displayMode to Scientific. */ assert(mode == Preferences::PrintFloatMode::Decimal); return INT_MAX; } - assert(numberOfCharsForMantissaWithSign < PrintFloat::k_maxFloatBufferLength); + assert(numberOfCharsForMantissaWithSign < bufferSize); PrintFloat::printBase10IntegerWithDecimalMarker(buffer, numberOfCharsForMantissaWithSign, dividend, decimalMarkerPosition); if (mode == Preferences::PrintFloatMode::Decimal || exponentInBase10 == 0) { buffer[numberOfCharsForMantissaWithSign] = 0; return numberOfCharsForMantissaWithSign; } // Print exponent - assert(numberOfCharsForMantissaWithSign < PrintFloat::k_maxFloatBufferLength); - buffer[numberOfCharsForMantissaWithSign] = Ion::Charset::Exponent; - assert(numberOfCharExponent+numberOfCharsForMantissaWithSign+1 < PrintFloat::k_maxFloatBufferLength); + assert(numberOfCharsForMantissaWithSign < bufferSize); + int currentNumberOfChar = numberOfCharsForMantissaWithSign; + currentNumberOfChar+= SerializationHelper::CodePoint(buffer + currentNumberOfChar, bufferSize - currentNumberOfChar, KDCodePointScriptSmallE); + assert(numberOfCharExponent+currentNumberOfChar < bufferSize); PrintFloat::printBase10IntegerWithDecimalMarker(buffer+numberOfCharsForMantissaWithSign+1, numberOfCharExponent, Integer(exponentInBase10), -1); buffer[numberOfCharsForMantissaWithSign+1+numberOfCharExponent] = 0; return (numberOfCharsForMantissaWithSign+1+numberOfCharExponent); diff --git a/poincare/src/serialization_helper.cpp b/poincare/src/serialization_helper.cpp index f66c02ec6..953013948 100644 --- a/poincare/src/serialization_helper.cpp +++ b/poincare/src/serialization_helper.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include @@ -158,10 +158,9 @@ int SerializationHelper::CodePoint(char * buffer, int bufferSize, class CodePoin buffer[0] = 0; return 0; } - constexpr int maxCodePointSize = sizeof(class CodePoint)/sizeof(char) + 1; // Null-terminating char - char helpBuffer[maxCodePointSize]; - size_t size = UTF8Decoder::CodePointToChars(c, helpBuffer, maxCodePointSize); - assert(size < maxCodePointSize); + char helpBuffer[MaxSerializedCodePointSize]; + size_t size = UTF8Decoder::CodePointToChars(c, helpBuffer, MaxSerializedCodePointSize); + assert(size < MaxSerializedCodePointSize); helpBuffer[size] = 0; strlcpy(buffer, helpBuffer, bufferSize); return strlen(buffer); diff --git a/poincare/src/store.cpp b/poincare/src/store.cpp index 76e646653..755b11b4a 100644 --- a/poincare/src/store.cpp +++ b/poincare/src/store.cpp @@ -28,13 +28,16 @@ Expression StoreNode::shallowReduce(Context & context, Preferences::ComplexForma } int StoreNode::serialize(char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { - return SerializationHelper::Infix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, "\x90"); + constexpr int stringMaxSize = CodePoint::MaxCodePointCharLength + 1; + char string[stringMaxSize]; + SerializationHelper::CodePoint(string, stringMaxSize, KDCodePointRightwardsArrow); + return SerializationHelper::Infix(this, buffer, bufferSize, floatDisplayMode, numberOfSignificantDigits, string); } Layout StoreNode::createLayout(Preferences::PrintFloatMode floatDisplayMode, int numberOfSignificantDigits) const { HorizontalLayout result = HorizontalLayout::Builder(); result.addOrMergeChildAtIndex(childAtIndex(0)->createLayout(floatDisplayMode, numberOfSignificantDigits), 0, false); - result.addChildAtIndex(CodePointLayout::Builder(Ion::Charset::Sto), result.numberOfChildren(), result.numberOfChildren(), nullptr); + result.addChildAtIndex(CodePointLayout::Builder(KDCodePointRightwardsArrow), result.numberOfChildren(), result.numberOfChildren(), nullptr); result.addOrMergeChildAtIndex(childAtIndex(1)->createLayout(floatDisplayMode, numberOfSignificantDigits), result.numberOfChildren(), false); return result; } diff --git a/poincare/src/trigonometry.cpp b/poincare/src/trigonometry.cpp index 71e174ca0..aa4e9c574 100644 --- a/poincare/src/trigonometry.cpp +++ b/poincare/src/trigonometry.cpp @@ -32,7 +32,7 @@ float Trigonometry::characteristicXRange(const Expression & e, Context & context if (d == 0) { return 0.0f; } - // e has the form cos/sin/tan(ax+b) so it is periodic of period 2*Pi/a + // e has the form cos/sin/tan(ax+b) so it is periodic of period 2*π/a assert(d == 1); /* To compute a, the slope of the expression child(0), we compute the * derivative of child(0) for any x value. */ @@ -175,8 +175,8 @@ Expression Trigonometry::shallowReduceDirectFunction(Expression & e, Context& co } } - /* Step 6. Look for an expression of type "cos(p/q * Pi)" in radians or - * "cos(p/q)" in degrees, put the argument in [0, Pi/2[ or [0, 90[ and + /* Step 6. Look for an expression of type "cos(p/q * π)" in radians or + * "cos(p/q)" in degrees, put the argument in [0, π/2[ or [0, 90[ and * multiply the cos/sin/tan by -1 if needed. * We know thanks to Step 3 that p/q > 0. */ if ((angleUnit == Preferences::AngleUnit::Radian @@ -190,25 +190,25 @@ Expression Trigonometry::shallowReduceDirectFunction(Expression & e, Context& co { Rational r = angleUnit == Preferences::AngleUnit::Radian ? e.childAtIndex(0).childAtIndex(0).convert() : e.childAtIndex(0).convert(); /* Step 4.1. In radians: - * We first check if p/q * Pi is already in the right quadrant: - * p/q * Pi < Pi/2 => p/q < 2 => 2p < q */ + * We first check if p/q * π is already in the right quadrant: + * p/q * π < π/2 => p/q < 2 => 2p < q */ Integer dividand = angleUnit == Preferences::AngleUnit::Radian ? Integer::Addition(r.unsignedIntegerNumerator(), r.unsignedIntegerNumerator()) : r.unsignedIntegerNumerator(); Integer divisor = angleUnit == Preferences::AngleUnit::Radian ? r.integerDenominator() : Integer::Multiplication(r.integerDenominator(), Integer(90)); if (divisor.isLowerThan(dividand)) { - /* Step 4.2. p/q * Pi is not in the wanted trigonometrical quadrant. - * We could subtract n*Pi to p/q with n an integer. + /* Step 4.2. p/q * π is not in the wanted trigonometrical quadrant. + * We could subtract n*π to p/q with n an integer. * Given p/q = (q'*q+r')/q, we have - * (p/q * Pi - q'*Pi) < Pi/2 => r'/q < 1/2 => 2*r' r'/q < 1/2 => 2*r'().isMinusOne()) { Expression x = e.childAtIndex(0).childAtIndex(0); /* This equality is not true if x = 0. We apply it under certain conditions: @@ -278,7 +278,7 @@ Expression Trigonometry::shallowReduceInverseFunction(Expression & e, Context& c * reduced to undef) */ if (target == ExpressionNode::ReductionTarget::User || x.isNumber()) { Expression sign = SignFunction::Builder(x.clone()); - Multiplication m0 = Multiplication::Builder(Rational::Builder(1,2), sign, Constant::Builder(Ion::Charset::SmallPi)); + Multiplication m0 = Multiplication::Builder(Rational::Builder(1,2), sign, Constant::Builder(KDCodePointGreekSmallLetterPi)); sign.shallowReduce(context, complexFormat, angleUnit, target); e.replaceChildAtIndexInPlace(0, x); Addition a = Addition::Builder(m0); @@ -305,16 +305,16 @@ Expression Trigonometry::shallowReduceInverseFunction(Expression & e, Context& c */ Expression p = e.parent(); bool letArcFunctionAtRoot = !p.isUninitialized() && isDirectTrigonometryFunction(p); - /* Step 5. Handle opposite argument: arccos(-x) = Pi-arcos(x), + /* Step 5. Handle opposite argument: arccos(-x) = π-arcos(x), * arcsin(-x) = -arcsin(x), arctan(-x)= -arctan(x) * */ if (!letArcFunctionAtRoot) { Expression positiveArg = e.childAtIndex(0).makePositiveAnyNegativeNumeralFactor(context, complexFormat, angleUnit, target); if (!positiveArg.isUninitialized()) { // The argument was made positive - // acos(-x) = pi-acos(x) + // acos(-x) = π-acos(x) if (e.type() == ExpressionNode::Type::ArcCosine) { - Expression pi = angleUnit == Preferences::AngleUnit::Radian ? static_cast(Constant::Builder(Ion::Charset::SmallPi)) : static_cast(Rational::Builder(180)); + Expression pi = angleUnit == Preferences::AngleUnit::Radian ? static_cast(Constant::Builder(KDCodePointGreekSmallLetterPi)) : static_cast(Rational::Builder(180)); Subtraction s = Subtraction::Builder(); e.replaceWithInPlace(s); s.replaceChildAtIndexInPlace(0, pi); @@ -355,11 +355,11 @@ template T Trigonometry::RoundToMeaningfulDigits(T result, T input) { /* Cheat: openbsd trigonometric functions are numerical implementation and * thus are approximative. - * The error epsilon is ~1E-7 on float and ~1E-15 on double. In order to - * avoid weird results as acos(1) = 6E-17 or cos(Pi/2) = 4E-17, we round - * the result to its 1E-6 or 1E-14 precision when its ratio with the - * argument (pi/2 in the exemple) is smaller than epsilon. This way, we - * have sin(pi) ~ 0 and sin(1E-15)=1E-15. + * The error epsilon is ~1E-7 on float and ~1E-15 on double. In order to avoid + * weird results as acos(1) = 6E-17 or cos(π/2) = 4E-17, we round the result + * to its 1E-6 or 1E-14 precision when its ratio with the argument (π/2 in the + * example) is smaller than epsilon. This way, we have sin(π) ~ 0 and + * sin(1E-15)=1E-15. * We can't do that for all evaluation as the user can operate on values as * small as 1E-308 (in double) and most results still be correct. */ if (input == 0.0 || std::fabs(result/input) <= Expression::Epsilon()) { diff --git a/poincare/src/trigonometry_cheat_table.cpp b/poincare/src/trigonometry_cheat_table.cpp index 62810698f..58b2cba17 100644 --- a/poincare/src/trigonometry_cheat_table.cpp +++ b/poincare/src/trigonometry_cheat_table.cpp @@ -72,70 +72,68 @@ Expression TrigonometryCheatTable::simplify(const Expression e, ExpressionNode:: return Expression(); } -static_assert('\x8A' == Ion::Charset::SmallPi, "Unicode error"); - /* Some cheat tables values were not entered because they would never be needed * For instance, when simplfy a Cosine, we always compute the value for an angle * in the top right trigonometric quadrant. */ const TrigonometryCheatTable * TrigonometryCheatTable::Table() { static Row sTableRows[] = { Row(Row::Pair("-90", -90.0f), - Row::Pair("\x8A*(-2)^(-1)", -1.5707963267948966f), + Row::Pair("π*(-2)^(-1)", -1.5707963267948966f), Row::Pair(""), Row::Pair("-1",-1.0f), Row::Pair("undef")), Row(Row::Pair("-75",-75.0), - Row::Pair("\x8A*(-5)*12^(-1)",-1.3089969389957472f), + Row::Pair("π*(-5)*12^(-1)",-1.3089969389957472f), Row::Pair(""), Row::Pair("(-1)*6^(1/2)*4^(-1)-2^(1/2)*4^(-1)",-0.9659258262890683f), Row::Pair("-(3^(1/2)+2)",-3.7320508075688776f)), Row(Row::Pair("-72",-72.0), - Row::Pair("\x8A*2*(-5)^(-1)",-1.2566370614359172f), + Row::Pair("π*2*(-5)^(-1)",-1.2566370614359172f), Row::Pair(""), Row::Pair("-(5/8+5^(1/2)/8)^(1/2)",-0.9510565162951535f), Row::Pair("-(5+2*5^(1/2))^(1/2)",-3.077683537175253f)), Row(Row::Pair("-135/2",67.5f), - Row::Pair("\x8A*(-3)*8^(-1)",-1.1780972450961724f), + Row::Pair("π*(-3)*8^(-1)",-1.1780972450961724f), Row::Pair(""), Row::Pair("-(2+2^(1/2))^(1/2)*2^(-1)",-0.9238795325112867f), Row::Pair("-1-2^(1/2)",-2.4142135623730945f)), Row(Row::Pair("-60",-60.0f), - Row::Pair("\x8A*(-3)^(-1)",-1.0471975511965976f), + Row::Pair("π*(-3)^(-1)",-1.0471975511965976f), Row::Pair(""), Row::Pair("-3^(1/2)*2^(-1)",-0.8660254037844386f), Row::Pair("-3^(1/2)",-1.7320508075688767f)), Row(Row::Pair("-54",-54.0f), - Row::Pair("\x8A*(-3)*10^(-1)",-0.9424777960769379), + Row::Pair("π*(-3)*10^(-1)",-0.9424777960769379), Row::Pair(""), Row::Pair("4^(-1)*(-1-5^(1/2))",-0.8090169943749473f), Row::Pair("-(1+2*5^(-1/2))^(1/2)",-1.3763819204711731f)), Row(Row::Pair("-45",-45.0f), - Row::Pair("\x8A*(-4)^(-1)",-0.7853981633974483f), + Row::Pair("π*(-4)^(-1)",-0.7853981633974483f), Row::Pair(""), Row::Pair("(-1)*(2^(-1/2))",-0.7071067811865475f), Row::Pair("-1",-1.0f)), Row(Row::Pair("-36",-36.0f), - Row::Pair("\x8A*(-5)^(-1)",-0.6283185307179586f), + Row::Pair("π*(-5)^(-1)",-0.6283185307179586f), Row::Pair(""), Row::Pair("-(5/8-5^(1/2)/8)^(1/2)",-0.5877852522924731f), Row::Pair("-(5-2*5^(1/2))^(1/2)",-0.7265425280053609f)), Row(Row::Pair("-30",-30.0f), - Row::Pair("\x8A*(-6)^(-1)",-0.5235987755982988f), + Row::Pair("π*(-6)^(-1)",-0.5235987755982988f), Row::Pair(""), Row::Pair("-0.5",-0.5f), Row::Pair("-3^(-1/2)",-0.5773502691896256f)), Row(Row::Pair("-45/2",-22.5f), - Row::Pair("\x8A*(-8)^(-1)",-0.39269908169872414f), + Row::Pair("π*(-8)^(-1)",-0.39269908169872414f), Row::Pair(""), Row::Pair("(2-2^(1/2))^(1/2)*(-2)^(-1)",-0.3826834323650898f), Row::Pair("1-2^(1/2)",-0.4142135623730951f)), Row(Row::Pair("-18",-18.0f), - Row::Pair("\x8A*(-10)^(-1)",-0.3141592653589793f), + Row::Pair("π*(-10)^(-1)",-0.3141592653589793f), Row::Pair(""), Row::Pair("4^(-1)*(1-5^(1/2))",-0.3090169943749474f), Row::Pair("-(1-2*5^(-1/2))^(1/2)",-0.3249196962329063f)), Row(Row::Pair("-15",-15.0f), - Row::Pair("\x8A*(-12)^(-1)",-0.2617993877991494f), + Row::Pair("π*(-12)^(-1)",-0.2617993877991494f), Row::Pair(""), Row::Pair("-6^(1/2)*4^(-1)+2^(1/2)*4^(-1)",-0.25881904510252074f), Row::Pair("3^(1/2)-2",-0.2679491924311227f)), @@ -145,122 +143,122 @@ const TrigonometryCheatTable * TrigonometryCheatTable::Table() { Row::Pair("0",0.0f), Row::Pair("0",0.0f)), Row(Row::Pair("15",15.0f), - Row::Pair("\x8A*12^(-1)",0.2617993877991494f), + Row::Pair("π*12^(-1)",0.2617993877991494f), Row::Pair("6^(1/2)*4^(-1)+2^(1/2)*4^(-1)",0.9659258262890683f), Row::Pair("6^(1/2)*4^(-1)+2^(1/2)*(-4)^(-1)",0.25881904510252074f), Row::Pair("-(3^(1/2)-2)",0.2679491924311227f)), Row(Row::Pair("18",18.0f), - Row::Pair("\x8A*10^(-1)",0.3141592653589793f), + Row::Pair("π*10^(-1)",0.3141592653589793f), Row::Pair("(5/8+5^(1/2)/8)^(1/2)",0.9510565162951535f), Row::Pair("4^(-1)*(5^(1/2)-1)",0.3090169943749474f), Row::Pair("(1-2*5^(-1/2))^(1/2)",0.3249196962329063f)), Row(Row::Pair("45/2",22.5f), - Row::Pair("\x8A*8^(-1)",0.39269908169872414f), + Row::Pair("π*8^(-1)",0.39269908169872414f), Row::Pair("(2+2^(1/2))^(1/2)*2^(-1)",0.9238795325112867f), Row::Pair("(2-2^(1/2))^(1/2)*2^(-1)",0.3826834323650898f), Row::Pair("2^(1/2)-1",0.4142135623730951f)), Row(Row::Pair("30",30.0f), - Row::Pair("\x8A*6^(-1)",0.5235987755982988f), + Row::Pair("π*6^(-1)",0.5235987755982988f), Row::Pair("3^(1/2)*2^(-1)",0.8660254037844387f), Row::Pair("0.5",0.5f), Row::Pair("3^(-1/2)",0.5773502691896256f)), Row(Row::Pair("36",36.0f), - Row::Pair("\x8A*5^(-1)",0.6283185307179586f), + Row::Pair("π*5^(-1)",0.6283185307179586f), Row::Pair("(5^(1/2)+1)*4^(-1)",0.8090169943749475f), Row::Pair("(5/8-5^(1/2)/8)^(1/2)",0.5877852522924731f), Row::Pair("(5-2*5^(1/2))^(1/2)",0.7265425280053609f)), Row(Row::Pair("45",45.0f), - Row::Pair("\x8A*4^(-1)",0.7853981633974483f), + Row::Pair("π*4^(-1)",0.7853981633974483f), Row::Pair("2^(-1/2)",0.7071067811865476f), Row::Pair("2^(-1/2)",0.7071067811865475f), Row::Pair("1",1.0f)), Row(Row::Pair("54",54.0f), - Row::Pair("\x8A*3*10^(-1)",0.9424777960769379f), + Row::Pair("π*3*10^(-1)",0.9424777960769379f), Row::Pair("(5/8-5^(1/2)/8)^(1/2)",0.5877852522924732f), Row::Pair("4^(-1)*(5^(1/2)+1)",0.8090169943749473f), Row::Pair("(1+2*5^(-1/2))^(1/2)",1.3763819204711731f)), Row(Row::Pair("60",60.0f), - Row::Pair("\x8A*3^(-1)",1.0471975511965976f), + Row::Pair("π*3^(-1)",1.0471975511965976f), Row::Pair("0.5",0.5f), Row::Pair("3^(1/2)*2^(-1)",0.8660254037844386f), Row::Pair("3^(1/2)",1.7320508075688767f)), Row(Row::Pair("135/2",67.5f), - Row::Pair("\x8A*3*8^(-1)",1.1780972450961724f), + Row::Pair("π*3*8^(-1)",1.1780972450961724f), Row::Pair("(2-2^(1/2))^(1/2)*2^(-1)",0.38268343236508984f), Row::Pair("(2+2^(1/2))^(1/2)*2^(-1)",0.9238795325112867f), Row::Pair("1+2^(1/2)",2.4142135623730945f)), Row(Row::Pair("72",72.0f), - Row::Pair("\x8A*2*5^(-1)",1.2566370614359172f), + Row::Pair("π*2*5^(-1)",1.2566370614359172f), Row::Pair("(5^(1/2)-1)*4^(-1)",0.30901699437494745f), Row::Pair("(5/8+5^(1/2)/8)^(1/2)",0.9510565162951535f), Row::Pair("(5+2*5^(1/2))^(1/2)",3.077683537175253f)), Row(Row::Pair("75",75.0f), - Row::Pair("\x8A*5*12^(-1)",1.3089969389957472f), + Row::Pair("π*5*12^(-1)",1.3089969389957472f), Row::Pair("6^(1/2)*4^(-1)+2^(1/2)*(-4)^(-1)",0.25881904510252074f), Row::Pair("6^(1/2)*4^(-1)+2^(1/2)*4^(-1)",0.9659258262890683f), Row::Pair("3^(1/2)+2",3.7320508075688776f)), Row(Row::Pair("90",90.0f), - Row::Pair("\x8A*2^(-1)",1.5707963267948966f), + Row::Pair("π*2^(-1)",1.5707963267948966f), Row::Pair("0",0.0f), Row::Pair("1",1.0f), Row::Pair("undef")), Row(Row::Pair("105",105.0f), - Row::Pair("\x8A*7*12^(-1)",1.832595714594046f), + Row::Pair("π*7*12^(-1)",1.832595714594046f), Row::Pair("-6^(1/2)*4^(-1)+2^(1/2)*4^(-1)",-0.25881904510252063f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("108",108.0f), - Row::Pair("\x8A*3*5^(-1)",1.8849555921538759f), + Row::Pair("π*3*5^(-1)",1.8849555921538759f), Row::Pair("(1-5^(1/2))*4^(-1)",-0.30901699437494734f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("225/2",112.5f), - Row::Pair("\x8A*5*8^(-1)",1.9634954084936207f), + Row::Pair("π*5*8^(-1)",1.9634954084936207f), Row::Pair("(2-2^(1/2))^(1/2)*(-2)^(-1)",-0.3826834323650897f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("120",120.0f), - Row::Pair("\x8A*2*3^(-1)",2.0943951023931953f), + Row::Pair("π*2*3^(-1)",2.0943951023931953f), Row::Pair("-0.5",-0.5f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("126",126.0f), - Row::Pair("\x8A*7*10^(-1)",2.199114857512855f), + Row::Pair("π*7*10^(-1)",2.199114857512855f), Row::Pair("-(5*8^(-1)-5^(1/2)*8^(-1))^(1/2)",-0.587785252292473f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("135",135.0f), - Row::Pair("\x8A*3*4^(-1)",2.356194490192345f), + Row::Pair("π*3*4^(-1)",2.356194490192345f), Row::Pair("(-1)*(2^(-1/2))",-0.7071067811865475f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("144",144.0f), - Row::Pair("\x8A*4*5^(-1)",2.5132741228718345f), + Row::Pair("π*4*5^(-1)",2.5132741228718345f), Row::Pair("(-5^(1/2)-1)*4^(-1)",-0.8090169943749473f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("150",150.0f), - Row::Pair("\x8A*5*6^(-1)",2.6179938779914944f), + Row::Pair("π*5*6^(-1)",2.6179938779914944f), Row::Pair("-3^(1/2)*2^(-1)",-0.8660254037844387f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("315/2",157.5f), - Row::Pair("\x8A*7*8^(-1)",2.748893571891069f), + Row::Pair("π*7*8^(-1)",2.748893571891069f), Row::Pair("-(2+2^(1/2))^(1/2)*2^(-1)",-0.9238795325112867f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("162",162.0f), - Row::Pair("\x8A*9*10^(-1)",2.827433388230814f), + Row::Pair("π*9*10^(-1)",2.827433388230814f), Row::Pair("-(5*8^(-1)+5^(1/2)*8^(-1))^(1/2)",-0.9510565162951535f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("165",165.0f), - Row::Pair("\x8A*11*12^(-1)",2.8797932657906435f), + Row::Pair("π*11*12^(-1)",2.8797932657906435f), Row::Pair("(-1)*6^(1/2)*4^(-1)-2^(1/2)*4^(-1)",-0.9659258262890682f), Row::Pair(""), Row::Pair("")), Row(Row::Pair("180",180.0f), - Row::Pair("\x8A",3.141592653589793f), + Row::Pair("π",3.141592653589793f), Row::Pair("-1",-1.0f), Row::Pair("0",0.0f), Row::Pair("0",0.0f)) diff --git a/poincare/src/vertical_offset_layout.cpp b/poincare/src/vertical_offset_layout.cpp index 5b41e6282..5bd450783 100644 --- a/poincare/src/vertical_offset_layout.cpp +++ b/poincare/src/vertical_offset_layout.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -175,12 +174,12 @@ int VerticalOffsetLayoutNode::serialize(char * buffer, int bufferSize, Preferenc } assert(m_type == Type::Superscript); /* If the layout is a superscript, write: - * "Ion::Charset::LeftSuperscript indice Ion::Charset::RightSuperscript" */ - int numberOfChar = SerializationHelper::Char(buffer, bufferSize, Ion::Charset::LeftSuperscript); + * "KDCodePointLeftSuperscript indice KDCodePointRightSuperscript" */ + int numberOfChar = SerializationHelper::CodePoint(buffer, bufferSize, KDCodePointLeftSuperscript); if (numberOfChar >= bufferSize-1) { return bufferSize-1; } numberOfChar += const_cast(this)->indiceLayout()->serialize(buffer+numberOfChar, bufferSize-numberOfChar, floatDisplayMode, numberOfSignificantDigits); if (numberOfChar >= bufferSize-1) { return bufferSize-1; } - numberOfChar += SerializationHelper::Char(buffer+numberOfChar, bufferSize-numberOfChar, Ion::Charset::RightSuperscript); + numberOfChar += SerializationHelper::CodePoint(buffer+numberOfChar, bufferSize-numberOfChar, KDCodePointRightSuperscript); if (numberOfChar >= bufferSize-1) { return bufferSize-1; } buffer[numberOfChar] = 0; diff --git a/poincare/test/convert_expression_to_text.cpp b/poincare/test/convert_expression_to_text.cpp index 85d81c602..37702f9b8 100644 --- a/poincare/test/convert_expression_to_text.cpp +++ b/poincare/test/convert_expression_to_text.cpp @@ -30,7 +30,6 @@ void assert_float_prints_to(T a, const char * result, Preferences::PrintFloatMod for (int i=tagSize+strlen(buffer)+1; i f, const char * result) { int numberOfDigits = sizeof(T) == sizeof(double) ? PrintFloat::k_numberOfStoredSignificantDigits : PrintFloat::k_numberOfPrintedSignificantDigits; char buffer[500]; f.template approximate(globalContext, Cartesian, Radian).serialize(buffer, sizeof(buffer), DecimalMode, numberOfDigits); - translate_in_ASCII_chars(buffer); quiz_assert(strcmp(buffer, result) == 0); } diff --git a/poincare/test/helper.cpp b/poincare/test/helper.cpp index e0e6d41be..e8afad338 100644 --- a/poincare/test/helper.cpp +++ b/poincare/test/helper.cpp @@ -41,33 +41,17 @@ bool expressions_are_equal(Poincare::Expression expected, Poincare::Expression g return identical; } -void translate_in_special_chars(char * expression) { - for (char *c = expression; *c; c++) { - switch (*c) { - case 'E': *c = Ion::Charset::Exponent; break; - case 'X': *c = Ion::Charset::Exponential; break; - case 'I': *c = Ion::Charset::IComplex; break; - case 'R': *c = Ion::Charset::Root; break; - case 'P': *c = Ion::Charset::SmallPi; break; - case '*': *c = Ion::Charset::MultiplicationSign; break; - case '>': *c = Ion::Charset::Sto; break; - case '?': *c = Poincare::Symbol::SpecialSymbols::UnknownX; break; - case '$': *c = Ion::Charset::LeftSuperscript; break; - case '#': *c = Ion::Charset::RightSuperscript; break; - } - } -} - -void translate_in_ASCII_chars(char * expression) { +/*TODO LEA + *void translate_in_ASCII_chars(char * expression) { for (char *c = expression; *c; c++) { switch (*c) { case Ion::Charset::Exponent: *c = 'E'; break; - case Ion::Charset::Exponential: *c = 'X'; break; - case Ion::Charset::IComplex: *c = 'I'; break; + case KDCodePointScriptSmallE: *c = 'X'; break; + case KDCodePointMathematicalBoldSmallI: *c = 'I'; break; case Ion::Charset::Root: *c = 'R'; break; - case Ion::Charset::SmallPi: *c = 'P'; break; + case KDCodePointGreekSmallLetterPi: *c = 'P'; break; case Ion::Charset::MultiplicationSign: *c = '*'; break; - case Ion::Charset::MiddleDot: *c = '*'; break; + case KDCodePointMiddleDot: *c = '*'; break; case Ion::Charset::Sto: *c = '>'; break; case Poincare::Symbol::SpecialSymbols::UnknownX: *c = '?'; break; case Ion::Charset::LeftSuperscript: *c = '$'; break; @@ -75,13 +59,11 @@ void translate_in_ASCII_chars(char * expression) { } } } +*/ Expression parse_expression(const char * expression, bool canBeUnparsable) { quiz_print(expression); - char buffer[500]; - strlcpy(buffer, expression, sizeof(buffer)); - translate_in_special_chars(buffer); - Expression result = Expression::Parse(buffer); + Expression result = Expression::Parse(expression); if (!canBeUnparsable) { quiz_assert(!result.isUninitialized()); } @@ -138,7 +120,6 @@ void assert_parsed_expression_process_to(const char * expression, const char * r Expression m = process(e, globalContext, target, complexFormat, angleUnit); char buffer[500]; m.serialize(buffer, sizeof(buffer), DecimalMode, numberOfSignifiantDigits); - translate_in_ASCII_chars(buffer); #if POINCARE_TESTS_PRINT_EXPRESSIONS cout << "---- serialize to: " << buffer << " ----" << endl; cout << "----- compared to: " << result << " ----\n" << endl; @@ -214,7 +195,6 @@ void assert_parsed_expression_serialize_to(Expression expression, const char * s #endif char buffer[500]; expression.serialize(buffer, sizeof(buffer), mode, numberOfSignifiantDigits); - translate_in_ASCII_chars(buffer); quiz_assert(strcmp(buffer, serializedExpression) == 0); } @@ -237,7 +217,6 @@ void assert_expression_layout_serialize_to(Poincare::Layout layout, const char * constexpr int bufferSize = 255; char buffer[bufferSize]; layout.serializeForParsing(buffer, bufferSize); - translate_in_ASCII_chars(buffer); #if POINCARE_TESTS_PRINT_EXPRESSIONS cout << "---- Serialize: " << serialization << "----" << endl; cout << "---- serialized to: " << buffer << " ----" << endl; diff --git a/poincare/test/helper.h b/poincare/test/helper.h index de0205017..91c8bcb46 100644 --- a/poincare/test/helper.h +++ b/poincare/test/helper.h @@ -19,8 +19,6 @@ constexpr Poincare::Preferences::PrintFloatMode DecimalMode = Poincare::Preferen constexpr Poincare::Preferences::PrintFloatMode ScientificMode = Poincare::Preferences::PrintFloatMode::Scientific; bool expressions_are_equal(Poincare::Expression expected, Poincare::Expression got); -void translate_in_special_chars(char * expression); -void translate_in_ASCII_chars(char * expression); Poincare::Expression parse_expression(const char * expression, bool canBeUnparsable = false); Poincare::Expression parse_and_simplify(const char * expression); diff --git a/poincare/test/layouts.cpp b/poincare/test/layouts.cpp index 7e7172748..05886d548 100644 --- a/poincare/test/layouts.cpp +++ b/poincare/test/layouts.cpp @@ -265,11 +265,11 @@ QUIZ_CASE(poincare_parse_layouts) { // 2e^3 l = HorizontalLayout::Builder( CodePointLayout::Builder('2'), - CodePointLayout::Builder(Ion::Charset::Exponential), + CodePointLayout::Builder(KDCodePointScriptSmallE), VerticalOffsetLayout::Builder( CodePointLayout::Builder('3'), VerticalOffsetLayoutNode::Type::Superscript)); - e = Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(Ion::Charset::Exponential),Parenthesis::Builder(Rational::Builder(3)))); - assert_parsed_expression_is("2X^(3)", Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(Ion::Charset::Exponential),Parenthesis::Builder(Rational::Builder(3))))); + e = Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(KDCodePointScriptSmallE),Parenthesis::Builder(Rational::Builder(3)))); + assert_parsed_expression_is("2X^(3)", Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(KDCodePointScriptSmallE),Parenthesis::Builder(Rational::Builder(3))))); assert_parsed_layout_is(l, e); } diff --git a/poincare/test/parser.cpp b/poincare/test/parser.cpp index 2c50972f9..762100eae 100644 --- a/poincare/test/parser.cpp +++ b/poincare/test/parser.cpp @@ -9,10 +9,7 @@ using namespace Poincare; void assert_tokenizes_as(const Token::Type * tokenTypes, const char * string) { - char buffer[500]; - strlcpy(buffer, string, sizeof(buffer)); - translate_in_special_chars(buffer); - Tokenizer tokenizer(buffer); + Tokenizer tokenizer(string); while (true) { Token token = tokenizer.popToken(); quiz_assert(token.type() == *tokenTypes); @@ -29,10 +26,7 @@ void assert_tokenizes_as_number(const char * string) { } void assert_tokenizes_as_undefined_token(const char * string) { - char buffer[500]; - strlcpy(buffer, string, sizeof(buffer)); - translate_in_special_chars(buffer); - Tokenizer tokenizer(buffer); + Tokenizer tokenizer(string); while (true) { Token token = tokenizer.popToken(); if (token.type() == Token::Undefined) { @@ -45,10 +39,7 @@ void assert_tokenizes_as_undefined_token(const char * string) { } void assert_raises_parsing_error(const char * text) { - char buffer[500]; - strlcpy(buffer, text, sizeof(buffer)); - translate_in_special_chars(buffer); - Parser p(buffer); + Parser p(text); Expression result = p.parse(); quiz_assert(p.getStatus() != Parser::Status::Success); } @@ -266,9 +257,9 @@ QUIZ_CASE(poincare_parser_symbols_and_functions) { // Reserved symbols assert_parsed_expression_is("ans", Symbol::Builder("ans", 3)); - assert_parsed_expression_is("I", Constant::Builder(Ion::Charset::IComplex)); - assert_parsed_expression_is("P", Constant::Builder(Ion::Charset::SmallPi)); - assert_parsed_expression_is("X", Constant::Builder(Ion::Charset::Exponential)); + assert_parsed_expression_is("I", Constant::Builder(KDCodePointMathematicalBoldSmallI)); + assert_parsed_expression_is("P", Constant::Builder(KDCodePointGreekSmallLetterPi)); + assert_parsed_expression_is("X", Constant::Builder(KDCodePointScriptSmallE)); assert_parsed_expression_is(Infinity::Name(), Infinity::Builder(false)); assert_parsed_expression_is(Undefined::Name(), Undefined::Builder()); @@ -373,7 +364,7 @@ QUIZ_CASE(poincare_parser_implicit_multiplication) { assert_parsed_expression_is("1ans", Multiplication::Builder(Rational::Builder(1),Symbol::Builder("ans", 3))); assert_parsed_expression_is("x1", Symbol::Builder("x1", 2)); assert_parsed_expression_is("1x+2", Addition::Builder(Multiplication::Builder(Rational::Builder(1),Symbol::Builder("x", 1)),Rational::Builder(2))); - assert_parsed_expression_is("1P", Multiplication::Builder(Rational::Builder(1),Constant::Builder(Ion::Charset::SmallPi))); + assert_parsed_expression_is("1P", Multiplication::Builder(Rational::Builder(1),Constant::Builder(KDCodePointGreekSmallLetterPi))); assert_parsed_expression_is("1x-2", Subtraction::Builder(Multiplication::Builder(Rational::Builder(1),Symbol::Builder("x", 1)),Rational::Builder(2))); assert_parsed_expression_is("-1x", Opposite::Builder(Multiplication::Builder(Rational::Builder(1),Symbol::Builder("x", 1)))); assert_parsed_expression_is("2*1x", Multiplication::Builder(Rational::Builder(2),Multiplication::Builder(Rational::Builder(1),Symbol::Builder("x", 1)))); @@ -386,7 +377,7 @@ QUIZ_CASE(poincare_parser_implicit_multiplication) { assert_parsed_expression_is("sin(1)2", Multiplication::Builder(Sine::Builder(Rational::Builder(1)),Rational::Builder(2))); assert_parsed_expression_is("1cos(2)", Multiplication::Builder(Rational::Builder(1),Cosine::Builder(Rational::Builder(2)))); assert_parsed_expression_is("1!2", Multiplication::Builder(Factorial::Builder(Rational::Builder(1)),Rational::Builder(2))); - assert_parsed_expression_is("2X^(3)", Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(Ion::Charset::Exponential),Parenthesis::Builder(Rational::Builder(3))))); + assert_parsed_expression_is("2X^(3)", Multiplication::Builder(Rational::Builder(2),Power::Builder(Constant::Builder(KDCodePointScriptSmallE),Parenthesis::Builder(Rational::Builder(3))))); Expression m1[] = {Rational::Builder(1)}; Matrix M1 = BuildMatrix(1,1,m1); Expression m2[] = {Rational::Builder(2)}; Matrix M2 = BuildMatrix(1,1,m2); assert_parsed_expression_is("[[1]][[2]]", Multiplication::Builder(M1,M2)); From 8d759c0b3b218b05b0eb21efee049a791d51ba9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Wed, 16 Jan 2019 17:03:30 +0100 Subject: [PATCH 0311/1750] [unicode] Use the UTF8Decoder to scan const char * --- apps/shared/toolbox_helpers.cpp | 26 ++++++---- apps/solver/equation.cpp | 4 +- apps/variable_box_controller.cpp | 3 +- escher/src/text_area.cpp | 19 +++---- escher/src/text_field.cpp | 19 +++---- escher/src/text_input_helpers.cpp | 31 ++++++++--- kandinsky/Makefile | 1 + .../include/kandinsky/unicode/utf8_decoder.h | 3 ++ .../include/kandinsky/unicode/utf8_helper.h | 17 +++++++ kandinsky/src/unicode/utf8_decoder.cpp | 14 ++++- kandinsky/src/unicode/utf8_helper.cpp | 51 +++++++++++++++++++ poincare/src/expression.cpp | 4 +- poincare/src/layout_cursor.cpp | 38 ++++++++------ poincare/src/layout_helper.cpp | 24 +++++++-- 14 files changed, 189 insertions(+), 65 deletions(-) create mode 100644 kandinsky/include/kandinsky/unicode/utf8_helper.h create mode 100644 kandinsky/src/unicode/utf8_helper.cpp diff --git a/apps/shared/toolbox_helpers.cpp b/apps/shared/toolbox_helpers.cpp index 99c89cd3b..9952847f6 100644 --- a/apps/shared/toolbox_helpers.cpp +++ b/apps/shared/toolbox_helpers.cpp @@ -1,5 +1,6 @@ #include "toolbox_helpers.h" #include +#include #include #include @@ -7,17 +8,24 @@ namespace Shared { namespace ToolboxHelpers { int CursorIndexInCommandText(const char * text) { - // TODO LEA - size_t textLength = strlen(text); - for (size_t i = 0; i < textLength; i++) { - if (text[i] == '(' || text[i] == '\'') { - return i + 1; + UTF8Decoder decoder(text); + size_t index = 0; + const char * currentPointer = text; + const char * nextPointer = decoder.nextCodePointPointer(); + CodePoint codePoint = decoder.nextCodePoint(); + while (codePoint != KDCodePointNull) { + if (codePoint == '(' || codePoint == '\'') { + return index + 1; } - if (text[i] == ']') { - return i; + if (codePoint == '[') { + return index; } + index+= nextPointer - currentPointer; + currentPointer = nextPointer; + nextPointer = decoder.nextCodePointPointer(); + codePoint = decoder.nextCodePoint(); } - return textLength; + return index; } void TextToInsertForCommandMessage(I18n::Message message, char * buffer, int bufferSize, bool replaceArgsWithEmptyChar) { @@ -49,7 +57,7 @@ void TextToInsertForCommandText(const char * command, char * buffer, int bufferS buffer[currentNewTextIndex++] = command[i]; } else { if (replaceArgsWithEmptyChar && !argumentAlreadyReplaced) { - // TODO LEA buffer[currentNewTextIndex++] = Ion::Charset::Empty; + currentNewTextIndex += UTF8Decoder::CodePointToChars(KDCodePointEmpty, buffer + currentNewTextIndex, bufferSize - currentNewTextIndex); argumentAlreadyReplaced = true; } } diff --git a/apps/solver/equation.cpp b/apps/solver/equation.cpp index 04edc3eb8..8f9d4aa7e 100644 --- a/apps/solver/equation.cpp +++ b/apps/solver/equation.cpp @@ -1,9 +1,9 @@ #include "equation.h" - #include #include #include #include +#include using namespace Poincare; @@ -50,7 +50,7 @@ Expression Equation::standardForm(Context * context) const { } bool Equation::containsIComplex() const { - return false; //TODO LEA strchr(text(), KDCodePointMathematicalBoldSmallI) != nullptr; + return UTF8Helper::CodePointSearch(text(), KDCodePointMathematicalBoldSmallI) != nullptr; } void Equation::tidyStandardForm() { diff --git a/apps/variable_box_controller.cpp b/apps/variable_box_controller.cpp index 54a6367c4..d4c2610e5 100644 --- a/apps/variable_box_controller.cpp +++ b/apps/variable_box_controller.cpp @@ -9,6 +9,7 @@ #include #include #include +#include using namespace Poincare; using namespace Shared; @@ -199,7 +200,7 @@ bool VariableBoxController::selectLeaf(int selectedRow) { assert(nameLength < nameToHandleMaxSize); nameToHandle[nameLength++] = '('; assert(nameLength < nameToHandleMaxSize); - // TODO LEA nameToHandle[nameLength++] = Ion::Charset::Empty; + nameLength+= UTF8Decoder::CodePointToChars(KDCodePointEmpty, nameToHandle+nameLength, nameToHandleMaxSize - nameLength); assert(nameLength < nameToHandleMaxSize); nameToHandle[nameLength++] = ')'; assert(nameLength < nameToHandleMaxSize); diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index e7bccb24d..408dd2dbf 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -24,21 +25,15 @@ bool TextArea::handleEventWithText(const char * text, bool indentation, bool for size_t cursorIndexInCommand = TextInputHelpers::CursorIndexInCommand(text); - size_t eventTextSize = min(strlen(text) + 1, TextField::maxBufferSize()); - char buffer[TextField::maxBufferSize()]; - size_t bufferIndex = 0; + constexpr int bufferSize = TextField::maxBufferSize(); + char buffer[bufferSize]; - // Remove EmptyChars - for (size_t i = bufferIndex; i < eventTextSize; i++) { - /* TODO LEA - if (text[i] != Ion::Charset::Empty) { - buffer[bufferIndex++] = text[i]; - } else if (i < cursorIndexInCommand) { - cursorIndexInCommand--; - } */ - } + // Remove the Empty code points + UTF8Helper::CopyAndRemoveCodePoint(buffer, bufferSize, text, KDCodePointEmpty, &cursorIndexInCommand); + // Insert the text if ((indentation && insertTextWithIndentation(buffer, cursorLocation())) || insertTextAtLocation(buffer, cursorLocation())) { + // Set the cursor location if (forceCursorRightOfText) { nextCursorLocation += strlen(buffer); } else { diff --git a/escher/src/text_field.cpp b/escher/src/text_field.cpp index 1a2190a42..00322a610 100644 --- a/escher/src/text_field.cpp +++ b/escher/src/text_field.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include /* TextField::ContentView */ @@ -405,29 +406,21 @@ bool TextField::privateHandleMoveEvent(Ion::Events::Event event) { } bool TextField::handleEventWithText(const char * eventText, bool indentation, bool forceCursorRightOfText) { -//TODO LEA size_t previousTextLength = strlen(text()); - size_t eventTextLength = strlen(eventText); if (!isEditing()) { setEditing(true); } - if (eventTextLength == 0) { + if (eventText[0] == 0) { setCursorLocation(0); return m_delegate->textFieldDidHandleEvent(this, true, previousTextLength != 0); } - size_t eventTextSize = min(eventTextLength + 1, TextField::maxBufferSize()); - char buffer[TextField::maxBufferSize()]; - - int newBufferIndex = 0; - // Remove EmptyChars - /* TODO for (size_t i = 0; i < eventTextSize; i++) { - if (eventText[i] != Ion::Charset::Empty) { - buffer[newBufferIndex++] = eventText[i]; - } - }*/ + // Remove the Empty code points + constexpr int bufferSize = TextField::maxBufferSize(); + char buffer[bufferSize]; + UTF8Helper::CopyAndRemoveCodePoint(buffer, bufferSize, eventText, KDCodePointEmpty); int nextCursorLocation = draftTextLength(); if (insertTextAtLocation(buffer, cursorLocation())) { diff --git a/escher/src/text_input_helpers.cpp b/escher/src/text_input_helpers.cpp index c210c33a0..d6d39c632 100644 --- a/escher/src/text_input_helpers.cpp +++ b/escher/src/text_input_helpers.cpp @@ -1,18 +1,35 @@ #include +#include #include namespace TextInputHelpers { size_t CursorIndexInCommand(const char * text) { - // TODO LEA size_t index = 0; - while (text[index] != 0) { - if (text[index] == '\'' && text[index+1] == '\'') { - return index + 1; - } /* TODO else if (text[index] == Ion::Charset::Empty) { + UTF8Decoder decoder(text); + const char * currentPointer = text; + const char * nextPointer = decoder.nextCodePointPointer(); + CodePoint codePoint = decoder.nextCodePoint(); + while (codePoint != KDCodePointNull) { + if (codePoint == KDCodePointEmpty) { return index; - }*/ - index++; + } + //TODO make sure changing empty / ' order was OK + if (codePoint == '\'') { + index+= nextPointer - currentPointer; + currentPointer = nextPointer; + nextPointer = decoder.nextCodePointPointer(); + codePoint = decoder.nextCodePoint(); + if (codePoint == '\'') { + return index; + } + // Continue because we already incremented codePoint + continue; + } + index+= nextPointer - currentPointer; + currentPointer = nextPointer; + nextPointer = decoder.nextCodePointPointer(); + codePoint = decoder.nextCodePoint(); } return index; } diff --git a/kandinsky/Makefile b/kandinsky/Makefile index 368948437..74c6772e7 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -14,6 +14,7 @@ src += $(addprefix kandinsky/src/,\ point.cpp \ rect.cpp \ unicode/utf8_decoder.cpp\ + unicode/utf8_helper.cpp\ ) src += $(addprefix kandinsky/fonts/, \ diff --git a/kandinsky/include/kandinsky/unicode/utf8_decoder.h b/kandinsky/include/kandinsky/unicode/utf8_decoder.h index abb768d7f..1f52cccb4 100644 --- a/kandinsky/include/kandinsky/unicode/utf8_decoder.h +++ b/kandinsky/include/kandinsky/unicode/utf8_decoder.h @@ -18,7 +18,10 @@ class UTF8Decoder { public: UTF8Decoder(const char * string) : m_string(string) {} + /* TODO: Rename methods? nextCodePoint increases m_string but + * nextCodePointPointer does not */ CodePoint nextCodePoint(); + const char * nextCodePointPointer(); static size_t CharSizeOfCodePoint(CodePoint c); static size_t CodePointToChars(CodePoint c, char * buffer, int bufferSize); private: diff --git a/kandinsky/include/kandinsky/unicode/utf8_helper.h b/kandinsky/include/kandinsky/unicode/utf8_helper.h new file mode 100644 index 000000000..70f4edc2a --- /dev/null +++ b/kandinsky/include/kandinsky/unicode/utf8_helper.h @@ -0,0 +1,17 @@ +#ifndef KANDINSKY_UNICODE_UTF8_HELPER_H +#define KANDINSKY_UNICODE_UTF8_HELPER_H + +#include "code_point.h" +#include + +namespace UTF8Helper { + +const char * CodePointSearch(const char * s, CodePoint c); +/* CopyAndRemoveCodePoint copies src into dst while removing all code points c. + * It also updates an index that should be lower if code points where removed + * before it. */ +void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePoint c, size_t * indexToDUpdate = nullptr); + +}; + +#endif diff --git a/kandinsky/src/unicode/utf8_decoder.cpp b/kandinsky/src/unicode/utf8_decoder.cpp index cb6f449d3..288fcea92 100644 --- a/kandinsky/src/unicode/utf8_decoder.cpp +++ b/kandinsky/src/unicode/utf8_decoder.cpp @@ -25,6 +25,10 @@ CodePoint UTF8Decoder::nextCodePoint() { return CodePoint(result); } +const char * UTF8Decoder::nextCodePointPointer() { + return m_string + leading_ones(*m_string); +} + size_t UTF8Decoder::CharSizeOfCodePoint(CodePoint c) { constexpr int bufferSize = CodePoint::MaxCodePointCharLength; char buffer[bufferSize]; @@ -32,21 +36,29 @@ size_t UTF8Decoder::CharSizeOfCodePoint(CodePoint c) { } size_t UTF8Decoder::CodePointToChars(CodePoint c, char * buffer, int bufferSize) { - assert(bufferSize >= CodePoint::MaxCodePointCharLength); + if (bufferSize <= 0) { + return 0; + } size_t i = 0; if (c <= 0x7F) { buffer[i++] = c; } else if (c <= 0x7FF) { buffer[i++] = 0b11000000 | (c >> 6); + if (bufferSize <= i) { return i; } buffer[i++] = 0b10000000 | (c & 0b111111); } else if (c <= 0xFFFF) { buffer[i++] = 0b11100000 | (c >> 12); + if (bufferSize <= i) { return i; } buffer[i++] = 0b10000000 | ((c >> 6) & 0b111111); + if (bufferSize <= i) { return i; } buffer[i++] = 0b10000000 | (c & 0b111111); } else { buffer[i++] = 0b11110000 | (c >> 18); + if (bufferSize <= i) { return i; } buffer[i++] = 0b10000000 | ((c >> 12) & 0b111111); + if (bufferSize <= i) { return i; } buffer[i++] = 0b10000000 | ((c >> 6) & 0b111111); + if (bufferSize <= i) { return i; } buffer[i++] = 0b10000000 | (c & 0b111111); } return i; diff --git a/kandinsky/src/unicode/utf8_helper.cpp b/kandinsky/src/unicode/utf8_helper.cpp new file mode 100644 index 000000000..3293e14fd --- /dev/null +++ b/kandinsky/src/unicode/utf8_helper.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +namespace UTF8Helper { + +static inline int min(int x, int y) { return x < y ? x : y; } + +const char * CodePointSearch(const char * s, CodePoint c) { + UTF8Decoder decoder(s); + const char * currentPointer = s; + const char * nextPointer = decoder.nextCodePointPointer(); + CodePoint codePoint = decoder.nextCodePoint(); + while (codePoint != KDCodePointNull && codePoint != c) { + currentPointer = nextPointer; + nextPointer = decoder.nextCodePointPointer(); + codePoint = decoder.nextCodePoint(); + } + if (codePoint == c) { + return currentPointer; + } + return nullptr; +} + +void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePoint c, size_t * indexToUpdate) { + UTF8Decoder decoder(src); + const char * currentPointer = src; + const char * nextPointer = decoder.nextCodePointPointer(); + const char * maxPointer = src + strlen(src) + 1; + CodePoint codePoint = decoder.nextCodePoint(); + size_t bufferIndex = 0; + size_t codePointCharSize = UTF8Decoder::CharSizeOfCodePoint(c); + + // Remove CodePoint c + while (currentPointer < maxPointer && bufferIndex < dstSize) { + if (codePoint != c) { + int copySize = min(nextPointer - currentPointer, dstSize - bufferIndex); + memcpy(dst + bufferIndex, currentPointer, copySize); + bufferIndex+= copySize; + } else if (indexToUpdate != nullptr && currentPointer - src < *indexToUpdate) { + assert(*indexToUpdate >= codePointCharSize); + *indexToUpdate-= codePointCharSize; + } + currentPointer = nextPointer; + nextPointer = decoder.nextCodePointPointer(); + codePoint = decoder.nextCodePoint(); + } +} + +}; diff --git a/poincare/src/expression.cpp b/poincare/src/expression.cpp index b08f8e3ff..044e6f9b5 100644 --- a/poincare/src/expression.cpp +++ b/poincare/src/expression.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -342,10 +343,9 @@ void Expression::SetEncounteredComplex(bool encounterComplex) { } Preferences::ComplexFormat Expression::UpdatedComplexFormatWithTextInput(Preferences::ComplexFormat complexFormat, const char * textInput) { - /* TODO LEA if (complexFormat == Preferences::ComplexFormat::Real && strchr(textInput, KDCodePointMathematicalBoldSmallI) != nullptr) { + if (complexFormat == Preferences::ComplexFormat::Real && UTF8Helper::CodePointSearch(textInput, KDCodePointMathematicalBoldSmallI) != nullptr) { return Preferences::ComplexFormat::Cartesian; } - */ return complexFormat; } diff --git a/poincare/src/layout_cursor.cpp b/poincare/src/layout_cursor.cpp index 9c29bd146..bdbfcb44b 100644 --- a/poincare/src/layout_cursor.cpp +++ b/poincare/src/layout_cursor.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace Poincare { @@ -136,47 +137,54 @@ void LayoutCursor::addXNTCodePointLayout() { } void LayoutCursor::insertText(const char * text) { -// TODO LEA -#if 0 - int textLength = strlen(text); - if (textLength <= 0) { - return; - } Layout newChild; Layout pointedChild; - for (int i = 0; i < textLength; i++) { - if (text[i] == //TODO Ion::Charset::Empty) { + UTF8Decoder decoder(text); + CodePoint codePoint = decoder.nextCodePoint(); + if (codePoint == KDCodePointNull) { + return; + } + assert(!codePoint.isCombining()); + while (codePoint != KDCodePointNull) { + if (codePoint == KDCodePointEmpty) { + codePoint = decoder.nextCodePoint(); + assert(!codePoint.isCombining()); continue; } - if (text[i] == //TODO Ion::Charset::MultiplicationSign) { + if (codePoint == KDCodePointMultiplicationSign) { newChild = CodePointLayout::Builder(KDCodePointMiddleDot); - } else*/ if (text[i] == '(') { + } else if (codePoint == '(') { newChild = LeftParenthesisLayout::Builder(); if (pointedChild.isUninitialized()) { pointedChild = newChild; } - } else if (text[i] == ')') { + } else if (codePoint == ')') { newChild = RightParenthesisLayout::Builder(); } /* We never insert text with brackets for now. Removing this code saves the * binary file 2K. */ #if 0 - else if (text[i] == '[') { + else if (codePoint == '[') { newChild = LeftSquareBracketLayout(); - } else if (text[i] == ']') { + } else if (codePoint == ']') { newChild = RightSquareBracketLayout(); } #endif else { - newChild = CodePointLayout::Builder(text[i]); + newChild = CodePointLayout::Builder(codePoint); } m_layout.addSibling(this, newChild, true); + + // Get the next code point + codePoint = decoder.nextCodePoint(); + while (codePoint.isCombining()) { + codePoint = decoder.nextCodePoint(); + } } if (!pointedChild.isUninitialized() && !pointedChild.parent().isUninitialized()) { m_layout = pointedChild; m_position = Position::Right; } -#endif } void LayoutCursor::addLayoutAndMoveCursor(Layout l) { diff --git a/poincare/src/layout_helper.cpp b/poincare/src/layout_helper.cpp index 9555eab3e..fd7919243 100644 --- a/poincare/src/layout_helper.cpp +++ b/poincare/src/layout_helper.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include namespace Poincare { @@ -56,9 +57,26 @@ Layout LayoutHelper::Parentheses(Layout layout, bool cloneLayout) { HorizontalLayout LayoutHelper::String(const char * buffer, int bufferLen, const KDFont * font) { assert(bufferLen > 0); HorizontalLayout resultLayout = HorizontalLayout::Builder(); - /* TODO LEA */ - for (int i = 0; i < bufferLen; i++) { - resultLayout.addChildAtIndex(CodePointLayout::Builder(buffer[i], font), i, i, nullptr); + UTF8Decoder decoder(buffer); + const char * currentPointer = buffer; + const char * nextPointer = decoder.nextCodePointPointer(); + CodePoint codePoint = decoder.nextCodePoint(); + assert(!codePoint.isCombining()); + int layoutIndex = 0; + int bufferIndex = 0; + while (codePoint != KDCodePointNull && bufferIndex < bufferLen) { + resultLayout.addChildAtIndex(CodePointLayout::Builder(codePoint, font), layoutIndex, layoutIndex, nullptr); + layoutIndex++; + bufferIndex+= nextPointer - currentPointer; + currentPointer = nextPointer; + nextPointer = decoder.nextCodePointPointer(); + codePoint = decoder.nextCodePoint(); + while (codePoint.isCombining()) { + bufferIndex+= nextPointer - currentPointer; + currentPointer = nextPointer; + nextPointer = decoder.nextCodePointPointer(); + codePoint = decoder.nextCodePoint(); + } } return resultLayout; } From 14426eea142e35fab2759cf20d0e4d3c891e5d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 17 Jan 2019 15:35:43 +0100 Subject: [PATCH 0312/1750] [parsing] Fix constant code point parsing --- poincare/src/parsing/parser.cpp | 3 ++- poincare/src/parsing/token.h | 5 ++++- poincare/src/parsing/tokenizer.cpp | 7 ++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index 0925f4f4a..4dee65c2f 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -1,4 +1,5 @@ #include "parser.h" +#include namespace Poincare { @@ -323,7 +324,7 @@ bool Parser::currentTokenIsSpecialIdentifier() const { } void Parser::parseConstant(Expression & leftHandSide, Token::Type stoppingType) { - leftHandSide = Constant::Builder(m_currentToken.text()[0]); + leftHandSide = Constant::Builder(m_currentToken.codePoint()); isThereImplicitMultiplication(); } diff --git a/poincare/src/parsing/token.h b/poincare/src/parsing/token.h index ea1eff7ab..83079df49 100644 --- a/poincare/src/parsing/token.h +++ b/poincare/src/parsing/token.h @@ -53,7 +53,7 @@ public: Undefined }; - Token(Type type) : m_type(type), m_text(0) {}; + Token(Type type) : m_type(type), m_text(0), m_codePoint(KDCodePointNull) {}; Type type() const { return m_type; } bool is(Type t) const { return m_type == t; } @@ -62,12 +62,14 @@ public: Expression expression() const { return m_expression; } const char * text() const { return m_text; } size_t length() const { return m_length; } + CodePoint codePoint() const { return m_codePoint; } void setExpression(Expression e) { m_expression = e; } void setString(const char * text, size_t length) { m_text = text; m_length = length; } + void setCodePoint(CodePoint c) { m_codePoint = c; } static int CompareNonNullTerminatedName(const char * nonNullTerminatedName, size_t nonNullTerminatedNameLength, const char * nullTerminatedName) { /* Compare m_text to name, similarly to strcmp, assuming * - m_text is not null-terminated @@ -84,6 +86,7 @@ private: Expression m_expression; const char * m_text; size_t m_length; + CodePoint m_codePoint; }; } diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index f5143f235..3ec666548 100644 --- a/poincare/src/parsing/tokenizer.cpp +++ b/poincare/src/parsing/tokenizer.cpp @@ -19,7 +19,7 @@ const CodePoint Tokenizer::nextCodePoint(PopTest popTest, CodePoint context, boo if (firstCodePoint != KDCodePointNull) { CodePoint codePoint = decoder.nextCodePoint(); while (codePoint.isCombining()) { - numberOfBytesForCodePoint = UTF8Decoder::CharSizeOfCodePoint(codePoint); + numberOfBytesForCodePoint+= UTF8Decoder::CharSizeOfCodePoint(codePoint); codePoint = decoder.nextCodePoint(); } } @@ -175,12 +175,13 @@ Token Tokenizer::popToken() { || c == KDCodePointScriptSmallE) { Token result(Token::Constant); - result.setString(start, 1); + result.setCodePoint(c); return result; } if (c == KDCodePointSquareRoot) { Token result(Token::Identifier); - result.setString(start, 1); + // TODO compute size manually? + result.setString(start, UTF8Decoder::CharSizeOfCodePoint(KDCodePointSquareRoot)); return result; } if (c == KDCodePointEmpty) { From 9d68560a32d8e4dee063cf96f32f7089a98d1c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 17 Jan 2019 17:14:34 +0100 Subject: [PATCH 0313/1750] [kandinsky] Remove obsolete values --- kandinsky/fonts/unicode_for_symbol.c | 3 --- kandinsky/fonts/unicode_for_symbol.h | 10 ---------- kandinsky/include/kandinsky/font.h | 2 -- 3 files changed, 15 deletions(-) delete mode 100644 kandinsky/fonts/unicode_for_symbol.c delete mode 100644 kandinsky/fonts/unicode_for_symbol.h diff --git a/kandinsky/fonts/unicode_for_symbol.c b/kandinsky/fonts/unicode_for_symbol.c deleted file mode 100644 index 9a1ad769e..000000000 --- a/kandinsky/fonts/unicode_for_symbol.c +++ /dev/null @@ -1,3 +0,0 @@ -#include "unicode_for_symbol.h" - -wchar_t codePointForSymbol[NUMBER_OF_SYMBOLS] = {0x222b, 0x0078, 0x0305, 0x0079, 0x0305, 0x0393, 0x0394, 0x03a3, 0x03b8, 0x03bb, 0x03bc, 0x03c0, 0x03c3, 0x0456, 0x1D07, 0x2032, 0x212e, 0x2192, 0x221A, 0x2264, 0x2265, 0x00D7, 0x00B7, 0x2248, 0x00B0}; diff --git a/kandinsky/fonts/unicode_for_symbol.h b/kandinsky/fonts/unicode_for_symbol.h deleted file mode 100644 index 1f1476c9d..000000000 --- a/kandinsky/fonts/unicode_for_symbol.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef KANDINSKY_FONT_UNICODE_H -#define KANDINSKY_FONT_UNICODE_H - -#include - -#define NUMBER_OF_SYMBOLS 25 - -extern wchar_t codePointForSymbol[NUMBER_OF_SYMBOLS]; - -#endif diff --git a/kandinsky/include/kandinsky/font.h b/kandinsky/include/kandinsky/font.h index 4af33758c..1e5c296be 100644 --- a/kandinsky/include/kandinsky/font.h +++ b/kandinsky/include/kandinsky/font.h @@ -87,8 +87,6 @@ private: KDSize m_glyphSize; const uint16_t * m_glyphDataOffset; const uint8_t * m_data; - static constexpr uint8_t k_magicCharOffsetValue = 0x20; // FIXME: Value from kandinsky/fonts/rasterizer.c (CHARACTER_RANGE_START). 0x20 because we do not want have a glyph for the first 20 ASCII characters - static constexpr uint8_t k_numberOfGlyphs = 120; // FIXME: Value from kandinsky/fonts/rasterizer.c (GLYPH_COUNT) }; #endif From f5b4a7473ec2e863e2fa7ba314811443238279e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 17 Jan 2019 17:15:48 +0100 Subject: [PATCH 0314/1750] [kandinsky] Fix CodePoint uint32_t cast operator --- kandinsky/include/kandinsky/unicode/code_point.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kandinsky/include/kandinsky/unicode/code_point.h b/kandinsky/include/kandinsky/unicode/code_point.h index e3648384d..7e885d028 100644 --- a/kandinsky/include/kandinsky/unicode/code_point.h +++ b/kandinsky/include/kandinsky/unicode/code_point.h @@ -7,7 +7,7 @@ class CodePoint { public: constexpr static int MaxCodePointCharLength = sizeof(uint32_t) / sizeof(char); constexpr CodePoint(uint32_t c) : m_code(c) {} - constexpr operator uint16_t() const { return m_code; } + constexpr operator uint32_t() const { return m_code; } bool isCombining() const { return (m_code >= 0x300 && m_code <= 0x036F); From 852303f321a44ae27b1363a716cdaca6f4fcaed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 17 Jan 2019 17:28:01 +0100 Subject: [PATCH 0315/1750] [kandinsky] Remove obsolete calls to files --- kandinsky/Makefile | 2 +- kandinsky/fonts/rasterizer.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/kandinsky/Makefile b/kandinsky/Makefile index 74c6772e7..65d536af8 100644 --- a/kandinsky/Makefile +++ b/kandinsky/Makefile @@ -41,7 +41,7 @@ endif $(eval $(call rule_for, \ HOSTCC, \ kandinsky/fonts/rasterizer, \ - kandinsky/fonts/rasterizer.c kandinsky/fonts/unicode_for_symbol.c $(addprefix ion/src/external/lz4/, lz4.c lz4hc.c), \ + kandinsky/fonts/rasterizer.c $(addprefix ion/src/external/lz4/, lz4.c lz4hc.c), \ $$(HOSTCC) $$(RASTERIZER_CFLAGS) $$^ $$(RASTERIZER_LDFLAGS) -o $$@ \ )) diff --git a/kandinsky/fonts/rasterizer.c b/kandinsky/fonts/rasterizer.c index 1990ba56c..708d027ca 100644 --- a/kandinsky/fonts/rasterizer.c +++ b/kandinsky/fonts/rasterizer.c @@ -15,7 +15,6 @@ #include #include FT_FREETYPE_H -#include "unicode_for_symbol.h" #include "code_points.h" #include "../../ion/src/external/lz4/lz4hc.h" From 780579265f8befbd2a088ea03a468758d053a62b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Thu, 17 Jan 2019 17:43:27 +0100 Subject: [PATCH 0316/1750] [kandinsky] Use new unicodes in fonts for Exponential, mu and i complex --- kandinsky/fonts/LargeSourcePixel.ttf | Bin 219468 -> 220768 bytes kandinsky/fonts/SmallSourcePixel.ttf | Bin 213592 -> 214268 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/kandinsky/fonts/LargeSourcePixel.ttf b/kandinsky/fonts/LargeSourcePixel.ttf index 9def301b4e5a05efc3c4589701a79058dc70cc81..6271613a06a8cebfa01146cd1be4651b8d5b267c 100644 GIT binary patch literal 220768 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

dG3Fn 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 Nip;+eW?1J~{}&yWxn2MO literal 219468 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

+3aHWQ14L+dB5Q$f(Oz%(%e>5^Ppx82lBl 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≪+K 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 LNdK`4lhpqL32ip9 diff --git a/kandinsky/fonts/SmallSourcePixel.ttf b/kandinsky/fonts/SmallSourcePixel.ttf index 766d3ff79a8d10d1011256c95ce94757e971c323..24a6760a83ac995a2360b5b31f9b4071fdcb91b4 100644 GIT binary patch literal 214268 zcmce<31D2+c`rWa&S*v&jb>l(+}ZbCvuj^%Nw#Ep8QU0d@x~G&j3I^)5-1QzQc5-+ z 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±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

u@7tl*xy%~ZxwdfQtO!SrC?|K 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 DCr2EJ From 217bbf024e90085d54f747b77f233269d4ea2f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 18 Jan 2019 09:41:07 +0100 Subject: [PATCH 0317/1750] [poincare] Clean parsing with unicodes --- poincare/src/parsing/parser.cpp | 23 ++++++++++++++--------- poincare/src/parsing/tokenizer.cpp | 16 ++++++++++++---- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/poincare/src/parsing/parser.cpp b/poincare/src/parsing/parser.cpp index 4dee65c2f..308c507f5 100644 --- a/poincare/src/parsing/parser.cpp +++ b/poincare/src/parsing/parser.cpp @@ -361,12 +361,14 @@ void Parser::parseSequence(Expression & leftHandSide, const char name, Token::Ty if (m_status != Status::Progress) { } else if (!popTokenIfType(rightDelimiter)) { m_status = Status::Error; // Right delimiter missing. - } else if (rank.isIdenticalTo(Symbol::Builder("n",1))) { - char sym[5] = {name, '(', 'n', ')', 0}; - leftHandSide = Symbol::Builder(sym, 4); - } else if (rank.isIdenticalTo(Addition::Builder(Symbol::Builder("n",1),Rational::Builder("1")))) { - char sym[7] = {name, '(', 'n', '+', '1', ')', 0}; - leftHandSide = Symbol::Builder(sym, 6); + } else if (rank.isIdenticalTo(Symbol::Builder('n'))) { + constexpr int symbolNameSize = 5; + char sym[symbolNameSize] = {name, '(', 'n', ')', 0}; + leftHandSide = Symbol::Builder(sym, symbolNameSize); + } else if (rank.isIdenticalTo(Addition::Builder(Symbol::Builder('n'), Rational::Builder("1")))) { + constexpr int symbolNameSize = 7; + char sym[symbolNameSize] = {name, '(', 'n', '+', '1', ')', 0}; + leftHandSide = Symbol::Builder(sym, symbolNameSize); } else { m_status = Status::Error; // Unexpected parameter. } @@ -383,8 +385,12 @@ void Parser::parseSpecialIdentifier(Expression & leftHandSide) { } else if (m_currentToken.compareTo(Unreal::Name()) == 0) { leftHandSide = Unreal::Builder(); } else if (m_currentToken.compareTo("u_") == 0 || m_currentToken.compareTo("v_") == 0) { // Special case for sequences (e.g. "u_{n}") + /* We now that m_currentToken.text()[0] is either 'u' or 'v', so we do not + * need to pass a code point to parseSequence. */ parseSequence(leftHandSide, m_currentToken.text()[0], Token::LeftBrace, Token::RightBrace); } else if (m_currentToken.compareTo("u") == 0 || m_currentToken.compareTo("v") == 0) { // Special case for sequences (e.g. "u(n)") + /* We now that m_currentToken.text()[0] is either 'u' or 'v', so we do not + * need to pass a code point to parseSequence. */ parseSequence(leftHandSide, m_currentToken.text()[0], Token::LeftParenthesis, Token::RightParenthesis); } else if (m_currentToken.compareTo("log_") == 0) { // Special case for the log function (e.g. "log_{2}(8)") if (!popTokenIfType(Token::LeftBrace)) { @@ -426,7 +432,7 @@ void Parser::parseCustomIdentifier(Expression & leftHandSide, const char * name, return; } parameter = parameter.childAtIndex(0); - if (parameter.type() == ExpressionNode::Type::Symbol && strncmp(static_cast(parameter).name(),name, length) == 0) { + if (parameter.type() == ExpressionNode::Type::Symbol && strncmp(static_cast(parameter).name(), name, length) == 0) { m_status = Status::Error; // Function and variable must have distinct names. } else if (!popTokenIfType(Token::RightParenthesis)) { m_status = Status::Error; // Right parenthesis missing. @@ -487,8 +493,7 @@ void Parser::parseMatrix(Expression & leftHandSide, Token::Type stoppingType) { return; } if ((numberOfRows == 0 && (numberOfColumns = row.numberOfChildren()) == 0) - || - (numberOfColumns != row.numberOfChildren())) { + || (numberOfColumns != row.numberOfChildren())) { m_status = Status::Error; // Incorrect matrix. return; } else { diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index 3ec666548..d1812f48d 100644 --- a/poincare/src/parsing/tokenizer.cpp +++ b/poincare/src/parsing/tokenizer.cpp @@ -14,16 +14,22 @@ static inline bool isDigit(const CodePoint c) { const CodePoint Tokenizer::nextCodePoint(PopTest popTest, CodePoint context, bool * testResult) { UTF8Decoder decoder(m_text); + const char * currentPointer = m_text; + const char * nextPointer = decoder.nextCodePointPointer(); CodePoint firstCodePoint = decoder.nextCodePoint(); - size_t numberOfBytesForCodePoint = UTF8Decoder::CharSizeOfCodePoint(firstCodePoint); + size_t numberOfBytesForCodePoint = nextPointer - currentPointer; if (firstCodePoint != KDCodePointNull) { + currentPointer = nextPointer; + nextPointer = decoder.nextCodePointPointer(); CodePoint codePoint = decoder.nextCodePoint(); while (codePoint.isCombining()) { - numberOfBytesForCodePoint+= UTF8Decoder::CharSizeOfCodePoint(codePoint); + numberOfBytesForCodePoint+= nextPointer - currentPointer; + currentPointer = nextPointer; + nextPointer = decoder.nextCodePointPointer(); codePoint = decoder.nextCodePoint(); } } - // TODO handle combined code points? + // TODO handle combined code points? For now the combining codepoints get dropped. bool shouldPop = popTest(firstCodePoint, context); if (testResult != nullptr) { *testResult = shouldPop; @@ -181,7 +187,9 @@ Token Tokenizer::popToken() { if (c == KDCodePointSquareRoot) { Token result(Token::Identifier); // TODO compute size manually? - result.setString(start, UTF8Decoder::CharSizeOfCodePoint(KDCodePointSquareRoot)); + constexpr int squareRootCharLength = 3; + assert(UTF8Decoder::CharSizeOfCodePoint(KDCodePointSquareRoot) == squareRootCharLength); + result.setString(start, squareRootCharLength); return result; } if (c == KDCodePointEmpty) { From acb169a9d4ffca088c7ea383d1f3627485f21138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 18 Jan 2019 10:07:16 +0100 Subject: [PATCH 0318/1750] [poincare/serialization_helper] Clean CodePoint --- poincare/src/serialization_helper.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/poincare/src/serialization_helper.cpp b/poincare/src/serialization_helper.cpp index 953013948..71cb9dc39 100644 --- a/poincare/src/serialization_helper.cpp +++ b/poincare/src/serialization_helper.cpp @@ -158,12 +158,10 @@ int SerializationHelper::CodePoint(char * buffer, int bufferSize, class CodePoin buffer[0] = 0; return 0; } - char helpBuffer[MaxSerializedCodePointSize]; - size_t size = UTF8Decoder::CodePointToChars(c, helpBuffer, MaxSerializedCodePointSize); - assert(size < MaxSerializedCodePointSize); - helpBuffer[size] = 0; - strlcpy(buffer, helpBuffer, bufferSize); - return strlen(buffer); + size_t size = UTF8Decoder::CodePointToChars(c, buffer, bufferSize); + int nullTerminatingIndex = min(size, bufferSize - 1); + buffer[nullTerminatingIndex] = 0; + return nullTerminatingIndex; } } From 66898b207aa697b84adc6064f79ea115309bd049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 18 Jan 2019 09:58:24 +0100 Subject: [PATCH 0319/1750] [poincare/serialization_helper] Clean --- .../include/poincare/serialization_helper.h | 7 +- poincare/src/constant.cpp | 2 +- poincare/src/serialization_helper.cpp | 110 +++++++++++------- 3 files changed, 78 insertions(+), 41 deletions(-) diff --git a/poincare/include/poincare/serialization_helper.h b/poincare/include/poincare/serialization_helper.h index e0ed71193..061e587ce 100644 --- a/poincare/include/poincare/serialization_helper.h +++ b/poincare/include/poincare/serialization_helper.h @@ -6,6 +6,12 @@ namespace Poincare { +/* The serialization methods write their argument as a string in the given + * buffer, with a null-terminating 0. The return value is: + * -> -1 if the buffer size is 0 + * -> Otherwise, the number of chars written, without the null terminating 0 + */ + namespace SerializationHelper { // SerializableReference to text int Infix( @@ -30,7 +36,6 @@ namespace SerializationHelper { // Write one char in a buffer int Char(char * buffer, int bufferSize, char c); // Write one code point in a buffer - constexpr int MaxSerializedCodePointSize = CodePoint::MaxCodePointCharLength + 1; // Null-terminating char int CodePoint(char * buffer, int bufferSize, CodePoint c); }; diff --git a/poincare/src/constant.cpp b/poincare/src/constant.cpp index 788cf5135..329babeda 100644 --- a/poincare/src/constant.cpp +++ b/poincare/src/constant.cpp @@ -93,7 +93,7 @@ bool ConstantNode::isConstantCodePoint(CodePoint c) const { } Constant Constant::Builder(CodePoint c) { - constexpr int bufferSize = SerializationHelper::MaxSerializedCodePointSize; + constexpr int bufferSize = CodePoint::MaxCodePointCharLength + 1; char buffer[bufferSize]; size_t codePointSize = SerializationHelper::CodePoint(buffer, bufferSize, c); return SymbolAbstract::Builder(buffer, codePointSize); diff --git a/poincare/src/serialization_helper.cpp b/poincare/src/serialization_helper.cpp index 71cb9dc39..f77ddd247 100644 --- a/poincare/src/serialization_helper.cpp +++ b/poincare/src/serialization_helper.cpp @@ -5,24 +5,57 @@ namespace Poincare { -static void serializeChild(const TreeNode * childNode, const TreeNode * parentNode, char * buffer, int bufferSize, Preferences::PrintFloatMode floatDisplayMode, int numberOfDigits, int * numberOfChar) { +static inline int min(int x, int y) { return x < y ? x : y; } + +static bool checkBufferSize(char * buffer, int bufferSize, int * result) { + // If buffer has size 0 or 1, put a zero if it fits and return + if (bufferSize == 0) { + *result = -1; + return true; + } + + buffer[bufferSize-1] = 0; // Null-terminate the buffer + if (bufferSize == 1) { + *result = 0; + return true; + } + return false; +} + +static int serializeChild( + const TreeNode * childNode, + const TreeNode * parentNode, + char * buffer, + int bufferSize, + Preferences::PrintFloatMode floatDisplayMode, + int numberOfDigits) +{ + { + int result = 0; + if (checkBufferSize(buffer, bufferSize, &result)) { + return result; + } + } + + int numberOfChar = 0; // Write the child with parentheses if needed bool addParentheses = parentNode->childNeedsParenthesis(childNode); if (addParentheses) { - buffer[*numberOfChar] = '('; - *numberOfChar = *numberOfChar + 1; - if (*numberOfChar >= bufferSize-1) { - return; + buffer[numberOfChar++] = '('; + if (numberOfChar >= bufferSize-1) { + return bufferSize-1; } } - *numberOfChar += childNode->serialize(buffer + *numberOfChar, bufferSize - *numberOfChar, floatDisplayMode, numberOfDigits); - if (*numberOfChar >= bufferSize-1) { - return; + numberOfChar+= childNode->serialize(buffer + numberOfChar, bufferSize - numberOfChar, floatDisplayMode, numberOfDigits); + if (numberOfChar >= bufferSize-1) { + assert(buffer[bufferSize - 1] == 0); + return bufferSize-1; } if (addParentheses) { - buffer[*numberOfChar] = ')'; - *numberOfChar = *numberOfChar + 1; + buffer[numberOfChar++] = ')'; } + buffer[numberOfChar] = 0; + return numberOfChar; } int SerializationHelper::Infix( @@ -35,14 +68,11 @@ int SerializationHelper::Infix( int firstChildIndex, int lastChildIndex) { - // If buffer has size 0 or 1, put a zero if it fits and return - if (bufferSize == 0) { - return -1; - } - - buffer[bufferSize-1] = 0; // Null-terminate the buffer - if (bufferSize == 1) { - return 0; + { + int result = 0; + if (checkBufferSize(buffer, bufferSize, &result)) { + return result; + } } // Get some information on the node @@ -51,8 +81,9 @@ int SerializationHelper::Infix( assert(numberOfChildren > 0); // Write the first child, with parentheses if needed - serializeChild(node->childAtIndex(firstChildIndex), node, buffer, bufferSize, floatDisplayMode, numberOfDigits, &numberOfChar); + numberOfChar+= serializeChild(node->childAtIndex(firstChildIndex), node, buffer + numberOfChar, bufferSize - numberOfChar, floatDisplayMode, numberOfDigits); if (numberOfChar >= bufferSize-1) { + assert(buffer[bufferSize - 1] == 0); return bufferSize-1; } // For all remaining children: @@ -61,11 +92,13 @@ int SerializationHelper::Infix( // Write the operator numberOfChar += strlcpy(buffer+numberOfChar, operatorName, bufferSize-numberOfChar); if (numberOfChar >= bufferSize-1) { + assert(buffer[bufferSize - 1] == 0); return bufferSize-1; } // Write the child, with parentheses if needed - serializeChild(node->childAtIndex(i), node, buffer, bufferSize, floatDisplayMode, numberOfDigits, &numberOfChar); + numberOfChar+= serializeChild(node->childAtIndex(i), node, buffer + numberOfChar, bufferSize - numberOfChar, floatDisplayMode, numberOfDigits); if (numberOfChar >= bufferSize-1) { + assert(buffer[bufferSize - 1] == 0); return bufferSize-1; } } @@ -84,18 +117,17 @@ int SerializationHelper::Prefix( const char * operatorName, bool writeFirstChild) { - // If buffer has size 0 or 1, put a zero if it fits and return - if (bufferSize == 0) { - return -1; - } - buffer[bufferSize-1] = 0; - if (bufferSize == 1) { - return 0; + { + int result = 0; + if (checkBufferSize(buffer, bufferSize, &result)) { + return result; + } } // Copy the operator name int numberOfChar = strlcpy(buffer, operatorName, bufferSize); if (numberOfChar >= bufferSize-1) { + assert(buffer[bufferSize - 1] == 0); return bufferSize-1; } @@ -115,6 +147,7 @@ int SerializationHelper::Prefix( // Write the first child numberOfChar += node->childAtIndex(firstChildIndex)->serialize(buffer+numberOfChar, bufferSize-numberOfChar, floatDisplayMode, numberOfDigits); if (numberOfChar >= bufferSize-1) { + assert(buffer[bufferSize - 1] == 0); return bufferSize-1; } @@ -126,6 +159,7 @@ int SerializationHelper::Prefix( } numberOfChar += node->childAtIndex(i)->serialize(buffer+numberOfChar, bufferSize-numberOfChar, floatDisplayMode, numberOfDigits); if (numberOfChar >= bufferSize-1) { + assert(buffer[bufferSize - 1] == 0); return bufferSize-1; } } @@ -138,12 +172,11 @@ int SerializationHelper::Prefix( } int SerializationHelper::Char(char * buffer, int bufferSize, char c) { - if (bufferSize == 0) { - return -1; - } - if (bufferSize == 1) { - buffer[0] = 0; - return 0; + { + int result = 0; + if (checkBufferSize(buffer, bufferSize, &result)) { + return result; + } } buffer[0] = c; buffer[1] = 0; @@ -151,12 +184,11 @@ int SerializationHelper::Char(char * buffer, int bufferSize, char c) { } int SerializationHelper::CodePoint(char * buffer, int bufferSize, class CodePoint c) { - if (bufferSize == 0) { - return -1; - } - if (bufferSize == 1) { - buffer[0] = 0; - return 0; + { + int result = 0; + if (checkBufferSize(buffer, bufferSize, &result)) { + return result; + } } size_t size = UTF8Decoder::CodePointToChars(c, buffer, bufferSize); int nullTerminatingIndex = min(size, bufferSize - 1); From f90e709201abc2fc40e9381366800ea18ac2e297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 18 Jan 2019 10:29:10 +0100 Subject: [PATCH 0320/1750] [poincare/utf8_decoder] nextCodePointPointer is now stringPosition --- apps/shared/toolbox_helpers.cpp | 4 ++-- escher/src/text_input_helpers.cpp | 6 +++--- kandinsky/include/kandinsky/unicode/utf8_decoder.h | 4 +--- kandinsky/src/unicode/utf8_decoder.cpp | 4 ---- kandinsky/src/unicode/utf8_helper.cpp | 8 ++++---- poincare/src/layout_helper.cpp | 6 +++--- poincare/src/parsing/tokenizer.cpp | 6 +++--- 7 files changed, 16 insertions(+), 22 deletions(-) diff --git a/apps/shared/toolbox_helpers.cpp b/apps/shared/toolbox_helpers.cpp index 9952847f6..10fb4a95b 100644 --- a/apps/shared/toolbox_helpers.cpp +++ b/apps/shared/toolbox_helpers.cpp @@ -11,8 +11,8 @@ int CursorIndexInCommandText(const char * text) { UTF8Decoder decoder(text); size_t index = 0; const char * currentPointer = text; - const char * nextPointer = decoder.nextCodePointPointer(); CodePoint codePoint = decoder.nextCodePoint(); + const char * nextPointer = decoder.stringPosition(); while (codePoint != KDCodePointNull) { if (codePoint == '(' || codePoint == '\'') { return index + 1; @@ -22,8 +22,8 @@ int CursorIndexInCommandText(const char * text) { } index+= nextPointer - currentPointer; currentPointer = nextPointer; - nextPointer = decoder.nextCodePointPointer(); codePoint = decoder.nextCodePoint(); + nextPointer = decoder.stringPosition(); } return index; } diff --git a/escher/src/text_input_helpers.cpp b/escher/src/text_input_helpers.cpp index d6d39c632..5a653c3d3 100644 --- a/escher/src/text_input_helpers.cpp +++ b/escher/src/text_input_helpers.cpp @@ -8,8 +8,8 @@ size_t CursorIndexInCommand(const char * text) { size_t index = 0; UTF8Decoder decoder(text); const char * currentPointer = text; - const char * nextPointer = decoder.nextCodePointPointer(); CodePoint codePoint = decoder.nextCodePoint(); + const char * nextPointer = decoder.stringPosition(); while (codePoint != KDCodePointNull) { if (codePoint == KDCodePointEmpty) { return index; @@ -18,8 +18,8 @@ size_t CursorIndexInCommand(const char * text) { if (codePoint == '\'') { index+= nextPointer - currentPointer; currentPointer = nextPointer; - nextPointer = decoder.nextCodePointPointer(); codePoint = decoder.nextCodePoint(); + nextPointer = decoder.stringPosition(); if (codePoint == '\'') { return index; } @@ -28,8 +28,8 @@ size_t CursorIndexInCommand(const char * text) { } index+= nextPointer - currentPointer; currentPointer = nextPointer; - nextPointer = decoder.nextCodePointPointer(); codePoint = decoder.nextCodePoint(); + nextPointer = decoder.stringPosition(); } return index; } diff --git a/kandinsky/include/kandinsky/unicode/utf8_decoder.h b/kandinsky/include/kandinsky/unicode/utf8_decoder.h index 1f52cccb4..084765b9f 100644 --- a/kandinsky/include/kandinsky/unicode/utf8_decoder.h +++ b/kandinsky/include/kandinsky/unicode/utf8_decoder.h @@ -18,10 +18,8 @@ class UTF8Decoder { public: UTF8Decoder(const char * string) : m_string(string) {} - /* TODO: Rename methods? nextCodePoint increases m_string but - * nextCodePointPointer does not */ CodePoint nextCodePoint(); - const char * nextCodePointPointer(); + const char * stringPosition() const { return m_string; } static size_t CharSizeOfCodePoint(CodePoint c); static size_t CodePointToChars(CodePoint c, char * buffer, int bufferSize); private: diff --git a/kandinsky/src/unicode/utf8_decoder.cpp b/kandinsky/src/unicode/utf8_decoder.cpp index 288fcea92..5b23cbdb2 100644 --- a/kandinsky/src/unicode/utf8_decoder.cpp +++ b/kandinsky/src/unicode/utf8_decoder.cpp @@ -25,10 +25,6 @@ CodePoint UTF8Decoder::nextCodePoint() { return CodePoint(result); } -const char * UTF8Decoder::nextCodePointPointer() { - return m_string + leading_ones(*m_string); -} - size_t UTF8Decoder::CharSizeOfCodePoint(CodePoint c) { constexpr int bufferSize = CodePoint::MaxCodePointCharLength; char buffer[bufferSize]; diff --git a/kandinsky/src/unicode/utf8_helper.cpp b/kandinsky/src/unicode/utf8_helper.cpp index 3293e14fd..da2d47f2b 100644 --- a/kandinsky/src/unicode/utf8_helper.cpp +++ b/kandinsky/src/unicode/utf8_helper.cpp @@ -10,12 +10,12 @@ static inline int min(int x, int y) { return x < y ? x : y; } const char * CodePointSearch(const char * s, CodePoint c) { UTF8Decoder decoder(s); const char * currentPointer = s; - const char * nextPointer = decoder.nextCodePointPointer(); CodePoint codePoint = decoder.nextCodePoint(); + const char * nextPointer = decoder.stringPosition(); while (codePoint != KDCodePointNull && codePoint != c) { currentPointer = nextPointer; - nextPointer = decoder.nextCodePointPointer(); codePoint = decoder.nextCodePoint(); + nextPointer = decoder.stringPosition(); } if (codePoint == c) { return currentPointer; @@ -26,9 +26,9 @@ const char * CodePointSearch(const char * s, CodePoint c) { void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePoint c, size_t * indexToUpdate) { UTF8Decoder decoder(src); const char * currentPointer = src; - const char * nextPointer = decoder.nextCodePointPointer(); const char * maxPointer = src + strlen(src) + 1; CodePoint codePoint = decoder.nextCodePoint(); + const char * nextPointer = decoder.stringPosition(); size_t bufferIndex = 0; size_t codePointCharSize = UTF8Decoder::CharSizeOfCodePoint(c); @@ -43,8 +43,8 @@ void CopyAndRemoveCodePoint(char * dst, size_t dstSize, const char * src, CodePo *indexToUpdate-= codePointCharSize; } currentPointer = nextPointer; - nextPointer = decoder.nextCodePointPointer(); codePoint = decoder.nextCodePoint(); + nextPointer = decoder.stringPosition(); } } diff --git a/poincare/src/layout_helper.cpp b/poincare/src/layout_helper.cpp index fd7919243..2b41a2b45 100644 --- a/poincare/src/layout_helper.cpp +++ b/poincare/src/layout_helper.cpp @@ -59,8 +59,8 @@ HorizontalLayout LayoutHelper::String(const char * buffer, int bufferLen, const HorizontalLayout resultLayout = HorizontalLayout::Builder(); UTF8Decoder decoder(buffer); const char * currentPointer = buffer; - const char * nextPointer = decoder.nextCodePointPointer(); CodePoint codePoint = decoder.nextCodePoint(); + const char * nextPointer = decoder.stringPosition(); assert(!codePoint.isCombining()); int layoutIndex = 0; int bufferIndex = 0; @@ -69,13 +69,13 @@ HorizontalLayout LayoutHelper::String(const char * buffer, int bufferLen, const layoutIndex++; bufferIndex+= nextPointer - currentPointer; currentPointer = nextPointer; - nextPointer = decoder.nextCodePointPointer(); codePoint = decoder.nextCodePoint(); + nextPointer = decoder.stringPosition(); while (codePoint.isCombining()) { bufferIndex+= nextPointer - currentPointer; currentPointer = nextPointer; - nextPointer = decoder.nextCodePointPointer(); codePoint = decoder.nextCodePoint(); + nextPointer = decoder.stringPosition(); } } return resultLayout; diff --git a/poincare/src/parsing/tokenizer.cpp b/poincare/src/parsing/tokenizer.cpp index d1812f48d..ea30387a4 100644 --- a/poincare/src/parsing/tokenizer.cpp +++ b/poincare/src/parsing/tokenizer.cpp @@ -15,18 +15,18 @@ static inline bool isDigit(const CodePoint c) { const CodePoint Tokenizer::nextCodePoint(PopTest popTest, CodePoint context, bool * testResult) { UTF8Decoder decoder(m_text); const char * currentPointer = m_text; - const char * nextPointer = decoder.nextCodePointPointer(); CodePoint firstCodePoint = decoder.nextCodePoint(); + const char * nextPointer = decoder.stringPosition(); size_t numberOfBytesForCodePoint = nextPointer - currentPointer; if (firstCodePoint != KDCodePointNull) { currentPointer = nextPointer; - nextPointer = decoder.nextCodePointPointer(); CodePoint codePoint = decoder.nextCodePoint(); + nextPointer = decoder.stringPosition(); while (codePoint.isCombining()) { numberOfBytesForCodePoint+= nextPointer - currentPointer; currentPointer = nextPointer; - nextPointer = decoder.nextCodePointPointer(); codePoint = decoder.nextCodePoint(); + nextPointer = decoder.stringPosition(); } } // TODO handle combined code points? For now the combining codepoints get dropped. From 242bcda631418c4e6693253e0796689a2473922f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 18 Jan 2019 10:54:21 +0100 Subject: [PATCH 0321/1750] [escher] Clean text_input --- escher/include/escher/text_input.h | 20 ++++++++++----- escher/src/text_input.cpp | 40 ++++++++---------------------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index 826b92d21..f4a705c77 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -1,14 +1,14 @@ #ifndef ESCHER_TEXT_INPUT_H #define ESCHER_TEXT_INPUT_H -#include -#include #include #include +#include +#include class TextInput : public ScrollableView, public ScrollViewDataSource { public: - TextInput(Responder * parentResponder, View * contentView); + TextInput(Responder * parentResponder, View * contentView) : ScrollableView(parentResponder, contentView, this) {} void setFont(const KDFont * font) { contentView()->setFont(font); } const char * text() const { return nonEditableContentView()->text(); } bool removeChar(); @@ -18,7 +18,12 @@ public: protected: class ContentView : public View { public: - ContentView(const KDFont * font); + ContentView(const KDFont * font) : + View(), + m_cursorView(), + m_font(font), + m_cursorIndex(0) + {} void setFont(const KDFont * font); const KDFont * font() const { return m_font; } size_t cursorLocation() const { return m_cursorIndex; } @@ -37,8 +42,11 @@ protected: const KDFont * m_font; size_t m_cursorIndex; private: - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; + int numberOfSubviews() const override { return 1; } + View * subviewAtIndex(int index) override { + assert(index == 1); + return &m_cursorView; + } virtual size_t editedTextLength() const = 0; }; protected: diff --git a/escher/src/text_input.cpp b/escher/src/text_input.cpp index 51c7d6dfd..dee7c1f79 100644 --- a/escher/src/text_input.cpp +++ b/escher/src/text_input.cpp @@ -3,14 +3,6 @@ /* TextInput::ContentView */ -TextInput::ContentView::ContentView(const KDFont * font) : - View(), - m_cursorView(), - m_font(font), - m_cursorIndex(0) -{ -} - void TextInput::ContentView::setCursorLocation(int location) { assert(location >= 0); int adjustedLocation = location > (signed int)editedTextLength() ? (signed int)editedTextLength() : location; @@ -27,14 +19,6 @@ KDRect TextInput::ContentView::cursorRect() { return characterFrameAtIndex(m_cursorIndex); } -int TextInput::ContentView::numberOfSubviews() const { - return 1; -} - -View * TextInput::ContentView::subviewAtIndex(int index) { - return &m_cursorView; -} - void TextInput::ContentView::layoutSubviews() { m_cursorView.setFrame(cursorRect()); } @@ -43,7 +27,7 @@ KDRect TextInput::ContentView::dirtyRectFromCursorPosition(size_t index, bool li KDRect charRect = characterFrameAtIndex(index); KDRect dirtyRect = KDRect(charRect.x(), charRect.y(), bounds().width() - charRect.x(), charRect.height()); if (lineBreak) { - dirtyRect = dirtyRect.unionedWith(KDRect(0, charRect.bottom()+1, bounds().width(), bounds().height()-charRect.bottom()-1)); + dirtyRect = dirtyRect.unionedWith(KDRect(0, charRect.bottom()+1, bounds().width(), bounds().height()-charRect.bottom()-1)); } return dirtyRect; } @@ -54,11 +38,6 @@ void TextInput::ContentView::reloadRectFromCursorPosition(size_t index, bool lin /* TextInput */ -TextInput::TextInput(Responder * parentResponder, View * contentView) : - ScrollableView(parentResponder, contentView, this) -{ -} - bool TextInput::removeChar() { contentView()->removeChar(); scrollToCursor(); @@ -66,13 +45,14 @@ bool TextInput::removeChar() { } void TextInput::scrollToCursor() { - /* Technically, we do not need to overscroll in text input. However, - * logically, we should layout the scroll view before calling - * scrollToContentRect in case the size of the scroll view has changed and - * then call scrollToContentRect which call another layout of the scroll view - * if the offset has evolved. In order to avoid requiring two layouts, we - * allow overscrolling in scrollToContentRect and the last layout of the - * scroll view corrects the size of the scroll view only once. */ + /* Technically, we do not need to overscroll in text input. However, we should + * layout the scroll view before calling scrollToContentRect (in case the size + * of the scroll view has changed) and then call scrollToContentRect which + * calls another layout of the scroll view if the offset has evolved. + * + * In order to avoid requiring two layouts, we allow overscrolling in + * scrollToContentRect, and the last layout of the scroll view corrects the + * size of the scroll view only once. */ scrollToContentRect(contentView()->cursorRect(), true); } @@ -87,7 +67,7 @@ bool TextInput::setCursorLocation(int location) { bool TextInput::insertTextAtLocation(const char * text, int location) { if (contentView()->insertTextAtLocation(text, location)) { /* We layout the scrollable view before scrolling to cursor because the - * content size might have changed. */ + * content size might have changed. */ layoutSubviews(); scrollToCursor(); return true; From 5142c071df8a04c328806fc5f8508d74bfe4a8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9a=20Saviot?= Date: Fri, 18 Jan 2019 16:41:39 +0100 Subject: [PATCH 0322/1750] [escher] Fix text inputs so they use UTF8 --- apps/code/editor_controller.cpp | 61 ++-- apps/code/editor_controller.h | 2 +- apps/code/editor_view.h | 4 +- apps/code/menu_controller.cpp | 15 +- apps/code/python_text_area.cpp | 6 +- apps/code/python_text_area.h | 2 +- apps/probability/calculation_controller.cpp | 7 +- .../editable_cell_table_view_controller.cpp | 4 +- apps/shared/text_field_with_extension.cpp | 23 +- apps/shared/text_field_with_extension.h | 2 +- escher/include/escher/text_area.h | 28 +- escher/include/escher/text_field.h | 19 +- escher/include/escher/text_input.h | 30 +- escher/include/escher/text_input_helpers.h | 10 +- escher/src/text_area.cpp | 304 +++++++++++------- escher/src/text_field.cpp | 202 +++++++----- escher/src/text_input.cpp | 53 +-- escher/src/text_input_helpers.cpp | 18 +- kandinsky/include/kandinsky/font.h | 5 +- .../include/kandinsky/unicode/utf8_decoder.h | 16 +- .../include/kandinsky/unicode/utf8_helper.h | 2 +- kandinsky/src/font.cpp | 11 +- kandinsky/src/unicode/utf8_decoder.cpp | 38 ++- kandinsky/src/unicode/utf8_helper.cpp | 8 +- 24 files changed, 510 insertions(+), 360 deletions(-) diff --git a/apps/code/editor_controller.cpp b/apps/code/editor_controller.cpp index 51f1f2397..11a22cb07 100644 --- a/apps/code/editor_controller.cpp +++ b/apps/code/editor_controller.cpp @@ -44,7 +44,7 @@ void EditorController::didBecomeFirstResponder() { void EditorController::viewWillAppear() { m_editorView.loadSyntaxHighlighter(); - m_editorView.setCursorLocation(strlen(m_editorView.text())); + m_editorView.setCursorTextLocation(m_editorView.text() + strlen(m_editorView.text())); } void EditorController::viewDidDisappear() { @@ -53,7 +53,8 @@ void EditorController::viewDidDisappear() { bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events::Event event) { if (event == Ion::Events::Var) { - // We save script before displaying the Variable box to add new functions or variables + /* We save the script before displaying the Variable box to add new + * functions or variables. */ saveScript(); return false; } @@ -61,60 +62,38 @@ bool EditorController::textAreaDidReceiveEvent(TextArea * textArea, Ion::Events: return true; } if (event == Ion::Events::EXE) { - // Auto-Indent - char * text = const_cast(textArea->text()); - int charBeforeCursorIndex = textArea->cursorLocation()-1; - int indentationSize = 0; - // Indent more if the previous line ends with ':'. - if (charBeforeCursorIndex >= 0 && text[charBeforeCursorIndex] == ':') { - indentationSize += k_indentationSpacesNumber; - } - // Compute the indentation of the current line. - int indentationIndex = charBeforeCursorIndex; - while (indentationIndex >= 0 && text[indentationIndex] != '\n') { - indentationIndex--; - } - if (indentationIndex >= 0) { - indentationIndex++; - while (text[indentationIndex] == ' ') { - indentationSize++; - indentationIndex++; - } - } - textArea->handleEventWithText("\n"); - for (int i = 0; i < indentationSize; i++) { - textArea->handleEventWithText(" "); - } + textArea->handleEventWithText("\n", true, false); return true; } if (event == Ion::Events::Backspace) { - // If the cursor is on the left of the text of a line, - // backspace one intentation space at a time. + /* If the cursor is on the left of the text of a line, backspace one + * indentation space at a time. */ char * text = const_cast(textArea->text()); - int charBeforeCursorIndex = textArea->cursorLocation()-1; + const char * charBeforeCursorPointer = textArea->cursorTextLocation()-1; int indentationSize = 0; - while (charBeforeCursorIndex >= 0 && text[charBeforeCursorIndex] == ' ') { - charBeforeCursorIndex--; + while (charBeforeCursorPointer >= text && *charBeforeCursorPointer == ' ') { + charBeforeCursorPointer--; indentationSize++; } - if (charBeforeCursorIndex >= 0 && text[charBeforeCursorIndex] == '\n' + if (charBeforeCursorPointer >= text + && *charBeforeCursorPointer == '\n' && indentationSize >= k_indentationSpacesNumber) { for (int i = 0; i < k_indentationSpacesNumber; i++) { - textArea->removeChar(); + textArea->removeCodePoint(); } return true; } } else if (event == Ion::Events::Space) { - // If the cursor is on the left of the text of a line, - // a space triggers an indentation. + /* If the cursor is on the left of the text of a line, a space triggers an + * indentation. */ char * text = const_cast(textArea->text()); - int charBeforeCursorIndex = textArea->cursorLocation()-1; - while (charBeforeCursorIndex >= 0 && text[charBeforeCursorIndex] == ' ') { - charBeforeCursorIndex--; + const char * charBeforeCursorPointer = textArea->cursorTextLocation()-1; + while (charBeforeCursorPointer >= text && *charBeforeCursorPointer == ' ') { + charBeforeCursorPointer--; } - if (charBeforeCursorIndex >= 0 && text[charBeforeCursorIndex] == '\n') { + if (charBeforeCursorPointer >= text && *charBeforeCursorPointer == '\n') { char indentationBuffer[k_indentationSpacesNumber+1]; for (int i = 0; i < k_indentationSpacesNumber; i++) { indentationBuffer[i] = ' '; @@ -144,9 +123,7 @@ StackViewController * EditorController::stackController() { void EditorController::saveScript() { size_t sizeOfValue = strlen(m_areaBuffer+1)+1+1; // size of scriptContent + size of importation status Script::ErrorStatus err = m_script.setValue({.buffer=m_areaBuffer, .size=sizeOfValue}); - if (err == Script::ErrorStatus::NotEnoughSpaceAvailable || err == Script::ErrorStatus::RecordDoesNotExist) { - assert(false); // This should not happen as we set the text area according to the available space in the Kallax - } + assert(err != Script::ErrorStatus::NotEnoughSpaceAvailable && err != Script::ErrorStatus::RecordDoesNotExist); // This should not happen as we set the text area according to the available space in the Kallax } } diff --git a/apps/code/editor_controller.h b/apps/code/editor_controller.h index 34e3eadab..93e3adb60 100644 --- a/apps/code/editor_controller.h +++ b/apps/code/editor_controller.h @@ -34,7 +34,7 @@ public: private: Shared::InputEventHandlerDelegateApp * inputEventHandlerDelegateApp() override; - static constexpr int k_indentationSpacesNumber = 2; + static constexpr int k_indentationSpacesNumber = 2; //TODO LEA merge with text area k_indentationSpaces StackViewController * stackController(); void saveScript(); EditorView m_editorView; diff --git a/apps/code/editor_view.h b/apps/code/editor_view.h index 16cb0749c..170fc9b54 100644 --- a/apps/code/editor_view.h +++ b/apps/code/editor_view.h @@ -16,8 +16,8 @@ public: void setText(char * textBuffer, size_t textBufferSize) { m_textArea.setText(textBuffer, textBufferSize); } - bool setCursorLocation(int location) { - return m_textArea.setCursorLocation(location); + bool setCursorTextLocation(const char * location) { + return m_textArea.setCursorTextLocation(location); } void loadSyntaxHighlighter() { m_textArea.loadSyntaxHighlighter(); }; void unloadSyntaxHighlighter() { m_textArea.unloadSyntaxHighlighter(); }; diff --git a/apps/code/menu_controller.cpp b/apps/code/menu_controller.cpp index bf4533456..363cdce52 100644 --- a/apps/code/menu_controller.cpp +++ b/apps/code/menu_controller.cpp @@ -126,7 +126,7 @@ void MenuController::renameSelectedScript() { app()->setFirstResponder(myCell); myCell->setHighlighted(false); myCell->textField()->setEditing(true, false); - myCell->textField()->setCursorLocation(strlen(myCell->textField()->text())); + myCell->textField()->setCursorTextLocation(myCell->textField()->text() + strlen(myCell->textField()->text())); } void MenuController::deleteScript(Script script) { @@ -283,7 +283,9 @@ bool MenuController::textFieldShouldFinishEditing(TextField * textField, Ion::Ev } bool MenuController::textFieldDidReceiveEvent(TextField * textField, Ion::Events::Event event) { - if (event == Ion::Events::Right && textField->isEditing() && textField->cursorLocation() == textField->draftTextLength()) { + if (event == Ion::Events::Right + && textField->isEditing() + && textField->cursorTextLocation() == textField->text() + textField->draftTextLength()) { return true; } if (event == Ion::Events::Clear && textField->isEditing()) { @@ -292,7 +294,7 @@ bool MenuController::textFieldDidReceiveEvent(TextField * textField, Ion::Events assert(k_bufferSize >= 1 + strlen(ScriptStore::k_scriptExtension) + 1); strlcpy(&buffer[1], ScriptStore::k_scriptExtension, strlen(ScriptStore::k_scriptExtension) + 1); textField->setText(buffer); - textField->setCursorLocation(0); + textField->setCursorTextLocation(textField->text()); return true; } return false; @@ -316,7 +318,7 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char * default name and let the user modify it. */ if (!foundDefaultName) { textField->setText(numberedDefaultName); - textField->setCursorLocation(defaultNameLength); + textField->setCursorTextLocation(textField->draftTextBuffer() + defaultNameLength); } newName = const_cast(numberedDefaultName); } @@ -348,8 +350,9 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char bool MenuController::textFieldDidHandleEvent(TextField * textField, bool returnValue, bool textSizeDidChange) { int scriptExtensionLength = 1 + strlen(ScriptStore::k_scriptExtension); - if (textField->isEditing() && textField->cursorLocation() > textField->draftTextLength() - scriptExtensionLength) { - textField->setCursorLocation(textField->draftTextLength() - scriptExtensionLength); + const char * maxPointerLocation = textField->text() + textField->draftTextLength() - scriptExtensionLength; + if (textField->isEditing() && textField->cursorTextLocation() > maxPointerLocation) { + textField->setCursorTextLocation(maxPointerLocation); } return returnValue; } diff --git a/apps/code/python_text_area.cpp b/apps/code/python_text_area.cpp index e8764be2d..d99d26e60 100644 --- a/apps/code/python_text_area.cpp +++ b/apps/code/python_text_area.cpp @@ -18,7 +18,7 @@ constexpr KDColor OperatorColor = KDColor::RGB24(0xd73a49); constexpr KDColor StringColor = KDColor::RGB24(0x032f62); constexpr KDColor BackgroundColor = KDColorWhite; -static inline int min(int x, int y) { return (xcursorLocation() == textField->draftTextLength() && selectedColumn() < m_calculation->numberOfParameters()) - || (event == Ion::Events::Left && textField->cursorLocation() == 0); + || (event == Ion::Events::Right + && textField->cursorTextLocation() == textField->text() + textField->draftTextLength() + && selectedColumn() < m_calculation->numberOfParameters()) + || (event == Ion::Events::Left + && textField->cursorTextLocation() == textField->text()); } bool CalculationController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { diff --git a/apps/shared/editable_cell_table_view_controller.cpp b/apps/shared/editable_cell_table_view_controller.cpp index b9a5af534..a6c411bd8 100644 --- a/apps/shared/editable_cell_table_view_controller.cpp +++ b/apps/shared/editable_cell_table_view_controller.cpp @@ -19,8 +19,8 @@ bool EditableCellTableViewController::textFieldShouldFinishEditing(TextField * t return TextFieldDelegate::textFieldShouldFinishEditing(textField, event) || (event == Ion::Events::Down && selectedRow() < numberOfRows()-1) || (event == Ion::Events::Up && selectedRow() > 0) - || (event == Ion::Events::Right && textField->cursorLocation() == textField->draftTextLength() && selectedColumn() < numberOfColumns()-1) - || (event == Ion::Events::Left && textField->cursorLocation() == 0 && selectedColumn() > 0); + || (event == Ion::Events::Right && (textField->cursorTextLocation() == textField->draftTextBuffer() + textField->draftTextLength()) && selectedColumn() < numberOfColumns()-1) + || (event == Ion::Events::Left && textField->cursorTextLocation() == textField->draftTextBuffer() && selectedColumn() > 0); } bool EditableCellTableViewController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { diff --git a/apps/shared/text_field_with_extension.cpp b/apps/shared/text_field_with_extension.cpp index b2a3ca9fe..5d74cb531 100644 --- a/apps/shared/text_field_with_extension.cpp +++ b/apps/shared/text_field_with_extension.cpp @@ -2,11 +2,11 @@ namespace Shared { -void TextFieldWithExtension::willSetCursorLocation(int * location) { +void TextFieldWithExtension::willSetCursorTextLocation(const char * * location) { size_t textLength = strlen(text()); assert(textLength >= m_extensionLength); - size_t maxLocation = textLength - m_extensionLength; - if (*location > (int)maxLocation) { + const char * maxLocation = m_contentView.draftTextBuffer() + (textLength - m_extensionLength); + if (*location > maxLocation) { *location = maxLocation; } } @@ -21,17 +21,18 @@ void TextFieldWithExtension::removeWholeText() { } bool TextFieldWithExtension::removeTextBeforeExtension(bool whole) { - int extensionIndex = strlen(text()) - m_extensionLength; - assert(extensionIndex >= 0 && extensionIndex < ContentView::k_maxBufferSize - m_extensionLength); - int destinationIndex = whole ? 0 : cursorLocation(); - if (destinationIndex == extensionIndex) { + assert(isEditing()); + const char * extension = m_contentView.draftTextBuffer() + (strlen(text()) - m_extensionLength); + assert(extension >= m_contentView.draftTextBuffer() && extension < m_contentView.draftTextBuffer() + (ContentView::k_maxBufferSize - m_extensionLength)); + char * destination = whole ? m_contentView.draftTextBuffer() : const_cast(cursorTextLocation()); + if (destination == extension) { return false; } - assert(destinationIndex >= 0); - assert(destinationIndex < extensionIndex); + assert(destination >= m_contentView.draftTextBuffer()); + assert(destination < extension); m_contentView.willModifyTextBuffer(); - strlcpy(&(m_contentView.textBuffer()[destinationIndex]), &(m_contentView.textBuffer()[extensionIndex]), ContentView::k_maxBufferSize); - m_contentView.setCursorLocation(destinationIndex); + strlcpy(destination, extension, ContentView::k_maxBufferSize - (destination - m_contentView.draftTextBuffer())); + m_contentView.setCursorTextLocation(destination); m_contentView.didModifyTextBuffer(); layoutSubviews(); return true; diff --git a/apps/shared/text_field_with_extension.h b/apps/shared/text_field_with_extension.h index 12e087ef5..fa2264be4 100644 --- a/apps/shared/text_field_with_extension.h +++ b/apps/shared/text_field_with_extension.h @@ -24,7 +24,7 @@ public: m_extensionLength(extensionLength) {} private: - void willSetCursorLocation(int * location) override; + void willSetCursorTextLocation(const char * * location) override; bool privateRemoveEndOfLine() override; void removeWholeText() override; bool removeTextBeforeExtension(bool whole); diff --git a/escher/include/escher/text_area.h b/escher/include/escher/text_area.h index 94a663e9f..2e60c9d52 100644 --- a/escher/include/escher/text_area.h +++ b/escher/include/escher/text_area.h @@ -17,7 +17,7 @@ public: protected: int indentationBeforeCursor() const; - bool insertTextWithIndentation(const char * textBuffer, int location); + bool insertTextWithIndentation(const char * textBuffer, const char * location); class Text { public: @@ -36,11 +36,12 @@ protected: public: Line(const char * text); const char * text() const { return m_text; } - size_t length() const { return m_length; } + size_t charLength() const { return m_charLength; } + KDCoordinate glyphWidth(const KDFont * const font) const; bool contains(const char * c) const; private: const char * m_text; - size_t m_length; + size_t m_charLength; }; class LineIterator { @@ -66,14 +67,16 @@ protected: LineIterator begin() const { return LineIterator(m_buffer); }; LineIterator end() const { return LineIterator(nullptr); }; - Position span() const; + KDSize span(const KDFont * const font) const; - Position positionAtIndex(size_t index) const; - size_t indexAtPosition(Position p); + Position positionAtPointer(const char * pointer) const; + const char * pointerAtPosition(Position p); - void insertChar(char c, size_t index); - char removeChar(size_t index); - size_t removeRemainingLine(size_t index, int direction); + void insertText(const char * s, int textLength, char * location); + void insertSpacesAtLocation(int numberOfSpaces, char * location); + + CodePoint removeCodePoint(const char * * position); + size_t removeRemainingLine(const char * position, int direction); char operator[](size_t index) { assert(index < m_bufferSize); return m_buffer[index]; @@ -105,18 +108,19 @@ protected: const char * text() const override { return m_text.text(); } size_t editedTextLength() const override { return m_text.textLength(); } const Text * getText() const { return &m_text; } - bool insertTextAtLocation(const char * text, int location) override; + bool insertTextAtLocation(const char * text, const char * location) override; void moveCursorGeo(int deltaX, int deltaY); - bool removeChar() override; + bool removeCodePoint() override; bool removeEndOfLine() override; bool removeStartOfLine(); protected: - KDRect characterFrameAtIndex(size_t index) const override; + KDRect glyphFrameAtPosition(const char * position) const override; Text m_text; }; ContentView * contentView() { return static_cast(TextInput::contentView()); } private: + static constexpr int k_indentationSpaces = 2; TextAreaDelegate * m_delegate; }; diff --git a/escher/include/escher/text_field.h b/escher/include/escher/text_field.h index 46a9c73f2..9077e2332 100644 --- a/escher/include/escher/text_field.h +++ b/escher/include/escher/text_field.h @@ -16,7 +16,8 @@ public: void setDelegates(InputEventHandlerDelegate * inputEventHandlerDelegate, TextFieldDelegate * delegate) { m_inputEventHandlerDelegate = inputEventHandlerDelegate; m_delegate = delegate; } void setDraftTextBuffer(char * draftTextBuffer); bool isEditing() const override; - size_t draftTextLength() const; + const char * draftTextBuffer() const { return const_cast(this)->m_contentView.draftTextBuffer(); } + size_t draftTextLength() const; //TODO keep ? void setText(const char * text); void setAlignment(float horizontalAlignment, float verticalAlignment); virtual void setEditing(bool isEditing, bool reinitDraftBuffer = true) override; @@ -40,7 +41,7 @@ protected: void drawRect(KDContext * ctx, KDRect rect) const override; bool isEditing() const { return m_isEditing; } const char * text() const override; - size_t editedTextLength() const override; + size_t editedTextLength() const override { return m_currentDraftTextLength; } //TODO keep ? char * textBuffer() { return m_textBuffer; } char * draftTextBuffer() { return m_draftTextBuffer; } int bufferSize() { return k_maxBufferSize; } @@ -48,12 +49,12 @@ protected: void setAlignment(float horizontalAlignment, float verticalAlignment); void setEditing(bool isEditing, bool reinitDraftBuffer); void reinitDraftTextBuffer(); - /* If the text to be appended is too long to be added without overflowing the - * buffer, nothing is done (not even adding few letters from the text to reach - * the maximum buffer capacity) and false is returned. */ - bool insertTextAtLocation(const char * text, int location) override; + /* If the text to be appended is too long to be added without overflowing the + * buffer, nothing is done (not even adding few letters from the text to reach + * the maximum buffer capacity) and false is returned. */ + bool insertTextAtLocation(const char * text, const char * location) override; // TODO KDSize minimalSizeForOptimalDisplay() const override; - bool removeChar() override; + bool removeCodePoint() override; bool removeEndOfLine() override; void willModifyTextBuffer(); void didModifyTextBuffer(); @@ -65,11 +66,11 @@ protected: constexpr static int k_maxBufferSize = 152; private: void layoutSubviews() override; - KDRect characterFrameAtIndex(size_t index) const override; + KDRect glyphFrameAtPosition(const char * position) const override; bool m_isEditing; char * m_textBuffer; char * m_draftTextBuffer; - size_t m_currentDraftTextLength; + size_t m_currentDraftTextLength; //TODO keep ? size_t m_textBufferSize; float m_horizontalAlignment; float m_verticalAlignment; diff --git a/escher/include/escher/text_input.h b/escher/include/escher/text_input.h index f4a705c77..283d3db90 100644 --- a/escher/include/escher/text_input.h +++ b/escher/include/escher/text_input.h @@ -11,9 +11,9 @@ public: TextInput(Responder * parentResponder, View * contentView) : ScrollableView(parentResponder, contentView, this) {} void setFont(const KDFont * font) { contentView()->setFont(font); } const char * text() const { return nonEditableContentView()->text(); } - bool removeChar(); - size_t cursorLocation() const { return nonEditableContentView()->cursorLocation(); } - bool setCursorLocation(int location); + bool removeCodePoint(); + const char * cursorTextLocation() const { return nonEditableContentView()->cursorTextLocation(); } + bool setCursorTextLocation(const char * location); virtual void scrollToCursor(); protected: class ContentView : public View { @@ -22,29 +22,29 @@ protected: View(), m_cursorView(), m_font(font), - m_cursorIndex(0) + m_cursorTextLocation(nullptr) {} void setFont(const KDFont * font); const KDFont * font() const { return m_font; } - size_t cursorLocation() const { return m_cursorIndex; } - void setCursorLocation(int cursorLocation); + const char * cursorTextLocation() const { assert(m_cursorTextLocation != nullptr); return m_cursorTextLocation; } + void setCursorTextLocation(const char * cursorTextLocation); virtual const char * text() const = 0; - virtual bool insertTextAtLocation(const char * text, int location) = 0; - virtual bool removeChar() = 0; + virtual bool insertTextAtLocation(const char * text, const char * location) = 0; + virtual bool removeCodePoint() = 0; virtual bool removeEndOfLine() = 0; KDRect cursorRect(); protected: virtual void layoutSubviews() override; - virtual KDRect dirtyRectFromCursorPosition(size_t index, bool lineBreak) const; - void reloadRectFromCursorPosition(size_t index, bool lineBreak = false); - virtual KDRect characterFrameAtIndex(size_t index) const = 0; + void reloadRectFromPosition(const char * position, bool lineBreak = false); + virtual KDRect glyphFrameAtPosition(const char * position) const = 0; TextCursorView m_cursorView; const KDFont * m_font; - size_t m_cursorIndex; + const char * m_cursorTextLocation; + virtual KDRect dirtyRectFromPosition(const char * position, bool lineBreak) const; private: int numberOfSubviews() const override { return 1; } View * subviewAtIndex(int index) override { - assert(index == 1); + assert(index == 0); return &m_cursorView; } virtual size_t editedTextLength() const = 0; @@ -53,14 +53,14 @@ protected: /* If the text to be appended is too long to be added without overflowing the * buffer, nothing is done (not even adding few letters from the text to reach * the maximum buffer capacity) and false is returned. */ - bool insertTextAtLocation(const char * textBuffer, int location); + bool insertTextAtLocation(const char * textBuffer, const char * location); bool removeEndOfLine(); ContentView * contentView() { return const_cast(nonEditableContentView()); } virtual const ContentView * nonEditableContentView() const = 0; private: - virtual void willSetCursorLocation(int * location) {} + virtual void willSetCursorTextLocation(const char * * location) {} virtual bool privateRemoveEndOfLine(); }; diff --git a/escher/include/escher/text_input_helpers.h b/escher/include/escher/text_input_helpers.h index 8eab24830..04718b4b5 100644 --- a/escher/include/escher/text_input_helpers.h +++ b/escher/include/escher/text_input_helpers.h @@ -6,11 +6,11 @@ namespace TextInputHelpers { -size_t CursorIndexInCommand(const char * text); -/* Returns the index of the cursor position in a Command, which is the smallest - * index between : - * - The first EmptyChar index (which is the position of the first argument) - * - The first empty quote +const char * CursorPositionInCommand(const char * text); +/* Returns the pointer to the char that should be right of the cursor, which is + * the first char between : + * - The first EmptyChar (which is the position of the first argument) + * - The char after the first empty quote * - The end of the text */ } diff --git a/escher/src/text_area.cpp b/escher/src/text_area.cpp index 408dd2dbf..997227404 100644 --- a/escher/src/text_area.cpp +++ b/escher/src/text_area.cpp @@ -1,15 +1,15 @@ #include #include #include +#include #include #include #include #include -static inline size_t min(size_t a, size_t b) { - return (a>b ? b : a); -} +static inline size_t minSize(size_t a, size_t b) { return a < b ? a : b; } +static inline size_t maxSize(size_t a, size_t b) { return a > b ? a : b; } /* TextArea */ @@ -21,26 +21,26 @@ TextArea::TextArea(Responder * parentResponder, View * contentView, const KDFont } bool TextArea::handleEventWithText(const char * text, bool indentation, bool forceCursorRightOfText) { - int nextCursorLocation = cursorLocation(); + const char * nextCursorLocation = cursorTextLocation(); - size_t cursorIndexInCommand = TextInputHelpers::CursorIndexInCommand(text); + const char * cursorPositionInCommand = TextInputHelpers::CursorPositionInCommand(text); constexpr int bufferSize = TextField::maxBufferSize(); char buffer[bufferSize]; // Remove the Empty code points - UTF8Helper::CopyAndRemoveCodePoint(buffer, bufferSize, text, KDCodePointEmpty, &cursorIndexInCommand); + UTF8Helper::CopyAndRemoveCodePoint(buffer, bufferSize, text, KDCodePointEmpty, &cursorPositionInCommand); // Insert the text - if ((indentation && insertTextWithIndentation(buffer, cursorLocation())) || insertTextAtLocation(buffer, cursorLocation())) { + if ((indentation && insertTextWithIndentation(buffer, cursorTextLocation())) || insertTextAtLocation(buffer, cursorTextLocation())) { // Set the cursor location if (forceCursorRightOfText) { nextCursorLocation += strlen(buffer); } else { - nextCursorLocation += cursorIndexInCommand; + nextCursorLocation += cursorPositionInCommand - text; } } - setCursorLocation(nextCursorLocation); + setCursorTextLocation(nextCursorLocation); return true; } @@ -50,9 +50,20 @@ bool TextArea::handleEvent(Ion::Events::Event event) { } else if (handleBoxEvent(app(), event)) { return true; } else if (event == Ion::Events::Left) { - return setCursorLocation(cursorLocation()-1); + if (cursorTextLocation() <= text()) { + assert(cursorTextLocation() == text()); + return false; + } + UTF8Decoder decoder(text(), cursorTextLocation()); + decoder.previousCodePoint(); + return setCursorTextLocation(decoder.stringPosition()); } else if (event == Ion::Events::Right) { - return setCursorLocation(cursorLocation()+1); + if (*cursorTextLocation() == 0) { + return false; + } + UTF8Decoder decoder(text(), cursorTextLocation()); + decoder.nextCodePoint(); + return setCursorTextLocation(decoder.stringPosition()); } else if (event == Ion::Events::Up) { contentView()->moveCursorGeo(0, -1); } else if (event == Ion::Events::Down) { @@ -62,7 +73,7 @@ bool TextArea::handleEvent(Ion::Events::Event event) { } else if (event == Ion::Events::ShiftRight) { contentView()->moveCursorGeo(INT_MAX/2, 0); } else if (event == Ion::Events::Backspace) { - return removeChar(); + return removeCodePoint(); } else if (event.hasText()) { return handleEventWithText(event.text()); } else if (event == Ion::Events::EXE) { @@ -85,80 +96,75 @@ void TextArea::setText(char * textBuffer, size_t textBufferSize) { contentView()->moveCursorGeo(0, 0); } -bool TextArea::insertTextWithIndentation(const char * textBuffer, int location) { +bool TextArea::insertTextWithIndentation(const char * textBuffer, const char * location) { int indentation = indentationBeforeCursor(); - char spaceString[indentation+1]; - for (int i = 0; i < indentation; i++) { - spaceString[i] = ' '; + const char * previousChar = cursorTextLocation()-1; + if (previousChar >= const_cast