-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathrumble.c
215 lines (167 loc) · 5 KB
/
rumble.c
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
#include <stdbool.h>
#include "rumble.h"
/*
NOTE:
Here is a brief explanation, from GBATEK:
After detecting/unlocking the Gameboy Player, init RCNT and SIOCNT to 32bit
normal mode, external clock, SO=high, with IRQ enabled, and set the transfer
start bit. You should then receive the following sequence (about once per
frame), and your serial IRQ handler should send responses accordingly:
Receive Response
0000494E 494EB6B1
xxxx494E 494EB6B1
B6B1494E 544EB6B1
B6B1544E 544EABB1
ABB1544E 4E45ABB1
ABB14E45 4E45B1BA
B1BA4E45 4F44B1BA
B1BA4F44 4F44B0BB
B0BB4F44 8000B0BB
B0BB8002 10000010
10000010 20000013
20000013 40000004
30000003 40000004
30000003 40000004
30000003 40000004
30000003 400000yy
30000003 40000004
The first part of the transfer just contains the string “NINTENDO” split into
16bit fragments, and bitwise inversions thereof (eg. 494Eh=”NI”, and
B6B1h=NOT 494Eh). In the second part, <yy> should be 04h=RumbleOff, or
26h=RumbleOn.
*/
typedef unsigned int u32;
typedef unsigned short u16;
#define GPIO_PORT_DATA (*(volatile u16 *)0x80000C4)
#define GPIO_PORT_DIRECTION (*(volatile u16 *)0x80000C6)
#define REG_BASE 0x04000000
#define REG_RCNT *(volatile u16*)(REG_BASE + 0x134)
#define REG_SIOCNT *(volatile u16*)(REG_BASE + 0x128)
#define REG_SIODATA32 *(volatile u32*)(REG_BASE + 0x120)
#define SIO_SO_HIGH (1<<3)
#define SIO_START (1<<7)
#define SIO_32BIT 0x1000
#define SIO_IRQ 0x4000
#define R_NORMAL 0x0000
static bool gbp_configured;
static enum RumbleState rumble_state;
enum GBPCommsStage {
gbp_comms_nintendo_handshake,
gbp_comms_check_magic1,
gbp_comms_check_magic2,
gbp_comms_rumble,
gbp_comms_finalize
};
static struct GBPComms {
enum GBPCommsStage stage_;
u32 serial_in_;
u16 index_;
u16 out_0_;
u16 out_1_;
} gbp_comms;
static void gbp_serial_start()
{
if (gbp_comms.serial_in_ == 0x30000003) {
/* We're currently in the middle of the GBP comms rumble stage. */
return;
}
REG_SIOCNT &= ~1;
REG_SIOCNT |= SIO_START;
}
static void gbp_serial_isr()
{
gbp_comms.serial_in_ = REG_SIODATA32;
u32 result = 0;
switch (gbp_comms.stage_) {
case gbp_comms_rumble:
if (gbp_comms.serial_in_ == 0x30000003) {
result = rumble_state;
} else {
gbp_comms.stage_ = gbp_comms_finalize;
}
break;
case gbp_comms_finalize: {
struct GBPComms reset = {0};
gbp_comms = reset;
return;
}
case gbp_comms_nintendo_handshake: {
const u16 in_lower = gbp_comms.serial_in_;
if (in_lower == 0x8002) {
result = 0x10000010;
gbp_comms.stage_ = gbp_comms_check_magic1;
break;
}
if ((gbp_comms.serial_in_ >> 16) != gbp_comms.out_1_) {
gbp_comms.index_ = 0;
}
if (gbp_comms.index_ > 3) {
gbp_comms.out_0_ = 0x8000;
} else {
if (gbp_comms.serial_in_ ==
(u32) ~(gbp_comms.out_1_ | (gbp_comms.out_0_ << 16))) {
gbp_comms.index_ += 1;
}
static char const comms_handshake_data[] = {"NINTENDO"};
gbp_comms.out_0_ =
((const u16*)comms_handshake_data)[gbp_comms.index_];
}
gbp_comms.out_1_ = ~in_lower;
result = gbp_comms.out_1_;
result |= gbp_comms.out_0_ << 16;
break;
}
case gbp_comms_check_magic1:
/* The GBATEK reference says to check for these integer constants in
this order... but why? Who knows. Anyway, it seems to work. */
if (gbp_comms.serial_in_ == 0x10000010) {
result = 0x20000013;
gbp_comms.stage_ = gbp_comms_check_magic2;
} else {
gbp_comms.stage_ = gbp_comms_finalize;
}
break;
case gbp_comms_check_magic2:
if (gbp_comms.serial_in_ == 0x20000013) {
result = 0x40000004;
gbp_comms.stage_ = gbp_comms_rumble;
} else {
gbp_comms.stage_ = gbp_comms_finalize;
}
break;
}
REG_SIODATA32 = result;
REG_SIOCNT |= SIO_START;
}
void rumble_init(struct RumbleGBPConfig* config)
{
rumble_state = rumble_stop;
if (config) {
config->serial_irq_setup_(gbp_serial_isr);
REG_RCNT = R_NORMAL;
REG_SIOCNT = SIO_32BIT | SIO_SO_HIGH;
REG_SIOCNT |= SIO_IRQ;
gbp_configured = true;
gbp_comms.serial_in_ = 0;
gbp_comms.stage_ = gbp_comms_nintendo_handshake;
gbp_comms.index_ = 0;
gbp_comms.out_0_ = 0;
gbp_comms.out_1_ = 0;
} else {
gbp_configured = false;
}
}
void rumble_update()
{
if (gbp_configured) {
gbp_serial_start();
}
}
void rumble_set_state(enum RumbleState state)
{
rumble_state = state;
if (!gbp_configured) {
GPIO_PORT_DIRECTION = 1 << 3;
GPIO_PORT_DATA = (rumble_state == rumble_start) << 3;
}
}