aboutsummaryrefslogtreecommitdiff
path: root/keyboards/splitkb/elora/rev1
diff options
context:
space:
mode:
authorleah-splitkb <[email protected]>2025-03-02 22:33:03 +0100
committerGitHub <[email protected]>2025-03-02 14:33:03 -0700
commitcb6e1c1af54790e778062ba7203aa23370c263b0 (patch)
tree5fb1b3c42c293af6c03741f96e52a5e9370351fa /keyboards/splitkb/elora/rev1
parent563eb6fdc9224a772d19e2d5714e903fa7c021c6 (diff)
downloadqmk_firmware-master.tar.gz
qmk_firmware-master.zip
Add splitkb.com's Elora (#22557)master
* Add splitkb's Elora * WIP * Make requested changes * fix missing call to keyboard init user for elora * sync vial_qmk with qmk * Add fixes * Add encoder quadrature and update files * Update Readme * Implement changes * Remove encoder driver from keyboard.json * make requested changed * Implement changes and remove myriad_task as it wasn't actually doing anything. * Remove myriad.h from matrix.c * Simplify OLED code debug * Update RGB keycodes (qmk#24484) * remove rules.mk (qmk#23281) * Add matrix state sync --------- Co-authored-by: NapOli1084 <[email protected]> Co-authored-by: harveysch <[email protected]> Co-authored-by: harvey <[email protected]> Co-authored-by: VeyPatch <[email protected]>
Diffstat (limited to 'keyboards/splitkb/elora/rev1')
-rw-r--r--keyboards/splitkb/elora/rev1/config.h60
-rw-r--r--keyboards/splitkb/elora/rev1/halconf.h13
-rw-r--r--keyboards/splitkb/elora/rev1/keyboard.json108
-rw-r--r--keyboards/splitkb/elora/rev1/matrix.c57
-rw-r--r--keyboards/splitkb/elora/rev1/mcuconf.h18
-rw-r--r--keyboards/splitkb/elora/rev1/myriad.c315
-rw-r--r--keyboards/splitkb/elora/rev1/myriad.h52
-rw-r--r--keyboards/splitkb/elora/rev1/rev1.c214
-rw-r--r--keyboards/splitkb/elora/rev1/rev1.h14
-rw-r--r--keyboards/splitkb/elora/rev1/rules.mk7
10 files changed, 858 insertions, 0 deletions
diff --git a/keyboards/splitkb/elora/rev1/config.h b/keyboards/splitkb/elora/rev1/config.h
new file mode 100644
index 0000000000..7633cf00af
--- /dev/null
+++ b/keyboards/splitkb/elora/rev1/config.h
@@ -0,0 +1,60 @@
+// Copyright 2024 splitkb.com ([email protected])
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+// Make it easier to enter the bootloader
+#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET
+#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_TIMEOUT 1000U
+
+// I2C0, onboard SSD1306 socket and I2C to Myriad module
+#define I2C_DRIVER I2CD0
+#define I2C1_SDA_PIN GP0
+#define I2C1_SCL_PIN GP1
+// We need to slow down the I2C clock because the default of 400.000
+// fails to communicate with Zetta ZD24C02A EEPROM on a Myriad card.
+#define I2C1_CLOCK_SPEED 100000
+
+// SPI1, both for onboard matrix data and SPI to Myriad module
+#define SPI_DRIVER SPID1
+#define SPI_SCK_PIN GP10
+#define SPI_MOSI_PIN GP11
+#define SPI_MISO_PIN GP12
+
+// UART1, communication between the two halves
+#define SERIAL_USART_FULL_DUPLEX
+#define SERIAL_USART_DRIVER SIOD1
+#define SERIAL_USART_TX_PIN GP20
+#define SERIAL_USART_RX_PIN GP21
+
+// Potential onboard speaker, not populated by default
+#define AUDIO_PIN GP23
+
+// Transmitting pointing device status to the master side
+#define SPLIT_POINTING_ENABLE
+#define POINTING_DEVICE_COMBINED
+
+// VBUS detection
+#define USB_VBUS_PIN GP25
+
+// Define matrix size
+#define MATRIX_COLS 8
+#define MATRIX_ROWS 12
+
+// Encoders
+// 3 onboard, 1 for Myriad
+#define NUM_ENCODERS_LEFT 4
+#define NUM_ENCODERS_RIGHT 4
+#define ENCODER_RESOLUTION 2
+
+// OLED display
+#define OLED_DISPLAY_128X64
+// If someone has only one OLED display
+// and that display was on the slave side.
+// It wouldn't work at all. This fixes that
+// including some code in rev1.c but makes
+// it so the timeout's are not synced
+// between halves.
+#undef SPLIT_OLED_ENABLE
+#define OLED_TIMEOUT 0
+
diff --git a/keyboards/splitkb/elora/rev1/halconf.h b/keyboards/splitkb/elora/rev1/halconf.h
new file mode 100644
index 0000000000..9798aa34f9
--- /dev/null
+++ b/keyboards/splitkb/elora/rev1/halconf.h
@@ -0,0 +1,13 @@
+// Copyright 2024 splitkb.com ([email protected])
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#define HAL_USE_SIO TRUE
+#define HAL_USE_I2C TRUE
+#define HAL_USE_SPI TRUE
+#define SPI_USE_WAIT TRUE
+#define SPI_SELECT_MODE SPI_SELECT_MODE_PAD
+#define HAL_USE_ADC TRUE
+
+#include_next <halconf.h>
diff --git a/keyboards/splitkb/elora/rev1/keyboard.json b/keyboards/splitkb/elora/rev1/keyboard.json
new file mode 100644
index 0000000000..a2d02a9a49
--- /dev/null
+++ b/keyboards/splitkb/elora/rev1/keyboard.json
@@ -0,0 +1,108 @@
+{
+ "keyboard_name": "Elora rev1",
+ "bootloader": "rp2040",
+ "features": {
+ "pointing_device": true,
+ "rgb_matrix": true,
+ "rgblight": false,
+ "encoder": true,
+ "oled": true,
+ "extrakey": true,
+ "mousekey": true,
+ "nkro": true,
+ "audio": false
+ },
+ "processor": "RP2040",
+ "rgb_matrix": {
+ "animations": {
+ "alphas_mods": true,
+ "band_pinwheel_sat": true,
+ "band_pinwheel_val": true,
+ "band_sat": true,
+ "band_spiral_sat": true,
+ "band_spiral_val": true,
+ "band_val": true,
+ "breathing": true,
+ "cycle_all": true,
+ "cycle_left_right": true,
+ "cycle_out_in": true,
+ "cycle_out_in_dual": true,
+ "cycle_pinwheel": true,
+ "cycle_spiral": true,
+ "cycle_up_down": true,
+ "digital_rain": true,
+ "dual_beacon": true,
+ "gradient_left_right": true,
+ "gradient_up_down": true,
+ "hue_breathing": true,
+ "hue_pendulum": true,
+ "hue_wave": true,
+ "jellybean_raindrops": true,
+ "multisplash": true,
+ "pixel_flow": true,
+ "pixel_fractal": true,
+ "pixel_rain": true,
+ "rainbow_beacon": true,
+ "rainbow_moving_chevron": true,
+ "rainbow_pinwheels": true,
+ "raindrops": true,
+ "solid_color": true,
+ "solid_multisplash": true,
+ "solid_reactive": true,
+ "solid_reactive_cross": true,
+ "solid_reactive_multicross": true,
+ "solid_reactive_multinexus": true,
+ "solid_reactive_multiwide": true,
+ "solid_reactive_nexus": true,
+ "solid_reactive_simple": true,
+ "solid_reactive_wide": true,
+ "solid_splash": true,
+ "splash": true,
+ "typing_heatmap": true
+ },
+ "driver": "ws2812",
+ "layout": [
+ {"x": 90, "y": 0, "flags": 2},
+ {"x": 45, "y": 0, "flags": 2},
+ {"x": 0, "y": 0, "flags": 2},
+ {"x": 0, "y": 64, "flags": 2},
+ {"x": 45, "y": 64, "flags": 2},
+ {"x": 90, "y": 64, "flags": 2},
+ {"x": 134, "y": 0, "flags": 2},
+ {"x": 179, "y": 0, "flags": 2},
+ {"x": 224, "y": 0, "flags": 2},
+ {"x": 224, "y": 64, "flags": 2},
+ {"x": 179, "y": 64, "flags": 2},
+ {"x": 134, "y": 64, "flags": 2}
+ ],
+ "max_brightness": 150,
+ "split_count": [6, 6]
+ },
+ "rgblight": {
+ "led_count": 12,
+ "split": true
+ },
+ "split": {
+ "enabled": true,
+ "handedness": {
+ "pin": "GP14"
+ },
+ "serial": {
+ "driver": "usart"
+ },
+ "transport": {
+ "sync": {
+ "matrix_state": true
+ },
+ "watchdog": true,
+ "watchdog_timeout": 3000
+ }
+ },
+ "usb": {
+ "pid": "0x9D9D"
+ },
+ "ws2812": {
+ "driver": "vendor",
+ "pin": "GP15"
+ }
+}
diff --git a/keyboards/splitkb/elora/rev1/matrix.c b/keyboards/splitkb/elora/rev1/matrix.c
new file mode 100644
index 0000000000..625eafcd85
--- /dev/null
+++ b/keyboards/splitkb/elora/rev1/matrix.c
@@ -0,0 +1,57 @@
+// Copyright 2024 splitkb.com ([email protected])
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "matrix.h"
+#include "spi_master.h"
+
+// The matrix is hooked up to a chain of 74xx165 shift registers.
+// Pin F0 acts as Chip Select (active-low)
+// The signal goes to a NOT gate, whose output is wired to
+// a) the latch pin of the shift registers
+// b) the "enable" pin of a tri-state buffer,
+// attached between the shift registers and MISO
+// F0 has an external pull-up.
+// SCK works as usual.
+//
+// Note that the matrix contains a variety of data.
+// In addition to the keys, it also reads the rotary encoders
+// and whether the board is the left/right half.
+
+void matrix_init_custom(void) {
+ // Note: `spi_init` has already been called
+ // in `keyboard_pre_init_kb()`, so nothing to do here
+}
+
+bool matrix_scan_custom(matrix_row_t current_matrix[]) {
+ // Enough to hold the shift registers
+ uint16_t length = 5;
+ uint8_t data[length];
+
+ // Matrix SPI config
+ // 1) Pin
+ // 2) Mode: Register shifts on rising clock, and clock idles low
+ // pol = 0 & pha = 0 => mode 0
+ // 3) LSB first: Register outputs H first, and we want H as MSB,
+ // as this result in a neat A-H order in the layout macro.
+ // 4) Divisor: 2 is the fastest possible, at Fclk / 2.
+ // range is 2-128
+ spi_start(GP13, false, 0, 128);
+ spi_receive(data, length);
+ spi_stop();
+
+ bool matrix_has_changed = false;
+ for (uint8_t i = 0; i < length; i++) {
+ // Bitwise NOT because we use pull-ups,
+ // and switches short the pin to ground,
+ // but the matrix uses 1 to indicate a pressed switch
+ uint8_t word = ~data[i];
+ matrix_has_changed |= current_matrix[i] ^ word;
+ current_matrix[i] = word;
+ }
+#ifdef MYRIAD_ENABLE
+ bool myriad_hook_matrix(matrix_row_t current_matrix[]);
+ return matrix_has_changed || myriad_hook_matrix(current_matrix);
+#else
+ return matrix_has_changed;
+#endif
+} \ No newline at end of file
diff --git a/keyboards/splitkb/elora/rev1/mcuconf.h b/keyboards/splitkb/elora/rev1/mcuconf.h
new file mode 100644
index 0000000000..58290187cf
--- /dev/null
+++ b/keyboards/splitkb/elora/rev1/mcuconf.h
@@ -0,0 +1,18 @@
+// Copyright 2024 splitkb.com ([email protected])
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include_next <mcuconf.h>
+
+#undef RP_SIO_USE_UART1
+#define RP_SIO_USE_UART1 TRUE
+
+#undef RP_I2C_USE_I2C0
+#define RP_I2C_USE_I2C0 TRUE
+
+#undef RP_SPI_USE_SPI1
+#define RP_SPI_USE_SPI1 TRUE
+
+#undef RP_ADC_USE_ADC1
+#define RP_ADC_USE_ADC1 TRUE
diff --git a/keyboards/splitkb/elora/rev1/myriad.c b/keyboards/splitkb/elora/rev1/myriad.c
new file mode 100644
index 0000000000..8bb6054df9
--- /dev/null
+++ b/keyboards/splitkb/elora/rev1/myriad.c
@@ -0,0 +1,315 @@
+// Copyright 2024 splitkb.com ([email protected])
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include QMK_KEYBOARD_H
+
+#include "myriad.h"
+
+#include "i2c_master.h"
+#include "analog.h"
+
+typedef struct __attribute__((__packed__)) {
+ char magic_numbers[3];
+ uint8_t version_major;
+ uint8_t version_minor;
+ uint8_t version_patch;
+ uint32_t checksum;
+ uint16_t payload_length;
+} myriad_header_t;
+
+typedef struct __attribute__((__packed__)) {
+ uint16_t vendor_id;
+ uint16_t product_id;
+ uint8_t revision;
+} identity_record_t;
+
+static bool myriad_reader(uint8_t *data, uint16_t length) {
+ const uint8_t eeprom_address = 0x50; // 1010 000 - NOT shifted for R/W bit
+ const uint16_t i2c_timeout = 100; // in milliseconds
+
+ uint8_t num_pages = (length / 256) + 1;
+ uint8_t last_page_size = length % 256;
+
+ for (int i = 0; i < num_pages; i++) {
+ uint8_t reg = 0; // We always start on a page boundary, so this is always zero
+ uint16_t read_length;
+ if (i == num_pages - 1) {
+ read_length = last_page_size;
+ } else {
+ read_length = 256;
+ }
+ i2c_status_t s = i2c_read_register((eeprom_address + i) << 1, reg, &(data[i*256]), read_length, i2c_timeout);
+ if (s != I2C_STATUS_SUCCESS) { return false; }
+ }
+ return true;
+}
+
+static bool verify_header(myriad_header_t *header) {
+ char magic_numbers[] = {'M', 'Y', 'R'};
+ uint8_t version_major = 1;
+ uint16_t version_minor = 0;
+
+ for (int i = 0; i < sizeof(magic_numbers); i++) {
+ // Check that the header starts with 'MYR', indicating that this is indeed a Myriad card.
+ if (header->magic_numbers[i] != magic_numbers[i]) {
+ return false;
+ }
+ }
+
+ if (header->version_major != version_major || header->version_minor > version_minor) {
+ // We obviously don't support cards with a different major version, because that indicates a breaking change.
+ // We also don't support cards with HIGHER minor version,
+ // as we are not guaranteed to be able to properly use all its features.
+ return false;
+ }
+
+ if (header->payload_length > (2048 - sizeof(myriad_header_t))) {
+ // The EEPROM chips are *at most* 16kb / 2kB large,
+ // and some of that is taken up by the header.
+ // We obviously can't have a payload which exceeds the EEPROM's size.
+ return false;
+ }
+
+ return true;
+}
+
+// Sourced from https://en.wikipedia.org/wiki/Adler-32#Example_implementation
+static bool verify_checksum(uint8_t *data, uint16_t length, uint32_t checksum) {
+ // Skip the header
+ data += sizeof(myriad_header_t);
+ length -= sizeof(myriad_header_t);
+
+ const uint32_t MOD_ADLER = 65521;
+
+ uint32_t a = 1, b = 0;
+ size_t index;
+
+ // Process each byte of the data in order
+ for (index = 0; index < length; ++index)
+ {
+ a = (a + data[index]) % MOD_ADLER;
+ b = (b + a) % MOD_ADLER;
+ }
+ uint32_t calculated = ((b << 16) | a);
+
+ return calculated == checksum;
+}
+
+// Locates a specific entry by type
+// Returns the offset of the PAYLOAD.
+static int16_t locate_entry(uint8_t entry_type, uint8_t entry_data_length, uint8_t *data, uint16_t minimum, uint16_t maximum) {
+ if (minimum < sizeof(myriad_header_t)) {
+ // Records must start *after* the header.
+ // We silently allow this so the caller can just specify `0` as minimum for the first entry.
+ minimum = sizeof(myriad_header_t);
+ }
+
+ uint16_t offset = minimum;
+ while (offset < maximum) {
+ if (data[offset] == entry_type) {
+ // Type matches!
+ if (data[offset+1] == entry_data_length) {
+ // We found what we are looking for, so return payload reference.
+ return offset+2;
+ } else {
+ // The entry is the wrong length?
+ return -2;
+ }
+ } else {
+ // No type match, so skip this one
+ // We skip the type byte, the length byte, and any potential data (with length stored in the length byte)
+ offset += 2 + data[offset+1];
+ }
+ }
+
+ // We hit the maximum and didn't find what we are looking for
+ return -1;
+}
+
+static bool read_card_identity(uint8_t *data, uint16_t length, identity_record_t *record) {
+ const uint8_t identity_type = 0x01;
+ const uint8_t entry_data_length = sizeof(identity_record_t);
+ int16_t result = locate_entry(identity_type, entry_data_length, data, 0, length);
+ if (result < 0) { return false; }
+
+ for (int i = 0; i < sizeof(identity_record_t); i++) {
+ ((uint8_t*)record)[i] = data[result + i];
+ }
+ return true;
+}
+
+static myriad_card_t _detect_myriad(void) {
+ gpio_set_pin_input(MYRIAD_PRESENT);
+ wait_ms(100);
+ // The pin has an external pull-up, and a Myriad card shorts it to ground.
+ #ifndef MYRIAD_OVERRIDE_PRESENCE
+ if (gpio_read_pin(MYRIAD_PRESENT)) {
+ return NONE;
+ }
+ #endif
+
+ // Attempt to read header
+ myriad_header_t header;
+ if (!myriad_reader((uint8_t*)&header, sizeof(header))) { return INVALID; }
+ if (!verify_header(&header)) { return INVALID; }
+
+ // Now that we have determined that the header is valid
+ // and we know the payload length, read the entire thing
+ uint8_t data[2048]; // Guaranteed to be large enough.
+ uint16_t data_size = sizeof(header)+header.payload_length;
+ if (!myriad_reader(data, data_size)) { return INVALID; }
+ if (!verify_checksum(data, data_size, header.checksum)) { return INVALID; }
+
+ identity_record_t identity;
+ if (!read_card_identity(data, data_size, &identity)) { return INVALID; }
+
+ if (identity.vendor_id == 0x0001 && identity.product_id == 0x0001) {
+ return SKB_ENCODER;
+ } else if (identity.vendor_id == 0x0001 && identity.product_id == 0x0002) {
+ return SKB_JOYSTICK;
+ } else if (identity.vendor_id == 0x0001 && identity.product_id == 0x0003) {
+ return SKB_SWITCHES;
+ }
+
+ return UNKNOWN;
+}
+
+// Determine card presence & identity
+// Does NOT initialize the card for use!
+myriad_card_t detect_myriad(void) {
+ static myriad_card_t card = UNINITIALIZED;
+
+ if (card == UNINITIALIZED) {
+ i2c_init();
+ card = _detect_myriad();
+ }
+
+ return card;
+}
+
+static void myr_switches_init(void) {
+ gpio_set_pin_input_high(MYRIAD_GPIO1); // S4
+ gpio_set_pin_input_high(MYRIAD_GPIO2); // S2
+ gpio_set_pin_input_high(MYRIAD_GPIO3); // S1
+ gpio_set_pin_input_high(MYRIAD_GPIO4); // S3
+}
+
+static void myr_encoder_init(void) {
+ gpio_set_pin_input_high(MYRIAD_GPIO1); // Press
+ gpio_set_pin_input_high(MYRIAD_GPIO2); // A
+ gpio_set_pin_input_high(MYRIAD_GPIO3); // B
+}
+
+static uint16_t myr_joystick_timer;
+static void myr_joystick_init(void) {
+ gpio_set_pin_input_high(MYRIAD_GPIO1); // Press
+
+ myr_joystick_timer = timer_read();
+}
+
+// Make sure any card present is ready for use
+static myriad_card_t myriad_card_init(void) {
+ static bool initialized = false;
+
+ myriad_card_t card = detect_myriad();
+ if (initialized) {
+ return card;
+ }
+ initialized = true;
+
+ switch (card) {
+ case SKB_SWITCHES:
+ myr_switches_init();
+ break;
+ case SKB_ENCODER:
+ myr_encoder_init();
+ break;
+ case SKB_JOYSTICK:
+ myr_joystick_init();
+ break;
+ default:
+ break;
+ }
+ return card;
+}
+
+bool myriad_hook_matrix(matrix_row_t current_matrix[]) {
+ myriad_card_t card = myriad_card_init();
+ uint8_t word = 0;
+
+ if (card == SKB_SWITCHES) {
+ word |= ((!gpio_read_pin(MYRIAD_GPIO3)) & 1) << 0;
+ word |= ((!gpio_read_pin(MYRIAD_GPIO2)) & 1) << 1;
+ word |= ((!gpio_read_pin(MYRIAD_GPIO4)) & 1) << 2;
+ word |= ((!gpio_read_pin(MYRIAD_GPIO1)) & 1) << 3;
+ } else if (card == SKB_ENCODER) {
+ word |= ((!gpio_read_pin(MYRIAD_GPIO1)) & 1) << 4;
+ } else if (card == SKB_JOYSTICK) {
+ word |= ((!gpio_read_pin(MYRIAD_GPIO1)) & 1) << 4;
+ } else {
+ return false;
+ }
+
+ // 5 bytes of on-board keys, so we are the 6th
+ bool matrix_has_changed = current_matrix[5] ^ word;
+ current_matrix[5] = word;
+
+ return matrix_has_changed;
+}
+
+static pin_t encoders_pad_a[NUM_ENCODERS_MAX_PER_SIDE];
+static pin_t encoders_pad_b[NUM_ENCODERS_MAX_PER_SIDE];
+
+uint8_t myriad_hook_encoder(uint8_t index, bool pad_b) {
+ if (myriad_card_init() != SKB_ENCODER) { return 0; }
+ // 3 onboard encoders, so we are number 4
+ pin_t pin = pad_b ? encoders_pad_b[index] : encoders_pad_a[index];
+ encoders_pad_a[3] = MYRIAD_GPIO2;
+ encoders_pad_b[3] = MYRIAD_GPIO3;
+ return gpio_read_pin(pin) ? 1 : 0;
+}
+
+report_mouse_t pointing_device_driver_get_report(report_mouse_t mouse_report) {
+ if (myriad_card_init() != SKB_JOYSTICK) { return mouse_report; }
+
+ if (timer_elapsed(myr_joystick_timer) < 10) {
+ wait_ms(2);
+ return mouse_report;
+ }
+
+ myr_joystick_timer = timer_read();
+
+ // `analogReadPin` returns 0..1023
+ int32_t y = (analogReadPin(MYRIAD_ADC1) - 512) * -1; // Note: axis is flipped
+ int32_t x = analogReadPin(MYRIAD_ADC2) - 512;
+ // Values are now -512..512
+
+ // Create a dead zone in the middle where the mouse doesn't move
+ const int16_t dead_zone = 10;
+ if ((y < 0 && y > -1*dead_zone) || (y > 0 && y < dead_zone)) {
+ y = 0;
+ }
+ if ((x < 0 && x > -1*dead_zone) || (x > 0 && x < dead_zone)) {
+ x = 0;
+ }
+
+ // quadratic movement
+ x = abs(x) * x / 5000;
+ y = abs(y) * y / 5000;
+
+ // Clamp final value to make sure we don't under/overflow
+ if (y < -127) { y = -127; }
+ if (y > 127) { y = 127; }
+ if (x < -127) { x = -127; }
+ if (x > 127) { x = 127; }
+
+ mouse_report.x = x;
+ mouse_report.y = y;
+
+ return mouse_report;
+}
+
+void pointing_device_driver_init(void) {
+ gpio_set_pin_input(MYRIAD_ADC1); // Y
+ gpio_set_pin_input(MYRIAD_ADC2); // X
+} \ No newline at end of file
diff --git a/keyboards/splitkb/elora/rev1/myriad.h b/keyboards/splitkb/elora/rev1/myriad.h
new file mode 100644
index 0000000000..03fe2b5e6b
--- /dev/null
+++ b/keyboards/splitkb/elora/rev1/myriad.h
@@ -0,0 +1,52 @@
+// Copyright 2024 splitkb.com ([email protected])
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "matrix.h"
+
+typedef enum { UNINITIALIZED, NONE, INVALID, UNKNOWN, SKB_SWITCHES, SKB_ENCODER, SKB_JOYSTICK } myriad_card_t;
+
+bool myriad_hook_matrix(matrix_row_t current_matrix[]);
+uint8_t myriad_hook_encoder(uint8_t index, bool pad_b);
+myriad_card_t detect_myriad(void);
+
+//// Elora-specific pinout
+
+// Control signals
+#define MYRIAD_PRESENT GP3
+#define MYRIAD_MCU_OVERRIDE GP2
+
+// General I/O
+#define MYRIAD_ADC1 GP26
+#define MYRIAD_ADC2 GP27
+#define MYRIAD_ADC1_CHANNEL 0
+#define MYRIAD_ADC2_CHANNEL 1
+#define MYRIAD_PWM1 GP23
+#define MYRIAD_PWM2 GP24
+#define MYRIAD_GPIO1 GP4
+#define MYRIAD_GPIO2 GP5
+#define MYRIAD_GPIO3 GP6
+#define MYRIAD_GPIO4 GP8
+
+// UART
+#define MYRIAD_RX GP17
+#define MYRIAD_TX GP16
+#define MYRIAD_CTS GP18
+#define MYRIAD_RTS GP19
+
+// SPI
+#define MYRIAD_MISO GP12
+#define MYRIAD_MOSI GP11
+#define MYRIAD_SCK GP10
+#define MYRIAD_MODULE_CS GP9
+#define MYRIAD_MATRIX_CS GP13
+
+// I2C
+#define MYRIAD_SDA GP0
+#define MYRIAD_SCL GP1
+
+// RGB
+#define MYRIAD_RGB GP15
diff --git a/keyboards/splitkb/elora/rev1/rev1.c b/keyboards/splitkb/elora/rev1/rev1.c
new file mode 100644
index 0000000000..6ffc23a4cf
--- /dev/null
+++ b/keyboards/splitkb/elora/rev1/rev1.c
@@ -0,0 +1,214 @@
+// Copyright 2024 splitkb.com ([email protected])
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "rev1.h"
+#include "spi_master.h"
+#include "myriad.h"
+
+bool is_oled_enabled = true;
+
+//// HW init
+
+// Make sure all external hardware is
+// in a known-good state on powerup
+void keyboard_pre_init_kb(void) {
+ /// SPI Chip Select pins for various hardware
+ // Matrix CS
+ gpio_set_pin_output(GP13);
+ gpio_write_pin_high(GP13);
+ // Myriad Module CS
+ gpio_set_pin_output(GP9);
+ gpio_write_pin_high(GP9);
+
+ gpio_set_pin_output(ELORA_CC1_PIN);
+ gpio_write_pin_low(ELORA_CC1_PIN);
+
+ gpio_set_pin_output(ELORA_CC2_PIN);
+ gpio_write_pin_low(ELORA_CC2_PIN);
+
+ // We have to get the SPI interface working quite early,
+ // So make sure it is available well before we need it
+ spi_init();
+
+ keyboard_pre_init_user();
+}
+
+//// Encoder functionality
+
+// The encoders are hooked in to the same shift registers as the switch matrix, so we can just piggyback on that.
+
+// Clone of a variant in quantum/matrix_common.c, but matrix-agnostic
+bool mat_is_on(matrix_row_t mat[], uint8_t row, uint8_t col) {
+ return (mat[row] & ((matrix_row_t)1 << col));
+}
+
+uint8_t encoder_read_pads_from(uint8_t index, bool pad_b, matrix_row_t mat[]) {
+ // First two matrix rows:
+ //
+ // Pin A B C D E F G H
+ // Left:
+ // { __, __, __, __, __, __, A1, B1 },
+ // { A3, B3, A2, B2, __, __, __, __ }
+ // Right:
+ // { A1, B1, __, __, __, __, __, __ },
+ // { __, __, __, __, A2, B2, A3, B3 }
+ //
+ // See also rev1.h
+
+ bool pad_value = false;
+
+ if (is_keyboard_left()) {
+ if (index == 0) {
+ pad_value = pad_b ? mat_is_on(mat, 0, 7) : mat_is_on(mat, 0, 6); // B1, A1
+ } else if (index == 1) {
+ pad_value = pad_b ? mat_is_on(mat, 1, 3) : mat_is_on(mat, 1, 2); // B2, A2
+ } else if (index == 2) {
+ pad_value = pad_b ? mat_is_on(mat, 1, 1) : mat_is_on(mat, 1, 0); // B3, A3
+ }
+ } else {
+ if (index == 0) {
+ pad_value = pad_b ? mat_is_on(mat, 0, 1) : mat_is_on(mat, 0, 0); // B1, A1
+ } else if (index == 1) {
+ pad_value = pad_b ? mat_is_on(mat, 1, 5) : mat_is_on(mat, 1, 4); // B2, A2
+ } else if (index == 2) {
+ pad_value = pad_b ? mat_is_on(mat, 1, 7) : mat_is_on(mat, 1, 6); // B3, A3
+ }
+ }
+
+ return pad_value ? 1 : 0;
+}
+
+void encoder_quadrature_init_pin(uint8_t index, bool pad_b) {
+ // At this point the first matrix scan hasn't happened yet,
+ // so we can't use raw_matrix to initialize our encoder state
+ // as it contains all zeroes - so we have to do our own first scan
+ // The pins for myriad are initialized in myriad.c
+
+ matrix_row_t mat[MATRIX_ROWS];
+
+ encoder_read_pads_from(index, pad_b, mat);
+}
+
+extern matrix_row_t raw_matrix[MATRIX_ROWS]; // From quantum/matrix_common.c
+
+uint8_t encoder_quadrature_read_pin(uint8_t index, bool pad_b) {
+ // The matrix code already keeps the raw matrix up-to-date,
+ // so we only have to read the values from it
+ if(index <= 2) {
+ return encoder_read_pads_from(index, pad_b, raw_matrix);
+ } else {
+ #ifdef MYRIAD_ENABLE
+ return myriad_hook_encoder(index, pad_b);
+ #endif
+ return 0;
+ }
+ return 0;
+}
+
+//// Default functionality
+
+#ifdef OLED_ENABLE
+oled_rotation_t oled_init_kb(oled_rotation_t rotation) {
+ if (is_keyboard_left()) {
+ return OLED_ROTATION_270;
+ } else {
+ return OLED_ROTATION_90;
+ }
+}
+
+bool oled_task_kb(void) {
+ if (!oled_task_user()) {
+ return false;
+ }
+
+ if (!is_oled_enabled) {
+ oled_off();
+ return false;
+ } else {
+ oled_on();
+ }
+
+ if (is_keyboard_master()) {
+ oled_write_P(PSTR("Elora rev1\n\n"), false);
+
+ // Keyboard Layer Status
+ // Ideally we'd print the layer name, but no way to know that for sure
+ // Fallback option: just print the layer number
+ uint8_t layer = get_highest_layer(layer_state | default_layer_state);
+ oled_write_P(PSTR("Layer: "), false);
+ oled_write(get_u8_str(layer, ' '), false);
+
+ // Keyboard LED Status
+ led_t led_state = host_keyboard_led_state();
+ oled_write_P(led_state.num_lock ? PSTR(" NUM") : PSTR(" "), false);
+ oled_write_P(led_state.caps_lock ? PSTR("CAP") : PSTR(" "), false);
+ oled_write_P(led_state.scroll_lock ? PSTR("SCR") : PSTR(" "), false);
+
+ // QMK Logo
+ // clang-format off
+ static const char PROGMEM qmk_logo[] = {
+ 0x81,0x82,0x83,0x84,0x0a,
+ 0xa1,0xa2,0xa3,0xa4,0x85,0x86,0x87,0x88,0x89,0x0a,
+ 0xc1,0xc2,0xc3,0xc4,0xa5,0xa6,0xa7,0xa8,0xa9,0x0a,
+ 0x8a,0x8b,0x8c,0x8d,0xc5,0xc6,0xc7,0xc8,0xc9,0x0a,
+ 0xaa,0xab,0xac,0xad,0xae,0xaf,0xb0,0xb1,0xb2,0xb3,0x00
+ };
+ // clang-format on
+ oled_set_cursor(0, oled_max_lines()-5);
+ oled_write_P(qmk_logo, false);
+ } else {
+ // Elora sigil
+ // clang-format off
+ static const char PROGMEM elora_logo[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,128,128,192,224,224,240,248,120, 56, 60,188,158,158,222,206,207,207,207,239,239,239,239,239,239,207,207,207,206,222,158,158,188, 60, 56,120,248,240,224,224,192,128,128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,192,224,248,252,126, 31,143,199,227,243,249,124, 60, 30, 31, 15, 7, 7, 3, 3, 3,131,193,225,241,249,253,255,255,255,255,127, 63, 31, 15, 7, 7, 7,143,223,254,252,252,249,243,227,199,143, 31,126,252,248,224,192, 0, 0, 0, 0, 0,
+ 0,192,240,254,255, 63, 7,227,248,252,127, 31, 15, 3, 1, 0, 0, 0,128,192,224,240,248,252,254,255,255,255,127, 63, 31, 15, 7, 3, 1,128,192,224,240,248,252,254,255,255,255,255,127, 63, 31, 15, 7, 15, 31,255,252,248,227, 7, 63,255,254,240,192, 0,252,255,255,255, 1,224,255,255,255, 7, 0, 0, 0, 0, 0, 0, 0, 0, 31, 31, 31, 31, 31, 15, 7, 3, 1, 0, 0, 0,240,248,252,254,255,255,255,255,127, 63, 31, 15, 7, 3, 1,128,192,224,240,248,252,254,255,255,255,255,255,255,224, 1,255,255,255,252,
+ 63,255,255,255,128, 7,255,255,255,224, 0, 0, 0, 0, 0, 0, 0, 0, 0,128,192,224,240,248,248,248,248,248,248, 0, 3, 3, 3, 3, 3, 3, 1,128,192,224,240,248,252,254,255,255,255,127, 63, 31, 15, 7, 3, 1,224,255,255,255, 7,128,255,255,255, 63, 0, 3, 15,127,255,252,224,199, 31, 63,254,248,240,192,128, 0, 0, 0, 0, 31, 31, 31, 31, 31, 31, 15, 7, 3, 1, 0, 0, 0, 0, 0, 0, 62, 63, 63, 63, 63, 63, 31, 15, 7, 3, 1, 0, 0, 0,128,192,240,248,254, 63, 31,199,224,252,255,127, 15, 3, 0,
+ 0, 0, 0, 0, 0, 3, 7, 31, 63,126,248,241,227,199,207,159, 62, 60,120,248,240,224,224,192,192,192,192,128,128,128,128,128,128,128,128,128,128,192,192,192,192,224,224,240,248,120, 60, 62,159,207,199,227,241,248,126, 63, 31, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 3, 7, 7, 15, 31, 30, 28, 60, 61,121,121,123,115,243,243,243,247,247,247,247,247,247,243,243,243,115,123,121,121, 61, 60, 28, 30, 31, 15, 7, 7, 3, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+ // clang-format on
+ oled_set_cursor(0, (oled_max_lines()/2)-4); // logo is 8 lines high, so center vertically
+ oled_write_raw_P(elora_logo, sizeof(elora_logo));
+ }
+
+ return false;
+}
+
+void housekeeping_task_kb(void) {
+ is_oled_enabled = last_input_activity_elapsed() < 60000;
+}
+#endif
+
+#ifdef ENCODER_ENABLE
+bool encoder_update_kb(uint8_t index, bool clockwise) {
+ if (!encoder_update_user(index, clockwise)) {
+ return false;
+ }
+
+ if (index == 0 || index == 1 || index == 2) {
+ // Left side
+ // Arrow keys
+ if (clockwise) {
+ tap_code(KC_RIGHT);
+ } else {
+ tap_code(KC_LEFT);
+ }
+ } else if (index == 4 || index == 5 || index == 6) {
+ // Right side
+ // Page up/Page down
+ if (clockwise) {
+ tap_code(KC_PGDN);
+ } else {
+ tap_code(KC_PGUP);
+ }
+ } else if (index == 3 || index == 7) {
+ // Myriad
+ // Volume control
+ if (clockwise) {
+ tap_code(KC_VOLU);
+ } else {
+ tap_code(KC_VOLD);
+ }
+ }
+ return true;
+}
+#endif
diff --git a/keyboards/splitkb/elora/rev1/rev1.h b/keyboards/splitkb/elora/rev1/rev1.h
new file mode 100644
index 0000000000..d4c1ffedb2
--- /dev/null
+++ b/keyboards/splitkb/elora/rev1/rev1.h
@@ -0,0 +1,14 @@
+// Copyright 2024 splitkb.com ([email protected])
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "quantum.h"
+
+//// Custom pins, might be useful in a keymap
+// Wired to input of last shift register, overrides 10k pullup/pulldown
+#define ELORA_SELFTEST_PIN GP14
+
+// Can be used for USB current detection
+#define ELORA_CC1_PIN GP28
+#define ELORA_CC2_PIN GP29
diff --git a/keyboards/splitkb/elora/rev1/rules.mk b/keyboards/splitkb/elora/rev1/rules.mk
new file mode 100644
index 0000000000..643fdc9a1b
--- /dev/null
+++ b/keyboards/splitkb/elora/rev1/rules.mk
@@ -0,0 +1,7 @@
+SPI_DRIVER_REQUIRED = yes
+CUSTOM_MATRIX = lite
+
+# Myriad boilerplate
+SRC += myriad.c matrix.c
+ANALOG_DRIVER_REQUIRED = yes
+POINTING_DEVICE_DRIVER = custom \ No newline at end of file