You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
355 lines
9.3 KiB
355 lines
9.3 KiB
/* gc_n64_usb : Gamecube or N64 controller to USB firmware |
|
Copyright (C) 2007-2021 Raphael Assenat <raph@raphnet.net> |
|
|
|
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 <http://www.gnu.org/licenses/>. |
|
*/ |
|
#include <avr/io.h> |
|
#include <avr/interrupt.h> |
|
#include <util/delay.h> |
|
#include <string.h> |
|
#include "gamepads.h" |
|
#include "n64.h" |
|
#include "gcn64_protocol.h" |
|
#include "eeprom.h" |
|
#include "main.h" // for num_players |
|
|
|
#undef BUTTON_A_RUMBLE_TEST |
|
|
|
/*********** prototypes *************/ |
|
static void n64Init(unsigned char chn); |
|
static char n64Update(unsigned char chn); |
|
static char n64Changed(unsigned char chn); |
|
static void n64GetReport(unsigned char chn, gamepad_data *dst); |
|
static void n64SetVibration(unsigned char chn, char enable); |
|
|
|
static char must_rumble[GAMEPAD_MAX_CHANNELS] = { }; |
|
#ifdef BUTTON_A_RUMBLE_TEST |
|
static char force_rumble[GAMEPAD_MAX_CHANNELS] = { }; |
|
#endif |
|
static unsigned char n64_rumble_state[GAMEPAD_MAX_CHANNELS] = { }; |
|
|
|
unsigned char tmpdata[40]; // Shared between channels |
|
|
|
#define RSTATE_UNAVAILABLE 0 |
|
#define RSTATE_OFF 1 |
|
#define RSTATE_TURNON 2 |
|
#define RSTATE_ON 3 |
|
#define RSTATE_TURNOFF 4 |
|
#define RSTATE_INIT 5 |
|
|
|
static void n64Init(unsigned char chn) |
|
{ |
|
n64Update(chn); |
|
} |
|
|
|
static char initRumble(unsigned char chn) |
|
{ |
|
int count; |
|
unsigned char data[4]; |
|
|
|
tmpdata[0] = N64_EXPANSION_WRITE; |
|
tmpdata[1] = 0x80; |
|
tmpdata[2] = 0x01; |
|
memset(tmpdata+3, 0x80, 32); |
|
|
|
count = gcn64_transaction(chn, tmpdata, 35, data, sizeof(data)); |
|
if (count == 1) |
|
return 0; |
|
|
|
return -1; |
|
} |
|
|
|
static char controlRumble(unsigned char chn, char enable) |
|
{ |
|
int count; |
|
unsigned char data[4]; |
|
|
|
tmpdata[0] = N64_EXPANSION_WRITE; |
|
tmpdata[1] = 0xc0; |
|
tmpdata[2] = 0x1b; |
|
memset(tmpdata+3, enable ? 0x01 : 0x00, 32); |
|
count = gcn64_transaction(chn, tmpdata, 35, data, sizeof(data)); |
|
if (count == 1) |
|
return 0; |
|
|
|
return -1; |
|
} |
|
|
|
static char n64Update(unsigned char chn) |
|
{ |
|
unsigned char count; |
|
unsigned char x,y; |
|
unsigned char btns1, btns2; |
|
unsigned char caps[N64_CAPS_REPLY_LENGTH]; |
|
unsigned char status[N64_GET_STATUS_REPLY_LENGTH]; |
|
|
|
/* Pad answer to N64_GET_CAPABILITIES |
|
* |
|
* 0x050000 : 0000 0101 0000 0000 0000 0000 : No expansion pack |
|
* 0x050001 : 0000 0101 0000 0000 0000 0001 : With expansion pack |
|
* 0x050002 : 0000 0101 0000 0000 0000 0010 : Expansion pack removed |
|
* |
|
* Bit 0 tells us if there is something connected to the expansion port. |
|
* Bit 1 tells is if there was something connected that has been removed. |
|
*/ |
|
tmpdata[0] = N64_GET_CAPABILITIES; |
|
count = gcn64_transaction(chn, tmpdata, 1, caps, sizeof(caps)); |
|
if (count != N64_CAPS_REPLY_LENGTH) { |
|
// a failed read could mean the pack or controller was gone. Init |
|
// will be necessary next time we detect a pack is present. |
|
n64_rumble_state[chn] = RSTATE_INIT; |
|
return -1; |
|
} |
|
|
|
/* The brawler 64 wireless gamepad does not like when the get caps command is followed |
|
* too closely by the get status command. Without a long pause between the two commands, |
|
* it just returns all zeros. */ |
|
if (num_players > 1) { |
|
// n64Update is called two times on two-player adapters. This |
|
// means time lost waiting here doubles. Without this, the controllers |
|
// are no longer being polled at the rate set in the gui, which is bad. |
|
// (I want this setting to be reliable) |
|
// |
|
// So on dual port adapters, brawler 64 wireless controllers will be |
|
// usable only at intervals >= 4ms. |
|
// |
|
// TODO: Interleave access like this to allow a higher polling |
|
// frequency: Read caps port 1, Read caps port 2, delay, Read status port 1, |
|
// read status port 2. (Maybe this could make 3ms intervals work) |
|
// |
|
if (g_eeprom_data.cfg.poll_interval[0] >= 8) { |
|
_delay_ms(2.5); |
|
} else if (g_eeprom_data.cfg.poll_interval[0] >= 6) { |
|
_delay_ms(1.5); |
|
} else if (g_eeprom_data.cfg.poll_interval[0] >= 4) { |
|
_delay_ms(1.25); |
|
} |
|
} |
|
else { |
|
if (g_eeprom_data.cfg.poll_interval[0] >= 4) { |
|
_delay_ms(2.5); |
|
} else if (g_eeprom_data.cfg.poll_interval[0] >= 3) { |
|
_delay_ms(1.5); |
|
} else if (g_eeprom_data.cfg.poll_interval[0] >= 2) { |
|
_delay_ms(1.25); // does not work at 1ms |
|
} |
|
} |
|
|
|
/* Detect when a pack becomes present and schedule initialisation when it happens. */ |
|
if ((caps[2] & 0x01) && (n64_rumble_state[chn] == RSTATE_UNAVAILABLE)) { |
|
n64_rumble_state[chn] = RSTATE_INIT; |
|
} |
|
|
|
/* Detect when a pack is removed. */ |
|
if (!(caps[2] & 0x01) || (caps[2] & 0x02) ) { |
|
n64_rumble_state[chn] = RSTATE_UNAVAILABLE; |
|
} |
|
#ifdef BUTTON_A_RUMBLE_TEST |
|
must_rumble[chn] = force_rumble[chn]; |
|
//printf("Caps: %02x %02x %02x\r\n", caps[0], caps[1], caps[2]); |
|
#endif |
|
|
|
|
|
switch (n64_rumble_state[chn]) |
|
{ |
|
case RSTATE_INIT: |
|
/* Retry until the controller answers with a full byte. */ |
|
if (initRumble(chn) != 0) { |
|
if (initRumble(chn) != 0) { |
|
n64_rumble_state[chn] = RSTATE_UNAVAILABLE; |
|
} |
|
break; |
|
} |
|
|
|
if (must_rumble[chn]) { |
|
controlRumble(chn, 1); |
|
n64_rumble_state[chn] = RSTATE_ON; |
|
} else { |
|
controlRumble(chn, 0); |
|
n64_rumble_state[chn] = RSTATE_OFF; |
|
} |
|
break; |
|
|
|
case RSTATE_TURNON: |
|
if (0 == controlRumble(chn, 1)) { |
|
n64_rumble_state[chn] = RSTATE_ON; |
|
} |
|
break; |
|
|
|
case RSTATE_TURNOFF: |
|
if (0 == controlRumble(chn, 0)) { |
|
n64_rumble_state[chn] = RSTATE_OFF; |
|
} |
|
break; |
|
|
|
case RSTATE_ON: |
|
if (!must_rumble[chn]) { |
|
controlRumble(chn, 0); |
|
n64_rumble_state[chn] = RSTATE_OFF; |
|
} |
|
break; |
|
|
|
case RSTATE_OFF: |
|
if (must_rumble[chn]) { |
|
controlRumble(chn, 1); |
|
n64_rumble_state[chn] = RSTATE_ON; |
|
} |
|
break; |
|
} |
|
|
|
tmpdata[0] = N64_GET_STATUS; |
|
count = gcn64_transaction(chn, tmpdata, 1, status, sizeof(status)); |
|
if (count != N64_GET_STATUS_REPLY_LENGTH) { |
|
return -1; |
|
} |
|
|
|
/* |
|
Bit Function |
|
0 A |
|
1 B |
|
2 Z |
|
3 Start |
|
4 Directional Up |
|
5 Directional Down |
|
6 Directional Left |
|
7 Directional Right |
|
8 unknown (always 0) |
|
9 unknown (always 0) |
|
10 L |
|
11 R |
|
12 C Up |
|
13 C Down |
|
14 C Left |
|
15 C Right |
|
16-23: analog X axis |
|
24-31: analog Y axis |
|
*/ |
|
|
|
btns1 = status[0]; |
|
btns2 = status[1]; |
|
x = status[2]; |
|
y = status[3]; |
|
|
|
#ifdef BUTTON_A_RUMBLE_TEST |
|
if (btns1 & 0x80) { |
|
force_rumble[chn] = 1; |
|
} else { |
|
force_rumble[chn] = 0; |
|
} |
|
#endif |
|
|
|
last_built_report[chn].pad_type = PAD_TYPE_N64; |
|
last_built_report[chn].n64.buttons = (btns1 << 8) | btns2; |
|
last_built_report[chn].n64.x = x; |
|
last_built_report[chn].n64.y = y; |
|
|
|
#ifdef PAD_DATA_HAS_RAW |
|
/* Copy all the data as-is for the raw field */ |
|
last_built_report[chn].n64.raw_data[0] = btns1; |
|
last_built_report[chn].n64.raw_data[1] = btns2; |
|
last_built_report[chn].n64.raw_data[2] = x; |
|
last_built_report[chn].n64.raw_data[3] = y; |
|
#endif |
|
|
|
/* Some cheap non-official controllers |
|
* use the full 8 bit range instead of the |
|
* normal +-80 observed on official controllers. In |
|
* particular, some units (but not all!) produced |
|
* by TTX. The symptom is usually "The joystick |
|
* left direction does not work". |
|
* |
|
* So I limit values to the -127 to +127 range, |
|
* otherwise it causes problem later |
|
* when the sign is inverted. Using 16 bit |
|
* signed numbers instead of 8 bit would solve |
|
* this, but this is only for cheap, not |
|
* even worth using controllers so I don't |
|
* care. |
|
* |
|
* The joystick will now "work" as bad as it would |
|
* on a N64, or maybe a little better. This should |
|
* help people realise they got what the paid for |
|
* instead of suspecting the adapter. */ |
|
if (last_built_report[chn].n64.x == -128) |
|
last_built_report[chn].n64.x = -127; |
|
if (last_built_report[chn].n64.y == -128) |
|
last_built_report[chn].n64.y = -127; |
|
|
|
return 0; |
|
} |
|
|
|
static char n64Probe(unsigned char chn) |
|
{ |
|
int count; |
|
char i; |
|
unsigned char tmp; |
|
unsigned char data[4]; |
|
|
|
/* Pad answer to N64_GET_CAPABILITIES |
|
* |
|
* 0x050000 : 0000 0101 0000 0000 0000 0000 : No expansion pack |
|
* 0x050001 : 0000 0101 0000 0000 0000 0001 : With expansion pack |
|
* 0x050002 : 0000 0101 0000 0000 0000 0010 : Expansion pack removed |
|
* |
|
* Bit 0 tells us if there is something connected to the expansion port. |
|
* Bit 1 tells is if there was something connected that has been removed. |
|
*/ |
|
|
|
n64_rumble_state[chn] = RSTATE_UNAVAILABLE; |
|
|
|
for (i=0; i<15; i++) |
|
{ |
|
_delay_ms(30); |
|
|
|
tmp = N64_GET_CAPABILITIES; |
|
count = gcn64_transaction(chn, &tmp, 1, data, sizeof(data)); |
|
|
|
if (count == N64_CAPS_REPLY_LENGTH) { |
|
return 1; |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
static char n64Changed(unsigned char chn) |
|
{ |
|
return memcmp(&last_built_report[chn], &last_sent_report[chn], sizeof(gamepad_data)); |
|
} |
|
|
|
static void n64GetReport(unsigned char chn, gamepad_data *dst) |
|
{ |
|
if (dst) |
|
memcpy(dst, &last_built_report[chn], sizeof(gamepad_data)); |
|
|
|
memcpy(&last_sent_report[chn], &last_built_report[chn], sizeof(gamepad_data)); |
|
} |
|
|
|
static void n64SetVibration(unsigned char chn, char enable) |
|
{ |
|
must_rumble[chn] = enable; |
|
} |
|
|
|
static Gamepad N64Gamepad = { |
|
.init = n64Init, |
|
.update = n64Update, |
|
.changed = n64Changed, |
|
.getReport = n64GetReport, |
|
.probe = n64Probe, |
|
.setVibration = n64SetVibration, |
|
}; |
|
|
|
Gamepad *n64GetGamepad(void) |
|
{ |
|
return &N64Gamepad; |
|
}
|
|
|