/* Copyright 2023 splitkb.com <support@splitkb.com>
|
|
*
|
|
* 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, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include QMK_KEYBOARD_H
|
|
|
|
#include "rev1.h"
|
|
|
|
#include "spi_master.h"
|
|
#include "matrix.h"
|
|
#include "keyboard.h"
|
|
|
|
#include "myriad.h"
|
|
|
|
// Needed for early boot
|
|
#include "hardware/xosc.h"
|
|
|
|
bool is_oled_enabled = true;
|
|
|
|
//// Early boot
|
|
|
|
// The Elora has support for Myriad Controller Modules.
|
|
// Essentially, this is a plug-in controller which takes over all functionality from the onboard MCU.
|
|
// This makes it possible to convert the keyboard into a wireless board, for example.
|
|
// In order for this to work, we need to prevent QMK from booting.
|
|
static void enter_standby_mode(void) {
|
|
while (true) {
|
|
// Todo: Look into more approaches to save power
|
|
// - deinit PLL
|
|
// - MEMPOWERDOWN
|
|
// - QSPI power-down (idle use is 10-50 uA)
|
|
|
|
// The RP2040 *should* be able to power-down to about 180uA,
|
|
// while the QSPI chip can do 1-15 uA.
|
|
|
|
// Additional 3V3 power usage which can't be disabled:
|
|
// - Matrix SPI NOT gate: 0.1-4 uA
|
|
// - Matrix SPI tri-state buffer: 0.1-10 uA
|
|
// - Shift registers: 5x 0.1-2 uA
|
|
|
|
// Turns off the crystal oscillator until the core is woken by an interrupt.
|
|
// This will block and hence the entire system will stop, until an interrupt wakes it up.
|
|
// This function will continue to block until the oscillator becomes stable after its wakeup.
|
|
xosc_dormant();
|
|
}
|
|
}
|
|
|
|
void early_hardware_init_pre(void) {
|
|
// GP2 has an external pullup. It is shorted to ground when a controller module is connected.
|
|
setPinInput(GP2);
|
|
if (!readPin(GP2)) {
|
|
// A Myriad Controller Module is present,
|
|
// so we are not needed to run QMK.
|
|
enter_standby_mode();
|
|
}
|
|
}
|
|
|
|
//// 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
|
|
setPinOutput(GP13);
|
|
writePinHigh(GP13);
|
|
// Myriad Module CS
|
|
setPinOutput(GP9);
|
|
writePinHigh(GP9);
|
|
|
|
setPinOutput(ELORA_CC1_PIN);
|
|
writePinLow(ELORA_CC1_PIN);
|
|
|
|
setPinOutput(ELORA_CC2_PIN);
|
|
writePinLow(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();
|
|
}
|
|
|
|
void keyboard_post_init_kb(void) {
|
|
|
|
keyboard_post_init_user();
|
|
}
|
|
|
|
//// Matrix functionality
|
|
|
|
// 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];
|
|
spi_status_t res;
|
|
|
|
// 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);
|
|
res = spi_receive(data, length);
|
|
spi_stop();
|
|
if (res != SPI_STATUS_SUCCESS) {
|
|
dprint("ERROR: SPI timed out while reading matrix!");
|
|
}
|
|
|
|
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
|
|
// It's a bit of a weird place to call a `_task`,
|
|
// but we want to do it relatively early because we mess with a lot of functionality
|
|
myriad_task();
|
|
return matrix_has_changed || myriad_hook_matrix(current_matrix);
|
|
#else
|
|
return matrix_has_changed;
|
|
#endif
|
|
}
|
|
|
|
//// 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));
|
|
}
|
|
|
|
void encoder_read_pads_from(bool pads[], 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
|
|
|
|
if (is_keyboard_left()) {
|
|
pads[0] = mat_is_on(mat, 0, 6);
|
|
pads[1] = mat_is_on(mat, 0, 7);
|
|
pads[2] = mat_is_on(mat, 1, 2);
|
|
pads[3] = mat_is_on(mat, 1, 3);
|
|
pads[4] = mat_is_on(mat, 1, 0);
|
|
pads[5] = mat_is_on(mat, 1, 1);
|
|
} else {
|
|
pads[0] = mat_is_on(mat, 0, 0);
|
|
pads[1] = mat_is_on(mat, 0, 1);
|
|
pads[2] = mat_is_on(mat, 1, 4);
|
|
pads[3] = mat_is_on(mat, 1, 5);
|
|
pads[4] = mat_is_on(mat, 1, 6);
|
|
pads[5] = mat_is_on(mat, 1, 7);
|
|
}
|
|
}
|
|
|
|
void encoder_init_pads(uint8_t count, bool pads[]) {
|
|
// 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
|
|
matrix_row_t mat[MATRIX_ROWS];
|
|
matrix_scan_custom(mat);
|
|
|
|
encoder_read_pads_from(pads, mat);
|
|
|
|
// Make sure the Myriad pads are in a well-defined state
|
|
pads[6] = 0;
|
|
pads[7] = 0;
|
|
#ifdef MYRIAD_ENABLE
|
|
myriad_hook_encoder(count, pads);
|
|
#endif
|
|
}
|
|
|
|
extern matrix_row_t raw_matrix[MATRIX_ROWS]; // From quantum/matrix_common.c
|
|
void encoder_read_pads(uint8_t count, bool pads[]) {
|
|
// The matrix code already keeps the raw matrix up-to-date,
|
|
// so we only have to read the values from it
|
|
encoder_read_pads_from(pads, raw_matrix);
|
|
|
|
// Make sure the Myriad pads are in a well-defined state
|
|
pads[6] = 0;
|
|
pads[7] = 0;
|
|
#ifdef MYRIAD_ENABLE
|
|
myriad_hook_encoder(count, pads);
|
|
#endif
|
|
}
|
|
|
|
//// Default functionality
|
|
|
|
// RGB Matrix definition for Elora
|
|
#ifdef RGB_MATRIX_ENABLE
|
|
|
|
#define NLD NO_LED
|
|
|
|
// Layout
|
|
// 2 1 0 6 7 8
|
|
// ┌───────────────────────────────────────────┐ ┌───────────────────────────────────────────┐
|
|
// │ MX101, MX105, MX109, MX113, MX117, MX121, │ │ MX221, MX217, MX213, MX209, MX205, MX201, │
|
|
// ├───────────────────────────────────────────┤ ├───────────────────────────────────────────┤
|
|
// │ MX102, MX106, MX110, MX114, MX118, MX122, │ │ MX222, MX218, MX214, MX210, MX206, MX202, │
|
|
// ├───────────────────────────────────────────┤ ├───────────────────────────────────────────┤
|
|
// │ MX103, MX107, MX111, MX115, MX119, MX123, │ │ MX223, MX219, MX215, MX211, MX207, MX203, │
|
|
// ├───────────────────────────────────────────┴─────────────┐ ┌─────────────┴───────────────────────────────────────────┤
|
|
// │ MX104, MX108, MX112, MX116, MX120, MX124, MX131, MX130, │ │ MX230, MX231, MX224, MX220, MX216, MX212, MX208, MX204, │
|
|
// └────────────────────┬────────────────────────────────────┤ ├───────────────────────────────────┬─────────────────────┘
|
|
// 3 │ MX125, MX126, MX127, MX128, MX129, │ │ MX229, MX228, MX227, MX226, MX225 │ 9
|
|
// └────────────────────────────────────┘ └───────────────────────────────────┘
|
|
// 4 5 11 10
|
|
|
|
// #define LAYOUT_myr(
|
|
// 2 2 2 1 1 0 NLD NLD 6 7 7 8 8 8
|
|
// 2 2 2 1 1 0 NLD NLD 6 7 7 8 8 8
|
|
// 3 3 3 4 4 0 NLD NLD 6 10 10 9 9 9
|
|
// 3 3 3 4 4 4 5 5 11 11 10 10 10 9 9 9
|
|
// 3 4 4 5 5 11 11 10 10 9
|
|
// NLD NLD NLD NLD NLD NLD NLD NLD NLD NLD
|
|
// )
|
|
|
|
|
|
led_config_t g_led_config = {
|
|
{
|
|
//COL 01 02 03 04 05 011 010 09 ROW
|
|
{ 5, NLD, NLD, NLD, 5, 5, NLD, NLD }, // 00
|
|
{ NLD, NLD, NLD, NLD, 5, 4, 4, 3 }, // 01
|
|
{ 1, 1, 0, 0, 0, 4, 4, 4 }, // 02
|
|
{ 2, 2, 1, 1, 4, 4, 3, 3 }, // 03
|
|
{ 2, 2, 2, 2, 3, 3, 3, 3 }, // 04
|
|
{ NLD, NLD, NLD, NLD, NLD, NLD, NLD, NLD }, // 05
|
|
|
|
{ NLD, NLD, 11, 11, NLD, NLD, NLD, 11 }, // 06
|
|
{ 9, 10, 10, 11, NLD, NLD, NLD, NLD }, // 07
|
|
{ 10, 10, 10, 6, 6, 6, 7, 7,}, // 08
|
|
{ 9, 9, 10, 10, 7, 7, 8, 8 }, // 09
|
|
{ 9, 9, 9, 9, 8, 8, 8, 8 }, // 10
|
|
{ NLD, NLD, NLD, NLD, NLD, NLD, NLD, NLD }, // 11
|
|
},
|
|
{
|
|
// { 112, 32 } is the center
|
|
{90 , 0}, // 0
|
|
{45 , 0}, // 1
|
|
{0 , 0}, // 2
|
|
{0 , 64}, // 3
|
|
{45 , 64}, // 4
|
|
{90 , 64}, // 5
|
|
{134, 0}, // 6
|
|
{179, 0}, // 7
|
|
{224, 0}, // 8
|
|
{224, 64}, // 9
|
|
{179, 64}, // 10
|
|
{134, 64} // 11
|
|
},
|
|
{
|
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#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_usb_state = host_keyboard_led_state();
|
|
oled_write_P(led_usb_state.num_lock ? PSTR(" NUM") : PSTR(" "), false);
|
|
oled_write_P(led_usb_state.caps_lock ? PSTR("CAP") : PSTR(" "), false);
|
|
oled_write_P(led_usb_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
|