From b547f8bfd9aa8f7a56eca3508a8f74a38b2dfe4d Mon Sep 17 00:00:00 2001 From: Romain Goyet Date: Wed, 9 Jan 2019 10:01:28 +0100 Subject: [PATCH] [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