summaryrefslogtreecommitdiff
path: root/keyboards/handwired/lagrange/transport.c
blob: 2a567f24b949ce22039eccbce496995d35ffbbba (plain)
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
31
32
33
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
/* 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 "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, doens'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. */

void shake_hands(bool master) {
    const uint8_t m = master ? 0xf8 : 0;
    const uint8_t a = 0xa8 ^ m, b = 0x50 ^ m;

    uint8_t i;

    i = SPSR;
    i = SPDR;

    do {
        /* Cylcling 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) {
                break;
            }
        }

        if (master) {
            writePinHigh(SPI_SS_PIN);
        }
    } while (i < 8);
}

bool transport_master(matrix_row_t matrix[]) {
    const struct led_context context = {
        host_keyboard_led_state(),
        layer_state
    };

    uint8_t i;

    /* Shake hands and then receive the matrix from the other side,
     * while transmitting LED and layer states. */

    shake_hands(true);

    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 *)matrix)[i] = (uint8_t)x;
    }

    spi_stop();

    return true;
}

void transport_slave(matrix_row_t 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);

    for (i = 0 ; i < sizeof(matrix_row_t[MATRIX_ROWS / 2]) ; i += 1) {
        uint8_t b;

        b = transceive(((uint8_t *)matrix)[i]);

        if (i < sizeof(struct led_context)) {
            ((uint8_t *)&new_context)[i] = b;
        }
    }

    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);
}