diff options
| author | spicyjpeg <88942473+spicyjpeg@users.noreply.github.com> | 2022-02-09 22:59:16 +0100 |
|---|---|---|
| committer | spicyjpeg <88942473+spicyjpeg@users.noreply.github.com> | 2022-02-09 22:59:16 +0100 |
| commit | aca79b2a75c9a6106bc0047f767a475a2c3aaf8e (patch) | |
| tree | a9536efe30ce2d3a40414948494a0eda3e45dbbd /examples | |
| parent | c083d3f18ecf80297b45eeda2abdf2fd6719cd7b (diff) | |
| download | psn00bsdk-aca79b2a75c9a6106bc0047f767a475a2c3aaf8e.tar.gz | |
Rename hwregs_a definitions, add hwregs_c, fix io/pads
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/io/pads/main.c | 57 | ||||
| -rw-r--r-- | examples/io/pads/spi.c | 103 | ||||
| -rw-r--r-- | examples/io/pads/spi.h | 15 | ||||
| -rw-r--r-- | examples/sound/spustream/main.c | 46 |
4 files changed, 102 insertions, 119 deletions
diff --git a/examples/io/pads/main.c b/examples/io/pads/main.c index d100482..17bf331 100644 --- a/examples/io/pads/main.c +++ b/examples/io/pads/main.c @@ -15,12 +15,12 @@ * but the code in spi.c can be used to read/write sectors on a memory card and * combined with a higher-level filesystem driver for full support. * - * IMPORTANT: this example hasn't yet been tested on real hardware and/or with - * unofficial controllers, which might behave differently at higher poll rates. - * Also keep in mind that many emulators emulate controllers and memory cards - * inaccurately. It is thus recommended to test controller I/O code extensively - * and handle as many edge cases as possible (e.g. partial but valid responses, - * zerofilled responses, slow replies) for maximum compatibility. + * IMPORTANT: some controller models seem to be unable to respond to config + * mode commands reliably, even though simple high-speed polling usually works + * without issues. Also keep in mind that many emulators emulate controllers + * and memory cards inaccurately. It is thus recommended to test controller I/O + * code extensively and handle as many edge cases as possible (e.g. partial but + * valid responses, zerofilled responses, slow replies) for best compatibility. */ #include <stdint.h> @@ -116,7 +116,7 @@ void display(CONTEXT *ctx) { static volatile uint8_t pad_buff[2][34]; static volatile size_t pad_buff_len[2]; -static volatile uint32_t pad_digital_only[2] = { 0, 0 }; +static volatile uint32_t pad_config_attempt[2] = { 0, 0 }; // Just a wrapper around SPI_CreateRequest(). This does not send the command // immediately but adds it to the driver's request queue. @@ -148,7 +148,8 @@ void send_pad_cmd( // This callback determines whether a pad that identified as digital is // actually a DualShock in digital mode by checking if it started identifying -// as CONFIG_MODE after receiving a configuration command. +// as CONFIG_MODE after receiving a configuration command. Calls to printf() +// had to be commented out due to them being too slow. void dualshock_init_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_len) { PadResponse *pad = (PadResponse *) buff; @@ -157,13 +158,13 @@ void dualshock_init_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_le (pad->prefix != 0x5a) || (pad->type != PAD_ID_CONFIG_MODE) ) { - printf("no, pad is digital-only (len = %d)\n", rx_len); + //printf("no, pad is digital-only (len = %d)\n", rx_len); - pad_digital_only[port] = 1; + pad_config_attempt[port]++; return; } - printf("yes, forcing analog mode (len = %d)\n", rx_len); + //printf("yes, forcing analog mode (len = %d)\n", rx_len); // Issue further commands to force analog mode on, unlock rumble (not used // in this example) and enable longer responses containing button pressure @@ -171,6 +172,7 @@ void dualshock_init_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_le // TODO: find out if passing 0x03 instead of 0x02 in PAD_CMD_SET_ANALOG // locks the analog button, as emulated by DuckStation... // https://gist.github.com/scanlime/5042071 + send_pad_cmd(port, PAD_CMD_CONFIG_MODE, 0x01, 0x00, 0); send_pad_cmd(port, PAD_CMD_SET_ANALOG, 0x01, 0x02, 0); send_pad_cmd(port, PAD_CMD_INIT_PRESSURE, 0x00, 0x00, 0); // Ignored by DualShock 1 send_pad_cmd(port, PAD_CMD_REQUEST_CONFIG, 0x00, 0x01, 0); @@ -189,29 +191,37 @@ void poll_cb(uint32_t port, const volatile uint8_t *buff, size_t rx_len) { PadResponse *pad = (PadResponse *) buff; - // If this pad identifies as a digital pad and hasn't been flagged as a - // digital-only pad already, attempt to put it into analog mode by entering - // configuration mode. It this fails, it will be flagged as digital-only. - // The digital-only flag is reset when the controller is unplugged or stops + // If this pad identifies as a digital pad, attempt to put it into analog + // mode up to 3 times by entering configuration mode. Once the attempt + // counter exceeds the threshold, it will be treated as digital-only. The + // attempt counter is reset when the controller is unplugged or stops // returning digital pad responses. + // NOTE: according to nocash docs, there is a hardware bug in DualShock + // controllers that causes the prefix byte (normally 0x5a) to turn into + // 0x00 if the analog button is pressed after config commands have been + // used. if ( rx_len && - (pad->prefix == 0x5a) && + ((pad->prefix == 0x5a) || !(pad->prefix)) && (pad->type == PAD_ID_DIGITAL) ) { - if (!pad_digital_only[port]) { - printf("Detecting if pad %d supports config mode... ", port + 1); + if (pad_config_attempt[port] < 3) { + /*printf( + "Detecting if pad %d supports config mode: attempt %d... ", + port + 1, + pad_config_attempt[port] + 1 + );*/ // The pad only identifies as CONFIG_MODE after at least another // command is sent. send_pad_cmd(port, PAD_CMD_CONFIG_MODE, 0x01, 0x00, 0); - send_pad_cmd(port, PAD_CMD_CONFIG_MODE, 0x01, 0x00, &dualshock_init_cb); + send_pad_cmd(port, PAD_CMD_READ, 0x00, 0x00, &dualshock_init_cb); } } else { - //printf("Clearing digital-only flag for pad %d\n", port + 1); + //printf("Clearing attempt counter for pad %d\n", port + 1); - pad_digital_only[port] = 0; + pad_config_attempt[port] = 0; } } @@ -240,11 +250,6 @@ int main(int argc, const char* argv[]) { PadResponse *pad = (PadResponse *) pad_buff[port]; - // According to nocash docs, there is a hardware bug in DualShock - // controllers that causes the prefix byte (normally 0x5a) to turn - // into 0x00 if the analog button is pressed after configuration - // commands have been used. Thus making sure the prefix is 0x5a - // isn't enough to reliably detect pads. /*if ((pad->prefix != 0x5a) && (pad->type != PAD_ID_ANALOG)) { FntPrint(-1, "\n\nPORT %d: INVALID RESPONSE\n", port + 1); if ((counter % 64) < 32) diff --git a/examples/io/pads/spi.c b/examples/io/pads/spi.c index ef75ffc..05a0e59 100644 --- a/examples/io/pads/spi.c +++ b/examples/io/pads/spi.c @@ -32,43 +32,27 @@ #include <psxetc.h> #include <psxapi.h> #include <psxpad.h> +#include <hwregs_c.h> #include "spi.h" -/* Register definitions */ - -#define F_CPU 33868800UL - -#define TIM_VALUE(N) *((volatile uint32_t *) 0x1f801100 + 4 * (N)) -#define TIM_CTRL(N) *((volatile uint32_t *) 0x1f801104 + 4 * (N)) -#define TIM_RELOAD(N) *((volatile uint32_t *) 0x1f801108 + 4 * (N)) - -// IMPORTANT: even though JOY_TXRX is a 32-bit register, it should only be -// accessed as 8-bit. Reading it as 16 or 32-bit works fine on real hardware, -// but leads to problems in some emulators. -#define JOY_TXRX *((volatile uint8_t *) 0x1f801040) -#define JOY_STAT *((volatile uint16_t *) 0x1f801044) -#define JOY_MODE *((volatile uint16_t *) 0x1f801048) -#define JOY_CTRL *((volatile uint16_t *) 0x1f80104a) -#define JOY_BAUD *((volatile uint16_t *) 0x1f80104e) - /* Internal structures and globals */ -typedef struct _SPI_CONTEXT { +typedef struct _SPI_Context { uint8_t tx_buff[SPI_BUFF_LEN]; uint8_t rx_buff[SPI_BUFF_LEN]; uint32_t tx_len, rx_len, port; SPI_Callback callback; } SPI_Context; -static volatile SPI_Context ctx; -static volatile SPI_Request volatile *current_req; -static SPI_Callback default_cb; +static volatile SPI_Context _context; +static volatile SPI_Request volatile *_current_req; +static volatile SPI_Callback _default_cb; /* Request queue management */ static void _spi_create_poll_req(void) { - PadRequest *req = (PadRequest *) ctx.tx_buff; + PadRequest *req = (PadRequest *) _context.tx_buff; req->addr = 0x01; req->cmd = PAD_CMD_READ; @@ -76,27 +60,31 @@ static void _spi_create_poll_req(void) { req->motor_l = 0x00; req->motor_r = 0x00; - ctx.tx_len = 4; - ctx.rx_len = 0; - ctx.port ^= 1; - ctx.callback = default_cb; + _context.tx_len = 4; + _context.rx_len = 0; + _context.port ^= 1; + _context.callback = _default_cb; } static void _spi_next_req(void) { // Copy the contents of the first request in the queue into the TX buffer. - memcpy((void *) ctx.tx_buff, (void *) current_req->data, current_req->len); + memcpy( + (void *) _context.tx_buff, + (void *) _current_req->data, + _current_req->len + ); - ctx.tx_len = current_req->len; - ctx.rx_len = 0; - ctx.port = current_req->port; - ctx.callback = current_req->callback; + _context.tx_len = _current_req->len; + _context.rx_len = 0; + _context.port = _current_req->port; + _context.callback = _current_req->callback; // Pop the first request from the queue by deallocating it and adjusting // the pointer to the first queue item. - SPI_Request *next = current_req->next; + SPI_Request *next = _current_req->next; - free((void *) current_req); - current_req = next; + free((void *) _current_req); + _current_req = next; } /* Interrupt handlers */ @@ -105,13 +93,13 @@ static void _spi_poll_irq_handler(void) { // Fetch the last response byte, which wasn't followed by a pulse on /ACK, // from the RX FIFO. if (JOY_STAT & 0x0002) - ctx.rx_buff[ctx.rx_len - 1] = (uint8_t) JOY_TXRX; + _context.rx_buff[_context.rx_len - 1] = (uint8_t) JOY_TXRX; - if (ctx.callback) - ctx.callback(ctx.port, ctx.rx_buff, ctx.rx_len); + if (_context.callback) + _context.callback(_context.port, _context.rx_buff, _context.rx_len); // If the request queue is empty, create a pad polling request. - if (current_req) + if (_current_req) _spi_next_req(); else _spi_create_poll_req(); @@ -119,17 +107,18 @@ static void _spi_poll_irq_handler(void) { // Prepare the SPI port by clearing any pending IRQ, pulling /CS high and // enabling the /ACK IRQ. In order to communicate with controllers, /CS has // to be driven low again for about 20 us before sending the first byte. + // TODO: these delays can be probably tweaked for better performance JOY_CTRL = 0x0010; - for (uint32_t i = 0; i < 50; i++) - __asm__("nop"); + for (uint32_t i = 0; i < 1000; i++) + __asm__ volatile(""); - JOY_CTRL = 0x1003 | (ctx.port << 13); - for (uint32_t i = 0; i < 500; i++) - __asm__("nop"); + JOY_CTRL = 0x1003 | (_context.port << 13); + for (uint32_t i = 0; i < 2000; i++) + __asm__ volatile(""); // Send the first byte indicating which device to address. If the matching // device is connected, it will reply by triggering the /ACK IRQ. - JOY_TXRX = ctx.tx_buff[0]; + JOY_TXRX = _context.tx_buff[0]; } static void _spi_ack_irq_handler(void) { @@ -137,29 +126,29 @@ static void _spi_ack_irq_handler(void) { // byte. According to nocash docs, this has to be done before resetting the // IRQ. while (JOY_STAT & 0x0080) - __asm__("nop"); + __asm__ volatile(""); // Keep /CS pulled low and acknowledge the IRQ (bit 4) to ensure it can be // triggered again. - JOY_CTRL = 0x1013 | (ctx.port << 13); + JOY_CTRL = 0x1013 | (_context.port << 13); - if (!ctx.rx_len) { + if (!_context.rx_len) { // We just sent the first address byte. Obviously the response we // received was read from an open bus, so the SPI port's internal FIFO // must be flushed (by performing dummy reads) to ensure we are only // going to read valid data from now on. JOY_TXRX; - } else if (ctx.rx_len <= SPI_BUFF_LEN) { + } else if (_context.rx_len <= SPI_BUFF_LEN) { // If this is not the first byte, put it in the RX buffer. - ctx.rx_buff[ctx.rx_len - 1] = (uint8_t) JOY_TXRX; + _context.rx_buff[_context.rx_len - 1] = (uint8_t) JOY_TXRX; } // Send the next byte, or a null byte if there is no more data to send and // we're just reading a response. - ctx.rx_len++; - if (ctx.rx_len < ctx.tx_len) - JOY_TXRX = (uint32_t) ctx.tx_buff[ctx.rx_len]; + _context.rx_len++; + if (_context.rx_len < _context.tx_len) + JOY_TXRX = (uint32_t) _context.tx_buff[_context.rx_len]; else JOY_TXRX = 0x00; } @@ -176,10 +165,10 @@ SPI_Request *SPI_CreateRequest(void) { // Find the last queued request by traversing the linked list and append a // pointer to the new request. - if (!current_req) { - current_req = req; + if (!_current_req) { + _current_req = req; } else { - volatile SPI_Request *volatile last = current_req; + volatile SPI_Request *volatile last = _current_req; while (last->next) last = last->next; @@ -213,6 +202,6 @@ void SPI_Init(SPI_Callback callback) { JOY_BAUD = 0x0088; // 250000 bps SPI_SetPollRate(250); - current_req = 0; - default_cb = callback; + _current_req = 0; + _default_cb = callback; } diff --git a/examples/io/pads/spi.h b/examples/io/pads/spi.h index c50e065..7d4d75b 100644 --- a/examples/io/pads/spi.h +++ b/examples/io/pads/spi.h @@ -9,8 +9,9 @@ #include <stdint.h> #include <psxpad.h> -// Maximum request/response length (34 bytes for pads, 140 for memory cards) -//#define SPI_BUFF_LEN 34 +// Maximum request/response length (34 bytes for pads, 140 for memory cards). +// Must be a multiple of 4 to avoid memory alignment issues. +//#define SPI_BUFF_LEN 36 #define SPI_BUFF_LEN 140 /* Request structures */ @@ -30,6 +31,10 @@ typedef struct _SPI_Request { /* Public API */ +#ifdef __cplusplus +extern "C" { +#endif + /** * @brief Allocates a new request object and adds it to the request queue. The * object must be populated afterwards by setting the length, callback and @@ -49,7 +54,7 @@ void SPI_SetPollRate(uint32_t value); /** * @brief Installs the SPI and timer 2 interrupt handlers and starts the poll * timer. By default the polling rate is set to 250 Hz (125 Hz per port), - * however it can be changed at any time by calling spi_set_poll_rate(). + * however it can be changed at any time by calling SPI_SetPollRate(). * * The provided callback (if any) is called to report the result of poll * requests, which are issued automatically when no other request is in the @@ -59,4 +64,8 @@ void SPI_SetPollRate(uint32_t value); */ void SPI_Init(SPI_Callback callback); +#ifdef __cplusplus +} +#endif + #endif diff --git a/examples/sound/spustream/main.c b/examples/sound/spustream/main.c index be095cb..6284c6d 100644 --- a/examples/sound/spustream/main.c +++ b/examples/sound/spustream/main.c @@ -11,8 +11,8 @@ * SPU ADPCM data (one for each channel, so a stereo stream would have 2 * buffers per chunk). All buffers in a chunk are played simultaneously using * multiple SPU channels; each buffer has the loop flag set at the end, so each - * channel will jump to its loop address (SPU_CHANNELS[n].loop_addr) once the - * chunk is played. + * channel will jump to its loop address (SPU_CH_LOOP_ADDR(n)) once the chunk + * is played. * * Since the loop point doesn't necessarily have to be within the chunk itself, * we can abuse it to "queue" another set of buffers to be played immediately @@ -94,6 +94,7 @@ #include <psxpad.h> #include <psxspu.h> #include <psxcd.h> +#include <hwregs_c.h> // To maximize STREAM.BIN packing efficiency and get rid of padding between // chunks, buffer size should be a multiple of sector size (2048 bytes). Buffer @@ -105,27 +106,6 @@ #define NUM_CHANNELS 2 #define CHANNEL_MASK 0x03 -/* Register definitions */ - -// For some reason SpuVoiceRaw doesn't actually match the layout of SPU -// registers, so here we go. -typedef struct { - uint16_t vol_left; - uint16_t vol_right; - uint16_t freq; - uint16_t addr; - uint32_t adsr_param; - uint16_t _reserved; - uint16_t loop_addr; -} SPUChannel; - -#define SPU_CTRL *((volatile uint16_t *) 0x1f801daa) -#define SPU_IRQ_ADDR *((volatile uint16_t *) 0x1f801da4) -#define SPU_KEY_ON *((volatile uint32_t *) 0x1f801d88) -#define SPU_KEY_OFF *((volatile uint32_t *) 0x1f801d8c) - -// SPU RAM is addressed in 8-byte units, using 16-bit pointers. -#define SPU_CHANNELS ((volatile SPUChannel *) 0x1f801c00) #define SPU_RAM_ADDR(x) ((uint16_t) (((uint32_t) (x)) >> 3)) /* Display/GPU context utilities */ @@ -252,7 +232,7 @@ void spu_irq_handler(void) { SPU_IRQ_ADDR = SPU_RAM_ADDR(str_ctx.spu_addr); for (uint32_t i = 0; i < NUM_CHANNELS; i++) - SPU_CHANNELS[i].loop_addr = SPU_RAM_ADDR(str_ctx.spu_addr + BUFFER_SIZE * i); + SPU_CH_LOOP_ADDR(i) = SPU_RAM_ADDR(str_ctx.spu_addr + BUFFER_SIZE * i); // Start loading the next chunk. cd_event_handler() will be called // repeatedly for each sector until the entire chunk is read. @@ -317,7 +297,7 @@ void init_spu_channels(void) { SPU_KEY_OFF = 0x00ffffff; for (uint32_t i = 0; i < 24; i++) - SPU_CHANNELS[i].addr = SPU_RAM_ADDR(DUMMY_BLOCK_ADDR); + SPU_CH_ADDR(i) = SPU_RAM_ADDR(DUMMY_BLOCK_ADDR); SPU_KEY_ON = 0x00ffffff; } @@ -347,18 +327,18 @@ void start_stream(void) { SPU_KEY_OFF = CHANNEL_MASK; for (uint32_t i = 0; i < NUM_CHANNELS; i++) { - SPU_CHANNELS[i].addr = SPU_RAM_ADDR(BUFFER_START_ADDR + BUFFER_SIZE * i); - SPU_CHANNELS[i].freq = SAMPLE_RATE; - SPU_CHANNELS[i].adsr_param = 0x1fee80ff; // or 0x9fc080ff, 0xdff18087 + SPU_CH_ADDR(i) = SPU_RAM_ADDR(BUFFER_START_ADDR + BUFFER_SIZE * i); + SPU_CH_FREQ(i) = SAMPLE_RATE; + SPU_CH_ADSR(i) = 0x1fee80ff; // or 0x9fc080ff, 0xdff18087 } // Unmute the channels and route them for stereo output. You'll want to // edit this if you are using more than 2 channels, and/or if you want to // provide an option to output mono audio instead of stereo. - SPU_CHANNELS[0].vol_left = 0x3fff; - SPU_CHANNELS[0].vol_right = 0x0000; - SPU_CHANNELS[1].vol_left = 0x0000; - SPU_CHANNELS[1].vol_right = 0x3fff; + SPU_CH_VOL_L(0) = 0x3fff; + SPU_CH_VOL_R(0) = 0x0000; + SPU_CH_VOL_L(1) = 0x0000; + SPU_CH_VOL_R(1) = 0x3fff; SPU_KEY_ON = CHANNEL_MASK; spu_irq_handler(); @@ -446,7 +426,7 @@ int main(int argc, const char* argv[]) { // Only set the sample rate registers if necessary. if (pad->btn != 0xffff) { for (uint32_t i = 0; i < NUM_CHANNELS; i++) - SPU_CHANNELS[i].freq = sample_rate; + SPU_CH_FREQ(i) = sample_rate; } last_buttons = pad->btn; |
