mirror of
https://gitee.com/anod/open_agb_firm.git
synced 2025-05-08 23:04:13 +08:00
171 lines
5.5 KiB
C
171 lines
5.5 KiB
C
#include "arm11/drivers/wifi/sdio.h"
|
|
#include "arm11/drivers/wifi/sdio_spec.h"
|
|
#include "drivers/tmio.h"
|
|
#include "arm11/drivers/timer.h"
|
|
#include "arm11/fmt.h"
|
|
|
|
|
|
#define INIT_CLOCK (400000u)
|
|
#define DEFAULT_CLOCK (25000000u) // TODO: Maximum default clock for SDIO (25 MHz like SD?)? Also test overclocking.
|
|
#define HS_CLOCK (50000000u)
|
|
|
|
|
|
#define SDIO_OCR_VOLT_MASK (SDIO_OCR_3_2_3_3V) // We support 3.3V only.
|
|
#define SDIO_OP_COND_ARG (SDIO_OCR_VOLT_MASK) // No additional bits needed.
|
|
|
|
|
|
typedef struct
|
|
{
|
|
TmioPort port;
|
|
u16 rca;
|
|
} SdioDev;
|
|
|
|
static SdioDev g_sdioDev = {0};
|
|
|
|
|
|
|
|
u32 SDIO_init(void)
|
|
{
|
|
// Init port, enable init clock and wait 74 clocks.
|
|
SdioDev *const dev = &g_sdioDev;
|
|
TmioPort *const port = &dev->port;
|
|
TMIO_initPort(port, 0);
|
|
TMIO_startInitClock(port, INIT_CLOCK);
|
|
TIMER_sleepTicks(2 * TMIO_CLK2DIV(INIT_CLOCK) * 74);
|
|
|
|
// This CMD does nothing for SDIO cards but it's used for detecting SPI mode.
|
|
u32 res = TMIO_sendCommand(port, SDIO_GO_IDLE_STATE, 0);
|
|
if(res != 0) return SDIO_ERR_GO_IDLE_STATE;
|
|
|
|
// 3.1.2 Initialization by I/O Aware Host
|
|
// "As shown in Figure 3-2 and Figure 3-3, an SDIO aware host should send CMD5 arg=0 as part of the
|
|
// initialization sequence after either Power On or a CMD 52 with write to I/O Reset. CMD5 arg=0 is
|
|
// required for using a removal card due to the following reasons:
|
|
// (1) There was 3.1V-3.5V voltage range legacy card and then whether host power supply can provide
|
|
// the voltage in this range should be checked.
|
|
// (2) There may be the card which requires CMD5 with WV= 0 to initialize the card.
|
|
//
|
|
// CMD5 arg=0 may not be necessary for embedded system if the device does not require it."
|
|
//
|
|
// So for builtin SDIO devices we can skip this? Tested and works fine without.
|
|
//res = TMIO_sendCommand(port, SDIO_IO_SEND_OP_COND, 0); // Arg = 0 is probing OCR.
|
|
//if(res != 0) return SDIO_ERR_IO_SEND_OP_COND;
|
|
|
|
u32 tries = 200;
|
|
u32 ocr;
|
|
while(1)
|
|
{
|
|
res = TMIO_sendCommand(port, SDIO_IO_SEND_OP_COND, SDIO_OP_COND_ARG);
|
|
if(res != 0) return SDIO_ERR_IO_SEND_OP_COND;
|
|
|
|
ocr = port->resp[0];
|
|
if(!--tries || ocr & SDIO_READY) break;
|
|
|
|
TIMER_sleepMs(5);
|
|
}
|
|
|
|
if(tries == 0) return SDIO_ERR_IO_OP_COND_TMOUT; // OCR at this point: 0x90FFFF00. Usually 2 tries.
|
|
|
|
if(!(ocr & SDIO_OCR_VOLT_MASK)) return SDIO_ERR_VOLT_SUPPORT;
|
|
|
|
// Stop clock at idle, init clock.
|
|
TMIO_setClock(port, INIT_CLOCK);
|
|
|
|
// TODO: Can we increase clock to default clock after this CMD? This will quit open-drain mode.
|
|
// Seems to work just fine.
|
|
res = TMIO_sendCommand(port, SDIO_SEND_RELATIVE_ADDR, 0);
|
|
if(res != 0) return SDIO_ERR_SEND_RCA;
|
|
u32 rca = port->resp[0];
|
|
dev->rca = rca>>16; // RCA is usually 0x0001. TODO: Set this at the end (initialized flag).
|
|
debug_printf("[SDIO] RCA 0x%" PRIX16 "\n", dev->rca);
|
|
|
|
TMIO_setClock(port, DEFAULT_CLOCK);
|
|
|
|
res = TMIO_sendCommand(port, SDIO_SELECT_CARD, rca);
|
|
if(res != 0) return SDIO_ERR_SELECT_CARD;
|
|
|
|
res = SDIO_writeReg8(0, 7, 1u<<7 | 2); // Disable pull-up and switch to 4 bit bus width.
|
|
TMIO_setBusWidth(port, 4);
|
|
ee_printf("Set bus width: res 0x%lx, out 0x%X\n", res, SDIO_readReg8(0, 7));
|
|
res = SDIO_writeReg8(0, 2, 1u<<1); // Enable function 1.
|
|
TIMER_sleepMs(5); // The WiFi module incorrectly reports a timeout of 0 ms. 5 ms is the lowest working.
|
|
ee_printf("Enable IO 1: res 0x%lx, out 0x%X\n", res, SDIO_readReg8(0, 3));
|
|
// TODO: Read regs, increase clock and set bus width to 4 bit.
|
|
ee_puts("[SDIO] Init done.");
|
|
|
|
return SDIO_ERR_NONE;
|
|
}
|
|
|
|
u32 SDIO_reset(void)
|
|
{
|
|
// 4.4 Reset for SDIO.
|
|
// Linux does a read-modify-write but this reg is write-only.
|
|
return SDIO_writeReg8(0, 6, 1u<<3);
|
|
}
|
|
|
|
u32 SDIO_io_rw_direct(const bool write, const u8 func, const u32 addr, const u8 in, u8 *const out)
|
|
{
|
|
if(func > 7 || addr >= 0x20000) return SDIO_ERR_INVALID_ARG;
|
|
|
|
TmioPort *const port = &g_sdioDev.port;
|
|
|
|
const u32 arg = write<<31 | func<<28 | (write && out != NULL ? 1u<<27 : 0) | addr<<9 | in;
|
|
const u32 res = TMIO_sendCommand(port, SDIO_IO_RW_DIRECT, arg);
|
|
if(res != 0) return SDIO_ERR_IO_RW_DIRECT;
|
|
|
|
const u32 resp = port->resp[0];
|
|
if(resp & SDIO_R5_ERROR) return SDIO_ERR_R5_ERROR;
|
|
if(resp & SDIO_R5_FUNCTION_NUMBER) return SDIO_ERR_R5_INVALID_FUNC;
|
|
if(resp & SDIO_R5_OUT_OF_RANGE) return SDIO_ERR_R5_OUT_OF_RANGE;
|
|
|
|
if(out != NULL) *out = resp & 0xFFu;
|
|
|
|
return SDIO_ERR_NONE;
|
|
}
|
|
|
|
// TODO: Instead of count + size only use size (block mode if size > block size)?
|
|
u32 SDIO_io_rw_extended(const bool write, const u8 func, const u32 addr, const bool incAddr, u8 *buf, const u16 count, const u16 size)
|
|
{
|
|
if(func > 7 || addr >= 0x20000) return SDIO_ERR_INVALID_ARG;
|
|
|
|
TmioPort *const port = &g_sdioDev.port;
|
|
|
|
u32 arg = write<<31;
|
|
arg |= func<<28;
|
|
arg |= incAddr<<26;
|
|
arg |= addr<<9;
|
|
arg |= (count == 0 ? (size == 512 ? 0 : size) : 1u<<27 | count);
|
|
const u32 res = TMIO_sendCommand(port, SDIO_IO_RW_EXTENDED(!write), arg);
|
|
if(res != 0) return SDIO_ERR_IO_RW_EXTENDED;
|
|
|
|
const u32 resp = port->resp[0];
|
|
if(resp & SDIO_R5_ERROR) return SDIO_ERR_R5_ERROR;
|
|
if(resp & SDIO_R5_FUNCTION_NUMBER) return SDIO_ERR_R5_INVALID_FUNC;
|
|
if(resp & SDIO_R5_OUT_OF_RANGE) return SDIO_ERR_R5_OUT_OF_RANGE;
|
|
|
|
return SDIO_ERR_NONE;
|
|
}
|
|
|
|
u8 SDIO_readReg8(const u8 func, const u32 addr)
|
|
{
|
|
u8 out;
|
|
if(SDIO_io_rw_direct(false, func, addr, 0, &out) != SDIO_ERR_NONE)
|
|
out = 0xFF;
|
|
|
|
return out;
|
|
}
|
|
|
|
u32 SDIO_writeReg8(const u8 func, const u32 addr, const u8 in)
|
|
{
|
|
return SDIO_io_rw_direct(true, func, addr, in, NULL);
|
|
}
|
|
|
|
u8 SDIO_writeReadReg8(const u8 func, const u32 addr, const u8 in)
|
|
{
|
|
u8 out;
|
|
if(SDIO_io_rw_direct(true, func, addr, in, &out) != SDIO_ERR_NONE)
|
|
out = 0xFF;
|
|
|
|
return out;
|
|
}
|