#include "bba.h" #include #include #include u32 bba_speed = EXI_SPEED32MHZ; /* nominal name; ~27 MHz in reality */ u32 bba_gap_us = 16; /* see bba.h -- 0 reproduces the raw failure */ /* Busy-wait, not usleep(): we are holding the EXI lock with CS asserted, so * yielding here is both unnecessary and imprecise at these timescales. */ static void gap(void) { u64 t0; if (!bba_gap_us) return; t0 = gettime(); while (ticks_to_microsecs(diff_ticks(t0, gettime())) < bba_gap_us) ; } /* Build the 2-byte EXI transaction header. * byte0: [7]=write [6:0]=addr[12:6] * byte1: [7:2]=addr[5:0] [1:0]=len-1 * Returned packed big-endian in a u16 so EXI_Imm(...,2,...) clocks byte0 first * (the GameCube is big-endian, so the high byte goes out first). */ static u16 hdr(u16 addr, u32 len, int write) { u8 b0 = (write ? 0x80 : 0x00) | ((addr >> 6) & 0x7F); u8 b1 = (u8)(((addr & 0x3F) << 2) | ((len - 1) & 0x03)); return ((u16)b0 << 8) | b1; } static int begin(void) { int tries; /* EXI_Lock fails outright (rather than blocking) when another subsystem * holds the channel -- Swiss or a libogc driver may have it briefly. * Retry for ~100 ms before giving up so a transient collision does not * surface as a test failure. */ for (tries = 0; tries < 100; tries++) { if (EXI_Lock(BBA_EXI_CHAN, BBA_EXI_DEV, NULL) > 0) break; usleep(1000); } if (tries == 100) return -1; if (EXI_Select(BBA_EXI_CHAN, BBA_EXI_DEV, bba_speed) <= 0) { EXI_Unlock(BBA_EXI_CHAN); return -2; } return 0; } static void end(void) { EXI_Deselect(BBA_EXI_CHAN); EXI_Unlock(BBA_EXI_CHAN); } static int put_hdr(u16 addr, u32 len, int write) { u16 h = hdr(addr, len, write); if (EXI_Imm(BBA_EXI_CHAN, &h, 2, EXI_WRITE, NULL) <= 0) return -3; if (EXI_Sync(BBA_EXI_CHAN) <= 0) return -4; return 0; } int bba_read(u16 addr, void *buf, u32 len) { int rc; if (len < 1 || len > 4) return -10; if ((rc = begin()) < 0) return rc; if ((rc = put_hdr(addr, len, 0)) < 0) { end(); return rc; } /* Hold the clock idle while the device decodes the header and prefetches * the response into its TX FIFO. libogc's inter-call overhead alone is * NOT enough at full EXI speed -- see bba_gap_us. */ gap(); memset(buf, 0, len); if (EXI_ImmEx(BBA_EXI_CHAN, buf, len, EXI_READ) <= 0) { end(); return -5; } end(); return 0; } int bba_write(u16 addr, const void *buf, u32 len) { int rc; u8 tmp[4]; if (len < 1 || len > 4) return -10; memcpy(tmp, buf, len); if ((rc = begin()) < 0) return rc; if ((rc = put_hdr(addr, len, 1)) < 0) { end(); return rc; } if (EXI_ImmEx(BBA_EXI_CHAN, tmp, len, EXI_WRITE) <= 0) { end(); return -6; } end(); return 0; } int bba_r8(u16 addr) { u8 v; int rc = bba_read(addr, &v, 1); return rc < 0 ? rc : (int)v; } int bba_w8(u16 addr, u8 val) { return bba_write(addr, &val, 1); } int bba_stream(u16 addr, void *buf, u32 len) { int rc; if (len < 1) return -10; if ((rc = begin()) < 0) return rc; /* Length field is capped at 2 bits; the device ignores it for addresses * >= 0x0100 and streams until CS rises, so ask for 4 and clock as many * bytes as we actually want. */ if ((rc = put_hdr(addr, 4, 0)) < 0) { end(); return rc; } gap(); memset(buf, 0, len); if (EXI_ImmEx(BBA_EXI_CHAN, buf, len, EXI_READ) <= 0) { end(); return -5; } end(); return 0; } int bba_tx_bytes(const u8 *buf, u32 len) { u32 i; for (i = 0; i < len; i++) { int rc = bba_w8(REG_TXDATA, buf[i]); if (rc < 0) return rc; } return 0; }