1d6df54df2
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@21205 a1c6a512-1295-4272-9138-f99709370657
1017 lines
28 KiB
C
1017 lines
28 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2004 by Jens Arnold
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include <stdbool.h>
|
|
#include "mmc.h"
|
|
#include "ata_mmc.h"
|
|
#include "ata_idle_notify.h"
|
|
#include "kernel.h"
|
|
#include "thread.h"
|
|
#include "led.h"
|
|
#include "sh7034.h"
|
|
#include "system.h"
|
|
#include "debug.h"
|
|
#include "panic.h"
|
|
#include "usb.h"
|
|
#include "power.h"
|
|
#include "string.h"
|
|
#include "hwcompat.h"
|
|
#include "adc.h"
|
|
#include "bitswap.h"
|
|
#include "disk.h" /* for mount/unmount */
|
|
#include "storage.h"
|
|
|
|
#define BLOCK_SIZE 512 /* fixed */
|
|
|
|
/* Command definitions */
|
|
#define CMD_GO_IDLE_STATE 0x40 /* R1 */
|
|
#define CMD_SEND_OP_COND 0x41 /* R1 */
|
|
#define CMD_SEND_CSD 0x49 /* R1 */
|
|
#define CMD_SEND_CID 0x4a /* R1 */
|
|
#define CMD_STOP_TRANSMISSION 0x4c /* R1 */
|
|
#define CMD_SEND_STATUS 0x4d /* R2 */
|
|
#define CMD_SET_BLOCKLEN 0x50 /* R1 */
|
|
#define CMD_READ_SINGLE_BLOCK 0x51 /* R1 */
|
|
#define CMD_READ_MULTIPLE_BLOCK 0x52 /* R1 */
|
|
#define CMD_WRITE_BLOCK 0x58 /* R1b */
|
|
#define CMD_WRITE_MULTIPLE_BLOCK 0x59 /* R1b */
|
|
#define CMD_READ_OCR 0x7a /* R3 */
|
|
|
|
/* Response formats:
|
|
R1 = single byte, msb=0, various error flags
|
|
R1b = R1 + busy token(s)
|
|
R2 = 2 bytes (1st byte identical to R1), additional flags
|
|
R3 = 5 bytes (R1 + OCR register)
|
|
*/
|
|
|
|
#define R1_PARAMETER_ERR 0x40
|
|
#define R1_ADDRESS_ERR 0x20
|
|
#define R1_ERASE_SEQ_ERR 0x10
|
|
#define R1_COM_CRC_ERR 0x08
|
|
#define R1_ILLEGAL_CMD 0x04
|
|
#define R1_ERASE_RESET 0x02
|
|
#define R1_IN_IDLE_STATE 0x01
|
|
|
|
#define R2_OUT_OF_RANGE 0x80
|
|
#define R2_ERASE_PARAM 0x40
|
|
#define R2_WP_VIOLATION 0x20
|
|
#define R2_CARD_ECC_FAIL 0x10
|
|
#define R2_CC_ERROR 0x08
|
|
#define R2_ERROR 0x04
|
|
#define R2_ERASE_SKIP 0x02
|
|
#define R2_CARD_LOCKED 0x01
|
|
|
|
/* Data start tokens */
|
|
|
|
#define DT_START_BLOCK 0xfe
|
|
#define DT_START_WRITE_MULTIPLE 0xfc
|
|
#define DT_STOP_TRAN 0xfd
|
|
|
|
/* for compatibility */
|
|
static long last_disk_activity = -1;
|
|
|
|
/* private variables */
|
|
|
|
static struct mutex mmc_mutex;
|
|
|
|
#ifdef HAVE_HOTSWAP
|
|
static bool mmc_monitor_enabled = true;
|
|
static long mmc_stack[((DEFAULT_STACK_SIZE*2) + 0x800)/sizeof(long)];
|
|
#else
|
|
static long mmc_stack[(DEFAULT_STACK_SIZE*2)/sizeof(long)];
|
|
#endif
|
|
static const char mmc_thread_name[] = "mmc";
|
|
static struct event_queue mmc_queue;
|
|
static bool initialized = false;
|
|
static bool new_mmc_circuit;
|
|
|
|
static enum {
|
|
MMC_UNKNOWN,
|
|
MMC_UNTOUCHED,
|
|
MMC_TOUCHED
|
|
} mmc_status = MMC_UNKNOWN;
|
|
|
|
static enum {
|
|
SER_POLL_WRITE,
|
|
SER_POLL_READ,
|
|
SER_DISABLED
|
|
} serial_mode;
|
|
|
|
static const unsigned char dummy[] = {
|
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
|
};
|
|
|
|
/* 2 buffers used alternatively for writing, including start token,
|
|
* dummy CRC and an extra byte to keep word alignment. */
|
|
static unsigned char write_buffer[2][BLOCK_SIZE+4];
|
|
static int current_buffer = 0;
|
|
static const unsigned char *send_block_addr = NULL;
|
|
|
|
static tCardInfo card_info[2];
|
|
#ifndef HAVE_MULTIVOLUME
|
|
static int current_card = 0;
|
|
#endif
|
|
static bool last_mmc_status = false;
|
|
static int countdown = HZ/3; /* for mmc switch debouncing */
|
|
static bool usb_activity; /* monitoring the USB bridge */
|
|
static long last_usb_activity;
|
|
|
|
/* private function declarations */
|
|
|
|
static int select_card(int card_no);
|
|
static void deselect_card(void);
|
|
static void setup_sci1(int bitrate_register);
|
|
static void set_sci1_poll_read(void);
|
|
static void write_transfer(const unsigned char *buf, int len)
|
|
__attribute__ ((section(".icode")));
|
|
static void read_transfer(unsigned char *buf, int len)
|
|
__attribute__ ((section(".icode")));
|
|
static unsigned char poll_byte(long timeout);
|
|
static unsigned char poll_busy(long timeout);
|
|
static unsigned char send_cmd(int cmd, unsigned long parameter, void *data);
|
|
static int receive_cxd(unsigned char *buf);
|
|
static int initialize_card(int card_no);
|
|
static int receive_block(unsigned char *inbuf, long timeout);
|
|
static void send_block_prepare(void);
|
|
static int send_block_send(unsigned char start_token, long timeout,
|
|
bool prepare_next);
|
|
static void mmc_tick(void);
|
|
|
|
/* implementation */
|
|
|
|
void mmc_enable_int_flash_clock(bool on)
|
|
{
|
|
/* Internal flash clock is enabled by setting PA12 high with the new
|
|
* clock circuit, and by setting it low with the old clock circuit */
|
|
if (on ^ new_mmc_circuit)
|
|
and_b(~0x10, &PADRH); /* clear clock gate PA12 */
|
|
else
|
|
or_b(0x10, &PADRH); /* set clock gate PA12 */
|
|
}
|
|
|
|
static int select_card(int card_no)
|
|
{
|
|
mutex_lock(&mmc_mutex);
|
|
led(true);
|
|
last_disk_activity = current_tick;
|
|
|
|
mmc_enable_int_flash_clock(card_no == 0);
|
|
|
|
if (!card_info[card_no].initialized)
|
|
{
|
|
setup_sci1(7); /* Initial rate: 375 kbps (need <= 400 per mmc specs) */
|
|
write_transfer(dummy, 10); /* allow the card to synchronize */
|
|
while (!(SSR1 & SCI_TEND));
|
|
}
|
|
|
|
if (card_no == 0) /* internal */
|
|
and_b(~0x04, &PADRH); /* assert CS */
|
|
else /* external */
|
|
and_b(~0x02, &PADRH); /* assert CS */
|
|
|
|
if (card_info[card_no].initialized)
|
|
{
|
|
setup_sci1(card_info[card_no].bitrate_register);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return initialize_card(card_no);
|
|
}
|
|
}
|
|
|
|
static void deselect_card(void)
|
|
{
|
|
while (!(SSR1 & SCI_TEND)); /* wait for end of transfer */
|
|
or_b(0x06, &PADRH); /* deassert CS (both cards) */
|
|
|
|
led(false);
|
|
mutex_unlock(&mmc_mutex);
|
|
last_disk_activity = current_tick;
|
|
}
|
|
|
|
static void setup_sci1(int bitrate_register)
|
|
{
|
|
while (!(SSR1 & SCI_TEND)); /* wait for end of transfer */
|
|
|
|
SCR1 = 0; /* disable serial port */
|
|
SMR1 = SYNC_MODE; /* no prescale */
|
|
BRR1 = bitrate_register;
|
|
SSR1 = 0;
|
|
|
|
SCR1 = SCI_TE; /* enable transmitter */
|
|
serial_mode = SER_POLL_WRITE;
|
|
}
|
|
|
|
static void set_sci1_poll_read(void)
|
|
{
|
|
while (!(SSR1 & SCI_TEND)); /* wait for end of transfer */
|
|
SCR1 = 0; /* disable transmitter (& receiver) */
|
|
SCR1 = (SCI_TE|SCI_RE); /* re-enable transmitter & receiver */
|
|
while (!(SSR1 & SCI_TEND)); /* wait for SCI init completion (!) */
|
|
serial_mode = SER_POLL_READ;
|
|
TDR1 = 0xFF; /* send do-nothing while reading */
|
|
}
|
|
|
|
static void write_transfer(const unsigned char *buf, int len)
|
|
{
|
|
const unsigned char *buf_end = buf + len;
|
|
register unsigned char data;
|
|
|
|
if (serial_mode != SER_POLL_WRITE)
|
|
{
|
|
while (!(SSR1 & SCI_TEND)); /* wait for end of transfer */
|
|
SCR1 = 0; /* disable transmitter & receiver */
|
|
SSR1 = 0; /* clear all flags */
|
|
SCR1 = SCI_TE; /* enable transmitter only */
|
|
serial_mode = SER_POLL_WRITE;
|
|
}
|
|
|
|
while (buf < buf_end)
|
|
{
|
|
data = fliptable[(signed char)(*buf++)]; /* bitswap */
|
|
while (!(SSR1 & SCI_TDRE)); /* wait for end of transfer */
|
|
TDR1 = data; /* write byte */
|
|
SSR1 = 0; /* start transmitting */
|
|
}
|
|
}
|
|
|
|
/* don't call this with len == 0 */
|
|
static void read_transfer(unsigned char *buf, int len)
|
|
{
|
|
unsigned char *buf_end = buf + len - 1;
|
|
register signed char data;
|
|
|
|
if (serial_mode != SER_POLL_READ)
|
|
set_sci1_poll_read();
|
|
|
|
SSR1 = 0; /* start receiving first byte */
|
|
while (buf < buf_end)
|
|
{
|
|
while (!(SSR1 & SCI_RDRF)); /* wait for data */
|
|
data = RDR1; /* read byte */
|
|
SSR1 = 0; /* start receiving */
|
|
*buf++ = fliptable[data]; /* bitswap */
|
|
}
|
|
while (!(SSR1 & SCI_RDRF)); /* wait for last byte */
|
|
*buf = fliptable[(signed char)(RDR1)]; /* read & bitswap */
|
|
}
|
|
|
|
/* returns 0xFF on timeout, timeout is in bytes */
|
|
static unsigned char poll_byte(long timeout)
|
|
{
|
|
long i;
|
|
unsigned char data = 0; /* stop the compiler complaining */
|
|
|
|
if (serial_mode != SER_POLL_READ)
|
|
set_sci1_poll_read();
|
|
|
|
i = 0;
|
|
do {
|
|
SSR1 = 0; /* start receiving */
|
|
while (!(SSR1 & SCI_RDRF)); /* wait for data */
|
|
data = RDR1; /* read byte */
|
|
} while ((data == 0xFF) && (++i < timeout));
|
|
|
|
return fliptable[(signed char)data];
|
|
}
|
|
|
|
/* returns 0 on timeout, timeout is in bytes */
|
|
static unsigned char poll_busy(long timeout)
|
|
{
|
|
long i;
|
|
unsigned char data, dummy;
|
|
|
|
if (serial_mode != SER_POLL_READ)
|
|
set_sci1_poll_read();
|
|
|
|
/* get data response */
|
|
SSR1 = 0; /* start receiving */
|
|
while (!(SSR1 & SCI_RDRF)); /* wait for data */
|
|
data = fliptable[(signed char)(RDR1)]; /* read byte */
|
|
|
|
/* wait until the card is ready again */
|
|
i = 0;
|
|
do {
|
|
SSR1 = 0; /* start receiving */
|
|
while (!(SSR1 & SCI_RDRF)); /* wait for data */
|
|
dummy = RDR1; /* read byte */
|
|
} while ((dummy != 0xFF) && (++i < timeout));
|
|
|
|
return (dummy == 0xFF) ? data : 0;
|
|
}
|
|
|
|
/* Send MMC command and get response. Returns R1 byte directly.
|
|
* Returns further R2 or R3 bytes in *data (can be NULL for other commands) */
|
|
static unsigned char send_cmd(int cmd, unsigned long parameter, void *data)
|
|
{
|
|
static struct {
|
|
unsigned char cmd;
|
|
unsigned long parameter;
|
|
const unsigned char crc7; /* fixed, valid for CMD0 only */
|
|
const unsigned char trailer;
|
|
} __attribute__((packed)) command = {0x40, 0, 0x95, 0xFF};
|
|
|
|
unsigned char ret;
|
|
|
|
command.cmd = cmd;
|
|
command.parameter = htobe32(parameter);
|
|
|
|
write_transfer((unsigned char *)&command, sizeof(command));
|
|
|
|
ret = poll_byte(20);
|
|
|
|
switch (cmd)
|
|
{
|
|
case CMD_SEND_CSD: /* R1 response, leave open */
|
|
case CMD_SEND_CID:
|
|
case CMD_READ_SINGLE_BLOCK:
|
|
case CMD_READ_MULTIPLE_BLOCK:
|
|
return ret;
|
|
|
|
case CMD_SEND_STATUS: /* R2 response, close with dummy */
|
|
read_transfer(data, 1);
|
|
break;
|
|
|
|
case CMD_READ_OCR: /* R3 response, close with dummy */
|
|
read_transfer(data, 4);
|
|
break;
|
|
|
|
default: /* R1 response, close with dummy */
|
|
break; /* also catches block writes */
|
|
}
|
|
write_transfer(dummy, 1);
|
|
return ret;
|
|
}
|
|
|
|
/* Receive CID/ CSD data (16 bytes) */
|
|
static int receive_cxd(unsigned char *buf)
|
|
{
|
|
if (poll_byte(20) != DT_START_BLOCK)
|
|
{
|
|
write_transfer(dummy, 1);
|
|
return -1; /* not start of data */
|
|
}
|
|
|
|
read_transfer(buf, 16);
|
|
write_transfer(dummy, 3); /* 2 bytes dontcare crc + 1 byte trailer */
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int initialize_card(int card_no)
|
|
{
|
|
int rc, i;
|
|
int blk_exp, ts_exp, taac_exp;
|
|
tCardInfo *card = &card_info[card_no];
|
|
|
|
static const char mantissa[] = { /* *10 */
|
|
0, 10, 12, 13, 15, 20, 25, 30,
|
|
35, 40, 45, 50, 55, 60, 70, 80
|
|
};
|
|
static const int exponent[] = { /* use varies */
|
|
1, 10, 100, 1000, 10000, 100000, 1000000,
|
|
10000000, 100000000, 1000000000
|
|
};
|
|
|
|
if (card_no == 1)
|
|
mmc_status = MMC_TOUCHED;
|
|
|
|
/* switch to SPI mode */
|
|
if (send_cmd(CMD_GO_IDLE_STATE, 0, NULL) != 0x01)
|
|
return -1; /* error or no response */
|
|
|
|
/* initialize card */
|
|
for (i = HZ;;) /* try for 1 second*/
|
|
{
|
|
sleep(1);
|
|
if (send_cmd(CMD_SEND_OP_COND, 0, NULL) == 0)
|
|
break;
|
|
if (--i <= 0)
|
|
return -2; /* timeout */
|
|
}
|
|
|
|
/* get OCR register */
|
|
if (send_cmd(CMD_READ_OCR, 0, &card->ocr))
|
|
return -3;
|
|
card->ocr = betoh32(card->ocr); /* no-op on big endian */
|
|
|
|
/* check voltage */
|
|
if (!(card->ocr & 0x00100000)) /* 3.2 .. 3.3 V */
|
|
return -4;
|
|
|
|
/* get CSD register */
|
|
if (send_cmd(CMD_SEND_CSD, 0, NULL))
|
|
return -5;
|
|
rc = receive_cxd((unsigned char*)card->csd);
|
|
if (rc)
|
|
return rc * 10 - 5;
|
|
|
|
blk_exp = card_extract_bits(card->csd, 44, 4);
|
|
if (blk_exp < 9) /* block size < 512 bytes not supported */
|
|
return -6;
|
|
|
|
card->numblocks = (card_extract_bits(card->csd, 54, 12) + 1)
|
|
<< (card_extract_bits(card->csd, 78, 3) + 2 + blk_exp - 9);
|
|
card->blocksize = BLOCK_SIZE;
|
|
|
|
/* max transmission speed, clock divider */
|
|
ts_exp = card_extract_bits(card->csd, 29, 3);
|
|
ts_exp = (ts_exp > 3) ? 3 : ts_exp;
|
|
card->speed = mantissa[card_extract_bits(card->csd, 25, 4)]
|
|
* exponent[ts_exp + 4];
|
|
card->bitrate_register = (FREQ/4-1) / card->speed;
|
|
|
|
/* NSAC, TSAC, read timeout */
|
|
card->nsac = 100 * card_extract_bits(card->csd, 16, 8);
|
|
card->tsac = mantissa[card_extract_bits(card->csd, 9, 4)];
|
|
taac_exp = card_extract_bits(card->csd, 13, 3);
|
|
card->read_timeout = ((FREQ/4) / (card->bitrate_register + 1)
|
|
* card->tsac / exponent[9 - taac_exp]
|
|
+ (10 * card->nsac));
|
|
card->read_timeout /= 8; /* clocks -> bytes */
|
|
card->tsac = card->tsac * exponent[taac_exp] / 10;
|
|
|
|
/* r2w_factor, write timeout */
|
|
card->r2w_factor = BIT_N(card_extract_bits(card->csd, 99, 3));
|
|
card->write_timeout = card->read_timeout * card->r2w_factor;
|
|
|
|
if (card->r2w_factor > 32) /* Such cards often need extra read delay */
|
|
card->read_timeout *= 4;
|
|
|
|
/* switch to full speed */
|
|
setup_sci1(card->bitrate_register);
|
|
|
|
/* always use 512 byte blocks */
|
|
if (send_cmd(CMD_SET_BLOCKLEN, BLOCK_SIZE, NULL))
|
|
return -7;
|
|
|
|
/* get CID register */
|
|
if (send_cmd(CMD_SEND_CID, 0, NULL))
|
|
return -8;
|
|
rc = receive_cxd((unsigned char*)card->cid);
|
|
if (rc)
|
|
return rc * 10 - 8;
|
|
|
|
card->initialized = true;
|
|
return 0;
|
|
}
|
|
|
|
tCardInfo *mmc_card_info(int card_no)
|
|
{
|
|
tCardInfo *card = &card_info[card_no];
|
|
|
|
if (!card->initialized && ((card_no == 0) || mmc_detect()))
|
|
{
|
|
select_card(card_no);
|
|
deselect_card();
|
|
}
|
|
return card;
|
|
}
|
|
|
|
/* Receive one block with DMA and bitswap it (chasing bitswap). */
|
|
static int receive_block(unsigned char *inbuf, long timeout)
|
|
{
|
|
unsigned long buf_end;
|
|
|
|
if (poll_byte(timeout) != DT_START_BLOCK)
|
|
{
|
|
write_transfer(dummy, 1);
|
|
return -1; /* not start of data */
|
|
}
|
|
|
|
while (!(SSR1 & SCI_TEND)); /* wait for end of transfer */
|
|
|
|
SCR1 = 0; /* disable serial */
|
|
SSR1 = 0; /* clear all flags */
|
|
|
|
/* setup DMA channel 0 */
|
|
CHCR0 = 0; /* disable */
|
|
SAR0 = RDR1_ADDR;
|
|
DAR0 = (unsigned long) inbuf;
|
|
DTCR0 = BLOCK_SIZE;
|
|
CHCR0 = 0x4601; /* fixed source address, RXI1, enable */
|
|
DMAOR = 0x0001;
|
|
SCR1 = (SCI_RE|SCI_RIE); /* kick off DMA */
|
|
|
|
/* DMA receives 2 bytes more than DTCR2, but the last 2 bytes are not
|
|
* stored. The first extra byte is available from RDR1 after the DMA ends,
|
|
* the second one is lost because of the SCI overrun. However, this
|
|
* behaviour conveniently discards the crc. */
|
|
|
|
yield(); /* be nice */
|
|
|
|
/* Bitswap received data, chasing the DMA pointer */
|
|
buf_end = (unsigned long)inbuf + BLOCK_SIZE;
|
|
do
|
|
{
|
|
/* Call bitswap whenever (a multiple of) 8 bytes are
|
|
* available (value optimised by experimentation). */
|
|
int swap_now = (DAR0 - (unsigned long)inbuf) & ~0x00000007;
|
|
if (swap_now)
|
|
{
|
|
bitswap(inbuf, swap_now);
|
|
inbuf += swap_now;
|
|
}
|
|
}
|
|
while ((unsigned long)inbuf < buf_end);
|
|
|
|
while (!(CHCR0 & 0x0002)); /* wait for end of DMA */
|
|
while (!(SSR1 & SCI_ORER)); /* wait for the trailing bytes */
|
|
SCR1 = 0;
|
|
serial_mode = SER_DISABLED;
|
|
|
|
write_transfer(dummy, 1); /* send trailer */
|
|
last_disk_activity = current_tick;
|
|
return 0;
|
|
}
|
|
|
|
/* Prepare a block for sending by copying it to the next write buffer
|
|
* and bitswapping it. */
|
|
static void send_block_prepare(void)
|
|
{
|
|
unsigned char *dest;
|
|
|
|
current_buffer ^= 1; /* toggle buffer */
|
|
dest = write_buffer[current_buffer] + 2;
|
|
|
|
memcpy(dest, send_block_addr, BLOCK_SIZE);
|
|
bitswap(dest, BLOCK_SIZE);
|
|
|
|
send_block_addr += BLOCK_SIZE;
|
|
}
|
|
|
|
/* Send one block with DMA from the current write buffer, possibly preparing
|
|
* the next block within the next write buffer in the background. */
|
|
static int send_block_send(unsigned char start_token, long timeout,
|
|
bool prepare_next)
|
|
{
|
|
int rc = 0;
|
|
unsigned char *curbuf = write_buffer[current_buffer];
|
|
|
|
curbuf[1] = fliptable[(signed char)start_token];
|
|
*(unsigned short *)(curbuf + BLOCK_SIZE + 2) = 0xFFFF;
|
|
|
|
while (!(SSR1 & SCI_TEND)); /* wait for end of transfer */
|
|
|
|
SCR1 = 0; /* disable serial */
|
|
SSR1 = 0; /* clear all flags */
|
|
|
|
/* setup DMA channel 0 */
|
|
CHCR0 = 0; /* disable */
|
|
SAR0 = (unsigned long)(curbuf + 1);
|
|
DAR0 = TDR1_ADDR;
|
|
DTCR0 = BLOCK_SIZE + 3; /* start token + block + dummy crc */
|
|
CHCR0 = 0x1701; /* fixed dest. address, TXI1, enable */
|
|
DMAOR = 0x0001;
|
|
SCR1 = (SCI_TE|SCI_TIE); /* kick off DMA */
|
|
|
|
if (prepare_next)
|
|
send_block_prepare();
|
|
yield(); /* be nice */
|
|
|
|
while (!(CHCR0 & 0x0002)); /* wait for end of DMA */
|
|
while (!(SSR1 & SCI_TEND)); /* wait for end of transfer */
|
|
SCR1 = 0;
|
|
serial_mode = SER_DISABLED;
|
|
|
|
if ((poll_busy(timeout) & 0x1F) != 0x05) /* something went wrong */
|
|
rc = -1;
|
|
|
|
write_transfer(dummy, 1);
|
|
last_disk_activity = current_tick;
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mmc_read_sectors(IF_MV2(int drive,)
|
|
unsigned long start,
|
|
int incount,
|
|
void* inbuf)
|
|
{
|
|
int rc = 0;
|
|
int lastblock = 0;
|
|
unsigned long end_block;
|
|
tCardInfo *card;
|
|
#ifndef HAVE_MULTIVOLUME
|
|
int drive = current_card;
|
|
#endif
|
|
|
|
card = &card_info[drive];
|
|
rc = select_card(drive);
|
|
if (rc)
|
|
{
|
|
rc = rc * 10 - 1;
|
|
goto error;
|
|
}
|
|
|
|
end_block = start + incount;
|
|
if (end_block > card->numblocks)
|
|
{
|
|
rc = -2;
|
|
goto error;
|
|
}
|
|
|
|
/* Some cards don't like reading the very last block with
|
|
* CMD_READ_MULTIPLE_BLOCK, so make sure this block is always
|
|
* read with CMD_READ_SINGLE_BLOCK. */
|
|
if (end_block == card->numblocks)
|
|
lastblock = 1;
|
|
|
|
if (incount > 1)
|
|
{
|
|
/* MMC4.2: make multiplication conditional */
|
|
if (send_cmd(CMD_READ_MULTIPLE_BLOCK, start * BLOCK_SIZE, NULL))
|
|
{
|
|
rc = -3;
|
|
goto error;
|
|
}
|
|
while (--incount >= lastblock)
|
|
{
|
|
rc = receive_block(inbuf, card->read_timeout);
|
|
if (rc)
|
|
{
|
|
/* If an error occurs during multiple block reading, the
|
|
* host still needs to send CMD_STOP_TRANSMISSION */
|
|
send_cmd(CMD_STOP_TRANSMISSION, 0, NULL);
|
|
rc = rc * 10 - 4;
|
|
goto error;
|
|
}
|
|
inbuf += BLOCK_SIZE;
|
|
start++;
|
|
/* ^^ necessary for the abovementioned last block special case */
|
|
}
|
|
if (send_cmd(CMD_STOP_TRANSMISSION, 0, NULL))
|
|
{
|
|
rc = -5;
|
|
goto error;
|
|
}
|
|
}
|
|
if (incount > 0)
|
|
{
|
|
/* MMC4.2: make multiplication conditional */
|
|
if (send_cmd(CMD_READ_SINGLE_BLOCK, start * BLOCK_SIZE, NULL))
|
|
{
|
|
rc = -6;
|
|
goto error;
|
|
}
|
|
rc = receive_block(inbuf, card->read_timeout);
|
|
if (rc)
|
|
{
|
|
rc = rc * 10 - 7;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
error:
|
|
|
|
deselect_card();
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mmc_write_sectors(IF_MV2(int drive,)
|
|
unsigned long start,
|
|
int count,
|
|
const void* buf)
|
|
{
|
|
int rc = 0;
|
|
int write_cmd;
|
|
unsigned char start_token;
|
|
tCardInfo *card;
|
|
#ifndef HAVE_MULTIVOLUME
|
|
int drive = current_card;
|
|
#endif
|
|
|
|
card = &card_info[drive];
|
|
rc = select_card(drive);
|
|
if (rc)
|
|
{
|
|
rc = rc * 10 - 1;
|
|
goto error;
|
|
}
|
|
|
|
if (start + count > card->numblocks)
|
|
panicf("Writing past end of card");
|
|
|
|
send_block_addr = buf;
|
|
send_block_prepare();
|
|
|
|
if (count > 1)
|
|
{
|
|
write_cmd = CMD_WRITE_MULTIPLE_BLOCK;
|
|
start_token = DT_START_WRITE_MULTIPLE;
|
|
}
|
|
else
|
|
{
|
|
write_cmd = CMD_WRITE_BLOCK;
|
|
start_token = DT_START_BLOCK;
|
|
}
|
|
/* MMC4.2: make multiplication conditional */
|
|
if (send_cmd(write_cmd, start * BLOCK_SIZE, NULL))
|
|
{
|
|
rc = -2;
|
|
goto error;
|
|
}
|
|
while (--count >= 0)
|
|
{
|
|
rc = send_block_send(start_token, card->write_timeout, count > 0);
|
|
if (rc)
|
|
{
|
|
rc = rc * 10 - 3;
|
|
break;
|
|
/* If an error occurs during multiple block writing,
|
|
* the STOP_TRAN token still needs to be sent. */
|
|
}
|
|
}
|
|
if (write_cmd == CMD_WRITE_MULTIPLE_BLOCK)
|
|
{
|
|
static const unsigned char stop_tran = DT_STOP_TRAN;
|
|
write_transfer(&stop_tran, 1);
|
|
poll_busy(card->write_timeout);
|
|
}
|
|
|
|
error:
|
|
|
|
deselect_card();
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool mmc_disk_is_active(void)
|
|
{
|
|
/* this is correct unless early return from write gets implemented */
|
|
return mmc_mutex.locked;
|
|
}
|
|
|
|
static void mmc_thread(void)
|
|
{
|
|
struct queue_event ev;
|
|
bool idle_notified = false;
|
|
|
|
while (1) {
|
|
queue_wait_w_tmo(&mmc_queue, &ev, HZ);
|
|
switch ( ev.id )
|
|
{
|
|
case SYS_USB_CONNECTED:
|
|
usb_acknowledge(SYS_USB_CONNECTED_ACK);
|
|
/* Wait until the USB cable is extracted again */
|
|
usb_wait_for_disconnect(&mmc_queue);
|
|
break;
|
|
|
|
#ifdef HAVE_HOTSWAP
|
|
case SYS_HOTSWAP_INSERTED:
|
|
disk_mount(1); /* mount MMC */
|
|
queue_broadcast(SYS_FS_CHANGED, 0);
|
|
break;
|
|
|
|
case SYS_HOTSWAP_EXTRACTED:
|
|
disk_unmount(1); /* release "by force" */
|
|
queue_broadcast(SYS_FS_CHANGED, 0);
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
|
|
{
|
|
idle_notified = false;
|
|
}
|
|
else
|
|
{
|
|
if (!idle_notified)
|
|
{
|
|
call_storage_idle_notifys(false);
|
|
idle_notified = true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_HOTSWAP
|
|
void mmc_enable_monitoring(bool on)
|
|
{
|
|
mmc_monitor_enabled = on;
|
|
}
|
|
#endif
|
|
|
|
bool mmc_detect(void)
|
|
{
|
|
return adc_read(ADC_MMC_SWITCH) < 0x200 ? true : false;
|
|
}
|
|
|
|
bool mmc_touched(void)
|
|
{
|
|
if (mmc_status == MMC_UNKNOWN) /* try to detect */
|
|
{
|
|
mutex_lock(&mmc_mutex);
|
|
setup_sci1(7); /* safe value */
|
|
and_b(~0x02, &PADRH); /* assert CS */
|
|
if (send_cmd(CMD_SEND_OP_COND, 0, NULL) == 0xFF)
|
|
mmc_status = MMC_UNTOUCHED;
|
|
else
|
|
mmc_status = MMC_TOUCHED;
|
|
|
|
deselect_card();
|
|
}
|
|
return mmc_status == MMC_TOUCHED;
|
|
}
|
|
|
|
bool mmc_usb_active(int delayticks)
|
|
{
|
|
/* reading "inactive" is delayed by user-supplied monoflop value */
|
|
return (usb_activity ||
|
|
TIME_BEFORE(current_tick, last_usb_activity + delayticks));
|
|
}
|
|
|
|
static void mmc_tick(void)
|
|
{
|
|
bool current_status;
|
|
#ifndef HAVE_HOTSWAP
|
|
const bool mmc_monitor_enabled = true;
|
|
#endif
|
|
|
|
if (new_mmc_circuit)
|
|
/* USB bridge activity is 0 on idle, ~527 on active */
|
|
current_status = adc_read(ADC_USB_ACTIVE) > 0x100;
|
|
else
|
|
current_status = adc_read(ADC_USB_ACTIVE) < 0x190;
|
|
|
|
if (!current_status && usb_activity)
|
|
last_usb_activity = current_tick;
|
|
usb_activity = current_status;
|
|
|
|
if (mmc_monitor_enabled)
|
|
{
|
|
current_status = mmc_detect();
|
|
/* Only report when the status has changed */
|
|
if (current_status != last_mmc_status)
|
|
{
|
|
last_mmc_status = current_status;
|
|
countdown = HZ/3;
|
|
}
|
|
else
|
|
{
|
|
/* Count down until it gets negative */
|
|
if (countdown >= 0)
|
|
countdown--;
|
|
|
|
if (countdown == 0)
|
|
{
|
|
if (current_status)
|
|
{
|
|
queue_broadcast(SYS_HOTSWAP_INSERTED, 0);
|
|
}
|
|
else
|
|
{
|
|
queue_broadcast(SYS_HOTSWAP_EXTRACTED, 0);
|
|
mmc_status = MMC_UNTOUCHED;
|
|
card_info[1].initialized = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void mmc_enable(bool on)
|
|
{
|
|
PBCR1 &= ~0x0CF0; /* PB13, PB11 and PB10 become GPIO,
|
|
* if not modified below */
|
|
if (on)
|
|
PBCR1 |= 0x08A0; /* as SCK1, TxD1, RxD1 */
|
|
|
|
and_b(~0x80, &PADRL); /* assert flash reset */
|
|
sleep(HZ/100);
|
|
or_b(0x80, &PADRL); /* de-assert flash reset */
|
|
sleep(HZ/100);
|
|
card_info[0].initialized = false;
|
|
card_info[1].initialized = false;
|
|
}
|
|
|
|
int mmc_init(void)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!initialized)
|
|
{
|
|
mutex_init(&mmc_mutex);
|
|
queue_init(&mmc_queue, true);
|
|
}
|
|
mutex_lock(&mmc_mutex);
|
|
led(false);
|
|
|
|
last_mmc_status = mmc_detect();
|
|
#ifndef HAVE_MULTIVOLUME
|
|
/* Use MMC if inserted, internal flash otherwise */
|
|
current_card = last_mmc_status ? 1 : 0;
|
|
#endif
|
|
|
|
if (!initialized)
|
|
{
|
|
if (!last_mmc_status)
|
|
mmc_status = MMC_UNTOUCHED;
|
|
|
|
/* Port setup */
|
|
PACR1 &= ~0x0F3C; /* GPIO function for PA13 (flash busy), PA12
|
|
* (clk gate), PA10 (flash CS), PA9 (MMC CS) */
|
|
PACR2 &= ~0x4000; /* GPIO for PA7 (flash reset) */
|
|
PADR |= 0x0680; /* set all the selects + reset high (=inactive) */
|
|
PAIOR |= 0x1680; /* make outputs for them and the PA12 clock gate */
|
|
|
|
PBCR1 &= ~0x0CF0; /* GPIO function for PB13, PB11 and PB10 */
|
|
PBDR |= 0x2C00; /* SCK1, TxD1 and RxD1 high in GPIO */
|
|
PBIOR |= 0x2000; /* SCK1 output */
|
|
PBIOR &= ~0x0C00; /* TxD1, RxD1 input */
|
|
|
|
IPRE &= 0x0FFF; /* disable SCI1 interrupts for the CPU */
|
|
|
|
new_mmc_circuit = ((HW_MASK & MMC_CLOCK_POLARITY) != 0);
|
|
|
|
create_thread(mmc_thread, mmc_stack,
|
|
sizeof(mmc_stack), 0, mmc_thread_name
|
|
IF_PRIO(, PRIORITY_SYSTEM)
|
|
IF_COP(, CPU));
|
|
tick_add_task(mmc_tick);
|
|
initialized = true;
|
|
}
|
|
mmc_enable(true);
|
|
|
|
mutex_unlock(&mmc_mutex);
|
|
return rc;
|
|
}
|
|
|
|
long mmc_last_disk_activity(void)
|
|
{
|
|
return last_disk_activity;
|
|
}
|
|
|
|
#ifdef STORAGE_GET_INFO
|
|
void mmc_get_info(IF_MV2(int drive,) struct storage_info *info)
|
|
{
|
|
#ifndef HAVE_MULTIVOLUME
|
|
const int drive=0;
|
|
#endif
|
|
info->sector_size=card_info[drive].blocksize;
|
|
info->num_sectors=card_info[drive].numblocks;
|
|
info->vendor="Rockbox";
|
|
if(drive==0)
|
|
{
|
|
info->product="Internal Storage";
|
|
}
|
|
else
|
|
{
|
|
info->product="MMC Card Slot";
|
|
}
|
|
info->revision="0.00";
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_HOTSWAP
|
|
bool mmc_removable(IF_MV_NONVOID(int drive))
|
|
{
|
|
#ifndef HAVE_MULTIVOLUME
|
|
const int drive=0;
|
|
#endif
|
|
return (drive==1);
|
|
}
|
|
|
|
bool mmc_present(IF_MV_NONVOID(int drive))
|
|
{
|
|
#ifndef HAVE_MULTIVOLUME
|
|
const int drive=0;
|
|
#endif
|
|
return (card_info[drive].initialized && card_info[drive].numblocks > 0);
|
|
}
|
|
#endif
|
|
|
|
|
|
void mmc_sleep(void)
|
|
{
|
|
}
|
|
|
|
void mmc_spin(void)
|
|
{
|
|
}
|
|
|
|
void mmc_spindown(int seconds)
|
|
{
|
|
(void)seconds;
|
|
}
|