Files
ClassiCube-PPC-for-MacOSX-10.4/third_party/dsiwifi/arm_iop/source/wifi_sdio.twl.c

374 lines
11 KiB
C

/*
* Copyright (c) 2021 Max Thomas
* This file is part of DSiWifi and is distributed under the MIT license.
* See dsiwifi_license.txt for terms of use.
*/
#include "wifi_sdio.h"
#ifdef WIFI_SDIO_DEBUG
#include "wifi_debug.h"
#endif
#include "wifi_ndma.h"
#define WIFI_SDIO_NDMA
void wifi_sdio_controller_init(void* controller)
{
void* c = controller;
if(!c) return;
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_IRQ32, 0x0800, 0x0000);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_IRQ32, 0x1000, 0x0000);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_IRQ32, 0x0000, 0x0402);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_DATA16_CNT, 0x0022, 0x0002);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_DATA16_CNT, 0x0020, 0x0000);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_DATA32_BLK_LEN, 128);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_DATA32_BLK_CNT, 0x0001);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_RESET, 0x0003, 0x0000);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_RESET, 0x0000, 0x0003);
// Disable all interrupts.
wifi_sdio_write32(c, WIFI_SDIO_OFFS_IRQ_MASK, 0xFFFFFFFF);
wifi_sdio_mask16(c, 0x0FC, 0x0000, 0x00DB);
wifi_sdio_mask16(c, 0x0FE, 0x0000, 0x00DB);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_PORT_SEL, 0b11, 0);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_CLK_CNT, 0x0020);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_CARD_OPT, 0x40EE);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_IRQ32, 0x8000, 0x0000);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_IRQ32, 0x0000, 0x0100);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_IRQ32, 0x0100, 0x0000);
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_PORT_SEL, 0b11, 0);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_DATA16_BLK_LEN, 128);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_STOP, 0x0100);
}
void wifi_sdio_send_command(wifi_sdio_ctx* ctx, wifi_sdio_command cmd, u32 args)
{
if(!ctx) return;
void* c = ctx->controller;
if(!c) return;
// Safety fallback
if (!ctx->block_size)
ctx->block_size = 512;
void* buffer = ctx->buffer;
size_t size = ctx->size;
ctx->status = 0;
u16 stat0 = 0, stat1 = 0;
u16 stat0_completion_flags = 0;
u16 cnt32 = 0;
// Are we expecting a response? We need to wait for it.
if(cmd.response_type != wifi_sdio_resp_none)
stat0_completion_flags |= WIFI_SDIO_STAT0_CMDRESPEND;
// Are we doing a data transfer? We need to wait for it to end.
if(cmd.data_transfer)
stat0_completion_flags |= WIFI_SDIO_STAT0_DATAEND;
#ifdef WIFI_SDIO_DEBUG
if(ctx->debug)
wifi_printlnf("CMD#: 0x%04X (%u) (%X) -> %u:%u",
cmd.raw, cmd.cmd, stat0_completion_flags, ctx->port, ctx->address);
#endif
bool buffer32 = false;
if(buffer && ((u32)buffer & 3) == 0) buffer32 = true;
// Force alignment
if (buffer && !buffer32) {
ctx->status |= 4;
return;
}
// Wait until the SDIO controller is not busy.
while(wifi_sdio_read16(c, WIFI_SDIO_OFFS_IRQ_STAT1) & WIFI_SDIO_STAT1_CMD_BUSY);
// ACK all interrupts and halt
wifi_sdio_write16(c, WIFI_SDIO_OFFS_IRQ_STAT0, 0);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_IRQ_STAT1, 0);
//wifi_sdio_mask16(c, WIFI_SDIO_OFFS_STOP, 1, 0);
// Write command arguments.
wifi_sdio_write16(c, WIFI_SDIO_OFFS_CMD_PARAM0, args & 0xFFFF);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_CMD_PARAM1, args >> 16);
// Set block len and counts
wifi_sdio_write16(c, WIFI_SDIO_OFFS_DATA16_BLK_LEN, ctx->block_size);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_DATA16_BLK_CNT, size / ctx->block_size);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_DATA32_BLK_LEN, ctx->block_size);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_DATA32_BLK_CNT, size / ctx->block_size);
// Data32 mode
bool is_block = (cmd.data_length == wifi_sdio_multiple_block);
if (is_block) {
wifi_sdio_write16(c, WIFI_SDIO_OFFS_DATA16_CNT, 0x0002);
if(cmd.data_direction == wifi_sdio_data_read)
wifi_sdio_write16(c, WIFI_SDIO_OFFS_IRQ32, 0xC02);
else
wifi_sdio_write16(c, WIFI_SDIO_OFFS_IRQ32, 0x1402);
}
// Write command.
wifi_sdio_write16(c, WIFI_SDIO_OFFS_CMD, cmd.raw);
#ifdef WIFI_SDIO_NDMA
if(cmd.data_direction == wifi_sdio_data_read && is_block && buffer)
{
wifi_ndma_read(buffer, size);
return;
}
else if (cmd.data_direction == wifi_sdio_data_write && is_block && buffer)
{
wifi_ndma_write(buffer, size);
return;
}
#endif
while(true)
{
stat1 = wifi_sdio_read16(c, WIFI_SDIO_OFFS_IRQ_STAT1);
cnt32 = wifi_sdio_read16(c, WIFI_SDIO_OFFS_IRQ32);
// Ready to receive data.
if(cnt32 & 0x100)
{
// Are we actually meant to receive data?
if(cmd.data_direction == wifi_sdio_data_read && buffer)
{
// ACK ready state.
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_IRQ_STAT1, WIFI_SDIO_STAT1_RXRDY, 0);
if(size > ctx->block_size - 1)
{
//#ifdef WIFI_SDIO_NDMA
//wifi_ndma_read(buffer, ctx->block_size);
//buffer += ctx->block_size;
//#else
void* buffer_end = buffer + ctx->block_size;
while(buffer != buffer_end)
{
*(u32*)buffer = wifi_sdio_read32(c, WIFI_SDIO_OFFS_DATA32_FIFO);
//wifi_printlnf("asdf %x", *(u32*)buffer);
buffer += sizeof(u32);
}
//#endif
size -= ctx->block_size;
}
}
}
// Data transmission requested.
//if(!(cnt32 & 0x200))
if(stat1 & WIFI_SDIO_STAT1_TXRQ)
{
// Are we actually meant to write data?
if(cmd.data_direction == wifi_sdio_data_write && buffer)
{
// ACK request.
wifi_sdio_mask16(c, WIFI_SDIO_OFFS_IRQ_STAT1, WIFI_SDIO_STAT1_TXRQ, 0);
if(size > ctx->block_size-1)
{
//#ifdef WIFI_SDIO_NDMA
//wifi_ndma_write(buffer, ctx->block_size);
//buffer += ctx->block_size;
//#else
void* buffer_end = buffer + ctx->block_size;
while(buffer != buffer_end)
{
u32 data = *(u32*)buffer;
buffer += sizeof(u32);
wifi_sdio_write32(c, WIFI_SDIO_OFFS_DATA32_FIFO, data);
}
//#endif
size -= ctx->block_size;
}
}
}
// Has an error been asserted? Exit if so.
if(stat1 & WIFI_SDIO_MASK_ERR)
{
#ifdef WIFI_SDIO_DEBUG
if(ctx->debug)
wifi_printlnf("ERR#: %04X 0000", stat1 & WIFI_SDIO_MASK_ERR);
#endif
// Error flag.
ctx->status |= 4;
break;
}
bool end_cond = !(stat1 & WIFI_SDIO_STAT1_CMD_BUSY);
if (is_block) {
stat0 = wifi_sdio_read16(c, WIFI_SDIO_OFFS_IRQ_STAT0);
end_cond = (stat0 & WIFI_SDIO_STAT0_CMDRESPEND && !size);
}
// Not busy...
if(end_cond)
{
stat0 = wifi_sdio_read16(c, WIFI_SDIO_OFFS_IRQ_STAT0);
// Set response end flag.
if(stat0 & WIFI_SDIO_STAT0_CMDRESPEND)
ctx->status |= 1;
// Set data end flag.
if(stat0 & WIFI_SDIO_STAT0_DATAEND)
ctx->status |= 2;
// If done (all completion criteria), exit.
if((stat0 & stat0_completion_flags) == stat0_completion_flags)
break;
}
if(ctx->break_early && !size)
{
break;
}
}
ctx->stat0 = wifi_sdio_read16(c, WIFI_SDIO_OFFS_IRQ_STAT0);
ctx->stat1 = wifi_sdio_read16(c, WIFI_SDIO_OFFS_IRQ_STAT1);
ctx->err0 = wifi_sdio_read16(c, WIFI_SDIO_OFFS_ERR_DETAIL0);
ctx->err1 = wifi_sdio_read16(c, WIFI_SDIO_OFFS_ERR_DETAIL1);
// ACK all interrupts.
wifi_sdio_write16(c, WIFI_SDIO_OFFS_IRQ_STAT0, 0);
wifi_sdio_write16(c, WIFI_SDIO_OFFS_IRQ_STAT1, 0);
// If the command has a response, pull it in to sdmmc_ctx.
if(cmd.response_type != wifi_sdio_resp_none)
{
ctx->resp[0] = wifi_sdio_read16(c, WIFI_SDIO_OFFS_RESP0) | (u32)(wifi_sdio_read16(c, WIFI_SDIO_OFFS_RESP1) << 16);
ctx->resp[1] = wifi_sdio_read16(c, WIFI_SDIO_OFFS_RESP2) | (u32)(wifi_sdio_read16(c, WIFI_SDIO_OFFS_RESP3) << 16);
ctx->resp[2] = wifi_sdio_read16(c, WIFI_SDIO_OFFS_RESP4) | (u32)(wifi_sdio_read16(c, WIFI_SDIO_OFFS_RESP5) << 16);
ctx->resp[3] = wifi_sdio_read16(c, WIFI_SDIO_OFFS_RESP6) | (u32)(wifi_sdio_read16(c, WIFI_SDIO_OFFS_RESP7) << 16);
}
#ifdef WIFI_SDIO_DEBUG
if(ctx->debug)
{
wifi_printlnf("STAT: %04X %04X (%X) INFO: %04X %04X", ctx->stat1, ctx->stat0,
ctx->status, ctx->err1, ctx->err0);
if(cmd.response_type != wifi_sdio_resp_none)
{
if(cmd.response_type == wifi_sdio_resp_136bit) {
wifi_printlnf("RESP: %08lX %08lX %08lX %08lX",
ctx->resp[0], ctx->resp[1], ctx->resp[2], ctx->resp[3]);
} else {
wifi_printlnf("RESP: %08lX", ctx->resp[0]);
}
}
wifi_printlnf("");
}
#endif
}
void wifi_sdio_switch_device(wifi_sdio_ctx* ctx)
{
if(!ctx) return;
// Reconfigure the bus to talk to the new device.
wifi_sdio_mask16(ctx->controller, WIFI_SDIO_OFFS_PORT_SEL, 0b11, ctx->port);
wifi_sdio_setclk(ctx->controller, ctx->clk_cnt);
// WIFI_SDIO_CARD_OPT bit 15: bus width (0 = 4-bit, 1 = 1-bit).
if(ctx->bus_width == 4)
wifi_sdio_mask16(ctx->controller, WIFI_SDIO_OFFS_CARD_OPT, BIT(15), 0);
else
wifi_sdio_mask16(ctx->controller, WIFI_SDIO_OFFS_CARD_OPT, 0, BIT(15));
}
u16 wifi_sdio_read16(void* controller, u32 reg)
{
return *(vu16*)(controller + reg);
}
u32 wifi_sdio_read32(void* controller, u32 reg)
{
return *(vu32*)(controller + reg);
}
void wifi_sdio_write16(void* controller, u32 reg, u16 val)
{
*(vu16*)(controller + reg) = val;
}
void wifi_sdio_write32(void* controller, u32 reg, u32 val)
{
*(vu32*)(controller + reg) = val;
}
void wifi_sdio_mask16(void* controller, u32 reg, u16 clear, u16 set)
{
u16 val = wifi_sdio_read16(controller, reg);
val &= ~clear;
val |= set;
wifi_sdio_write16(controller, reg, val);
}
void wifi_sdio_mask32(void* controller, u32 reg, u32 clear, u32 set)
{
u32 val = wifi_sdio_read32(controller, reg);
val &= ~clear;
val |= set;
wifi_sdio_write32(controller, reg, val);
}
void wifi_sdio_setclk(void* controller, u32 data)
{
wifi_sdio_mask16(controller, WIFI_SDIO_OFFS_CLK_CNT, 0x100, 0);
wifi_sdio_mask16(controller, WIFI_SDIO_OFFS_CLK_CNT, 0x2FF, data & 0x2FF);
wifi_sdio_mask16(controller, WIFI_SDIO_OFFS_CLK_CNT, 0x0, 0x100);
}
void wifi_sdio_stop(void* controller)
{
wifi_sdio_write16(controller, WIFI_SDIO_OFFS_STOP, 0x100);
}
void wifi_sdio_enable_cardirq(void* controller, bool en)
{
if (en)
{
wifi_sdio_mask16(controller, WIFI_SDIO_OFFS_CARDIRQ_CTL, 0, 0x0001);
wifi_sdio_mask16(controller, WIFI_SDIO_OFFS_CARDIRQ_MASK, 0x0003, 0);
}
else
{
wifi_sdio_mask16(controller, WIFI_SDIO_OFFS_CARDIRQ_CTL, 1, 0);
wifi_sdio_mask16(controller, WIFI_SDIO_OFFS_CARDIRQ_MASK, 0, 3);
}
}
u16 wifi_sdio_get_cardirq_stat(void* controller)
{
return wifi_sdio_read16(controller, WIFI_SDIO_OFFS_CARDIRQ_STAT);
}