- /* Copyright 2020 Dimitris Papavasiliou <dpapavas@protonmail.ch>
- *
- * 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 3 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 <https://www.gnu.org/licenses/>.
- */
-
- #include <spi_master.h>
-
- #include "quantum.h"
- #include "split_util.h"
- #include "transport.h"
- #include "timer.h"
-
- #include "lagrange.h"
-
- struct led_context {
- led_t led_state;
- layer_state_t layer_state;
- };
-
- uint8_t transceive(uint8_t b) {
- for (SPDR = b ; !(SPSR & _BV(SPIF)) ; );
- return SPDR;
- }
-
- /* The SPI bus, doesn't have any form of protocol built in, so when
- * the other side isn't present, any old noise on the line will appear
- * as matrix data. To avoid interpreting data as keystrokes, we do a
- * simple n-way (8-way here) handshake before each scan, where each
- * side sends a prearranged sequence of bytes. */
-
- bool shake_hands(bool master) {
- const uint8_t m = master ? 0xf8 : 0;
- const uint8_t a = 0xa8 ^ m, b = 0x50 ^ m;
- bool synchronized = true;
-
- uint8_t i;
-
- i = SPSR;
- i = SPDR;
-
- do {
- /* Cycling the SS pin on each attempt is necessary, as it
- * resets the AVR's SPI core and guarantees proper
- * alignment. */
-
- if (master) {
- writePinLow(SPI_SS_PIN);
- }
-
- for (i = 0 ; i < 8 ; i += 1) {
- if (transceive(a + i) != b + i) {
- synchronized = false;
- break;
- }
- }
-
- if (master) {
- writePinHigh(SPI_SS_PIN);
- }
- } while (i < 8);
-
- return synchronized;
- }
-
- bool transport_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
- const struct led_context context = {
- host_keyboard_led_state(),
- layer_state
- };
-
- uint8_t i;
-
- /* We shake hands both before and after transmitting the matrix.
- * Doing it before transmitting is necessary to ensure
- * synchronization: Due to the master-slave nature of the SPI bus,
- * the master calls the shots. If we just go ahead and start
- * clocking bits, the slave side might be otherwise engaged at
- * that moment, so we'll initially read zeros, or garbage. Then
- * when the slave gets around to transmitting its matrix, we'll
- * misinterpret the keys it sends, leading to spurious
- * keypresses. */
-
- /* The handshake forces the master to wait for the slave to be
- * ready to start transmitting. */
-
- do {
- shake_hands(true);
-
- /* Receive the matrix from the other side, while transmitting
- * LED and layer states. */
-
- spi_start(SPI_SS_PIN, 0, 0, 4);
-
- for (i = 0 ; i < sizeof(matrix_row_t[MATRIX_ROWS / 2]) ; i += 1) {
- spi_status_t x;
-
- x = spi_write(i < sizeof(struct led_context) ?
- ((uint8_t *)&context)[i] : 0);
-
- if (x == SPI_STATUS_TIMEOUT) {
- return false;
- }
-
- ((uint8_t *)slave_matrix)[i] = (uint8_t)x;
- }
-
- spi_stop();
-
- /* In case of errors during the transmission, e.g. if the
- * cable was disconnected and since there is no inherent
- * error-checking protocol, we would simply interpret noise as
- * data. */
-
- /* To avoid this, both sides shake hands after transmitting.
- * If synchronization was lost during transmission, the (first)
- * handshake will fail. In that case we go around and
- * re-transmit. */
-
- } while (!shake_hands(true));
-
- return true;
- }
-
- void transport_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) {
- static struct led_context context;
- struct led_context new_context;
-
- uint8_t i;
-
- /* Do the reverse of master above. Note that timing is critical,
- * so interrupts must be turned off. */
-
- cli();
- shake_hands(false);
-
- do {
- for (i = 0 ; i < sizeof(matrix_row_t[MATRIX_ROWS / 2]) ; i += 1) {
- uint8_t b;
-
- b = transceive(((uint8_t *)slave_matrix)[i]);
-
- if (i < sizeof(struct led_context)) {
- ((uint8_t *)&new_context)[i] = b;
- }
- }
- } while (!shake_hands(false));
-
- sei();
-
- /* Update the layer and LED state if necessary. */
-
- if (!isLeftHand) {
- if (context.led_state.raw != new_context.led_state.raw) {
- context.led_state.raw = new_context.led_state.raw;
- led_update_kb(context.led_state);
- }
-
- if (context.layer_state != new_context.layer_state) {
- context.layer_state = new_context.layer_state;
- layer_state_set_kb(context.layer_state);
- }
- }
- }
-
- void transport_master_init(void) {
- /* We need to set the SS pin as output as the handshake logic
- * above depends on it and the SPI master driver won't do it
- * before we call spi_start(). */
-
- writePinHigh(SPI_SS_PIN);
- setPinOutput(SPI_SS_PIN);
-
- spi_init();
-
- shake_hands(true);
- }
-
- void transport_slave_init(void) {
- /* The datasheet isn't very clear on whether the internal pull-up
- * is selectable when the SS pin is used by the SPI slave, but
- * experimentations shows that it is, at least on the ATMega32u4.
- * We enable the pull-up to guard against the case where both
- * halves end up as slaves. In that case the SS pin would
- * otherwise be floating and free to fluctuate due to picked up
- * noise, etc. When reading low it would make both halves think
- * they're asserted making the MISO pin an output on both ends and
- * leading to potential shorts. */
-
- setPinInputHigh(SPI_SS_PIN);
- setPinInput(SPI_SCK_PIN);
- setPinInput(SPI_MOSI_PIN);
- setPinOutput(SPI_MISO_PIN);
-
- SPCR = _BV(SPE);
-
- shake_hands(false);
- }
|