mini/sdmmc.c

476 lines
12 KiB
C
Raw Normal View History

2009-04-11 23:42:53 +02:00
/*
mini - a Free Software replacement for the Nintendo/BroadOn IOS.
SD/MMC interface
2009-04-13 23:38:37 +02:00
Copyright (C) 2008, 2009 Sven Peter <svenpeter@gmail.com>
2009-04-11 23:42:53 +02:00
2009-05-11 07:53:16 +02:00
# This code is licensed to you under the terms of the GNU GPL, version 2;
# see file COPYING or http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
2009-04-11 23:42:53 +02:00
*/
#include "bsdtypes.h"
2009-10-24 13:30:58 +02:00
#include "sdhc.h"
2009-04-11 23:42:53 +02:00
#include "gecko.h"
#include "string.h"
#include "utils.h"
#include "memory.h"
2009-04-11 23:42:53 +02:00
2009-05-08 18:25:29 +02:00
//#define SDMMC_DEBUG
2009-04-11 23:42:53 +02:00
#ifdef SDMMC_DEBUG
static int sdmmcdebug = 0;
2009-04-11 23:42:53 +02:00
#define DPRINTF(n,s) do { if ((n) <= sdmmcdebug) gecko_printf s; } while (0)
#else
#define DPRINTF(n,s) do {} while(0)
#endif
struct sdmmc_card {
struct sdmmc_chip_functions *functions;
sdmmc_chipset_handle_t handle;
2009-05-06 05:43:34 +02:00
char name[30];
2009-04-11 23:42:53 +02:00
int no;
int inserted;
int sdhc_blockmode;
int selected;
int new_card; // set to 1 everytime a new card is inserted
2009-04-12 02:21:32 +02:00
2009-05-06 05:43:34 +02:00
u32 timeout;
u32 num_sectors;
2009-04-12 02:21:32 +02:00
u32 cid;
u16 rca;
2009-04-11 23:42:53 +02:00
};
2009-05-06 05:43:34 +02:00
#ifdef LOADER
static struct sdmmc_card cards[SDHC_MAX_HOSTS];
#else
static struct sdmmc_card cards[SDHC_MAX_HOSTS] MEM2_BSS;
2009-05-06 05:43:34 +02:00
#endif
2009-04-11 23:42:53 +02:00
static int n_cards = 0;
void sdmmc_attach(sdmmc_chipset_handle_t handle, const char *name, int no)
2009-04-11 23:42:53 +02:00
{
struct sdmmc_card *c;
if (n_cards >= SDHC_MAX_HOSTS) {
gecko_printf("n_cards(%d) >= %d!\n", n_cards, SDHC_MAX_HOSTS);
gecko_printf("starlet is soo going to crash very soon...\n");
}
c = &cards[n_cards++];
memset(c, 0, sizeof(*c));
c->handle = handle;
c->no = no;
2009-04-17 12:19:44 +02:00
strlcpy(c->name, name, sizeof(c->name));
2009-04-11 23:42:53 +02:00
DPRINTF(0, ("sdmmc: attached new SD/MMC card %d for host [%s:%d]\n",
n_cards-1, c->name, c->no));
2009-10-26 07:20:16 +01:00
sdhc_host_reset(c->handle);
2009-04-11 23:42:53 +02:00
2009-10-26 07:20:16 +01:00
if (sdhc_card_detect(c->handle)) {
2009-04-11 23:42:53 +02:00
DPRINTF(1, ("card is inserted. starting init sequence.\n"));
sdmmc_needs_discover();
2009-04-11 23:42:53 +02:00
}
}
2009-05-06 05:43:34 +02:00
void sdmmc_abort(void) {
struct sdmmc_command cmd;
gecko_printf("abortion kthx\n");
memset(&cmd, 0, sizeof(cmd));
cmd.c_opcode = MMC_STOP_TRANSMISSION;
cmd.c_arg = 0;
cmd.c_flags = SCF_RSP_R1B;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(&cards[0].handle, &cmd);
2009-05-06 05:43:34 +02:00
}
void sdmmc_needs_discover(void)
2009-04-11 23:42:53 +02:00
{
struct sdmmc_card *c = &cards[0];
2009-04-11 23:42:53 +02:00
struct sdmmc_command cmd;
u32 ocr;
DPRINTF(0, ("sdmmc: card needs discovery.\n"));
2009-10-26 07:20:16 +01:00
sdhc_host_reset(c->handle);
c->new_card = 1;
2009-04-11 23:42:53 +02:00
2009-10-26 07:20:16 +01:00
if (!sdhc_card_detect(c->handle)) {
DPRINTF(1, ("sdmmc: card (no longer?) inserted.\n"));
2009-04-11 23:42:53 +02:00
c->inserted = 0;
return;
}
DPRINTF(1, ("sdmmc: enabling power\n"));
2009-10-26 07:20:16 +01:00
if (sdhc_bus_power(c->handle, 1) != 0) {
gecko_printf("sdmmc: powerup failed for card\n");
2009-04-11 23:42:53 +02:00
goto out;
}
DPRINTF(1, ("sdmmc: enabling clock\n"));
2009-10-26 07:20:16 +01:00
if (sdhc_bus_clock(c->handle, SDMMC_DEFAULT_CLOCK) != 0) {
gecko_printf("sdmmc: could not enable clock for card\n");
2009-04-11 23:42:53 +02:00
goto out_power;
}
DPRINTF(1, ("sdmmc: sending GO_IDLE_STATE\n"));
2009-04-11 23:42:53 +02:00
memset(&cmd, 0, sizeof(cmd));
cmd.c_opcode = MMC_GO_IDLE_STATE;
cmd.c_flags = SCF_RSP_R0;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
2009-04-11 23:42:53 +02:00
if (cmd.c_error) {
gecko_printf("sdmmc: GO_IDLE_STATE failed with %d\n", cmd.c_error);
2009-04-11 23:42:53 +02:00
goto out_clock;
}
DPRINTF(2, ("sdmmc: GO_IDLE_STATE response: %x\n", MMC_R1(cmd.c_resp)));
2009-04-11 23:42:53 +02:00
DPRINTF(1, ("sdmmc: sending SEND_IF_COND\n"));
2009-04-11 23:42:53 +02:00
memset(&cmd, 0, sizeof(cmd));
cmd.c_opcode = SD_SEND_IF_COND;
cmd.c_arg = 0x1aa;
cmd.c_flags = SCF_RSP_R7;
cmd.c_timeout = 100;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
2009-04-11 23:42:53 +02:00
2009-10-26 07:20:16 +01:00
ocr = sdhc_host_ocr(c->handle);
2009-04-11 23:42:53 +02:00
if (cmd.c_error || (cmd.c_resp[0] & 0xff) != 0xaa)
ocr &= ~SD_OCR_SDHC_CAP;
else
ocr |= SD_OCR_SDHC_CAP;
DPRINTF(2, ("sdmmc: SEND_IF_COND ocr: %x\n", ocr));
int tries;
2009-04-12 02:21:32 +02:00
for (tries = 100; tries > 0; tries--) {
udelay(100000);
2009-04-11 23:42:53 +02:00
memset(&cmd, 0, sizeof(cmd));
cmd.c_opcode = MMC_APP_CMD;
cmd.c_arg = 0;
cmd.c_flags = SCF_RSP_R1;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
2009-04-11 23:42:53 +02:00
if (cmd.c_error)
continue;
memset(&cmd, 0, sizeof(cmd));
cmd.c_opcode = SD_APP_OP_COND;
cmd.c_arg = ocr;
cmd.c_flags = SCF_RSP_R3;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
2009-04-11 23:42:53 +02:00
if (cmd.c_error)
continue;
2009-04-12 02:21:32 +02:00
DPRINTF(3, ("sdmmc: response for SEND_IF_COND: %08x\n",
MMC_R1(cmd.c_resp)));
if (ISSET(MMC_R1(cmd.c_resp), MMC_OCR_MEM_READY))
2009-04-11 23:42:53 +02:00
break;
}
if (!ISSET(cmd.c_resp[0], MMC_OCR_MEM_READY)) {
gecko_printf("sdmmc: card failed to powerup.\n");
2009-04-11 23:42:53 +02:00
goto out_power;
}
if (ISSET(MMC_R1(cmd.c_resp), SD_OCR_SDHC_CAP))
c->sdhc_blockmode = 1;
else
c->sdhc_blockmode = 0;
DPRINTF(2, ("sdmmc: SDHC: %d\n", c->sdhc_blockmode));
2009-05-06 05:43:34 +02:00
u8 *resp;
2009-04-12 02:21:32 +02:00
DPRINTF(2, ("sdmmc: MMC_ALL_SEND_CID\n"));
memset(&cmd, 0, sizeof(cmd));
cmd.c_opcode = MMC_ALL_SEND_CID;
cmd.c_arg = 0;
cmd.c_flags = SCF_RSP_R2;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
2009-04-12 02:21:32 +02:00
if (cmd.c_error) {
gecko_printf("sdmmc: MMC_ALL_SEND_CID failed with %d\n", cmd.c_error);
2009-04-12 02:21:32 +02:00
goto out_clock;
}
c->cid = MMC_R1(cmd.c_resp);
2009-05-06 05:43:34 +02:00
resp = (u8 *)cmd.c_resp;
gecko_printf("CID: mid=%02x name='%c%c%c%c%c%c%c' prv=%d.%d psn=%02x%02x%02x%02x mdt=%d/%d\n", resp[14],
resp[13],resp[12],resp[11],resp[10],resp[9],resp[8],resp[7], resp[6], resp[5] >> 4, resp[5] & 0xf,
resp[4], resp[3], resp[2], resp[0] & 0xf, 2000 + (resp[0] >> 4));
2009-04-12 02:21:32 +02:00
DPRINTF(2, ("sdmmc: SD_SEND_RELATIVE_ADDRESS\n"));
memset(&cmd, 0, sizeof(cmd));
cmd.c_opcode = SD_SEND_RELATIVE_ADDR;
cmd.c_arg = 0;
cmd.c_flags = SCF_RSP_R6;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
2009-04-12 02:21:32 +02:00
if (cmd.c_error) {
gecko_printf("sdmmc: SD_SEND_RCA failed with %d\n", cmd.c_error);
2009-04-12 02:21:32 +02:00
goto out_clock;
}
c->rca = MMC_R1(cmd.c_resp)>>16;
DPRINTF(2, ("sdmmc: rca: %08x\n", c->rca));
c->selected = 0;
2009-04-12 02:21:32 +02:00
c->inserted = 1;
2009-04-12 19:54:22 +02:00
memset(&cmd, 0, sizeof(cmd));
cmd.c_opcode = MMC_SEND_CSD;
cmd.c_arg = ((u32)c->rca)<<16;
cmd.c_flags = SCF_RSP_R2;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
2009-04-12 19:54:22 +02:00
if (cmd.c_error) {
gecko_printf("sdmmc: MMC_SEND_CSD failed with %d\n", cmd.c_error);
2009-04-12 19:54:22 +02:00
goto out_power;
}
resp = (u8 *)cmd.c_resp;
2009-05-06 05:43:34 +02:00
int i;
gecko_printf("csd: ");
for(i=15; i>=0; i--) gecko_printf("%02x ", (u32) resp[i]);
gecko_printf("\n");
2009-04-12 19:54:22 +02:00
if (resp[13] == 0xe) { // sdhc
unsigned int c_size = resp[7] << 16 | resp[6] << 8 | resp[5];
2009-05-06 05:43:34 +02:00
gecko_printf("sdmmc: sdhc mode, c_size=%u, card size = %uk\n", c_size, (c_size + 1)* 512);
c->timeout = 250 * 1000000; // spec says read timeout is 100ms and write/erase timeout is 250ms
2009-04-12 19:54:22 +02:00
c->num_sectors = (c_size + 1) * 1024; // number of 512-byte sectors
}
else {
unsigned int taac, nsac, read_bl_len, c_size, c_size_mult;
taac = resp[13];
nsac = resp[12];
read_bl_len = resp[9] & 0xF;
c_size = (resp[8] & 3) << 10;
c_size |= (resp[7] << 2);
c_size |= (resp[6] >> 6);
c_size_mult = (resp[5] & 3) << 1;
c_size_mult |= resp[4] >> 7;
2009-05-06 05:43:34 +02:00
gecko_printf("taac=%u nsac=%u read_bl_len=%u c_size=%u c_size_mult=%u card size=%u bytes\n",
taac, nsac, read_bl_len, c_size, c_size_mult, (c_size + 1) * (4 << c_size_mult) * (1 << read_bl_len));
static const unsigned int time_unit[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000};
static const unsigned int time_value[] = {1, 10, 12, 13, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80}; // must div by 10
c->timeout = time_unit[taac & 7] * time_value[(taac >> 3) & 0xf] / 10;
gecko_printf("calculated timeout = %uns\n", c->timeout);
2009-04-12 19:54:22 +02:00
c->num_sectors = (c_size + 1) * (4 << c_size_mult) * (1 << read_bl_len) / 512;
}
sdmmc_select();
DPRINTF(2, ("sdmmc: MMC_SET_BLOCKLEN\n"));
memset(&cmd, 0, sizeof(cmd));
cmd.c_opcode = MMC_SET_BLOCKLEN;
cmd.c_arg = SDMMC_DEFAULT_BLOCKLEN;
cmd.c_flags = SCF_RSP_R1;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
if (cmd.c_error) {
gecko_printf("sdmmc: MMC_SET_BLOCKLEN failed with %d\n", cmd.c_error);
c->inserted = c->selected = 0;
goto out_clock;
}
2009-04-11 23:42:53 +02:00
return;
out_clock:
sdhc_bus_clock(c->handle, SDMMC_SDCLK_OFF);
2009-04-11 23:42:53 +02:00
out_power:
2009-10-26 07:20:16 +01:00
sdhc_bus_power(c->handle, 0);
2009-04-11 23:42:53 +02:00
out:
return;
}
int sdmmc_select(void)
{
struct sdmmc_card *c = &cards[0];
struct sdmmc_command cmd;
DPRINTF(2, ("sdmmc: MMC_SELECT_CARD\n"));
memset(&cmd, 0, sizeof(cmd));
cmd.c_opcode = MMC_SELECT_CARD;
2009-10-24 13:05:14 +02:00
cmd.c_arg = ((u32)c->rca)<<16;
cmd.c_flags = SCF_RSP_R1B;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
2009-10-24 13:05:14 +02:00
gecko_printf("%s: resp=%x\n", __FUNCTION__, MMC_R1(cmd.c_resp));
sdhc_dump_regs(c->handle);
// gecko_printf("present state = %x\n", HREAD4(hp, SDHC_PRESENT_STATE));
if (cmd.c_error) {
gecko_printf("sdmmc: MMC_SELECT card failed with %d.\n", cmd.c_error);
return -1;
}
c->selected = 1;
return 0;
}
int sdmmc_check_card(void)
{
struct sdmmc_card *c = &cards[0];
2009-05-08 18:25:29 +02:00
if (c->inserted == 0)
return SDMMC_NO_CARD;
2009-05-08 18:25:29 +02:00
if (c->new_card == 1)
return SDMMC_NEW_CARD;
2009-05-08 18:25:29 +02:00
return SDMMC_INSERTED;
}
int sdmmc_ack_card(void)
{
struct sdmmc_card *c = &cards[0];
2009-05-08 18:25:29 +02:00
if (c->new_card == 1) {
c->new_card = 0;
return 0;
}
return -1;
}
int sdmmc_read(u32 blk_start, u32 blk_count, void *data)
{
struct sdmmc_card *c = &cards[0];
struct sdmmc_command cmd;
2009-10-24 13:05:14 +02:00
gecko_printf("%s(%u, %u, %p)\n", __FUNCTION__, blk_start, blk_count, data);
if (c->inserted == 0) {
gecko_printf("sdmmc: READ: no card inserted.\n");
return -1;
}
if (c->selected == 0) {
if (sdmmc_select() < 0) {
gecko_printf("sdmmc: READ: cannot select card.\n");
return -1;
}
}
if (c->new_card == 1) {
gecko_printf("sdmmc: new card inserted but not acknowledged yet.\n");
return -1;
}
DPRINTF(2, ("sdmmc: MMC_READ_BLOCK_MULTIPLE\n"));
memset(&cmd, 0, sizeof(cmd));
2009-04-12 16:41:21 +02:00
cmd.c_opcode = MMC_READ_BLOCK_MULTIPLE;
if (c->sdhc_blockmode)
cmd.c_arg = blk_start;
else
cmd.c_arg = blk_start * SDMMC_DEFAULT_BLOCKLEN;
cmd.c_data = data;
cmd.c_datalen = blk_count * SDMMC_DEFAULT_BLOCKLEN;
cmd.c_blklen = SDMMC_DEFAULT_BLOCKLEN;
cmd.c_flags = SCF_RSP_R1 | SCF_CMD_READ;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
if (cmd.c_error) {
gecko_printf("sdmmc: MMC_READ_BLOCK_MULTIPLE failed with %d\n", cmd.c_error);
return -1;
}
DPRINTF(2, ("sdmmc: MMC_READ_BLOCK_MULTIPLE done\n"));
return 0;
}
2009-05-06 05:43:34 +02:00
#ifndef LOADER
int sdmmc_write(u32 blk_start, u32 blk_count, void *data)
{
struct sdmmc_card *c = &cards[0];
struct sdmmc_command cmd;
if (c->inserted == 0) {
gecko_printf("sdmmc: READ: no card inserted.\n");
return -1;
}
if (c->selected == 0) {
if (sdmmc_select() < 0) {
gecko_printf("sdmmc: READ: cannot select card.\n");
return -1;
}
}
if (c->new_card == 1) {
gecko_printf("sdmmc: new card inserted but not acknowledged yet.\n");
return -1;
}
DPRINTF(2, ("sdmmc: MMC_WRITE_BLOCK_MULTIPLE\n"));
memset(&cmd, 0, sizeof(cmd));
2009-04-12 16:41:21 +02:00
cmd.c_opcode = MMC_WRITE_BLOCK_MULTIPLE;
if (c->sdhc_blockmode)
cmd.c_arg = blk_start;
else
cmd.c_arg = blk_start * SDMMC_DEFAULT_BLOCKLEN;
cmd.c_data = data;
cmd.c_datalen = blk_count * SDMMC_DEFAULT_BLOCKLEN;
cmd.c_blklen = SDMMC_DEFAULT_BLOCKLEN;
cmd.c_flags = SCF_RSP_R1;
2009-10-26 07:20:16 +01:00
sdhc_exec_command(c->handle, &cmd);
if (cmd.c_error) {
gecko_printf("sdmmc: MMC_READ_BLOCK_MULTIPLE failed with %d\n", cmd.c_error);
return -1;
}
DPRINTF(2, ("sdmmc: MMC_WRITE_BLOCK_MULTIPLE done\n"));
return 0;
}
int sdmmc_get_sectors(void)
2009-04-12 18:29:20 +02:00
{
struct sdmmc_card *c = &cards[0];
2009-04-12 18:29:20 +02:00
if (c->inserted == 0) {
gecko_printf("sdmmc: READ: no card inserted.\n");
return -1;
}
if (c->new_card == 1) {
gecko_printf("sdmmc: new card inserted but not acknowledged yet.\n");
return -1;
}
// sdhc_error(sdhci->reg_base, "num sectors = %u", sdhci->num_sectors);
2009-04-12 19:54:22 +02:00
return c->num_sectors;
2009-04-12 18:29:20 +02:00
}
2009-05-06 05:43:34 +02:00
#endif
2009-04-12 18:29:20 +02:00
2009-05-06 05:43:34 +02:00
#ifdef CAN_HAZ_IPC
void sdmmc_ipc(volatile ipc_request *req)
{
int ret;
switch (req->req) {
case IPC_SDMMC_ACK:
ret = sdmmc_ack_card();
2009-05-08 18:25:29 +02:00
ipc_post(req->code, req->tag, 1, ret);
break;
case IPC_SDMMC_READ:
ret = sdmmc_read(req->args[0], req->args[1], (void *)req->args[2]);
dc_flushrange((void *)req->args[2],
req->args[1]*SDMMC_DEFAULT_BLOCKLEN);
2009-04-12 16:41:21 +02:00
ipc_post(req->code, req->tag, 1, ret);
break;
case IPC_SDMMC_WRITE:
dc_invalidaterange((void *)req->args[2],
req->args[1]*SDMMC_DEFAULT_BLOCKLEN);
ret = sdmmc_write(req->args[0], req->args[1], (void *)req->args[2]);
2009-04-12 16:41:21 +02:00
ipc_post(req->code, req->tag, 1, ret);
break;
2009-04-12 18:29:20 +02:00
case IPC_SDMMC_STATE:
ipc_post(req->code, req->tag, 1,
sdmmc_check_card());
2009-04-12 18:29:20 +02:00
break;
case IPC_SDMMC_SIZE:
ipc_post(req->code, req->tag, 1,
sdmmc_get_sectors());
2009-04-12 18:29:20 +02:00
break;
}
}
2009-05-06 05:43:34 +02:00
#endif