-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathLinkSPI.hpp
361 lines (307 loc) · 10.5 KB
/
LinkSPI.hpp
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#ifndef LINK_SPI_H
#define LINK_SPI_H
// --------------------------------------------------------------------------
// An SPI handler for the Link Port (Normal Mode, either 32 or 8 bits).
// --------------------------------------------------------------------------
// Usage:
// - 1) Include this header in your main.cpp file and add:
// LinkSPI* linkSPI = new LinkSPI();
// - 2) (Optional) Add the interrupt service routines: (*)
// irq_init(NULL);
// irq_add(II_SERIAL, LINK_SPI_ISR_SERIAL);
// // (this is only required for `transferAsync`)
// - 3) Initialize the library with:
// linkSPI->activate(LinkSPI::Mode::MASTER_256KBPS);
// // (use LinkSPI::Mode::SLAVE on the other end)
// - 4) Exchange 32-bit data with the other end:
// u32 data = linkSPI->transfer(0x12345678);
// // (this blocks the console indefinitely)
// - 5) Exchange data with a cancellation callback:
// u32 data = linkSPI->transfer(0x12345678, []() {
// u16 keys = ~REG_KEYS & KEY_ANY;
// return keys & KEY_START;
// });
// - 6) Exchange data asynchronously:
// linkSPI->transferAsync(0x12345678);
// // ...
// if (linkSPI->getAsyncState() == LinkSPI::AsyncState::READY) {
// u32 data = linkSPI->getAsyncData();
// // ...
// }
// --------------------------------------------------------------------------
// (*) libtonc's interrupt handler sometimes ignores interrupts due to a bug.
// That causes packet loss. You REALLY want to use libugba's instead.
// (see examples)
// --------------------------------------------------------------------------
// considerations:
// - when using Normal Mode between two GBAs, use a GBC Link Cable!
// - only use the 2Mbps mode with custom hardware (very short wires)!
// - returns 0xFFFFFFFF (or 0xFF) on misuse or cancelled transfers!
// --------------------------------------------------------------------------
#ifndef LINK_DEVELOPMENT
#pragma GCC system_header
#endif
#include "_link_common.hpp"
static volatile char LINK_SPI_VERSION[] = "LinkSPI/v7.0.1";
#define LINK_SPI_NO_DATA_32 0xffffffff
#define LINK_SPI_NO_DATA_8 0xff
#define LINK_SPI_NO_DATA LINK_SPI_NO_DATA_32
/**
* @brief An SPI handler for the Link Port (Normal Mode, either 32 or 8 bits).
*/
class LinkSPI {
private:
using u32 = unsigned int;
using u16 = unsigned short;
using u8 = unsigned char;
static constexpr int BIT_CLOCK = 0;
static constexpr int BIT_CLOCK_SPEED = 1;
static constexpr int BIT_SI = 2;
static constexpr int BIT_SO = 3;
static constexpr int BIT_START = 7;
static constexpr int BIT_LENGTH = 12;
static constexpr int BIT_IRQ = 14;
static constexpr int BIT_GENERAL_PURPOSE_LOW = 14;
static constexpr int BIT_GENERAL_PURPOSE_HIGH = 15;
public:
enum Mode { SLAVE, MASTER_256KBPS, MASTER_2MBPS };
enum DataSize { SIZE_32BIT, SIZE_8BIT };
enum AsyncState { IDLE, WAITING, READY };
/**
* @brief Returns whether the library is active or not.
*/
[[nodiscard]] bool isActive() { return isEnabled; }
/**
* @brief Activates the library in a specific `mode`.
* @param mode One of the enum values from `LinkSPI::Mode`.
* @param dataSize One of the enum values from `LinkSPI::DataSize`.
*/
void activate(Mode mode, DataSize dataSize = SIZE_32BIT) {
this->mode = mode;
this->dataSize = dataSize;
this->waitMode = false;
this->asyncState = IDLE;
this->asyncData = 0;
setNormalMode();
disableTransfer();
if (mode == SLAVE)
setSlaveMode();
else {
setMasterMode();
if (mode == MASTER_256KBPS)
set256KbpsSpeed();
else if (mode == MASTER_2MBPS)
set2MbpsSpeed();
}
isEnabled = true;
}
/**
* @brief Deactivates the library.
*/
void deactivate() {
isEnabled = false;
setGeneralPurposeMode();
mode = SLAVE;
waitMode = false;
asyncState = IDLE;
asyncData = 0;
}
/**
* @brief Exchanges `data` with the other end. Returns the received data.
* @param data The value to be sent.
* \warning Blocks the system until completion.
*/
u32 transfer(u32 data) {
return transfer(data, []() { return false; });
}
/**
* @brief Exchanges `data` with the other end. Returns the received data.
* @param data The value to be sent.
* @param cancel A function that will be continuously invoked. If it returns
* `true`, the transfer will be aborted and the response will be empty.
* \warning Blocks the system until completion or cancellation.
*/
template <typename F>
u32 transfer(u32 data,
F cancel,
bool _async = false,
bool _customAck = false) {
if (asyncState != IDLE)
return noData();
setData(data);
if (_async) {
asyncState = WAITING;
setInterruptsOn();
} else {
setInterruptsOff();
}
while (isMaster() && waitMode && !isSlaveReady())
if (cancel()) {
disableTransfer();
setInterruptsOff();
asyncState = IDLE;
return noData();
}
enableTransfer();
startTransfer();
if (_async)
return noData();
while (!isReady())
if (cancel()) {
stopTransfer();
disableTransfer();
return noData();
}
if (!_customAck)
disableTransfer();
return getData();
}
/**
* @brief Schedules a `data` transfer and returns. After this, call
* `getAsyncState()` and `getAsyncData()`. Note that until you retrieve the
* async data, normal `transfer(...)`s won't do anything!
* @param data The value to be sent.
* \warning If `waitMode` (*) is active, blocks the system until completion.
* See `setWaitModeActive(...)`.
*/
void transferAsync(u32 data) {
transfer(data, []() { return false; }, true);
}
/**
* @brief Schedules a `data` transfer and returns. After this, call
* `getAsyncState()` and `getAsyncData()`. Note that until you retrieve the
* async data, normal `transfer(...)`s won't do anything!
* @param data The value to be sent.
* @param cancel A function that will be continuously invoked. If it returns
* `true`, the transfer will be aborted and the response will be empty.
* \warning If `waitMode` (*) is active, blocks the system until completion or
* cancellation. See `setWaitModeActive(...)`.
*/
template <typename F>
void transferAsync(u32 data, F cancel) {
transfer(data, cancel, true);
}
/**
* @brief Returns the state of the last async transfer.
* @return One of the enum values from `LinkSPI::AsyncState`.
*/
[[nodiscard]] AsyncState getAsyncState() { return asyncState; }
/**
* @brief If the async state is `READY`, returns the remote data and switches
* the state back to `IDLE`. If not, returns an empty response.
*/
[[nodiscard]] u32 getAsyncData() {
if (asyncState != READY)
return noData();
u32 data = asyncData;
asyncState = IDLE;
return data;
}
/**
* @brief Returns the current `mode`.
*/
[[nodiscard]] Mode getMode() { return mode; }
/**
* @brief Returns the current `dataSize`.
*/
[[nodiscard]] DataSize getDataSize() { return dataSize; }
/**
* @brief Enables or disables `waitMode`: The GBA adds an extra feature over
* SPI. When working as master, it can check whether the other terminal is
* ready to receive (ready: `MISO=LOW`), and wait if it's not (not ready:
* `MISO=HIGH`). That makes the connection more reliable, but it's not always
* supported on other hardware units (e.g. the Wireless Adapter), so it must
* be disabled in those cases.
* \warning `waitMode` is disabled by default.
* \warning `MISO` means `SO` on the slave side and `SI` on the master side.
*/
void setWaitModeActive(bool isActive) { waitMode = isActive; }
/**
* @brief Returns whether `waitMode` (*) is active or not.
* \warning See `setWaitModeActive(...)`.
*/
[[nodiscard]] bool isWaitModeActive() { return waitMode; }
/**
* @brief This method is called by the SERIAL interrupt handler.
* \warning This is internal API!
*/
void _onSerial(bool _customAck = false) {
if (!isEnabled || asyncState != WAITING)
return;
if (!_customAck)
disableTransfer();
setInterruptsOff();
asyncState = READY;
asyncData = getData();
}
/**
* @brief Sets SO output to HIGH.
* \warning This is internal API!
*/
void _setSOHigh() { setBitHigh(BIT_SO); }
/**
* @brief Sets SO output to LOW.
* \warning This is internal API!
*/
void _setSOLow() { setBitLow(BIT_SO); }
/**
* @brief Returns whether SI is HIGH or LOW.
* \warning This is internal API!
*/
[[nodiscard]] bool _isSIHigh() { return isBitHigh(BIT_SI); }
private:
Mode mode = Mode::SLAVE;
DataSize dataSize = DataSize::SIZE_32BIT;
bool waitMode = false;
AsyncState asyncState = IDLE;
u32 asyncData = 0;
volatile bool isEnabled = false;
void setNormalMode() {
Link::_REG_RCNT = Link::_REG_RCNT & ~(1 << BIT_GENERAL_PURPOSE_HIGH);
if (dataSize == SIZE_32BIT)
Link::_REG_SIOCNT = 1 << BIT_LENGTH;
else
Link::_REG_SIOCNT = 0;
}
void setGeneralPurposeMode() {
Link::_REG_RCNT = (Link::_REG_RCNT & ~(1 << BIT_GENERAL_PURPOSE_LOW)) |
(1 << BIT_GENERAL_PURPOSE_HIGH);
}
void setData(u32 data) {
if (dataSize == SIZE_32BIT)
Link::_REG_SIODATA32 = data;
else
Link::_REG_SIODATA8 = data & 0xff;
}
u32 getData() {
return dataSize == SIZE_32BIT ? Link::_REG_SIODATA32
: Link::_REG_SIODATA8 & 0xff;
}
u32 noData() {
return dataSize == SIZE_32BIT ? LINK_SPI_NO_DATA_32 : LINK_SPI_NO_DATA_8;
}
void enableTransfer() { _setSOLow(); }
void disableTransfer() { _setSOHigh(); }
void startTransfer() { setBitHigh(BIT_START); }
void stopTransfer() { setBitLow(BIT_START); }
bool isReady() { return !isBitHigh(BIT_START); }
bool isSlaveReady() { return !_isSIHigh(); }
void setMasterMode() { setBitHigh(BIT_CLOCK); }
void setSlaveMode() { setBitLow(BIT_CLOCK); }
void set256KbpsSpeed() { setBitLow(BIT_CLOCK_SPEED); }
void set2MbpsSpeed() { setBitHigh(BIT_CLOCK_SPEED); }
void setInterruptsOn() { setBitHigh(BIT_IRQ); }
void setInterruptsOff() { setBitLow(BIT_IRQ); }
bool isMaster() { return mode != SLAVE; }
bool isBitHigh(u8 bit) { return (Link::_REG_SIOCNT >> bit) & 1; }
void setBitHigh(u8 bit) { Link::_REG_SIOCNT |= 1 << bit; }
void setBitLow(u8 bit) { Link::_REG_SIOCNT &= ~(1 << bit); }
};
extern LinkSPI* linkSPI;
/**
* @brief SERIAL interrupt handler.
*/
inline void LINK_SPI_ISR_SERIAL() {
linkSPI->_onSerial();
}
#endif // LINK_SPI_H