1654efc313
* Editing a bunch of drivers' thread routines in order to implement a new feature is tedious. * No matter the number of storage drivers, they share one thread. No extra threads needed for CONFIG_STORAGE_MULTI. * Each has an event callback called by the storage thread. * A default callback is provided to fake sleeping in order to trigger idle callbacks. It could also do other default processing. Changes to it will be part of driver code without editing each one. * Drivers may sleep and wake as they please as long as they give a low pulse on their storage bit to ask to go into sleep mode. Idle callback is called on its behalf and driver immediately put into sleep mode. * Drivers may indicate they are to continue receiving events in USB mode, otherwise they receve nothing until disconnect (they do receive SYS_USB_DISCONNECTED no matter what). * Rework a few things to keep the callback implementation sane and maintainable. ata.c was dreadful with all those bools; make it a state machine and easier to follow. Remove last_user_activity; it has no purpose that isn't served by keeping the disk active through last_disk_activity instead. * Even-out stack sizes partly because of a lack of a decent place to define them by driver or SoC or whatever; it doesn't seem too critical to do that anyway. Many are simply too large while at least one isn't really adequate. They may be individually overridden if necessary (figure out where). The thread uses the greatest size demanded. Newer file code is much more frugal with stack space. I barely see use crack 50% after idle callbacks (usually mid-40s). Card insert/eject doesn't demand much. * No forcing of idle callbacks. If it isn't necessary for one or more non-disk storage types, it really isn't any more necessary for disk storage. Besides, it makes the whole thing easier to implement. Change-Id: Id30c284d82a8af66e47f2cfe104c52cbd8aa7215
952 lines
26 KiB
C
952 lines
26 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2009 by Bob Cousins
|
|
*
|
|
* 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.
|
|
*
|
|
****************************************************************************/
|
|
|
|
//#define SD_DEBUG
|
|
|
|
#include "system.h"
|
|
#include <string.h>
|
|
#include "gcc_extensions.h"
|
|
#include "panic.h"
|
|
|
|
#ifdef SD_DEBUG
|
|
#include "uart-s3c2440.h"
|
|
#endif
|
|
#ifdef HAVE_HOTSWAP
|
|
#include "sdmmc.h"
|
|
#endif
|
|
#include "storage.h"
|
|
#include "dma-target.h"
|
|
#include "system-target.h"
|
|
#include "led-mini2440.h"
|
|
|
|
/* The configuration method is not very flexible. */
|
|
#define CARD_NUM_SLOT 0
|
|
#define NUM_CARDS 2
|
|
|
|
#define EC_OK 0
|
|
#define EC_FAILED 1
|
|
#define EC_NOCARD 2
|
|
#define EC_WAIT_STATE_FAILED 3
|
|
#define EC_POWER_UP 4
|
|
#define EC_FIFO_WR_EMPTY 5
|
|
#define EC_FIFO_WR_DONE 6
|
|
#define EC_TRAN_READ_ENTRY 7
|
|
#define EC_TRAN_READ_EXIT 8
|
|
#define EC_TRAN_WRITE_ENTRY 9
|
|
#define EC_TRAN_WRITE_EXIT 10
|
|
#define EC_COMMAND 11
|
|
#define EC_WRITE_PROTECT 12
|
|
|
|
#define MIN_YIELD_PERIOD 1000
|
|
#define UNALIGNED_NUM_SECTORS 10
|
|
#define MAX_TRANSFER_ERRORS 10
|
|
|
|
/* command flags for send_cmd */
|
|
#define MCI_NO_FLAGS (0<<0)
|
|
#define MCI_RESP (1<<0)
|
|
#define MCI_LONG_RESP (1<<1)
|
|
#define MCI_ARG (1<<2)
|
|
|
|
#define INITIAL_CLK 400000 /* Initial clock */
|
|
#define SD_CLK 24000000 /* Clock for SD cards */
|
|
#define MMC_CLK 15000000 /* Clock for MMC cards */
|
|
|
|
#define SD_ACTIVE_LED LED4
|
|
|
|
#ifdef SD_DEBUG
|
|
#define dbgprintf uart_printf
|
|
#else
|
|
#define dbgprintf(...)
|
|
#endif
|
|
|
|
struct sd_card_status
|
|
{
|
|
int retry;
|
|
int retry_max;
|
|
};
|
|
|
|
/** static, private data **/
|
|
|
|
/* for compatibility */
|
|
static long last_disk_activity = -1;
|
|
|
|
#ifdef CONFIG_STORAGE_MULTI
|
|
static int sd_first_drive = 0;
|
|
#else
|
|
#define sd_first_drive 0
|
|
#endif
|
|
|
|
static bool initialized = false;
|
|
static bool sd_enabled = false;
|
|
static long next_yield = 0;
|
|
|
|
static tCardInfo card_info [NUM_CARDS];
|
|
|
|
#ifdef HAVE_MULTIDRIVE
|
|
static int curr_card = 0; /* current active card */
|
|
#if 0
|
|
static struct sd_card_status sd_status[NUM_CARDS] =
|
|
{
|
|
#if NUM_CARDS > 1
|
|
{0, 10},
|
|
#endif
|
|
{0, 10}
|
|
};
|
|
#endif
|
|
#endif
|
|
|
|
static struct mutex sd_mtx SHAREDBSS_ATTR;
|
|
static struct semaphore transfer_completion_signal;
|
|
static volatile unsigned int transfer_error[NUM_DRIVES];
|
|
/* align on cache line size */
|
|
static unsigned char aligned_buffer[UNALIGNED_NUM_SECTORS * SD_BLOCK_SIZE]
|
|
__attribute__((aligned(32)));
|
|
static unsigned char * uncached_buffer;
|
|
|
|
static inline void mci_delay(void)
|
|
{
|
|
int i = 0xffff;
|
|
while (i--)
|
|
asm volatile ("nop\n");
|
|
}
|
|
|
|
/* TODO: should be in target include file */
|
|
/*****************************************************************************
|
|
Definitions specific to Mini2440
|
|
*****************************************************************************/
|
|
|
|
#define SD_CD (1<<8) /* Port G */
|
|
#define SD_WP (1<<8) /* Port H */
|
|
|
|
/*****************************************************************************
|
|
Functions specific to S3C2440 SoC
|
|
*****************************************************************************/
|
|
|
|
#ifdef SD_DEBUG
|
|
static unsigned reg_copy[16], reg_copy2[16];
|
|
static void get_regs (unsigned *regs)
|
|
{
|
|
unsigned j;
|
|
volatile unsigned long *sdi_reg = &SDICON;
|
|
|
|
for (j=0; j < 16;j++)
|
|
{
|
|
*regs++ = *sdi_reg++;
|
|
}
|
|
}
|
|
|
|
static void dump_regs (unsigned *regs1, unsigned *regs2)
|
|
{
|
|
unsigned j;
|
|
volatile unsigned long*sdi_reg = &SDICON;
|
|
unsigned long diff;
|
|
|
|
for (j=0; j < 16;j++)
|
|
{
|
|
diff = *regs1 ^ *regs2;
|
|
if (diff)
|
|
dbgprintf ("%8x %8x %8x %8x\n", sdi_reg, *regs1, *regs2, diff );
|
|
regs1++;
|
|
regs2++;
|
|
sdi_reg++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void debug_r1(int cmd)
|
|
{
|
|
#if defined(SD_DEBUG)
|
|
dbgprintf("CMD%2.2d:SDICSTA=%04x [%c%c%c%c%c-%c%c%c%c%c%c%c] SDIRSP0=%08x [%d %s] \n",
|
|
cmd,
|
|
SDICSTA,
|
|
(SDICSTA & S3C2410_SDICMDSTAT_CRCFAIL) ? 'C' : ' ',
|
|
(SDICSTA & S3C2410_SDICMDSTAT_CMDSENT) ? 'S' : ' ',
|
|
(SDICSTA & S3C2410_SDICMDSTAT_CMDTIMEOUT) ? 'T' : ' ',
|
|
(SDICSTA & S3C2410_SDICMDSTAT_RSPFIN) ? 'R' : ' ',
|
|
(SDICSTA & S3C2410_SDICMDSTAT_XFERING) ? 'X' : ' ',
|
|
|
|
(SDICSTA & 0x40) ? 'P' : ' ',
|
|
(SDICSTA & 0x20) ? 'A' : ' ',
|
|
(SDICSTA & 0x10) ? 'E' : ' ',
|
|
(SDICSTA & 0x08) ? 'C' : ' ',
|
|
(SDICSTA & 0x04) ? 'I' : ' ',
|
|
(SDICSTA & 0x02) ? 'R' : ' ',
|
|
(SDICSTA & 0x01) ? 'Z' : ' ',
|
|
|
|
SDIRSP0,
|
|
SD_R1_CURRENT_STATE(SDIRSP0),
|
|
(SDIRSP0 & SD_R1_READY_FOR_DATA) ? "RDY " : " "
|
|
);
|
|
#else
|
|
(void)cmd;
|
|
#endif
|
|
}
|
|
|
|
void SDI (void)
|
|
{
|
|
int status = SDIDSTA;
|
|
#ifndef HAVE_MULTIDRIVE
|
|
const int curr_card = 0;
|
|
#endif
|
|
|
|
transfer_error[curr_card] = status
|
|
#if 0
|
|
& ( S3C2410_SDIDSTA_CRCFAIL | S3C2410_SDIDSTA_RXCRCFAIL |
|
|
S3C2410_SDIDSTA_DATATIMEOUT )
|
|
#endif
|
|
;
|
|
|
|
SDIDSTA |= S3C2410_SDIDSTA_CLEAR_BITS; /* needed to clear int */
|
|
|
|
dbgprintf ("SDI %x\n", transfer_error[curr_card]);
|
|
|
|
semaphore_release(&transfer_completion_signal);
|
|
|
|
/* Ack the interrupt */
|
|
SRCPND = SDI_MASK;
|
|
INTPND = SDI_MASK;
|
|
}
|
|
|
|
#if 0
|
|
void dma_callback (void)
|
|
{
|
|
const int status = SDIDSTA;
|
|
|
|
transfer_error[0] = status & (S3C2410_SDIDSTA_CRCFAIL |
|
|
S3C2410_SDIDSTA_RXCRCFAIL |
|
|
S3C2410_SDIDSTA_DATATIMEOUT );
|
|
|
|
SDIDSTA |= S3C2410_SDIDSTA_CLEAR_BITS; /* needed to clear int */
|
|
|
|
dbgprintf ("dma_cb\n");
|
|
semaphore_release(&transfer_completion_signal);
|
|
}
|
|
#endif
|
|
|
|
static void init_sdi_controller(const int card_no)
|
|
{
|
|
(void)card_no;
|
|
|
|
/*****************************************************************************/
|
|
#ifdef MINI2440
|
|
/* Specific to Mini2440 */
|
|
|
|
/* Enable pullups on SDCMD and SDDAT pins */
|
|
S3C2440_GPIO_PULLUP (GPEUP, 6, GPIO_PULLUP_ENABLE);
|
|
S3C2440_GPIO_PULLUP (GPEUP, 7, GPIO_PULLUP_ENABLE);
|
|
S3C2440_GPIO_PULLUP (GPEUP, 8, GPIO_PULLUP_ENABLE);
|
|
S3C2440_GPIO_PULLUP (GPEUP, 9, GPIO_PULLUP_ENABLE);
|
|
S3C2440_GPIO_PULLUP (GPEUP, 10, GPIO_PULLUP_ENABLE);
|
|
|
|
/* Enable special function for SDCMD, SDCLK and SDDAT pins */
|
|
S3C2440_GPIO_CONFIG (GPECON, 5, GPIO_FUNCTION);
|
|
S3C2440_GPIO_CONFIG (GPECON, 6, GPIO_FUNCTION);
|
|
S3C2440_GPIO_CONFIG (GPECON, 7, GPIO_FUNCTION);
|
|
S3C2440_GPIO_CONFIG (GPECON, 8, GPIO_FUNCTION);
|
|
S3C2440_GPIO_CONFIG (GPECON, 9, GPIO_FUNCTION);
|
|
S3C2440_GPIO_CONFIG (GPECON, 10, GPIO_FUNCTION);
|
|
|
|
/* Card Detect input */
|
|
S3C2440_GPIO_CONFIG (GPGCON, 8, GPIO_INPUT);
|
|
/* enable external irq 8-23 on the internal interrupt controller */
|
|
INTMSK &= ~1<<5;
|
|
/* enable GPG8 IRQ on the external interrupt controller */
|
|
EINTMASK &= ~(1<<16);
|
|
|
|
|
|
/* Write Protect input */
|
|
S3C2440_GPIO_CONFIG (GPHCON, 8, GPIO_INPUT);
|
|
/*****************************************************************************/
|
|
#else
|
|
#error Unsupported target
|
|
#endif
|
|
/*****************************************************************************/
|
|
|
|
/* About 400KHz for initial comms with card */
|
|
SDIPRE = PCLK / INITIAL_CLK - 1;
|
|
/* Byte order=Type A (Little Endian), clock enable */
|
|
SDICON = S3C2410_SDICON_CLOCKTYPE;
|
|
SDIFSTA |= S3C2440_SDIFSTA_FIFORESET;
|
|
SDIBSIZE = SD_BLOCK_SIZE;
|
|
SDIDTIMER= 0x7fffff; /* Set timeout count - max value */
|
|
|
|
/* Enable interupt on Data Finish or data transfer error */
|
|
/* Clear pending source */
|
|
SRCPND = SDI_MASK;
|
|
INTPND = SDI_MASK;
|
|
|
|
#if 1
|
|
/* Enable interrupt in controller */
|
|
bitclr32(&INTMOD, SDI_MASK);
|
|
bitclr32(&INTMSK, SDI_MASK);
|
|
|
|
SDIIMSK |= S3C2410_SDIIMSK_DATAFINISH
|
|
| S3C2410_SDIIMSK_DATATIMEOUT
|
|
| S3C2410_SDIIMSK_DATACRC
|
|
| S3C2410_SDIIMSK_CRCSTATUS
|
|
| S3C2410_SDIIMSK_FIFOFAIL
|
|
;
|
|
#endif
|
|
}
|
|
|
|
static bool send_cmd(const int card_no, const int cmd, const int arg,
|
|
const int flags, long *response)
|
|
{
|
|
bool ret;
|
|
unsigned val, status;
|
|
(void)card_no;
|
|
|
|
#ifdef SD_DEBUG
|
|
get_regs (reg_copy);
|
|
#endif
|
|
/* A major bodge. For some reason a delay is required here */
|
|
mci_delay();
|
|
dbgprintf ("send_cmd: c=%3.3d a=%08x f=%02x \n", cmd, arg, flags);
|
|
|
|
#ifdef SD_DEBUG
|
|
get_regs (reg_copy2);
|
|
dump_regs (reg_copy, reg_copy2);
|
|
#endif
|
|
|
|
#if 0
|
|
while (SDICSTA & S3C2410_SDICMDSTAT_XFERING)
|
|
; /* wait ?? */
|
|
#endif
|
|
/* set up new command */
|
|
|
|
if (flags & MCI_ARG)
|
|
SDICARG = arg;
|
|
else
|
|
SDICARG = 0;
|
|
|
|
val = cmd | S3C2410_SDICMDCON_CMDSTART | S3C2410_SDICMDCON_SENDERHOST;
|
|
if(flags & MCI_RESP)
|
|
{
|
|
val |= S3C2410_SDICMDCON_WAITRSP;
|
|
if(flags & MCI_LONG_RESP)
|
|
val |= S3C2410_SDICMDCON_LONGRSP;
|
|
}
|
|
|
|
/* Clear command/data status flags */
|
|
SDICSTA |= 0x0f << 9;
|
|
SDIDSTA |= S3C2410_SDIDSTA_CLEAR_BITS;
|
|
|
|
/* Initiate the command */
|
|
SDICCON = val;
|
|
|
|
if (flags & MCI_RESP)
|
|
{
|
|
/* wait for response or timeout */
|
|
do
|
|
{
|
|
status = SDICSTA;
|
|
} while ( (status & (S3C2410_SDICMDSTAT_RSPFIN |
|
|
S3C2410_SDICMDSTAT_CMDTIMEOUT) ) == 0);
|
|
debug_r1(cmd);
|
|
if (status & S3C2410_SDICMDSTAT_CMDTIMEOUT)
|
|
ret = false;
|
|
else if (status & (S3C2410_SDICMDSTAT_RSPFIN))
|
|
{
|
|
/* resp received */
|
|
if(flags & MCI_LONG_RESP)
|
|
{
|
|
/* store the response in reverse word order */
|
|
response[0] = SDIRSP3;
|
|
response[1] = SDIRSP2;
|
|
response[2] = SDIRSP1;
|
|
response[3] = SDIRSP0;
|
|
}
|
|
else
|
|
response[0] = SDIRSP0;
|
|
ret = true;
|
|
}
|
|
else
|
|
ret = true;
|
|
}
|
|
else
|
|
{
|
|
/* wait for command completion or timeout */
|
|
do
|
|
{
|
|
status = SDICSTA;
|
|
} while ( (status & (S3C2410_SDICMDSTAT_CMDSENT |
|
|
S3C2410_SDICMDSTAT_CMDTIMEOUT)) == 0);
|
|
debug_r1(cmd);
|
|
if (status & S3C2410_SDICMDSTAT_CMDTIMEOUT)
|
|
ret = false;
|
|
else
|
|
ret = true;
|
|
}
|
|
|
|
/* Clear Command status flags */
|
|
SDICSTA |= 0x0f << 9;
|
|
|
|
mci_delay();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int sd_init_card(const int card_no)
|
|
{
|
|
unsigned long temp_reg[4];
|
|
unsigned long response;
|
|
long init_timeout;
|
|
bool sdhc;
|
|
int i;
|
|
|
|
if(!send_cmd(card_no, SD_GO_IDLE_STATE, 0, MCI_NO_FLAGS, NULL))
|
|
return -1;
|
|
|
|
mci_delay();
|
|
|
|
sdhc = false;
|
|
if(send_cmd(card_no, SD_SEND_IF_COND, 0x1AA, MCI_RESP|MCI_ARG, &response))
|
|
if((response & 0xFFF) == 0x1AA)
|
|
sdhc = true;
|
|
|
|
/* timeout for initialization is 1sec, from SD Specification 2.00 */
|
|
init_timeout = current_tick + HZ;
|
|
|
|
do {
|
|
/* timeout */
|
|
if(current_tick > init_timeout)
|
|
return -2;
|
|
|
|
/* app_cmd */
|
|
if( !send_cmd(card_no, SD_APP_CMD, 0, MCI_RESP|MCI_ARG, &response) ||
|
|
!(response & (1<<5)) )
|
|
{
|
|
return -3;
|
|
}
|
|
|
|
/* acmd41 */
|
|
if(!send_cmd(card_no, SD_APP_OP_COND, (sdhc ? 0x40FF8000 : (1<<23)),
|
|
MCI_RESP|MCI_ARG, &card_info[card_no].ocr))
|
|
{
|
|
return -4;
|
|
}
|
|
|
|
} while(!(card_info[card_no].ocr & (1<<31)));
|
|
|
|
/* send CID */
|
|
if(!send_cmd(card_no, SD_ALL_SEND_CID, 0, MCI_RESP|MCI_LONG_RESP|MCI_ARG,
|
|
temp_reg))
|
|
return -5;
|
|
|
|
for(i=0; i<4; i++)
|
|
card_info[card_no].cid[3-i] = temp_reg[i];
|
|
|
|
/* send RCA */
|
|
if(!send_cmd(card_no, SD_SEND_RELATIVE_ADDR, 0, MCI_RESP|MCI_ARG,
|
|
&card_info[card_no].rca))
|
|
return -6;
|
|
|
|
/* send CSD */
|
|
if(!send_cmd(card_no, SD_SEND_CSD, card_info[card_no].rca,
|
|
MCI_RESP|MCI_LONG_RESP|MCI_ARG, temp_reg))
|
|
return -7;
|
|
|
|
for(i=0; i<4; i++)
|
|
card_info[card_no].csd[3-i] = temp_reg[i];
|
|
|
|
sd_parse_csd(&card_info[card_no]);
|
|
|
|
if(!send_cmd(card_no, SD_SELECT_CARD, card_info[card_no].rca, MCI_ARG, NULL))
|
|
return -9;
|
|
|
|
if(!send_cmd(card_no, SD_APP_CMD, card_info[card_no].rca, MCI_ARG, NULL))
|
|
return -10;
|
|
|
|
if(!send_cmd(card_no, SD_SET_BUS_WIDTH, card_info[card_no].rca | 2, MCI_ARG, NULL))
|
|
return -11;
|
|
|
|
if(!send_cmd(card_no, SD_SET_BLOCKLEN, card_info[card_no].blocksize, MCI_ARG,
|
|
NULL))
|
|
return -12;
|
|
|
|
card_info[card_no].initialized = 1;
|
|
|
|
/* full speed for controller clock */
|
|
SDIPRE = PCLK / SD_CLK - 1;
|
|
mci_delay();
|
|
|
|
return EC_OK;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Generic functions
|
|
*****************************************************************************/
|
|
|
|
static inline bool card_detect_target(void)
|
|
{
|
|
/* TODO - use interrupt on change? */
|
|
#ifdef MINI2440
|
|
return (GPGDAT & SD_CD) == 0;
|
|
#else
|
|
#error Unsupported target
|
|
#endif
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
#ifdef HAVE_HOTSWAP
|
|
|
|
static int sd1_oneshot_callback(struct timeout *tmo)
|
|
{
|
|
/* This is called only if the state was stable for 300ms - check state
|
|
* and post appropriate event. */
|
|
queue_broadcast(card_detect_target() ? SYS_HOTSWAP_INSERTED :
|
|
SYS_HOTSWAP_EXTRACTED,
|
|
sd_first_drive + CARD_NUM_SLOT);
|
|
return 0;
|
|
(void)tmo;
|
|
}
|
|
|
|
void EINT8_23(void)
|
|
{
|
|
static struct timeout sd1_oneshot;
|
|
EINTPEND = (1<<16); /* ack irq on external, then internal irq controller */
|
|
SRCPND = (1<<5);
|
|
INTPND = (1<<5);
|
|
/* add task to inform the system about the SD insertion
|
|
* sanity check if it's still inserted after 300ms */
|
|
timeout_register(&sd1_oneshot, sd1_oneshot_callback, (3*HZ/10), 0);
|
|
}
|
|
|
|
bool sd_removable(IF_MD_NONVOID(int card_no))
|
|
{
|
|
#ifndef HAVE_MULTIDRIVE
|
|
const int card_no = 0;
|
|
#endif
|
|
dbgprintf ("sd_remov (hs) [%d] %d\n", card_no, card_no == CARD_NUM_SLOT );
|
|
return (card_no == CARD_NUM_SLOT);
|
|
}
|
|
|
|
bool sd_present(IF_MD_NONVOID(int card_no))
|
|
{
|
|
#ifdef HAVE_MULTIDRIVE
|
|
(void)card_no;
|
|
#endif
|
|
dbgprintf ("sd_pres (hs) [%d] %d\n", card_no, card_detect_target());
|
|
return card_detect_target();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
#else
|
|
|
|
bool sd_removable(IF_MD_NONVOID(int card_no))
|
|
{
|
|
#ifndef HAVE_MULTIDRIVE
|
|
const int card_no = 0;
|
|
#endif
|
|
(void)card_no;
|
|
|
|
/* not applicable */
|
|
dbgprintf ("sd_remov");
|
|
return false;
|
|
}
|
|
|
|
#endif /* HAVE_HOTSWAP */
|
|
/*****************************************************************************/
|
|
|
|
static int sd_wait_for_state(const int card_no, unsigned int state)
|
|
{
|
|
unsigned long response = 0;
|
|
unsigned int timeout = HZ; /* ticks */
|
|
long t = current_tick;
|
|
|
|
while (1)
|
|
{
|
|
long tick;
|
|
|
|
if(!send_cmd(card_no, SD_SEND_STATUS, card_info[card_no].rca,
|
|
MCI_RESP|MCI_ARG, &response))
|
|
return -1;
|
|
|
|
if( (SD_R1_CURRENT_STATE(response) == state) )
|
|
return 0;
|
|
|
|
if(TIME_AFTER(current_tick, t + timeout))
|
|
return -2;
|
|
|
|
if (TIME_AFTER((tick = current_tick), next_yield))
|
|
{
|
|
yield();
|
|
timeout += current_tick - tick;
|
|
next_yield = tick + MIN_YIELD_PERIOD;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int sd_transfer_sectors(int card_no, unsigned long start,
|
|
int count, void* buf, const bool write)
|
|
{
|
|
int ret = EC_OK;
|
|
unsigned loops = 0;
|
|
struct dma_request request;
|
|
|
|
mutex_lock(&sd_mtx);
|
|
sd_enable(true);
|
|
set_leds(SD_ACTIVE_LED);
|
|
|
|
#ifdef HAVE_MULTIDRIVE
|
|
curr_card = card_no;
|
|
#endif
|
|
if (card_info[card_no].initialized <= 0)
|
|
{
|
|
ret = sd_init_card(card_no);
|
|
if (!(card_info[card_no].initialized))
|
|
goto sd_transfer_error;
|
|
}
|
|
|
|
last_disk_activity = current_tick;
|
|
|
|
ret = sd_wait_for_state(card_no, SD_TRAN);
|
|
if (ret < 0)
|
|
{
|
|
ret -= 20;
|
|
goto sd_transfer_error;
|
|
}
|
|
|
|
dma_retain();
|
|
|
|
while(count)
|
|
{
|
|
/* 128 * 512 = 2^16, and doesn't fit in the 16 bits of DATA_LENGTH
|
|
* register, so we have to transfer maximum 127 sectors at a time. */
|
|
unsigned int transfer = (count >= 128) ? 127 : count; /* sectors */
|
|
void *dma_buf;
|
|
const int cmd =
|
|
write ? SD_WRITE_MULTIPLE_BLOCK : SD_READ_MULTIPLE_BLOCK;
|
|
unsigned long start_addr = start;
|
|
|
|
dma_buf = aligned_buffer;
|
|
if(transfer > UNALIGNED_NUM_SECTORS)
|
|
transfer = UNALIGNED_NUM_SECTORS;
|
|
if(write)
|
|
memcpy(uncached_buffer, buf, transfer * SD_BLOCK_SIZE);
|
|
|
|
/* Set start_addr to the correct unit (blocks or bytes) */
|
|
if(!(card_info[card_no].ocr & SD_OCR_CARD_CAPACITY_STATUS))/* not SDHC */
|
|
start_addr *= SD_BLOCK_SIZE;
|
|
|
|
/* TODO? */
|
|
SDIFSTA = SDIFSTA | S3C2440_SDIFSTA_FIFORESET;
|
|
SDIDCON = S3C2440_SDIDCON_DS_WORD |
|
|
S3C2410_SDIDCON_BLOCKMODE | S3C2410_SDIDCON_WIDEBUS |
|
|
S3C2410_SDIDCON_DMAEN |
|
|
S3C2440_SDIDCON_DATSTART |
|
|
( transfer << 0);
|
|
if (write)
|
|
SDIDCON |= S3C2410_SDIDCON_TXAFTERRESP | S3C2410_SDIDCON_XFER_TXSTART;
|
|
else
|
|
SDIDCON |= S3C2410_SDIDCON_RXAFTERCMD | S3C2410_SDIDCON_XFER_RXSTART;
|
|
|
|
SDIDSTA |= S3C2410_SDIDSTA_CLEAR_BITS; /* needed to clear int */
|
|
SRCPND = SDI_MASK;
|
|
INTPND = SDI_MASK;
|
|
|
|
/* Initiate read/write command */
|
|
if(!send_cmd(card_no, cmd, start_addr, MCI_ARG | MCI_RESP, NULL))
|
|
{
|
|
ret -= 3*20;
|
|
goto sd_transfer_error;
|
|
}
|
|
|
|
if(write)
|
|
{
|
|
request.source_addr = dma_buf;
|
|
request.source_control = DISRCC_LOC_AHB | DISRCC_INC_AUTO;
|
|
request.dest_addr = &SDIDAT_LLE;
|
|
request.dest_control = DISRCC_LOC_APB | DISRCC_INC_FIXED;
|
|
request.count = transfer * SD_BLOCK_SIZE / sizeof(long);
|
|
request.source_map = DMA_SRC_MAP_SDI;
|
|
request.control = DCON_DMD_HS | DCON_SYNC_APB |
|
|
DCON_HW_SEL |
|
|
DCON_NO_RELOAD | DCON_DSZ_WORD;
|
|
request.callback = NULL;
|
|
|
|
dma_enable_channel(0, &request);
|
|
}
|
|
else
|
|
{
|
|
request.source_addr = &SDIDAT_LLE;
|
|
request.source_control = DISRCC_LOC_APB | DISRCC_INC_FIXED;
|
|
request.dest_addr = dma_buf;
|
|
request.dest_control = DISRCC_LOC_AHB | DISRCC_INC_AUTO;
|
|
request.count = transfer * SD_BLOCK_SIZE / sizeof(long);
|
|
request.source_map = DMA_SRC_MAP_SDI;
|
|
request.control = DCON_DMD_HS | DCON_SYNC_APB |
|
|
DCON_HW_SEL |
|
|
DCON_NO_RELOAD | DCON_DSZ_WORD;
|
|
request.callback = NULL;
|
|
|
|
dma_enable_channel(0, &request);
|
|
}
|
|
|
|
#if 0
|
|
/* FIXME : we should check if the timeouts calculated from the card's
|
|
* CSD are lower, and use them if it is the case
|
|
* Note : the OF doesn't seem to use them anyway */
|
|
MCI_DATA_TIMER(drive) = write ?
|
|
SD_MAX_WRITE_TIMEOUT : SD_MAX_READ_TIMEOUT;
|
|
MCI_DATA_LENGTH(drive) = transfer * card_info[drive].blocksize;
|
|
MCI_DATA_CTRL(drive) = (1<<0) /* enable */ |
|
|
(!write<<1) /* transfer direction */ |
|
|
(1<<3) /* DMA */ |
|
|
(9<<4) /* 2^9 = 512 */ ;
|
|
#endif
|
|
|
|
semaphore_wait(&transfer_completion_signal, 100 /*TIMEOUT_BLOCK*/);
|
|
|
|
/* wait for DMA to finish */
|
|
while (DSTAT0 & DSTAT_STAT_BUSY)
|
|
;
|
|
|
|
#if 0
|
|
status = SDIDSTA;
|
|
while ((status & (S3C2410_SDIDSTA_DATATIMEOUT|S3C2410_SDIDSTA_XFERFINISH)) == 0)
|
|
{
|
|
status = SDIDSTA;
|
|
}
|
|
dbgprintf("%x \n", status);
|
|
#endif
|
|
if( transfer_error[card_no] & S3C2410_SDIDSTA_XFERFINISH )
|
|
{
|
|
if(!write)
|
|
memcpy(buf, uncached_buffer, transfer * SD_BLOCK_SIZE);
|
|
buf += transfer * SD_BLOCK_SIZE;
|
|
start += transfer;
|
|
count -= transfer;
|
|
loops = 0; /* reset errors counter */
|
|
}
|
|
else
|
|
{
|
|
dbgprintf ("SD transfer error : 0x%x\n", transfer_error[card_no]);
|
|
|
|
if(loops++ > MAX_TRANSFER_ERRORS)
|
|
{
|
|
led_flash(LED1|LED2, LED3|LED4);
|
|
/* panicf("SD transfer error : 0x%x", transfer_error[card_no]); */
|
|
}
|
|
}
|
|
|
|
last_disk_activity = current_tick;
|
|
|
|
if(!send_cmd(card_no, SD_STOP_TRANSMISSION, 0, MCI_RESP, NULL))
|
|
{
|
|
ret = -4*20;
|
|
goto sd_transfer_error;
|
|
}
|
|
|
|
#if 0
|
|
ret = sd_wait_for_state(card_no, SD_TRAN);
|
|
if (ret < 0)
|
|
{
|
|
ret -= 5*20;
|
|
goto sd_transfer_error;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
ret = EC_OK;
|
|
|
|
sd_transfer_error:
|
|
|
|
dma_release();
|
|
|
|
clear_leds(SD_ACTIVE_LED);
|
|
sd_enable(false);
|
|
|
|
if (ret) /* error */
|
|
card_info[card_no].initialized = 0;
|
|
|
|
mutex_unlock(&sd_mtx);
|
|
return ret;
|
|
}
|
|
|
|
int sd_read_sectors(IF_MD(int card_no,) unsigned long start, int incount,
|
|
void* inbuf)
|
|
{
|
|
int ret;
|
|
|
|
#ifdef HAVE_MULTIDRIVE
|
|
dbgprintf ("sd_read %d %x %d\n", card_no, start, incount);
|
|
#else
|
|
dbgprintf ("sd_read %x %d\n", start, incount);
|
|
#endif
|
|
#ifdef HAVE_HOTSWAP_STORAGE_AS_MAIN
|
|
if (!card_detect_target())
|
|
ret = 0; /* assume success */
|
|
else
|
|
#endif
|
|
ret = sd_transfer_sectors(card_no, start, incount, inbuf, false);
|
|
dbgprintf ("sd_read, ret=%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
int sd_write_sectors(IF_MD(int drive,) unsigned long start, int count,
|
|
const void* outbuf)
|
|
{
|
|
#ifdef BOOTLOADER /* we don't need write support in bootloader */
|
|
#ifdef HAVE_MULTIDRIVE
|
|
(void) drive;
|
|
#endif
|
|
(void) start;
|
|
(void) count;
|
|
(void) outbuf;
|
|
return -1;
|
|
#else
|
|
#ifdef HAVE_MULTIDRIVE
|
|
dbgprintf ("sd_write %d %x %d\n", drive, start, count);
|
|
#else
|
|
dbgprintf ("sd_write %x %d\n", start, count);
|
|
#endif
|
|
#ifdef HAVE_HOTSWAP_STORAGE_AS_MAIN
|
|
if (!card_detect_target())
|
|
return 0; /* assume success */
|
|
else
|
|
#endif
|
|
return sd_transfer_sectors(drive, start, count, (void*)outbuf, true);
|
|
#endif
|
|
}
|
|
/*****************************************************************************/
|
|
|
|
void sd_enable(bool on)
|
|
{
|
|
dbgprintf ("sd_enable %d\n", on);
|
|
/* TODO: enable/disable SDI clock */
|
|
|
|
if (sd_enabled == on)
|
|
return; /* nothing to do */
|
|
if (on)
|
|
{
|
|
sd_enabled = true;
|
|
}
|
|
else
|
|
{
|
|
sd_enabled = false;
|
|
}
|
|
}
|
|
|
|
int sd_init(void)
|
|
{
|
|
int ret = EC_OK;
|
|
dbgprintf ("\n==============================\n");
|
|
dbgprintf (" sd_init\n");
|
|
dbgprintf ("==============================\n");
|
|
|
|
init_sdi_controller (0);
|
|
#ifndef BOOTLOADER
|
|
sd_enabled = true;
|
|
sd_enable(false);
|
|
#endif
|
|
semaphore_init(&transfer_completion_signal, 1, 0);
|
|
/* init mutex */
|
|
mutex_init(&sd_mtx);
|
|
|
|
uncached_buffer = UNCACHED_ADDR(&aligned_buffer[0]);
|
|
|
|
#ifdef HAVE_HOTSWAP
|
|
/*
|
|
* prepare detecting of SD insertion (not extraction) */
|
|
unsigned long for_extint = EXTINT2;
|
|
unsigned long for_gpgcon = GPGCON;
|
|
for_extint &= ~0x7;
|
|
#ifdef HAVE_HOTSWAP_STORAGE_AS_MAIN
|
|
for_extint |= 0x2; /* detect falling edge only (0 means SD inserted) */
|
|
#else
|
|
for_extint |= 0x3; /* detect both, raising and falling, edges */
|
|
#endif
|
|
for_gpgcon &= ~(0x3<<16);
|
|
for_gpgcon |= (0x2<<16); /* enable interrupt on pin 8 */
|
|
EXTINT2 = for_extint;
|
|
GPGCON = for_gpgcon;
|
|
#endif
|
|
|
|
initialized = true;
|
|
return ret;
|
|
}
|
|
|
|
long sd_last_disk_activity(void)
|
|
{
|
|
return last_disk_activity;
|
|
}
|
|
|
|
tCardInfo *card_get_info_target(int card_no)
|
|
{
|
|
return &card_info[card_no];
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
#ifdef CONFIG_STORAGE_MULTI
|
|
|
|
int sd_num_drives(int first_drive)
|
|
{
|
|
dbgprintf ("sd_num_drv");
|
|
/* Store which logical drive number(s) we have been assigned */
|
|
sd_first_drive = first_drive;
|
|
return NUM_CARDS;
|
|
}
|
|
|
|
bool sd_disk_is_active(void)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int sd_soft_reset(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int sd_spinup_time(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_STORAGE_MULTI */
|
|
/*****************************************************************************/
|
|
|
|
int sd_event(long id, intptr_t data)
|
|
{
|
|
int rc = 0;
|
|
|
|
switch (id)
|
|
{
|
|
#ifdef HAVE_HOTSWAP
|
|
case SYS_HOTSWAP_INSERTED:
|
|
case SYS_HOTSWAP_EXTRACTED:
|
|
mutex_lock(&sd_mtx);
|
|
/* Force card init for new card, re-init for re-inserted one or
|
|
* clear if the last attempt to init failed with an error. */
|
|
card_info[data].initialized = 0;
|
|
mutex_unlock(&sd_mtx);
|
|
break;
|
|
#endif /* HAVE_HOTSWAP */
|
|
default:
|
|
rc = storage_event_default_handler(id, data, last_disk_activity,
|
|
STORAGE_SD);
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|