diff --git a/src/Kaleidoscope-Hardware-Dygma-Raise.h b/src/Kaleidoscope-Hardware-Dygma-Raise.h
new file mode 100644
index 00000000..f87b9f87
--- /dev/null
+++ b/src/Kaleidoscope-Hardware-Dygma-Raise.h
@@ -0,0 +1,21 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ * Copyright (C) 2017-2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#include "kaleidoscope/device/dygma/Raise.h"
diff --git a/src/kaleidoscope/device/dygma/Raise.cpp b/src/kaleidoscope/device/dygma/Raise.cpp
new file mode 100644
index 00000000..45f134b7
--- /dev/null
+++ b/src/kaleidoscope/device/dygma/Raise.cpp
@@ -0,0 +1,515 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ * Copyright (C) 2017-2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#ifdef ARDUINO_SAMD_RAISE
+
+#include
+#include
+#include
+#include
+#include
+
+#include "kaleidoscope/util/crc16.h"
+#include "kaleidoscope/driver/color/GammaCorrection.h"
+
+#define I2C_CLOCK_KHZ 200
+#define I2C_FLASH_CLOCK_KHZ 100 // flashing doesn't work reliably at higher clock speeds
+
+#define SIDE_POWER 1 // side power switch pa10
+
+#define LAYOUT_ISO 0
+#define LAYOUT_ANSI 1
+
+namespace kaleidoscope {
+namespace device {
+namespace dygma {
+
+/********* RaiseHands *********/
+
+struct RaiseHands {
+ static raise::Hand leftHand;
+ static raise::Hand rightHand;
+
+ static void setup();
+ static void initializeSides();
+
+ static uint8_t layout;
+
+ static void setSidePower(bool power);
+ static bool getSidePower() {
+ return side_power_;
+ }
+
+ static void keyscanInterval(uint16_t interval);
+ static uint16_t keyscanInterval() {
+ return keyscan_interval_;
+ }
+
+ private:
+ static uint16_t keyscan_interval_;
+ static bool side_power_;
+ static uint16_t settings_base_;
+ static constexpr uint8_t iso_only_led_ = 19;
+};
+
+raise::Hand RaiseHands::leftHand(0);
+raise::Hand RaiseHands::rightHand(1);
+uint8_t RaiseHands::layout;
+bool RaiseHands::side_power_;
+uint16_t RaiseHands::settings_base_;
+uint16_t RaiseHands::keyscan_interval_ = 50;
+
+void RaiseHands::setSidePower(bool power) {
+ digitalWrite(SIDE_POWER, power ? HIGH : LOW);
+ side_power_ = power;
+}
+
+void RaiseHands::setup() {
+ settings_base_ = ::EEPROMSettings.requestSlice(sizeof(keyscan_interval_));
+
+ // If keyscan is max, assume that EEPROM is uninitialized, and store the
+ // defaults.
+ uint16_t interval;
+ Kaleidoscope.storage().get(settings_base_, interval);
+ if (interval == 0xffff) {
+ Kaleidoscope.storage().put(settings_base_, keyscan_interval_);
+ Kaleidoscope.storage().commit();
+ }
+ Kaleidoscope.storage().get(settings_base_, keyscan_interval_);
+}
+
+void RaiseHands::keyscanInterval(uint16_t interval) {
+ leftHand.setKeyscanInterval(interval);
+ rightHand.setKeyscanInterval(interval);
+ keyscan_interval_ = interval;
+ Kaleidoscope.storage().put(settings_base_, keyscan_interval_);
+ Kaleidoscope.storage().commit();
+}
+
+void RaiseHands::initializeSides() {
+ // key scan interval from eeprom
+ leftHand.setKeyscanInterval(keyscan_interval_);
+ rightHand.setKeyscanInterval(keyscan_interval_);
+
+ // get ANSI/ISO at every side replug
+ uint8_t l_layout = leftHand.readLayout();
+ uint8_t r_layout = rightHand.readLayout();
+
+ // setup layout variable, this will affect led mapping - defaults to ISO if
+ // nothing reported
+ // FIXME
+ if (l_layout == 1 || r_layout == 1)
+ layout = 1;
+ else
+ layout = 0;
+
+ /*
+ * if the neuron starts up with no sides connected, it will assume ISO. This
+ * turns on an extra LED (hardware LED 19 on left side). If an ANSI left is
+ * then plugged in, the keyboard will switch to ANSI, but LED 19 can't get
+ * wiped because the ANSI LED map doesn't include this LED. It will be driven
+ * from the SLED1735's memory with the same colour as before, which causes
+ * weird looking colours to come on on other seemingly unrelated keys. So: on
+ * a replug, set LED 19 to off to be safe.
+ */
+ leftHand.led_data.leds[iso_only_led_] = {0, 0, 0};
+
+ // get activated LED plugin to refresh
+ ::LEDControl.refreshAll();
+}
+
+/********* LED Driver *********/
+
+bool RaiseLEDDriver::isLEDChangedNeuron;
+uint8_t RaiseLEDDriver::isLEDChangedLeft[LED_BANKS];
+uint8_t RaiseLEDDriver::isLEDChangedRight[LED_BANKS];
+cRGB RaiseLEDDriver::neuronLED;
+constexpr uint8_t RaiseLEDDriver::led_map[][RaiseLEDDriverProps::led_count + 1];
+
+constexpr uint8_t RaiseLEDDriverProps::key_led_map[];
+
+void RaiseLEDDriver::syncLeds() {
+ // left and right sides
+ for (uint8_t i = 0; i < LED_BANKS; i ++) {
+ // only send the banks that have changed - try to improve jitter performance
+ if (isLEDChangedLeft[i]) {
+ RaiseHands::leftHand.sendLEDBank(i);
+ isLEDChangedLeft[i] = false;
+ }
+ if (isLEDChangedRight[i]) {
+ RaiseHands::rightHand.sendLEDBank(i);
+ isLEDChangedRight[i] = false;
+ }
+ }
+
+ if (isLEDChangedNeuron) {
+ updateNeuronLED();
+ isLEDChangedNeuron = false;
+ }
+}
+
+void RaiseLEDDriver::updateNeuronLED() {
+ static constexpr struct {
+ uint8_t r, g, b;
+ } pins = { 3, 5, 4 };
+ auto constexpr gamma8 = kaleidoscope::driver::color::gamma_correction;
+
+ // invert as these are common anode, and make sure we reach 65535 to be able
+ // to turn fully off.
+ analogWrite(pins.r, ((256 - pgm_read_byte(&gamma8[neuronLED.r])) << 8) - 1);
+ analogWrite(pins.g, ((256 - pgm_read_byte(&gamma8[neuronLED.g])) << 8) - 1);
+ analogWrite(pins.b, ((256 - pgm_read_byte(&gamma8[neuronLED.b])) << 8) - 1);
+}
+
+void RaiseLEDDriver::setCrgbAt(uint8_t i, cRGB crgb) {
+ // prevent reading off the end of the led_map array
+ if (i >= Props_::led_count)
+ return;
+
+ // neuron LED
+ if (i == Props_::led_count - 1) {
+ isLEDChangedNeuron |= !(neuronLED.r == crgb.r &&
+ neuronLED.g == crgb.g &&
+ neuronLED.b == crgb.b);
+ neuronLED = crgb;
+ return;
+ }
+
+ // get the SLED index
+ uint8_t sled_num = led_map[RaiseHands::layout][i];
+ if (sled_num < LEDS_PER_HAND) {
+ cRGB oldColor = RaiseHands::leftHand.led_data.leds[sled_num];
+ RaiseHands::leftHand.led_data.leds[sled_num] = crgb;
+ isLEDChangedLeft[uint8_t(sled_num / 8)] |= !(oldColor.r == crgb.r &&
+ oldColor.g == crgb.g &&
+ oldColor.b == crgb.b);
+ } else if (sled_num < 2 * LEDS_PER_HAND) {
+ cRGB oldColor = RaiseHands::rightHand.led_data.leds[sled_num - LEDS_PER_HAND];
+ RaiseHands::rightHand.led_data.leds[sled_num - LEDS_PER_HAND] = crgb;
+ isLEDChangedRight[uint8_t((sled_num - LEDS_PER_HAND) / 8)] |=
+ !(oldColor.r == crgb.r &&
+ oldColor.g == crgb.g &&
+ oldColor.b == crgb.b);
+ } else {
+ // TODO(anyone):
+ // how do we want to handle debugging assertions about crazy user
+ // code that would overwrite other memory?
+ }
+}
+
+cRGB RaiseLEDDriver::getCrgbAt(uint8_t i) {
+ if (i >= Props_::led_count)
+ return {0, 0, 0};
+
+ uint8_t sled_num = led_map[RaiseHands::layout][i];
+ if (sled_num < LEDS_PER_HAND) {
+ return RaiseHands::leftHand.led_data.leds[sled_num];
+ } else if (sled_num < 2 * LEDS_PER_HAND) {
+ return RaiseHands::rightHand.led_data.leds[sled_num - LEDS_PER_HAND];
+ } else {
+ return {0, 0, 0};
+ }
+}
+
+void RaiseLEDDriver::setup() {
+ pinMode(SIDE_POWER, OUTPUT);
+ RaiseHands::setSidePower(false);
+
+ // arduino zero analogWrite(255) isn't fully on as its actually working with a
+ // 16bit counter and the mapping is a bit shift.
+ // so change to 16 bit resolution to avoid the mapping and do the mapping
+ // ourselves in updateHubleLED() to ensure LEDs can be set fully off
+ analogWriteResolution(16);
+ updateNeuronLED();
+
+ delay(10);
+ RaiseHands::setSidePower(true);
+ delay(500); // wait for sides to power up and finish bootloader
+}
+
+/********* Key scanner *********/
+
+raise::keydata_t RaiseKeyScanner::leftHandState;
+raise::keydata_t RaiseKeyScanner::rightHandState;
+raise::keydata_t RaiseKeyScanner::previousLeftHandState;
+raise::keydata_t RaiseKeyScanner::previousRightHandState;
+raise::keydata_t RaiseKeyScanner::leftHandMask;
+raise::keydata_t RaiseKeyScanner::rightHandMask;
+bool RaiseKeyScanner::lastLeftOnline;
+bool RaiseKeyScanner::lastRightOnline;
+
+void RaiseKeyScanner::readMatrix() {
+ previousLeftHandState = leftHandState;
+ previousRightHandState = rightHandState;
+
+ if (RaiseHands::leftHand.readKeys()) {
+ leftHandState = RaiseHands::leftHand.getKeyData();
+ // if ANSI, then swap r3c0 and r3c1 to match the PCB
+ if (RaiseHands::layout == LAYOUT_ANSI)
+ // only swap if bits are different
+ if ((leftHandState.rows[3] & (1 << 0)) ^ leftHandState.rows[3] & (1 << 1)) {
+ leftHandState.rows[3] ^= (1 << 0); // flip the bit
+ leftHandState.rows[3] ^= (1 << 1); // flip the bit
+ }
+ }
+
+ if (RaiseHands::rightHand.readKeys()) {
+ rightHandState = RaiseHands::rightHand.getKeyData();
+ // if ANSI, then swap r1c0 and r2c0 to match the PCB
+ if (RaiseHands::layout == LAYOUT_ANSI)
+ if ((rightHandState.rows[1] & (1 << 0)) ^ rightHandState.rows[2] & (1 << 0)) {
+ rightHandState.rows[1] ^= (1 << 0);
+ rightHandState.rows[2] ^= (1 << 0);
+ }
+ }
+
+ // if a side has just been replugged, initialise it
+ if ((RaiseHands::leftHand.online && !lastLeftOnline) ||
+ (RaiseHands::rightHand.online && !lastRightOnline))
+ RaiseHands::initializeSides();
+
+ // if a side has just been unplugged, wipe its state
+ if (!RaiseHands::leftHand.online && lastLeftOnline)
+ leftHandState.all = 0;
+
+ if (!RaiseHands::rightHand.online && lastRightOnline)
+ rightHandState.all = 0;
+
+ // store previous state of whether the sides are plugged in
+ lastLeftOnline = RaiseHands::leftHand.online;
+ lastRightOnline = RaiseHands::rightHand.online;
+}
+
+void RaiseKeyScanner::actOnMatrixScan() {
+ for (byte row = 0; row < Props_::matrix_rows; row++) {
+ for (byte col = 0; col < Props_::matrix_columns; col++) {
+ uint8_t keynum = (row * Props_::matrix_rows) + (col);
+ uint8_t keyState;
+
+ // left
+ keyState = (bitRead(previousLeftHandState.all, keynum) << 0) |
+ (bitRead(leftHandState.all, keynum) << 1);
+ if (keyState)
+ ThisType::handleKeyswitchEvent(Key_NoKey, KeyAddr(row, col), keyState);
+
+ // right
+ keyState = (bitRead(previousRightHandState.all, keynum) << 0) |
+ (bitRead(rightHandState.all, keynum) << 1);
+ if (keyState)
+ ThisType::handleKeyswitchEvent(Key_NoKey, KeyAddr(row, (Props_::matrix_columns - 1) - col), keyState);
+ }
+ }
+}
+
+void RaiseKeyScanner::scanMatrix() {
+ readMatrix();
+ actOnMatrixScan();
+}
+
+void RaiseKeyScanner::maskKey(KeyAddr key_addr) {
+ if (!key_addr.isValid())
+ return;
+
+ auto row = key_addr.row();
+ auto col = key_addr.col();
+
+ if (col >= Props_::left_columns) {
+ rightHandMask.rows[row] |= 1 << (Props_::right_columns - (col - Props_::left_columns));
+ } else {
+ leftHandMask.rows[row] |= 1 << (Props_::right_columns - col);
+ }
+}
+
+void RaiseKeyScanner::unMaskKey(KeyAddr key_addr) {
+ if (!key_addr.isValid())
+ return;
+
+ auto row = key_addr.row();
+ auto col = key_addr.col();
+
+ if (col >= Props_::left_columns) {
+ rightHandMask.rows[row] &= ~(1 << (Props_::right_columns - (col - Props_::left_columns)));
+ } else {
+ leftHandMask.rows[row] &= ~(1 << (Props_::right_columns - col));
+ }
+}
+
+bool RaiseKeyScanner::isKeyMasked(KeyAddr key_addr) {
+ if (!key_addr.isValid())
+ return false;
+
+ auto row = key_addr.row();
+ auto col = key_addr.col();
+
+ if (col >= 8) {
+ return rightHandMask.rows[row] & (1 << (7 - (col - 8)));
+ } else {
+ return leftHandMask.rows[row] & (1 << (7 - col));
+ }
+}
+
+void RaiseKeyScanner::maskHeldKeys() {
+ memcpy(leftHandMask.rows, leftHandState.rows, sizeof(leftHandMask));
+ memcpy(rightHandMask.rows, rightHandState.rows, sizeof(rightHandMask));
+}
+
+bool RaiseKeyScanner::isKeyswitchPressed(KeyAddr key_addr) {
+ auto row = key_addr.row();
+ auto col = key_addr.col();
+
+ if (col >= Props_::left_columns) {
+ return (bitRead(rightHandState.rows[row], (Props_::matrix_columns - 1) - col) != 0);
+ } else {
+ return (bitRead(leftHandState.rows[row], col) != 0);
+ }
+}
+
+bool RaiseKeyScanner::wasKeyswitchPressed(KeyAddr key_addr) {
+ auto row = key_addr.row();
+ auto col = key_addr.col();
+
+ if (col >= Props_::left_columns) {
+ return (bitRead(previousRightHandState.rows[row], (Props_::matrix_columns - 1) - col) != 0);
+ } else {
+ return (bitRead(previousLeftHandState.rows[row], col) != 0);
+ }
+}
+
+uint8_t RaiseKeyScanner::pressedKeyswitchCount() {
+ return __builtin_popcountl(leftHandState.all) + __builtin_popcountl(rightHandState.all);
+}
+
+uint8_t RaiseKeyScanner::previousPressedKeyswitchCount() {
+ return __builtin_popcountl(previousLeftHandState.all) + __builtin_popcountl(previousRightHandState.all);
+}
+
+void RaiseKeyScanner::setKeyscanInterval(uint8_t interval) {
+ RaiseHands::leftHand.setKeyscanInterval(interval);
+ RaiseHands::rightHand.setKeyscanInterval(interval);
+}
+
+void RaiseKeyScanner::setup() {
+ static constexpr uint8_t keyscanner_pins[] = {
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42
+ };
+ for (int i = 0; i < sizeof(keyscanner_pins); i++) {
+ pinMode(keyscanner_pins[i], OUTPUT);
+ digitalWrite(keyscanner_pins[i], LOW);
+ }
+}
+
+void RaiseKeyScanner::reset() {
+ leftHandState.all = 0;
+ rightHandState.all = 0;
+ kaleidoscope::hid::releaseAllKeys();
+ kaleidoscope::hid::sendKeyboardReport();
+}
+
+/********* Hardware plugin *********/
+void Raise::setup() {
+ RaiseHands::setup();
+ KeyScanner::setup();
+ LEDDriver::setup();
+
+ // initialise Wire of scanner - have to do this here to avoid problem with
+ // static object intialisation ordering
+ Wire.begin();
+ Wire.setClock(I2C_CLOCK_KHZ * 1000);
+
+ RaiseHands::initializeSides();
+}
+
+void Raise::side::prepareForFlash() {
+ Wire.end();
+
+ setPower(LOW);
+ // also turn off i2c pins to stop attiny from getting enough current through i2c to stay on
+ pinMode(SCL, OUTPUT);
+ pinMode(SDA, OUTPUT);
+ digitalWrite(SCL, false);
+ digitalWrite(SDA, false);
+
+ // wipe key states, to prevent accidental key repeats
+ RaiseKeyScanner::reset();
+
+ setPower(HIGH);
+
+ Wire.begin();
+ Wire.setClock(I2C_FLASH_CLOCK_KHZ * 1000);
+ // wait for side bootloader to be ready
+ delay(100);
+}
+
+uint8_t Raise::side::getPower() {
+ return RaiseHands::getSidePower();
+}
+void Raise::side::setPower(uint8_t power) {
+ RaiseHands::setSidePower(power);
+}
+
+uint8_t Raise::side::leftVersion() {
+ return RaiseHands::leftHand.readVersion();
+}
+uint8_t Raise::side::rightVersion() {
+ return RaiseHands::rightHand.readVersion();
+}
+
+uint8_t Raise::side::leftCRCErrors() {
+ return RaiseHands::leftHand.crc_errors();
+}
+uint8_t Raise::side::rightCRCErrors() {
+ return RaiseHands::rightHand.crc_errors();
+}
+
+uint8_t Raise::side::leftSLEDVersion() {
+ return RaiseHands::leftHand.readSLEDVersion();
+}
+uint8_t Raise::side::rightSLEDVersion() {
+ return RaiseHands::rightHand.readSLEDVersion();
+}
+
+uint8_t Raise::side::leftSLEDCurrent() {
+ return RaiseHands::leftHand.readSLEDCurrent();
+}
+uint8_t Raise::side::rightSLEDCurrent() {
+ return RaiseHands::rightHand.readSLEDCurrent();
+}
+void Raise::side::setSLEDCurrent(uint8_t current) {
+ RaiseHands::rightHand.setSLEDCurrent(current);
+ RaiseHands::leftHand.setSLEDCurrent(current);
+}
+
+Raise::settings::Layout Raise::settings::layout() {
+ return RaiseHands::layout == LAYOUT_ANSI ? Layout::ANSI : Layout::ISO;
+}
+uint8_t Raise::settings::joint() {
+ return RaiseHands::rightHand.readJoint();
+}
+
+uint16_t Raise::settings::keyscanInterval() {
+ return RaiseHands::keyscanInterval();
+}
+void Raise::settings::keyscanInterval(uint16_t interval) {
+ RaiseHands::keyscanInterval(interval);
+}
+
+}
+}
+}
+
+#endif
diff --git a/src/kaleidoscope/device/dygma/Raise.h b/src/kaleidoscope/device/dygma/Raise.h
new file mode 100644
index 00000000..1edfbced
--- /dev/null
+++ b/src/kaleidoscope/device/dygma/Raise.h
@@ -0,0 +1,272 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ * Copyright (C) 2017-2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#ifdef ARDUINO_SAMD_RAISE
+#include
+
+#include "Kaleidoscope-HIDAdaptor-KeyboardioHID.h"
+#include "kaleidoscope/device/dygma/raise/Hand.h"
+
+#define CRGB(r,g,b) (cRGB){b, g, r}
+
+#include "kaleidoscope/driver/keyscanner/Base.h"
+#include "kaleidoscope/driver/led/Base.h"
+#include "kaleidoscope/driver/bootloader/samd/Bossac.h"
+#include "kaleidoscope/driver/storage/Flash.h"
+#include "kaleidoscope/device/Base.h"
+#include "kaleidoscope/util/flasher/KeyboardioI2CBootloader.h"
+
+namespace kaleidoscope {
+namespace device {
+namespace dygma {
+
+// LHK = Left Hand Keys
+#define LHK 33
+
+using kaleidoscope::driver::led::no_led;
+
+struct RaiseLEDDriverProps : public kaleidoscope::driver::led::BaseProps {
+ static constexpr uint8_t led_count = 132;
+ static constexpr uint8_t key_led_map[] = {
+ // ISO & ANSI (ANSI has no LED at 20, but this key can never be pressed so we can have just one map).
+ 0, 1, 2, 3, 4, 5, 6, no_led, no_led, 6 + LHK, 5 + LHK, 4 + LHK, 3 + LHK, 2 + LHK, 1 + LHK, 0 + LHK,
+ 7, 8, 9, 10, 11, 12, no_led, no_led, 14 + LHK, 13 + LHK, 12 + LHK, 11 + LHK, 10 + LHK, 9 + LHK, 8 + LHK, 7 + LHK,
+ 13, 14, 15, 16, 17, 18, no_led, no_led, no_led, 21 + LHK, 20 + LHK, 19 + LHK, 18 + LHK, 17 + LHK, 16 + LHK, 15 + LHK,
+ 19, 20, 21, 22, 23, 24, 25, no_led, no_led, no_led, 27 + LHK, 26 + LHK, 25 + LHK, 24 + LHK, 23 + LHK, 22 + LHK,
+ 26, 27, 28, 29, 30, no_led, 31, 32, 35 + LHK, 34 + LHK, 33 + LHK, 32 + LHK, 31 + LHK, 30 + LHK, 29 + LHK, 28 + LHK
+ };
+};
+#undef LHK
+
+class RaiseLEDDriver : public kaleidoscope::driver::led::Base {
+ public:
+ static void setup();
+
+ static void syncLeds();
+ static void setCrgbAt(uint8_t i, cRGB crgb);
+ static cRGB getCrgbAt(uint8_t i);
+
+ static void updateNeuronLED();
+ private:
+ static bool isLEDChangedNeuron;
+ static uint8_t isLEDChangedLeft[LED_BANKS];
+ static uint8_t isLEDChangedRight[LED_BANKS];
+ static cRGB neuronLED;
+
+ static constexpr uint8_t lph = LEDS_PER_HAND;
+ // led_count + 1, to account for the Neuron's LED. The last one is the
+ // Neuron's LED, never send that to SLED.
+ static constexpr uint8_t led_map[][RaiseLEDDriverProps::led_count + 1] = {
+ // ISO
+ {
+ // left side - 33 keys includes LP
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 68, 69,
+
+ // right side - 36 keys includes LP
+ 0 + LPH, 1 + LPH, 2 + LPH, 3 + LPH, 4 + LPH, 5 + LPH, 6 + LPH, 7 + LPH, 8 + LPH, 9 + LPH, 10 + LPH, 11 + LPH, 12 + LPH, 13 + LPH, 14 + LPH, 15 + LPH, 16 + LPH, 17 + LPH, 18 + LPH, 19 + LPH,
+ 20 + LPH, 21 + LPH, 22 + LPH, 23 + LPH, 24 + LPH, 25 + LPH, 26 + LPH, 27 + LPH, 28 + LPH, 29 + LPH, 30 + LPH, 31 + LPH, 32 + LPH, 33 + LPH, 68 + LPH, 69 + LPH,
+
+ // left under glow - 30
+ 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+
+ // right underglow - 32
+ 34 + LPH, 35 + LPH, 36 + LPH, 37 + LPH, 38 + LPH, 39 + LPH, 40 + LPH, 41 + LPH, 42 + LPH, 43 + LPH, 44 + LPH, 45 + LPH, 46 + LPH, 47 + LPH, 48 + LPH, 49 + LPH, 50 + LPH, 51 + LPH,
+ 52 + LPH, 53 + LPH, 54 + LPH, 55 + LPH, 56 + LPH, 57 + LPH, 58 + LPH, 59 + LPH, 60 + LPH, 61 + LPH, 62 + LPH, 63 + LPH, 64 + LPH, 65 + LPH, 0xff
+ },
+ // ANSI
+ {
+ // left side - 32 keys includes LP: key 19 is missing for ANSI layout
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 0xff, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 68, 69,
+
+ // right side - 36 keys includes LP
+ 0 + LPH, 1 + LPH, 2 + LPH, 3 + LPH, 4 + LPH, 5 + LPH, 6 + LPH, 15 + LPH, 8 + LPH, 9 + LPH, 10 + LPH, 11 + LPH, 12 + LPH, 13 + LPH, 14 + LPH, 7 + LPH, 16 + LPH, 17 + LPH, 18 + LPH, 19 + LPH,
+ 20 + LPH, 21 + LPH, 22 + LPH, 23 + LPH, 24 + LPH, 25 + LPH, 26 + LPH, 27 + LPH, 28 + LPH, 29 + LPH, 30 + LPH, 31 + LPH, 32 + LPH, 33 + LPH, 68 + LPH, 69 + LPH,
+
+ // left under glow - 30
+ 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+
+ // right underglow - 32
+ 34 + LPH, 35 + LPH, 36 + LPH, 37 + LPH, 38 + LPH, 39 + LPH, 40 + LPH, 41 + LPH, 42 + LPH, 43 + LPH, 44 + LPH, 45 + LPH, 46 + LPH, 47 + LPH, 48 + LPH, 49 + LPH, 50 + LPH, 51 + LPH,
+ 52 + LPH, 53 + LPH, 54 + LPH, 55 + LPH, 56 + LPH, 57 + LPH, 58 + LPH, 59 + LPH, 60 + LPH, 61 + LPH, 62 + LPH, 63 + LPH, 64 + LPH, 65 + LPH, 0xff
+ }
+ };
+};
+
+struct RaiseKeyScannerProps : public kaleidoscope::driver::keyscanner::BaseProps {
+ KEYSCANNER_PROPS(5, 16);
+ static constexpr uint8_t left_columns = 8;
+ static constexpr uint8_t right_columns = matrix_columns - left_columns;
+};
+
+class RaiseKeyScanner : public kaleidoscope::driver::keyscanner::Base {
+ private:
+ typedef RaiseKeyScanner ThisType;
+ typedef RaiseKeyScannerProps Props_;
+ public:
+ static void setup();
+ static void scanMatrix();
+ static void readMatrix();
+ static void actOnMatrixScan();
+
+ static void maskKey(KeyAddr key_addr);
+ static void unMaskKey(KeyAddr key_addr);
+ static bool isKeyMasked(KeyAddr key_addr);
+ static void maskHeldKeys();
+
+ static bool isKeyswitchPressed(KeyAddr key_addr);
+ static uint8_t pressedKeyswitchCount();
+
+ static bool wasKeyswitchPressed(KeyAddr key_addr);
+ static uint8_t previousPressedKeyswitchCount();
+
+ static void setKeyscanInterval(uint8_t interval);
+
+ static void reset();
+
+ protected:
+ static raise::keydata_t leftHandState;
+ static raise::keydata_t rightHandState;
+ static raise::keydata_t previousLeftHandState;
+ static raise::keydata_t previousRightHandState;
+
+ static raise::keydata_t leftHandMask;
+ static raise::keydata_t rightHandMask;
+
+ static bool lastLeftOnline;
+ static bool lastRightOnline;
+};
+
+struct RaiseStorageProps : public kaleidoscope::driver::storage::FlashProps {
+ static constexpr uint16_t length = EEPROM_EMULATION_SIZE;
+};
+
+struct RaiseSideFlasherProps : public kaleidoscope::util::flasher::BaseProps {};
+
+struct RaiseProps : kaleidoscope::device::BaseProps {
+ typedef RaiseLEDDriverProps LEDDriverProps;
+ typedef RaiseLEDDriver LEDDriver;
+ typedef RaiseKeyScannerProps KeyScannerProps;
+ typedef RaiseKeyScanner KeyScanner;
+ typedef RaiseStorageProps StorageProps;
+ typedef kaleidoscope::driver::storage::Flash Storage;
+ typedef kaleidoscope::driver::bootloader::samd::Bossac BootLoader;
+
+ typedef RaiseSideFlasherProps SideFlasherProps;
+ typedef kaleidoscope::util::flasher::KeyboardioI2CBootloader SideFlasher;
+};
+
+class Raise: public kaleidoscope::device::Base {
+ private:
+ static RaiseProps::SideFlasher SideFlasher;
+ public:
+ static void setup();
+
+ auto serialPort() -> decltype(SerialUSB) & {
+ return SerialUSB;
+ }
+
+ auto sideFlasher() -> decltype(SideFlasher) & {
+ return SideFlasher;
+ }
+
+ struct side {
+ uint8_t getPower();
+ void setPower(uint8_t power);
+
+ uint8_t leftVersion();
+ uint8_t rightVersion();
+
+ uint8_t leftCRCErrors();
+ uint8_t rightCRCErrors();
+
+ uint8_t leftSLEDVersion();
+ uint8_t rightSLEDVersion();
+
+ uint8_t leftSLEDCurrent();
+ uint8_t rightSLEDCurrent();
+ void setSLEDCurrent(uint8_t current);
+
+ void prepareForFlash();
+
+ // Side bootloader addresses
+ static constexpr uint8_t left_boot_address = 0x50;
+ static constexpr uint8_t right_boot_address = 0x51;
+ } side;
+
+ struct settings {
+ enum class Layout {
+ ISO,
+ ANSI
+ };
+ Layout layout();
+ uint8_t joint();
+
+ uint16_t keyscanInterval();
+ void keyscanInterval(uint16_t interval);
+ } settings;
+};
+
+
+}
+}
+
+typedef kaleidoscope::device::dygma::Raise Device;
+
+}
+
+#define PER_KEY_DATA(dflt, \
+ r0c0, r0c1, r0c2, r0c3, r0c4, r0c5, r0c6, r0c9, r0c10, r0c11, r0c12, r0c13, r0c14, r0c15, \
+ r1c0, r1c1, r1c2, r1c3, r1c4, r1c5, r1c8, r1c9, r1c10, r1c11, r1c12, r1c13, r1c14, r1c15, \
+ r2c0, r2c1, r2c2, r2c3, r2c4, r2c5, r2c9, r2c10, r2c11, r2c12, r2c13, r2c14, r2c15, \
+ r3c0, r3c1, r3c2, r3c3, r3c4, r3c5, r3c6, r3c10, r3c11, r3c12, r3c13, r3c14, r3c15, \
+ r4c0, r4c1, r4c2, r4c3, r4c4, r4c10, r4c11, r4c12, r4c13, r4c14, r4c15, \
+ r4c6, r4c7, r4c8, r4c9 \
+ ) \
+ \
+ r0c0, r0c1, r0c2, r0c3, r0c4, r0c5, r0c6, dflt, dflt, r0c9, r0c10, r0c11, r0c12, r0c13, r0c14, r0c15, \
+ r1c0, r1c1, r1c2, r1c3, r1c4, r1c5, dflt, dflt, r1c8, r1c9, r1c10, r1c11, r1c12, r1c13, r1c14, r1c15, \
+ r2c0, r2c1, r2c2, r2c3, r2c4, r2c5, dflt, dflt, dflt, r2c9, r2c10, r2c11, r2c12, r2c13, r2c14, r2c15, \
+ r3c0, r3c1, r3c2, r3c3, r3c4, r3c5, r3c6, dflt, dflt, dflt, r3c10, r3c11, r3c12, r3c13, r3c14, r3c15, \
+ r4c0, r4c1, r4c2, r4c3, r4c4, dflt, r4c6, r4c7, r4c8, r4c9, r4c10, r4c11, r4c12, r4c13, r4c14, r4c15
+
+
+#define PER_KEY_DATA_STACKED(dflt, \
+ r0c0, r0c1, r0c2, r0c3, r0c4, r0c5, r0c6, \
+ r1c0, r1c1, r1c2, r1c3, r1c4, r1c5, \
+ r2c0, r2c1, r2c2, r2c3, r2c4, r2c5, \
+ r3c0, r3c1, r3c2, r3c3, r3c4, r3c5, r3c6, \
+ r4c0, r4c1, r4c2, r4c3, r4c4, \
+ r4c6, r4c7, \
+ \
+ r0c9, r0c10, r0c11, r0c12, r0c13, r0c14, r0c15, \
+ r1c8, r1c9, r1c10, r1c11, r1c12, r1c13, r1c14, r1c15, \
+ r2c9, r2c10, r2c11, r2c12, r2c13, r2c14, r2c15, \
+ r3c10, r3c11, r3c12, r3c13, r3c14, r3c15, \
+ r4c10, r4c11, r4c12, r4c13, r4c14, r4c15, \
+ r4c8, r4c9 \
+ ) \
+ \
+ r0c0, r0c1, r0c2, r0c3, r0c4, r0c5, r0c6, dflt, dflt, r0c9, r0c10, r0c11, r0c12, r0c13, r0c14, r0c15, \
+ r1c0, r1c1, r1c2, r1c3, r1c4, r1c5, dflt, dflt, r1c8, r1c9, r1c10, r1c11, r1c12, r1c13, r1c14, r1c15, \
+ r2c0, r2c1, r2c2, r2c3, r2c4, r2c5, dflt, dflt, dflt, r2c9, r2c10, r2c11, r2c12, r2c13, r2c14, r2c15, \
+ r3c0, r3c1, r3c2, r3c3, r3c4, r3c5, r3c6, dflt, dflt, dflt, r3c10, r3c11, r3c12, r3c13, r3c14, r3c15, \
+ r4c0, r4c1, r4c2, r4c3, r4c4, dflt, r4c6, r4c7, r4c8, r4c9, r4c10, r4c11, r4c12, r4c13, r4c14, r4c15
+
+#endif
diff --git a/src/kaleidoscope/device/dygma/raise/Focus.cpp b/src/kaleidoscope/device/dygma/raise/Focus.cpp
new file mode 100644
index 00000000..137ac9d3
--- /dev/null
+++ b/src/kaleidoscope/device/dygma/raise/Focus.cpp
@@ -0,0 +1,133 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ * Copyright (C) 2017-2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#ifdef ARDUINO_SAMD_RAISE
+
+#include
+#include
+#include "kaleidoscope/device/dygma/raise/Focus.h"
+
+namespace kaleidoscope {
+namespace device {
+namespace dygma {
+namespace raise {
+
+#ifndef RAISE_FIRMWARE_VERSION
+#define RAISE_FIRMWARE_VERSION ""
+#endif
+
+EventHandlerResult Focus::onFocusEvent(const char *command) {
+ if (::Focus.handleHelp(command, PSTR("hardware.version\nhardware.side_power\nhardware.side_ver\nhardware.sled_ver\nhardware.sled_current\nhardware.layout\nhardware.joint\nhardware.keyscan\nhardware.crc_errors\nhardware.firmware")))
+ return EventHandlerResult::OK;
+
+ if (strncmp_P(command, PSTR("hardware."), 9) != 0)
+ return EventHandlerResult::OK;
+
+ if (strcmp_P(command + 9, PSTR("version")) == 0) {
+ ::Focus.send("Dygma Raise");
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+
+ if (strcmp_P(command + 9, PSTR("firmware")) == 0) {
+ ::Focus.send(RAISE_FIRMWARE_VERSION);
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+
+ if (strcmp_P(command + 9, PSTR("side_power")) == 0)
+ if (::Focus.isEOL()) {
+ ::Focus.send(Kaleidoscope.device().side.getPower());
+ return EventHandlerResult::EVENT_CONSUMED;
+ } else {
+ uint8_t power;
+ ::Focus.read(power);
+ Kaleidoscope.device().side.setPower(power);
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+
+ if (strcmp_P(command + 9, PSTR("side_ver")) == 0) {
+ ::Focus.send("left:");
+ ::Focus.send(Kaleidoscope.device().side.leftVersion());
+ ::Focus.send("\nright:");
+ ::Focus.send(Kaleidoscope.device().side.rightVersion());
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+
+ if (strcmp_P(command + 9, PSTR("crc_errors")) == 0) {
+ ::Focus.send("left:");
+ ::Focus.send(Kaleidoscope.device().side.leftCRCErrors());
+ ::Focus.send("\nright:");
+ ::Focus.send(Kaleidoscope.device().side.rightCRCErrors());
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+
+ if (strcmp_P(command + 9, PSTR("sled_ver")) == 0) {
+ ::Focus.send("left:");
+ ::Focus.send(Kaleidoscope.device().side.leftSLEDVersion());
+ ::Focus.send("\nright:");
+ ::Focus.send(Kaleidoscope.device().side.rightSLEDVersion());
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+
+ if (strcmp_P(command + 9, PSTR("sled_current")) == 0)
+ if (::Focus.isEOL()) {
+ ::Focus.send("left:");
+ ::Focus.send(Kaleidoscope.device().side.leftSLEDCurrent());
+ ::Focus.send("\nright:");
+ ::Focus.send(Kaleidoscope.device().side.rightSLEDCurrent());
+ return EventHandlerResult::EVENT_CONSUMED;
+ } else {
+ uint8_t current;
+ ::Focus.read(current);
+ Kaleidoscope.device().side.setSLEDCurrent(current);
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+
+ if (strcmp_P(command + 9, PSTR("layout")) == 0) {
+ static const auto ANSI = Kaleidoscope.device().settings.Layout::ANSI;
+ ::Focus.send(Kaleidoscope.device().settings.layout() == ANSI ? "ANSI" : "ISO");
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+
+ if (strcmp_P(command + 9, PSTR("joint")) == 0) {
+ ::Focus.send(Kaleidoscope.device().settings.joint());
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+
+ if (strcmp_P(command + 9, PSTR("keyscan")) == 0) {
+ if (::Focus.isEOL()) {
+ ::Focus.send(Kaleidoscope.device().settings.keyscanInterval());
+ return EventHandlerResult::EVENT_CONSUMED;
+ } else {
+ uint8_t keyscan;
+ ::Focus.read(keyscan);
+ Kaleidoscope.device().settings.keyscanInterval(keyscan);
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+ }
+
+ return EventHandlerResult::OK;
+}
+
+}
+}
+}
+}
+
+kaleidoscope::device::dygma::raise::Focus RaiseFocus;
+
+#endif
diff --git a/src/kaleidoscope/device/dygma/raise/Focus.h b/src/kaleidoscope/device/dygma/raise/Focus.h
new file mode 100644
index 00000000..9601147e
--- /dev/null
+++ b/src/kaleidoscope/device/dygma/raise/Focus.h
@@ -0,0 +1,42 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ * Copyright (C) 2017-2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#ifdef ARDUINO_SAMD_RAISE
+
+#include
+
+namespace kaleidoscope {
+namespace device {
+namespace dygma {
+namespace raise {
+
+class Focus : public kaleidoscope::Plugin {
+ public:
+ EventHandlerResult onFocusEvent(const char *command);
+};
+
+}
+}
+}
+}
+
+extern kaleidoscope::device::dygma::raise::Focus RaiseFocus;
+
+#endif
diff --git a/src/kaleidoscope/device/dygma/raise/Hand.cpp b/src/kaleidoscope/device/dygma/raise/Hand.cpp
new file mode 100644
index 00000000..33d9b387
--- /dev/null
+++ b/src/kaleidoscope/device/dygma/raise/Hand.cpp
@@ -0,0 +1,254 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ * Copyright (C) 2017-2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#ifdef ARDUINO_SAMD_RAISE
+
+#include
+#include "Hand.h"
+
+#include "kaleidoscope/driver/color/GammaCorrection.h"
+
+namespace kaleidoscope {
+namespace device {
+namespace dygma {
+namespace raise {
+
+#define TWI_CMD_NONE 0x00
+#define TWI_CMD_VERSION 0x01
+#define TWI_CMD_KEYSCAN_INTERVAL 0x02
+#define TWI_CMD_LED_SET_ALL_TO 0x03
+#define TWI_CMD_LED_SET_ONE_TO 0x04
+#define TWI_CMD_COLS_USE_PULLUPS 0x05
+#define TWI_CMD_LED_SPI_FREQUENCY 0x06
+#define TWI_CMD_LED_GLOBAL_BRIGHTNESS 0x07
+
+#define TWI_CMD_SLED_STATUS 0x08
+#define TWI_CMD_LED_OPEN 0x09
+#define TWI_CMD_LED_SHORT 0x0A
+#define TWI_CMD_JOINED 0x0B
+#define TWI_CMD_LAYOUT 0x0C
+#define TWI_CMD_SLED_CURRENT 0x0D
+#define TWI_CMD_SLED_SELF_TEST 0x0E
+
+#define LED_SPI_FREQUENCY_4MHZ 0x07
+#define LED_SPI_FREQUENCY_2MHZ 0x06
+#define LED_SPI_FREQUENCY_1MHZ 0x05
+#define LED_SPI_FREQUENCY_512KHZ 0x04
+#define LED_SPI_FREQUENCY_256KHZ 0x03
+#define LED_SPI_FREQUENCY_128KHZ 0x02
+#define LED_SPI_FREQUENCY_64KHZ 0x01
+#define LED_SPI_OFF 0x00
+
+// 512KHZ seems to be the sweet spot in early testing
+// so make it the default
+#define LED_SPI_FREQUENCY_DEFAULT LED_SPI_FREQUENCY_512KHZ
+
+#define TWI_CMD_LED_BASE 0x80
+
+#define TWI_REPLY_NONE 0x00
+#define TWI_REPLY_KEYDATA 0x01
+
+#define ELEMENTS(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+// Returns the relative controller addresss. The expected range is 0-3
+uint8_t Hand::controllerAddress() {
+ return ad01_;
+}
+
+// Sets the keyscan interval. We currently do three reads.
+// before declaring a key event debounced.
+//
+// Takes an integer value representing a counter.
+//
+// 0 - 0.1-0.25ms
+// 1 - 0.125ms
+// 10 - 0.35ms
+// 25 - 0.8ms
+// 50 - 1.6ms
+// 100 - 3.15ms
+//
+// You should think of this as the _minimum_ keyscan interval.
+// LED updates can cause a bit of jitter.
+//
+// returns the Wire.endTransmission code (0 = success)
+// https://www.arduino.cc/en/Reference/WireEndTransmission
+byte Hand::setKeyscanInterval(byte delay) {
+ uint8_t data[] = {TWI_CMD_KEYSCAN_INTERVAL, delay};
+ return twi_.writeTo(data, ELEMENTS(data));
+}
+
+// returns -1 on error, otherwise returns the scanner version integer
+int Hand::readVersion() {
+ return readRegister(TWI_CMD_VERSION);
+}
+
+// returns -1 on error, otherwise returns the sled version integer
+int Hand::readSLEDVersion() {
+ return readRegister(TWI_CMD_SLED_STATUS);
+}
+// returns -1 on error, otherwise returns the sled current settings
+int Hand::readSLEDCurrent() {
+ return readRegister(TWI_CMD_SLED_CURRENT);
+}
+
+byte Hand::setSLEDCurrent(byte current) {
+ uint8_t data[] = {TWI_CMD_SLED_CURRENT, current};
+ return twi_.writeTo(data, ELEMENTS(data));
+}
+
+// returns -1 on error, otherwise returns the scanner keyscan interval
+int Hand::readKeyscanInterval() {
+ return readRegister(TWI_CMD_KEYSCAN_INTERVAL);
+}
+
+// returns -1 on error, otherwise returns the layout (ANSI/ISO) setting
+int Hand::readLayout() {
+ return readRegister(TWI_CMD_LAYOUT);
+}
+
+// returns -1 on error, otherwise returns the LED SPI Frequncy
+int Hand::readLEDSPIFrequency() {
+ return readRegister(TWI_CMD_LED_SPI_FREQUENCY);
+}
+
+// Set the LED SPI Frequency. See wire-protocol-constants.h for
+// values.
+//
+// returns the Wire.endTransmission code (0 = success)
+// https://www.arduino.cc/en/Reference/WireEndTransmission
+byte Hand::setLEDSPIFrequency(byte frequency) {
+ uint8_t data[] = {TWI_CMD_LED_SPI_FREQUENCY, frequency};
+ return twi_.writeTo(data, ELEMENTS(data));
+}
+
+// returns -1 on error, otherwise returns the value of the hall sensor integer
+int Hand::readJoint() {
+ byte return_value = 0;
+
+ uint8_t data[] = {TWI_CMD_JOINED};
+ uint8_t result = twi_.writeTo(data, ELEMENTS(data));
+ if (result != 0)
+ return -1;
+
+ // needs to be long enough for the slave to respond
+ delayMicroseconds(40);
+
+ uint8_t rxBuffer[2];
+
+ // perform blocking read into buffer
+ uint8_t read = twi_.readFrom(rxBuffer, ELEMENTS(rxBuffer));
+ if (read == 2) {
+ return rxBuffer[0] + (rxBuffer[1] << 8);
+ } else {
+ return -1;
+ }
+}
+
+int Hand::readRegister(uint8_t cmd) {
+ byte return_value = 0;
+
+ uint8_t data[] = {cmd};
+ uint8_t result = twi_.writeTo(data, ELEMENTS(data));
+ if (result != 0)
+ return -1;
+
+ // needs to be long enough for the slave to respond
+ delayMicroseconds(40);
+
+ uint8_t rxBuffer[1];
+
+ // perform blocking read into buffer
+ uint8_t read = twi_.readFrom(rxBuffer, ELEMENTS(rxBuffer));
+ if (read > 0) {
+ return rxBuffer[0];
+ } else {
+ return -1;
+ }
+}
+
+// gives information on the key that was just pressed or released.
+bool Hand::readKeys() {
+ uint8_t rxBuffer[6] = {0, 0, 0, 0, 0, 0};
+
+ // perform blocking read into buffer
+ uint8_t result = twi_.readFrom(rxBuffer, ELEMENTS(rxBuffer));
+ // if result isn't 6? this can happens if slave nacks while trying to read
+ Hand::online = (result == 6) ? true : false;
+
+ if (result != 6)
+ // could also try reset pressed keys here
+ return false;
+
+ if (rxBuffer[0] == TWI_REPLY_KEYDATA) {
+ key_data_.rows[0] = rxBuffer[1];
+ key_data_.rows[1] = rxBuffer[2];
+ key_data_.rows[2] = rxBuffer[3];
+ key_data_.rows[3] = rxBuffer[4];
+ key_data_.rows[4] = rxBuffer[5];
+ return true;
+ } else {
+ return false;
+ }
+}
+
+keydata_t Hand::getKeyData() {
+ return key_data_;
+}
+
+void Hand::sendLEDData() {
+ sendLEDBank(next_led_bank_++);
+ if (next_led_bank_ == LED_BANKS) {
+ next_led_bank_ = 0;
+ }
+}
+
+auto constexpr gamma8 = kaleidoscope::driver::color::gamma_correction;
+
+void Hand::sendLEDBank(uint8_t bank) {
+ uint8_t data[LED_BYTES_PER_BANK + 1]; // + 1 for the update LED command itself
+ data[0] = TWI_CMD_LED_BASE + bank;
+ for (uint8_t i = 0 ; i < LED_BYTES_PER_BANK; i++) {
+ data[i + 1] = pgm_read_byte(&gamma8[led_data.bytes[bank][i]]);
+ }
+ uint8_t result = twi_.writeTo(data, ELEMENTS(data));
+}
+
+void Hand::setAllLEDsTo(cRGB color) {
+ uint8_t data[] = {TWI_CMD_LED_SET_ALL_TO,
+ pgm_read_byte(&gamma8[color.r]),
+ pgm_read_byte(&gamma8[color.g]),
+ pgm_read_byte(&gamma8[color.b])
+ };
+ twi_.writeTo(data, ELEMENTS(data));
+}
+
+void Hand::setOneLEDTo(byte led, cRGB color) {
+ uint8_t data[] = {TWI_CMD_LED_SET_ONE_TO,
+ led,
+ pgm_read_byte(&gamma8[color.r]),
+ pgm_read_byte(&gamma8[color.g]),
+ pgm_read_byte(&gamma8[color.b])
+ };
+ twi_.writeTo(data, ELEMENTS(data));
+}
+
+}
+}
+}
+}
+#endif
diff --git a/src/kaleidoscope/device/dygma/raise/Hand.h b/src/kaleidoscope/device/dygma/raise/Hand.h
new file mode 100644
index 00000000..d82f23c9
--- /dev/null
+++ b/src/kaleidoscope/device/dygma/raise/Hand.h
@@ -0,0 +1,105 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ * Copyright (C) 2017-2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#ifdef ARDUINO_SAMD_RAISE
+
+#include
+#include "TWI.h"
+
+struct cRGB {
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+};
+
+namespace kaleidoscope {
+namespace device {
+namespace dygma {
+namespace raise {
+
+#define LED_BANKS 9
+
+#define LEDS_PER_HAND 72
+#define LPH LEDS_PER_HAND
+#define LEDS_PER_BANK 8
+#define LED_BYTES_PER_BANK (sizeof(cRGB) * LEDS_PER_BANK)
+
+typedef union {
+ cRGB leds[LEDS_PER_HAND];
+ byte bytes[LED_BANKS][LED_BYTES_PER_BANK];
+} LEDData_t;
+
+// return what bank the led is in
+#define LED_TO_BANK(led) (led / LEDS_PER_BANK)
+
+typedef union {
+ uint8_t rows[5];
+ uint64_t all;
+} keydata_t;
+
+class Hand {
+ public:
+ Hand(byte ad01) : ad01_(ad01), twi_(i2c_addr_base_ | ad01) {}
+
+ int readVersion();
+ int readSLEDVersion();
+ int readSLEDCurrent();
+ byte setSLEDCurrent(byte current);
+ int readJoint();
+ int readLayout();
+
+ byte setKeyscanInterval(byte delay);
+ int readKeyscanInterval();
+
+ byte setLEDSPIFrequency(byte frequency);
+ int readLEDSPIFrequency();
+
+ bool moreKeysWaiting();
+ void sendLEDData();
+ void sendLEDBank(uint8_t bank);
+ void setOneLEDTo(byte led, cRGB color);
+ void setAllLEDsTo(cRGB color);
+ keydata_t getKeyData();
+ bool readKeys();
+ uint8_t controllerAddress();
+ uint8_t crc_errors() {
+ return twi_.crc_errors();
+ }
+
+ LEDData_t led_data;
+ bool online = false;
+
+ private:
+ int ad01_;
+ TWI twi_;
+ keydata_t key_data_;
+ uint8_t next_led_bank_ = 0;
+
+ static constexpr uint8_t i2c_addr_base_ = 0x58;
+
+ int readRegister(uint8_t cmd);
+};
+
+}
+}
+}
+}
+
+#endif
diff --git a/src/kaleidoscope/device/dygma/raise/SideFlash.h b/src/kaleidoscope/device/dygma/raise/SideFlash.h
new file mode 100644
index 00000000..f5b74b03
--- /dev/null
+++ b/src/kaleidoscope/device/dygma/raise/SideFlash.h
@@ -0,0 +1,84 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ * Copyright (C) 2017-2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#ifdef ARDUINO_SAMD_RAISE
+
+#include
+
+namespace kaleidoscope {
+namespace device {
+namespace dygma {
+namespace raise {
+
+template
+class SideFlash : public kaleidoscope::Plugin {
+ private:
+ _Firmware firmware;
+ public:
+ EventHandlerResult onFocusEvent(const char *command) {
+ if (::Focus.handleHelp(command, PSTR("hardware.flash_left_side\nhardware.flash_right_side\nhardware.verify_left_side\nhardware.verify_right_side")))
+ return EventHandlerResult::OK;
+
+ if (strncmp_P(command, PSTR("hardware."), 9) != 0)
+ return EventHandlerResult::OK;
+
+ auto sideFlasher = Kaleidoscope.device().sideFlasher();
+ uint8_t left_boot_address = Kaleidoscope.device().side.left_boot_address;
+ uint8_t right_boot_address = Kaleidoscope.device().side.right_boot_address;
+ enum {
+ FLASH,
+ VERIFY
+ } sub_command;
+ uint8_t address = 0;
+
+ if (strcmp_P(command + 9, PSTR("flash_left_side")) == 0) {
+ sub_command = FLASH;
+ address = left_boot_address;
+ } else if (strcmp_P(command + 9, PSTR("flash_right_side")) == 0) {
+ sub_command = FLASH;
+ address = right_boot_address;
+ } else if (strcmp_P(command + 9, PSTR("verify_left_side")) == 0) {
+ sub_command = VERIFY;
+ address = left_boot_address;
+ } else if (strcmp_P(command + 9, PSTR("verify_right_side")) == 0) {
+ sub_command = VERIFY;
+ address = right_boot_address;
+ } else {
+ return EventHandlerResult::OK;
+ }
+
+ bool result;
+ Kaleidoscope.device().side.prepareForFlash();
+ if (sub_command == FLASH)
+ result = sideFlasher.flash(address, firmware);
+ else
+ result = sideFlasher.verify(address, firmware);
+ ::Focus.send(result);
+
+ return EventHandlerResult::EVENT_CONSUMED;
+ }
+};
+
+}
+}
+}
+}
+
+#endif
diff --git a/src/kaleidoscope/device/dygma/raise/TWI.cpp b/src/kaleidoscope/device/dygma/raise/TWI.cpp
new file mode 100644
index 00000000..15fae04c
--- /dev/null
+++ b/src/kaleidoscope/device/dygma/raise/TWI.cpp
@@ -0,0 +1,99 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ * Copyright (C) 2017-2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#ifdef ARDUINO_SAMD_RAISE
+
+#include
+
+#include "TWI.h"
+#include "kaleidoscope/util/crc16.h"
+
+namespace kaleidoscope {
+namespace device {
+namespace dygma {
+namespace raise {
+
+uint8_t TWI::writeTo(uint8_t *data, size_t length) {
+ Wire.beginTransmission(addr_);
+
+ // calc cksum
+ uint16_t crc16 = 0xffff;
+ uint8_t *buffer = data;
+ for (uint8_t i = 0; i < length; i++) {
+ crc16 = _crc_ccitt_update(crc16, *buffer);
+ buffer++;
+ }
+
+ // make cksum high byte and low byte
+ uint8_t crc_bytes[2];
+ crc_bytes[0] = crc16 >> 8;
+ crc_bytes[1] = crc16;
+
+ if (!Wire.write(data, length)) return 1;
+ if (!Wire.write(crc_bytes, 2)) return 1;
+ if (Wire.endTransmission(true) != 0) return 1;
+ return 0;
+}
+
+uint8_t TWI::readFrom(uint8_t* data, size_t length) {
+ uint8_t counter = 0;
+ uint32_t timeout;
+ uint8_t *buffer = data;
+
+ if (!Wire.requestFrom(addr_, length + 2, true)) { // + 2 for the cksum
+ // in case slave is not responding - return 0 (0 length of received data).
+ return 0;
+ }
+ while (counter < length) {
+ *data = Wire.read();
+ data++;
+ counter++;
+ }
+
+ uint16_t crc16 = 0xffff;
+ uint16_t rx_cksum = (Wire.read() << 8) + Wire.read();
+ for (uint8_t i = 0; i < length; i++) {
+ crc16 = _crc_ccitt_update(crc16, *buffer);
+ buffer++;
+ }
+
+ // check received CRC16
+ if (crc16 != rx_cksum) {
+ crc_errors_++;
+ return 0;
+ }
+
+ return length;
+}
+
+void TWI::disable() {
+ Wire.end();
+}
+
+void TWI::init(uint16_t clock_khz) {
+ Wire.begin();
+ Wire.setClock(clock_khz * 1000);
+}
+
+
+}
+}
+}
+}
+
+#endif
diff --git a/src/kaleidoscope/device/dygma/raise/TWI.h b/src/kaleidoscope/device/dygma/raise/TWI.h
new file mode 100644
index 00000000..5d96abb2
--- /dev/null
+++ b/src/kaleidoscope/device/dygma/raise/TWI.h
@@ -0,0 +1,49 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::device::dygma::Raise -- Kaleidoscope device plugin for Dygma Raise
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ * Copyright (C) 2017-2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#ifdef ARDUINO_SAMD_RAISE
+
+#include
+
+namespace kaleidoscope {
+namespace device {
+namespace dygma {
+namespace raise {
+
+class TWI {
+ public:
+ TWI(int addr) : addr_(addr), crc_errors_(0) {};
+
+ uint8_t writeTo(uint8_t *data, size_t length);
+ uint8_t readFrom(uint8_t* data, size_t length);
+ void disable();
+ void init(uint16_t clock_khz);
+ uint8_t crc_errors() {
+ return crc_errors_;
+ }
+
+ private:
+ int addr_;
+ uint8_t crc_errors_;
+};
+
+}
+}
+}
+}
+#endif
diff --git a/src/kaleidoscope/driver/color/GammaCorrection.h b/src/kaleidoscope/driver/color/GammaCorrection.h
new file mode 100644
index 00000000..f11137e3
--- /dev/null
+++ b/src/kaleidoscope/driver/color/GammaCorrection.h
@@ -0,0 +1,47 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::driver::led::Gamma -- Gamma correction table
+ * Copyright (C) 2017-2019 Keyboard.io, Inc
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+#include
+
+namespace kaleidoscope {
+namespace driver {
+namespace color {
+
+const uint8_t PROGMEM gamma_correction[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
+ 2, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5,
+ 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10,
+ 10, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16,
+ 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25,
+ 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36,
+ 37, 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50,
+ 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68,
+ 69, 70, 72, 73, 74, 75, 77, 78, 79, 81, 82, 83, 85, 86, 87, 89,
+ 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 109, 110, 112, 114,
+ 115, 117, 119, 120, 122, 124, 126, 127, 129, 131, 133, 135, 137, 138, 140, 142,
+ 144, 146, 148, 150, 152, 154, 156, 158, 160, 162, 164, 167, 169, 171, 173, 175,
+ 177, 180, 182, 184, 186, 189, 191, 193, 196, 198, 200, 203, 205, 208, 210, 213,
+ 215, 218, 220, 223, 225, 228, 231, 233, 236, 239, 241, 244, 247, 249, 252, 255
+};
+
+}
+}
+}
diff --git a/src/kaleidoscope/util/crc16.h b/src/kaleidoscope/util/crc16.h
new file mode 100644
index 00000000..92cdb77a
--- /dev/null
+++ b/src/kaleidoscope/util/crc16.h
@@ -0,0 +1,94 @@
+// CRC compatibility, adapted from the C-only comments here:
+// http://svn.savannah.nongnu.org/viewvc/trunk/avr-libc/include/util/crc16.h?revision=933&root=avr-libc&view=markup
+
+/* Copyright (c) 2002, 2003, 2004 Marek Michalkiewicz
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of the copyright holders nor the names of
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE. */
+
+#ifndef _UTIL_CRC16_H_
+#define _UTIL_CRC16_H_
+
+#include
+
+static inline uint16_t _crc16_update(uint16_t crc, uint8_t data) __attribute__((always_inline, unused));
+static inline uint16_t _crc16_update(uint16_t crc, uint8_t data) {
+ unsigned int i;
+
+ crc ^= data;
+ for (i = 0; i < 8; ++i) {
+ if (crc & 1) {
+ crc = (crc >> 1) ^ 0xA001;
+ } else {
+ crc = (crc >> 1);
+ }
+ }
+ return crc;
+}
+
+static inline uint16_t _crc_xmodem_update(uint16_t crc, uint8_t data) __attribute__((always_inline, unused));
+static inline uint16_t _crc_xmodem_update(uint16_t crc, uint8_t data) {
+ unsigned int i;
+
+ crc = crc ^ ((uint16_t)data << 8);
+ for (i = 0; i < 8; i++) {
+ if (crc & 0x8000) {
+ crc = (crc << 1) ^ 0x1021;
+ } else {
+ crc <<= 1;
+ }
+ }
+ return crc;
+}
+
+static inline uint16_t _crc_ccitt_update(uint16_t crc, uint8_t data) __attribute__((always_inline, unused));
+static inline uint16_t _crc_ccitt_update(uint16_t crc, uint8_t data) {
+ data ^= (crc & 255);
+ data ^= data << 4;
+
+ return ((((uint16_t)data << 8) | (crc >> 8)) ^ (uint8_t)(data >> 4)
+ ^ ((uint16_t)data << 3));
+}
+
+static inline uint8_t _crc_ibutton_update(uint8_t crc, uint8_t data) __attribute__((always_inline, unused));
+static inline uint8_t _crc_ibutton_update(uint8_t crc, uint8_t data) {
+ unsigned int i;
+
+ crc = crc ^ data;
+ for (i = 0; i < 8; i++) {
+ if (crc & 0x01) {
+ crc = (crc >> 1) ^ 0x8C;
+ } else {
+ crc >>= 1;
+ }
+ }
+ return crc;
+}
+
+#endif
+
diff --git a/src/kaleidoscope/util/flasher/Base.h b/src/kaleidoscope/util/flasher/Base.h
new file mode 100644
index 00000000..753c0ef6
--- /dev/null
+++ b/src/kaleidoscope/util/flasher/Base.h
@@ -0,0 +1,60 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::util::flasher::Base -- Base flasher utility class
+ * Copyright (C) 2019 Keyboard.io, Inc
+ * Copyright (C) 2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+namespace kaleidoscope {
+namespace util {
+namespace flasher {
+
+struct BaseProps {
+ static constexpr uint8_t page_size = 64;
+ static constexpr uint8_t frame_size = 16;
+ static constexpr uint8_t blank = 0xff;
+ static constexpr uint8_t delay = 1;
+
+ static struct {
+ static constexpr uint8_t page_address = 0x01;
+ static constexpr uint8_t continue_page = 0x02;
+ static constexpr uint8_t execute = 0x03;
+ static constexpr uint8_t erase_program = 0x04;
+ static constexpr uint8_t get_version_and_crc = 0x06;
+ } command;
+};
+
+template
+class Base {
+ public:
+ Base() {}
+
+ template
+ static uint8_t flash(uint8_t address, T& firmware) {
+ return 0;
+ }
+ template
+ static uint8_t verify(uint8_t address, T& firmware) {
+ return 0;
+ }
+ static uint8_t command(uint8_t address, uint8_t command) {
+ return 0;
+ }
+};
+
+}
+}
+}
diff --git a/src/kaleidoscope/util/flasher/KeyboardioI2CBootloader.h b/src/kaleidoscope/util/flasher/KeyboardioI2CBootloader.h
new file mode 100644
index 00000000..88f789bc
--- /dev/null
+++ b/src/kaleidoscope/util/flasher/KeyboardioI2CBootloader.h
@@ -0,0 +1,226 @@
+/* -*- mode: c++ -*-
+ * kaleidoscope::util::flasher::KeyboardioI2CBootloader -- Flasher for Keyboardio's I2C Bootloader
+ * Copyright (C) 2019 Keyboard.io, Inc
+ * Copyright (C) 2019 Dygma Lab S.L.
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see .
+ */
+
+#pragma once
+
+// TODO(@algernon): We should support AVR here, too.
+#ifdef __SAMD21G18A__
+
+#include
+
+#include "kaleidoscope/util/flasher/Base.h"
+#include "kaleidoscope/util/crc16.h"
+
+namespace kaleidoscope {
+namespace util {
+namespace flasher {
+
+template
+class KeyboardioI2CBootloader: kaleidoscope::util::flasher::Base<_Props> {
+ public:
+ template
+ static bool flash(uint8_t address, T& firmware) {
+ if (!verify(address, firmware)) {
+ return false;
+ }
+
+ if (!erase_program(address)) {
+ return false;
+ }
+ if (!write_firmware(address, firmware)) {
+ return false;
+ }
+ if (!verify_firmware(address, firmware)) {
+ return false;
+ }
+
+ return command(address, _Props::command.execute) == 0 ? true : false;
+ }
+
+ template
+ static bool verify(uint8_t address, T& firmware) {
+ CRCAndVersion crc_and_version = get_version(address, firmware);
+ return (crc_and_version.version != 0xff) && (crc_and_version.crc != 0xffff);
+ }
+
+ static uint8_t command(uint8_t address, uint8_t command) {
+ Wire.beginTransmission(address);
+ Wire.write(command);
+ Wire.write(0x00);
+ uint8_t result = Wire.endTransmission();
+ return result;
+ }
+
+ private:
+ struct CRCAndVersion {
+ uint8_t version;
+ uint16_t crc;
+ };
+
+ static uint8_t read_crc16(uint8_t addr,
+ CRCAndVersion *crc_and_version,
+ uint16_t offset, uint16_t length) {
+ uint8_t result;
+
+ Wire.beginTransmission(addr);
+ Wire.write(_Props::command.get_version_and_crc);
+ Wire.write(offset & 0xff); // addr (lo)
+ Wire.write(offset >> 8); // addr (hi)
+ Wire.write(length & 0xff); // len (lo)
+ Wire.write(length >> 8); // len (hi)
+ result = Wire.endTransmission(false);
+ if (result != 0) {
+ return result;
+ }
+
+ // wait for cksum to be calculated - takes about 20ms
+ delay(100);
+
+ Wire.requestFrom(addr, 3);
+ uint8_t v = Wire.read();
+ crc_and_version->version = v;
+ if (Wire.available() == 0) {
+ return 0xFF;
+ }
+ uint8_t crc16_lo = Wire.read();
+ if (Wire.available() == 0) {
+ return 0xFF;
+ }
+ uint8_t crc16_hi = Wire.read();
+ while (Wire.available()) {
+ uint8_t c = Wire.read();
+ }
+ crc_and_version->crc = (crc16_hi << 8) | crc16_lo;
+ return result;
+ }
+
+ template
+ static CRCAndVersion get_version(uint8_t addr, T& firmware) {
+ static CRCAndVersion crc_and_version = {0xff, 0xff};
+
+ // This here to resolve some weird I2C startup bug.
+ // Usually in the RHS, get_version fails with the I2C master writing the
+ // address and the CRC request (0x06), the CRC parameters are never written
+ // doing a read first seems to let things settle in a way that allows the
+ // right to respond correctly
+ Wire.requestFrom(addr, (uint8_t) 3);
+ while (Wire.available()) {
+ // throw away the info, as cksum calculation request has yet to be issued.
+ Wire.read();
+ }
+
+ int result = read_crc16(addr, &crc_and_version,
+ firmware.offsets[0] + 4,
+ firmware.length - 4);
+ return crc_and_version;
+ }
+
+ static bool erase_program(uint8_t addr) {
+ Wire.beginTransmission(addr);
+ Wire.write(_Props::command.erase_program);
+ uint8_t result = Wire.endTransmission();
+
+ // wait for erase
+ delay(1000);
+
+ return result != 0;
+ }
+
+ template
+ static bool write_firmware(uint8_t addr, T& firmware) {
+ uint8_t result;
+ uint8_t o = 0;
+
+ for (uint16_t i = 0; i < firmware.length; i += _Props::page_size) {
+ Wire.beginTransmission(addr);
+ Wire.write(_Props::command.page_address);
+ Wire.write(firmware.offsets[o] & 0xff);
+ Wire.write(firmware.offsets[o] >> 8);
+ result = Wire.endTransmission();
+ delay(_Props::delay);
+
+ // got something other than ACK. Start over.
+ if (result != 0) {
+ return false;
+ }
+
+ // transmit each frame separately
+ for (uint8_t frame = 0; frame < _Props::page_size / _Props::frame_size; frame++) {
+ Wire.beginTransmission(addr);
+ Wire.write(_Props::command.continue_page);
+ uint16_t crc16 = 0xffff;
+ for (uint8_t j = frame * _Props::frame_size;
+ j < (frame + 1) * _Props::frame_size;
+ j++) {
+ if (i + j < firmware.length) {
+ uint8_t b = pgm_read_byte(&firmware.data[i + j]);
+ Wire.write(b);
+ crc16 = _crc16_update(crc16, b);
+ } else {
+ Wire.write(_Props::blank);
+ crc16 = _crc16_update(crc16, _Props::blank);
+ }
+ }
+ // write the CRC16, little end first
+ Wire.write(crc16 & 0xff);
+ Wire.write(crc16 >> 8);
+ Wire.write(0x00); // dummy end uint8_t
+ result = Wire.endTransmission();
+ // got something other than NACK. Start over.
+ if (result != 3) {
+ return false;
+ }
+ delay(_Props::delay);
+ }
+ o++;
+ }
+ return true;
+ }
+
+ template
+ static bool verify_firmware(uint8_t addr, T& firmware) {
+ uint8_t result = 3;
+ CRCAndVersion crc_and_version;
+
+ while (result != 0) {
+ // skip the first 4 uint8_ts, are they were probably overwritten by the
+ // reset vector preservation
+ result = read_crc16(addr, &crc_and_version,
+ firmware.offsets[0] + 4, firmware.length - 4);
+ if (result != 0) {
+ delay(100);
+ continue;
+ }
+ }
+
+ // calculate our own CRC16
+ uint16_t check_crc16 = 0xffff;
+ for (uint16_t i = 4; i < firmware.length; i++) {
+ check_crc16 = _crc16_update(check_crc16, pgm_read_byte(&(firmware.data[i])));
+ }
+
+ return crc_and_version.crc == check_crc16;
+ }
+
+};
+
+}
+}
+}
+
+#endif