summaryrefslogtreecommitdiffstats
path: root/firmware/target/sh
diff options
context:
space:
mode:
authorMarcin Bukat <marcin.bukat@gmail.com>2011-03-01 07:53:46 +0000
committerMarcin Bukat <marcin.bukat@gmail.com>2011-03-01 07:53:46 +0000
commitfec3193f8a32ce4158767ee063105692cce4c1b8 (patch)
tree15eed2f934e9acccbc6f7d594c73c639260baab8 /firmware/target/sh
parentfb3616368f5e9994716e30a040865f3cf398aa21 (diff)
downloadrockbox-fec3193f8a32ce4158767ee063105692cce4c1b8.tar.gz
rockbox-fec3193f8a32ce4158767ee063105692cce4c1b8.tar.bz2
rockbox-fec3193f8a32ce4158767ee063105692cce4c1b8.zip
Move ata_mmc.c into target tree as it is SH (ondio) specific. Associated header file is left intact as it seems to be used in many places for historical reasons
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29478 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/target/sh')
-rw-r--r--firmware/target/sh/archos/ondio/ata_mmc.c1027
1 files changed, 1027 insertions, 0 deletions
diff --git a/firmware/target/sh/archos/ondio/ata_mmc.c b/firmware/target/sh/archos/ondio/ata_mmc.c
new file mode 100644
index 0000000000..fc2efcd3e4
--- /dev/null
+++ b/firmware/target/sh/archos/ondio/ata_mmc.c
@@ -0,0 +1,1027 @@
+/***************************************************************************
+ * __________ __ ___.
+ * 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 "sdmmc.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 SHAREDBSS_ATTR;
+
+#ifdef HAVE_HOTSWAP
+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 SHAREDBSS_ATTR;
+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_MULTIDRIVE
+static int current_card = 0;
+#endif
+static bool last_mmc_status = false;
+static int countdown = -1; /* for mmc switch debouncing. -1 because the
+ countdown should not happen if the card
+ is inserted at boot */
+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, 83, 4);
+ if (blk_exp < 9) /* block size < 512 bytes not supported */
+ return -6;
+
+ card->numblocks = (card_extract_bits(card->csd, 73, 12) + 1)
+ << (card_extract_bits(card->csd, 49, 3) + 2 + blk_exp - 9);
+ card->blocksize = BLOCK_SIZE;
+
+ /* max transmission speed, clock divider */
+ ts_exp = card_extract_bits(card->csd, 98, 3);
+ ts_exp = (ts_exp > 3) ? 3 : ts_exp;
+ card->speed = mantissa[card_extract_bits(card->csd, 102, 4)]
+ * exponent[ts_exp + 4];
+ card->bitrate_register = (FREQ/4-1) / card->speed;
+
+ /* NSAC, TAAC, read timeout */
+ card->nsac = 100 * card_extract_bits(card->csd, 111, 8);
+ card->taac = mantissa[card_extract_bits(card->csd, 118, 4)];
+ taac_exp = card_extract_bits(card->csd, 114, 3);
+ card->read_timeout = ((FREQ/4) / (card->bitrate_register + 1)
+ * card->taac / exponent[9 - taac_exp]
+ + (10 * card->nsac));
+ card->read_timeout /= 8; /* clocks -> bytes */
+ card->taac = card->taac * exponent[taac_exp] / 10;
+
+ /* r2w_factor, write timeout */
+ card->r2w_factor = BIT_N(card_extract_bits(card->csd, 28, 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_MD2(int drive,)
+ unsigned long start,
+ int incount,
+ void* inbuf)
+{
+ int rc = 0;
+ int lastblock = 0;
+ unsigned long end_block;
+ tCardInfo *card;
+#ifndef HAVE_MULTIDRIVE
+ 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_MD2(int drive,)
+ unsigned long start,
+ int count,
+ const void* buf)
+{
+ int rc = 0;
+ int write_cmd;
+ unsigned char start_token;
+ tCardInfo *card;
+#ifndef HAVE_MULTIDRIVE
+ 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 mutex_test(&mmc_mutex);
+}
+
+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;
+ }
+ }
+}
+
+bool mmc_detect(void)
+{
+ return (adc_read(ADC_MMC_SWITCH) < 0x200);
+}
+
+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;
+
+ 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;
+
+ 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_MULTIDRIVE
+ /* 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_MD2(int drive,) struct storage_info *info)
+{
+#ifndef HAVE_MULTIDRIVE
+ 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_MD_NONVOID(int drive))
+{
+#ifndef HAVE_MULTIDRIVE
+ const int drive=0;
+#endif
+ return (drive==1);
+}
+
+bool mmc_present(IF_MD_NONVOID(int drive))
+{
+#ifndef HAVE_MULTIDRIVE
+ const int drive=0;
+#endif
+ if(drive==0)
+ {
+ return true;
+ }
+ else
+ {
+ return mmc_detect();
+ }
+}
+#endif
+
+
+void mmc_sleep(void)
+{
+}
+
+void mmc_spin(void)
+{
+}
+
+void mmc_spindown(int seconds)
+{
+ (void)seconds;
+}
+
+#ifdef CONFIG_STORAGE_MULTI
+int mmc_num_drives(int first_drive)
+{
+ /* We don't care which logical drive number(s) we have been assigned */
+ (void)first_drive;
+
+#ifdef HAVE_MULTIDRIVE
+ return 2;
+#else
+ return 1;
+#endif
+}
+#endif