2023-10-14 17:49:00 +08:00

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;
}