rockbox/firmware/drivers/ata_mmc.c
Daniel Stenberg 2acc0ac542 Updated our source code header to explicitly mention that we are GPL v2 or
later. We still need to hunt down snippets used that are not. 1324 modified
files...
http://www.rockbox.org/mail/archive/rockbox-dev-archive-2008-06/0060.shtml


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@17847 a1c6a512-1295-4272-9138-f99709370657
2008-06-28 18:10:04 +00:00

1168 lines
32 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 "ata.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 */
#define SECTOR_SIZE 512
#define MAX_BLOCK_SIZE 2048
/* 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 */
int ata_spinup_time = 0;
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
};
struct block_cache_entry {
bool inuse;
#ifdef HAVE_MULTIVOLUME
int drive;
#endif
unsigned long blocknum;
unsigned char data[MAX_BLOCK_SIZE+4];
/* include start token, dummy crc, and an extra byte at the start
* to keep the data word aligned. */
};
/* 2 buffers used alternatively for writing, and also for reading
* and sub-block writing if block size > sector size */
#define NUMCACHES 2
static struct block_cache_entry block_cache[NUMCACHES];
static int current_cache = 0;
/* globals for background copy and swap */
static const unsigned char *bcs_src = NULL;
static unsigned char *bcs_dest = NULL;
static unsigned long bcs_len = 0;
static tCardInfo card_info[2];
#ifndef HAVE_MULTIVOLUME
static int current_card = 0;
#endif
static bool last_mmc_status = false;
static int countdown; /* 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 int send_cmd(int cmd, unsigned long parameter, unsigned char *response);
static int receive_cxd(unsigned char *buf);
static int initialize_card(int card_no);
static void bg_copy_swap(void);
static int receive_block(unsigned char *inbuf, int size, long timeout);
static int send_block(int size, unsigned char start_token, long timeout);
static int cache_block(IF_MV2(int drive,) unsigned long blocknum,
int size, long timeout);
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;
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 */
static int send_cmd(int cmd, unsigned long parameter, unsigned char *response)
{
unsigned char command[] = {0x40, 0x00, 0x00, 0x00, 0x00, 0x95, 0xFF};
command[0] = cmd;
if (parameter != 0)
{
command[1] = (parameter >> 24) & 0xFF;
command[2] = (parameter >> 16) & 0xFF;
command[3] = (parameter >> 8) & 0xFF;
command[4] = parameter & 0xFF;
}
write_transfer(command, 7);
response[0] = poll_byte(20);
if (response[0] != 0x00)
{
write_transfer(dummy, 1);
return -1;
}
switch (cmd)
{
case CMD_SEND_CSD: /* R1 response, leave open */
case CMD_SEND_CID:
case CMD_READ_SINGLE_BLOCK:
case CMD_READ_MULTIPLE_BLOCK:
break;
case CMD_SEND_STATUS: /* R2 response, close with dummy */
read_transfer(response + 1, 1);
write_transfer(dummy, 1);
break;
case CMD_READ_OCR: /* R3 response, close with dummy */
read_transfer(response + 1, 4);
write_transfer(dummy, 1);
break;
default: /* R1 response, close with dummy */
write_transfer(dummy, 1);
break; /* also catches block writes */
}
return 0;
}
/* 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, temp;
unsigned char response[5];
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 */
send_cmd(CMD_GO_IDLE_STATE, 0, response);
if (response[0] != 0x01)
return -1; /* error response */
/* initialize card */
for (i = 0; i < 100; i++) /* timeout 1 sec */
{
sleep(1);
if (send_cmd(CMD_SEND_OP_COND, 0, response) == 0)
break;
}
if (response[0] != 0x00)
return -2; /* not ready */
/* get OCR register */
rc = send_cmd(CMD_READ_OCR, 0, response);
if (rc)
return rc * 10 - 3;
card->ocr = (response[1] << 24) | (response[2] << 16)
| (response[3] << 8) | response[4];
/* check voltage */
if (!(card->ocr & 0x00100000)) /* 3.2 .. 3.3 V */
return -4;
/* get CSD register */
rc = send_cmd(CMD_SEND_CSD, 0, response);
if (rc)
return rc * 10 - 5;
rc = receive_cxd((unsigned char*)card->csd);
if (rc)
return rc * 10 - 6;
/* check block sizes */
card->block_exp = card_extract_bits(card->csd, 44, 4);
card->blocksize = 1 << card->block_exp;
if ((card_extract_bits(card->csd, 102, 4) != card->block_exp)
|| card->blocksize > MAX_BLOCK_SIZE)
{
return -7;
}
if (card->blocksize != SECTOR_SIZE)
{
rc = send_cmd(CMD_SET_BLOCKLEN, card->blocksize, response);
if (rc)
return rc * 10 - 8;
}
/* max transmission speed, clock divider */
temp = card_extract_bits(card->csd, 29, 3);
temp = (temp > 3) ? 3 : temp;
card->speed = mantissa[card_extract_bits(card->csd, 25, 4)]
* exponent[temp + 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)];
temp = card_extract_bits(card->csd, 13, 3);
card->read_timeout = ((FREQ/4) / (card->bitrate_register + 1)
* card->tsac / exponent[9 - temp]
+ (10 * card->nsac));
card->read_timeout /= 8; /* clocks -> bytes */
card->tsac = card->tsac * exponent[temp] / 10;
/* r2w_factor, write timeout */
card->r2w_factor = 1 << card_extract_bits(card->csd, 99, 3);
if (card->r2w_factor > 32) /* dirty MMC spec violation */
{
card->read_timeout *= 4; /* add safety factor */
card->write_timeout = card->read_timeout * 8;
}
else
card->write_timeout = card->read_timeout * card->r2w_factor;
/* card size */
card->numblocks = (card_extract_bits(card->csd, 54, 12) + 1)
* (1 << (card_extract_bits(card->csd, 78, 3) + 2));
card->size = card->numblocks * card->blocksize;
/* switch to full speed */
setup_sci1(card->bitrate_register);
/* get CID register */
rc = send_cmd(CMD_SEND_CID, 0, response);
if (rc)
return rc * 10 - 9;
rc = receive_cxd((unsigned char*)card->cid);
if (rc)
return rc * 10 - 9;
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;
}
/* copy and swap in the background. If destination is NULL, use the next
* block cache entry */
static void bg_copy_swap(void)
{
if (!bcs_len)
return;
if (!bcs_dest)
{
current_cache = (current_cache + 1) % NUMCACHES; /* next cache */
block_cache[current_cache].inuse = false;
bcs_dest = block_cache[current_cache].data + 2;
}
if (bcs_src)
{
memcpy(bcs_dest, bcs_src, bcs_len);
bcs_src += bcs_len;
}
bitswap(bcs_dest, bcs_len);
bcs_dest += bcs_len;
bcs_len = 0;
}
/* Receive one block with dma, possibly swapping the previously received
* block in the background */
static int receive_block(unsigned char *inbuf, int size, long timeout)
{
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 = 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. */
bg_copy_swap();
yield(); /* be nice */
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;
}
/* Send one block with dma from the current block cache, possibly preparing
* the next block within the next block cache in the background. */
static int send_block(int size, unsigned char start_token, long timeout)
{
int rc = 0;
unsigned char *curbuf = block_cache[current_cache].data;
curbuf[1] = fliptable[(signed char)start_token];
*(unsigned short *)(curbuf + 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 = size + 3; /* start token + block + dummy crc */
CHCR0 = 0x1701; /* fixed dest. address, TXI1, enable */
DMAOR = 0x0001;
SCR1 = (SCI_TE|SCI_TIE); /* kick off DMA */
bg_copy_swap();
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;
}
static int cache_block(IF_MV2(int drive,) unsigned long blocknum,
int size, long timeout)
{
int rc, i;
unsigned char response;
/* check whether the block is already cached */
for (i = 0; i < NUMCACHES; i++)
{
if (block_cache[i].inuse && (block_cache[i].blocknum == blocknum)
#ifdef HAVE_MULTIVOLUME
&& (block_cache[i].drive == drive)
#endif
)
{
current_cache = i;
bg_copy_swap();
return 0;
}
}
/* not found: read the block */
current_cache = (current_cache + 1) % NUMCACHES;
rc = send_cmd(CMD_READ_SINGLE_BLOCK, blocknum * size, &response);
if (rc)
return rc * 10 - 1;
block_cache[current_cache].inuse = false;
rc = receive_block(block_cache[current_cache].data + 2, size, timeout);
if (rc)
return rc * 10 - 2;
#ifdef HAVE_MULTIVOLUME
block_cache[current_cache].drive = drive;
#endif
block_cache[current_cache].blocknum = blocknum;
block_cache[current_cache].inuse = true;
return 0;
}
int ata_read_sectors(IF_MV2(int drive,)
unsigned long start,
int incount,
void* inbuf)
{
int rc = 0;
unsigned int blocksize, offset;
unsigned long c_addr, c_end_addr;
unsigned long c_block, c_end_block;
unsigned char response;
tCardInfo *card;
#ifndef HAVE_MULTIVOLUME
int drive = current_card;
#endif
c_addr = start * SECTOR_SIZE;
c_end_addr = c_addr + incount * SECTOR_SIZE;
card = &card_info[drive];
rc = select_card(drive);
if (rc)
{
rc = rc * 10 - 1;
goto error;
}
if (c_end_addr > card->size)
{
rc = -2;
goto error;
}
blocksize = card->blocksize;
offset = c_addr & (blocksize - 1);
c_block = c_addr >> card->block_exp;
c_end_block = c_end_addr >> card->block_exp;
bcs_dest = inbuf;
if (offset) /* first partial block */
{
unsigned long len = MIN(c_end_addr - c_addr, blocksize - offset);
rc = cache_block(IF_MV2(drive,) c_block, blocksize,
card->read_timeout);
if (rc)
{
rc = rc * 10 - 3;
goto error;
}
bcs_src = block_cache[current_cache].data + 2 + offset;
bcs_len = len;
inbuf += len;
c_addr += len;
c_block++;
}
/* 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. Let the 'last partial block'
* read catch this. */
if (c_end_block == card->numblocks)
c_end_block--;
if (c_block < c_end_block)
{
int read_cmd = (c_end_block - c_block > 1) ?
CMD_READ_MULTIPLE_BLOCK : CMD_READ_SINGLE_BLOCK;
rc = send_cmd(read_cmd, c_addr, &response);
if (rc)
{
rc = rc * 10 - 4;
goto error;
}
while (c_block < c_end_block)
{
rc = receive_block(inbuf, blocksize, card->read_timeout);
if (rc)
{
rc = rc * 10 - 5;
goto error;
}
bcs_src = NULL;
bcs_len = blocksize;
inbuf += blocksize;
c_addr += blocksize;
c_block++;
}
if (read_cmd == CMD_READ_MULTIPLE_BLOCK)
{
rc = send_cmd(CMD_STOP_TRANSMISSION, 0, &response);
if (rc)
{
rc = rc * 10 - 6;
goto error;
}
}
}
if (c_addr < c_end_addr) /* last partial block */
{
rc = cache_block(IF_MV2(drive,) c_block, blocksize,
card->read_timeout);
if (rc)
{
rc = rc * 10 - 7;
goto error;
}
bcs_src = block_cache[current_cache].data + 2;
bcs_len = c_end_addr - c_addr;
}
bg_copy_swap();
error:
deselect_card();
return rc;
}
int ata_write_sectors(IF_MV2(int drive,)
unsigned long start,
int count,
const void* buf)
{
int rc = 0;
unsigned int blocksize, offset;
unsigned long c_addr, c_end_addr;
unsigned long c_block, c_end_block;
unsigned char response;
tCardInfo *card;
#ifndef HAVE_MULTIVOLUME
int drive = current_card;
#endif
c_addr = start * SECTOR_SIZE;
c_end_addr = c_addr + count * SECTOR_SIZE;
card = &card_info[drive];
rc = select_card(drive);
if (rc)
{
rc = rc * 10 - 1;
goto error;
}
if (c_end_addr > card->size)
panicf("Writing past end of card");
blocksize = card->blocksize;
offset = c_addr & (blocksize - 1);
c_block = c_addr >> card->block_exp;
c_end_block = c_end_addr >> card->block_exp;
bcs_src = buf;
/* Special case: first block is trimmed at both ends. May only happen
* if (blocksize > 2 * sectorsize), i.e. blocksize == 2048 */
if ((c_block == c_end_block) && offset)
c_end_block++;
if (c_block < c_end_block)
{
int write_cmd;
unsigned char start_token;
if (c_end_block - c_block > 1)
{
write_cmd = CMD_WRITE_MULTIPLE_BLOCK;
start_token = DT_START_WRITE_MULTIPLE;
}
else
{
write_cmd = CMD_WRITE_BLOCK;
start_token = DT_START_BLOCK;
}
if (offset)
{
unsigned long len = MIN(c_end_addr - c_addr, blocksize - offset);
rc = cache_block(IF_MV2(drive,) c_block, blocksize,
card->read_timeout);
if (rc)
{
rc = rc * 10 - 2;
goto error;
}
bcs_dest = block_cache[current_cache].data + 2 + offset;
bcs_len = len;
c_addr -= offset;
}
else
{
bcs_dest = NULL; /* next block cache */
bcs_len = blocksize;
}
bg_copy_swap();
rc = send_cmd(write_cmd, c_addr, &response);
if (rc)
{
rc = rc * 10 - 3;
goto error;
}
c_block++; /* early increment to simplify the loop */
while (c_block < c_end_block)
{
bcs_dest = NULL; /* next block cache */
bcs_len = blocksize;
rc = send_block(blocksize, start_token, card->write_timeout);
if (rc)
{
rc = rc * 10 - 4;
goto error;
}
c_addr += blocksize;
c_block++;
}
rc = send_block(blocksize, start_token, card->write_timeout);
if (rc)
{
rc = rc * 10 - 5;
goto error;
}
c_addr += blocksize;
/* c_block++ was done early */
if (write_cmd == CMD_WRITE_MULTIPLE_BLOCK)
{
response = DT_STOP_TRAN;
write_transfer(&response, 1);
poll_busy(card->write_timeout);
}
}
if (c_addr < c_end_addr) /* last partial block */
{
rc = cache_block(IF_MV2(drive,) c_block, blocksize,
card->read_timeout);
if (rc)
{
rc = rc * 10 - 6;
goto error;
}
bcs_dest = block_cache[current_cache].data + 2;
bcs_len = c_end_addr - c_addr;
bg_copy_swap();
rc = send_cmd(CMD_WRITE_BLOCK, c_addr, &response);
if (rc)
{
rc = rc * 10 - 7;
goto error;
}
rc = send_block(blocksize, DT_START_BLOCK, card->write_timeout);
if (rc)
{
rc = rc * 10 - 8;
goto error;
}
}
error:
deselect_card();
return rc;
}
void ata_spindown(int seconds)
{
(void)seconds;
}
bool ata_disk_is_active(void)
{
/* this is correct unless early return from write gets implemented */
return mmc_mutex.locked;
}
void ata_sleep(void)
{
}
void ata_spin(void)
{
}
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_ata_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 */
{
unsigned char response;
mutex_lock(&mmc_mutex);
setup_sci1(7); /* safe value */
and_b(~0x02, &PADRH); /* assert CS */
send_cmd(CMD_SEND_OP_COND, 0, &response);
if (response == 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 = 30;
}
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;
}
}
}
}
}
int ata_soft_reset(void)
{
return 0;
}
void ata_enable(bool on)
{
PBCR1 &= ~0x0CF0; /* PB13, PB11 and PB10 become GPIOs, if not modified below */
PACR2 &= ~0x4000; /* use PA7 (bridge reset) as GPIO */
if (on)
{
PBCR1 |= 0x08A0; /* as SCK1, TxD1, RxD1 */
IPRE &= 0x0FFF; /* disable SCI1 interrupts for the CPU */
mmc_enable_int_flash_clock(true); /* always enabled in SPI mode */
}
and_b(~0x80, &PADRL); /* assert reset */
sleep(HZ/20);
or_b(0x80, &PADRL); /* de-assert reset */
sleep(HZ/20);
card_info[0].initialized = false;
card_info[1].initialized = false;
}
int ata_init(void)
{
int rc = 0;
if (!initialized)
{
mutex_init(&mmc_mutex);
queue_init(&mmc_queue, true);
}
mutex_lock(&mmc_mutex);
led(false);
/* Port setup */
PACR1 &= ~0x0F00; /* GPIO function for PA12, /IRQ1 for PA13 */
PACR1 |= 0x0400;
PADR |= 0x0680; /* set all the selects + reset high (=inactive) */
PAIOR |= 0x1680; /* make outputs for them and the PA12 clock gate */
PBDR |= 0x2C00; /* SCK1, TxD1 and RxD1 high when GPIO CHECKME: mask */
PBIOR |= 0x2000; /* SCK1 output */
PBIOR &= ~0x0C00; /* TxD1, RxD1 input */
last_mmc_status = mmc_detect();
#ifndef HAVE_MULTIVOLUME
if (last_mmc_status)
{ /* MMC inserted */
current_card = 1;
}
else
{ /* no MMC, use internal memory */
current_card = 0;
}
#endif
new_mmc_circuit = ((HW_MASK & MMC_CLOCK_POLARITY) != 0);
ata_enable(true);
if ( !initialized )
{
if (!last_mmc_status)
mmc_status = MMC_UNTOUCHED;
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;
}
mutex_unlock(&mmc_mutex);
return rc;
}