a56f1ca1ed
When using variadic macros there's no need for IF_MD2/IF_MV2 to deal with function parameters. IF_MD/IF_MV are enough. Throw in IF_MD_DRV/ID_MV_VOL that return the parameter if MD/MV, or 0 if not. Change-Id: I7605e6039f3be19cb47110c84dcb3c5516f2c3eb
967 lines
23 KiB
C
967 lines
23 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id: $
|
|
*
|
|
* Copyright (C) 2011 by Tomasz Moń
|
|
*
|
|
* 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 "sd.h"
|
|
#include "system.h"
|
|
#include <string.h>
|
|
#include "gcc_extensions.h"
|
|
#include "thread.h"
|
|
#include "panic.h"
|
|
#include "kernel.h"
|
|
#include "dma-target.h"
|
|
#include "ata_idle_notify.h"
|
|
|
|
//#define SD_DEBUG
|
|
|
|
#ifdef SD_DEBUG
|
|
#include "lcd-target.h"
|
|
#include "lcd.h"
|
|
#include "font.h"
|
|
#ifdef BOOTLOADER
|
|
#include "common.h"
|
|
#else
|
|
#include "debug.h"
|
|
#endif
|
|
#endif
|
|
#include "sdmmc.h"
|
|
#include "disk.h"
|
|
#include "fat.h"
|
|
#include "system-target.h"
|
|
|
|
/* The configuration method is not very flexible. */
|
|
#define CARD_NUM_SLOT 1
|
|
#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 EC_DATA_TIMEOUT 13
|
|
#define EC_RESP_TIMEOUT 14
|
|
#define EC_CRC_ERROR 15
|
|
#define NUM_EC 16
|
|
|
|
#define MIN_YIELD_PERIOD 5
|
|
#define UNALIGNED_NUM_SECTORS 10
|
|
#define MAX_TRANSFER_ERRORS 10
|
|
|
|
#define SECTOR_SIZE 512
|
|
#define BLOCKS_PER_BANK 0x7A7800
|
|
|
|
/* command flags for send_cmd */
|
|
#define SDHC_RESP_FMT_NONE 0x0000
|
|
#define SDHC_RESP_FMT_1 0x0200
|
|
#define SDHC_RESP_FMT_2 0x0400
|
|
#define SDHC_RESP_FMT_3 0x0600
|
|
|
|
#define INITIAL_CLK 312500 /* Initial clock */
|
|
#define SD_CLK 24000000 /* Clock for SD cards */
|
|
#define MMC_CLK 15000000 /* Clock for MMC cards */
|
|
|
|
#ifdef SD_DEBUG
|
|
#ifdef BOOTLOADER
|
|
#define dbgprintf printf
|
|
#else
|
|
#define dbgprintf DEBUGF
|
|
#endif
|
|
#else
|
|
#define dbgprintf(...)
|
|
#endif
|
|
|
|
struct sd_card_status
|
|
{
|
|
int retry;
|
|
int retry_max;
|
|
};
|
|
|
|
/** static, private data **/
|
|
|
|
/* for compatibility */
|
|
static long last_disk_activity = -1;
|
|
|
|
static bool initialized = false;
|
|
static unsigned int sd_thread_id = 0;
|
|
|
|
static bool sd_enabled = false;
|
|
static long next_yield = 0;
|
|
|
|
static tCardInfo card_info [NUM_CARDS];
|
|
static tCardInfo *currcard;
|
|
|
|
static struct sd_card_status sd_status[NUM_CARDS] =
|
|
{
|
|
#if NUM_CARDS > 1
|
|
{0, 10},
|
|
#endif
|
|
{0, 10}
|
|
};
|
|
|
|
/* Shoot for around 75% usage */
|
|
static long sd_stack [(DEFAULT_STACK_SIZE*2 + 0x1c0)/sizeof(long)];
|
|
static const char sd_thread_name[] = "sd";
|
|
static struct mutex sd_mtx SHAREDBSS_ATTR;
|
|
static struct event_queue sd_queue;
|
|
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 void sd_card_mux(int card_no)
|
|
{
|
|
#ifdef HAVE_MULTIDRIVE
|
|
#ifdef SANSA_CONNECT
|
|
/* GIO6 - select Card; GIO5 - select iNAND (both active low) */
|
|
if (card_no == CARD_NUM_SLOT)
|
|
{
|
|
IO_GIO_BITSET0 = (1 << 5); /* deselect iNAND (GIO5) */
|
|
IO_GIO_BITCLR0 = (1 << 6); /* select card (GIO6) */
|
|
}
|
|
else
|
|
{
|
|
IO_GIO_BITSET0 = (1 << 6); /* deselect card (GIO6) */
|
|
IO_GIO_BITCLR0 = (1 << 5); /* select iNAND (GIO5) */
|
|
}
|
|
#else /* Different players */
|
|
(void)card_no;
|
|
#endif
|
|
#else /* No multidrive */
|
|
(void)card_no;
|
|
#endif
|
|
}
|
|
|
|
|
|
void sd_enable(bool on)
|
|
{
|
|
if (sd_enabled == on)
|
|
return; /* nothing to do */
|
|
|
|
if (on)
|
|
{
|
|
sd_enabled = true;
|
|
}
|
|
else
|
|
{
|
|
sd_enabled = false;
|
|
}
|
|
}
|
|
|
|
/* sets clock rate just like OF does */
|
|
static void sd_set_clock_rate(unsigned long rate)
|
|
{
|
|
unsigned char rate_val = 0;
|
|
|
|
if (rate == INITIAL_CLK)
|
|
{
|
|
rate_val = 0x3B;
|
|
}
|
|
else if (rate > INITIAL_CLK)
|
|
{
|
|
rate_val = 0;
|
|
}
|
|
else
|
|
{
|
|
rate_val = 0xFF;
|
|
}
|
|
|
|
IO_MMC_MEM_CLK_CONTROL = (IO_MMC_MEM_CLK_CONTROL & 0xFF00) | rate_val;
|
|
}
|
|
|
|
static int sd_poll_status(int st_reg_num, volatile unsigned int flag)
|
|
{
|
|
unsigned int status;
|
|
unsigned int status1;
|
|
bool done;
|
|
|
|
do
|
|
{
|
|
long time = current_tick;
|
|
|
|
if (TIME_AFTER(time, next_yield))
|
|
{
|
|
long ty = current_tick;
|
|
yield();
|
|
next_yield = ty + MIN_YIELD_PERIOD;
|
|
}
|
|
|
|
status = IO_MMC_STATUS0;
|
|
status1 = IO_MMC_STATUS1;
|
|
|
|
if (status & MMC_ST0_CMD_TIMEOUT)
|
|
{
|
|
dbgprintf("CMD timeout");
|
|
return -EC_RESP_TIMEOUT;
|
|
}
|
|
if (status & MMC_ST0_DATA_TIMEOUT)
|
|
{
|
|
dbgprintf("DATA timeout");
|
|
return -EC_DATA_TIMEOUT;
|
|
}
|
|
|
|
if (status &
|
|
(MMC_ST0_WR_CRCERR | MMC_ST0_RD_CRCERR | MMC_ST0_RESP_CRCERR))
|
|
{
|
|
dbgprintf("CRC error");
|
|
return -EC_CRC_ERROR;
|
|
}
|
|
|
|
if (st_reg_num == 0)
|
|
{
|
|
done = status & flag;
|
|
}
|
|
else
|
|
{
|
|
done = status1 & flag;
|
|
}
|
|
} while (!done);
|
|
|
|
return EC_OK;
|
|
}
|
|
|
|
static int dma_wait_for_completion(void)
|
|
{
|
|
unsigned short dma_status;
|
|
|
|
do
|
|
{
|
|
long time = current_tick;
|
|
|
|
if (TIME_AFTER(time, next_yield))
|
|
{
|
|
long ty = current_tick;
|
|
yield();
|
|
next_yield = ty + MIN_YIELD_PERIOD;
|
|
}
|
|
|
|
dma_status = IO_MMC_SD_DMA_STATUS1;
|
|
if (dma_status & (1 << 13))
|
|
{
|
|
return -EC_DATA_TIMEOUT;
|
|
}
|
|
} while (dma_status & (1 << 12));
|
|
|
|
return EC_OK;
|
|
}
|
|
|
|
static int sd_command(int cmd, unsigned long arg,
|
|
int cmdat, unsigned long *response)
|
|
{
|
|
int ret;
|
|
|
|
/* Clear response registers */
|
|
IO_MMC_RESPONSE0 = 0;
|
|
IO_MMC_RESPONSE1 = 0;
|
|
IO_MMC_RESPONSE2 = 0;
|
|
IO_MMC_RESPONSE3 = 0;
|
|
IO_MMC_RESPONSE4 = 0;
|
|
IO_MMC_RESPONSE5 = 0;
|
|
IO_MMC_RESPONSE6 = 0;
|
|
IO_MMC_RESPONSE7 = 0;
|
|
IO_MMC_COMMAND_INDEX = 0;
|
|
IO_MMC_SPI_DATA = 0;
|
|
|
|
IO_MMC_ARG_LOW = (unsigned int)((arg & 0xFFFF));
|
|
IO_MMC_ARG_HI = (unsigned int)((arg & 0xFFFF0000) >> 16);
|
|
|
|
/* SD is always in push-pull mode */
|
|
cmdat |= MMC_CMD_PPLEN;
|
|
|
|
cmdat |= (cmd & MMC_CMD_CMD_MASK);
|
|
|
|
if (cmdat & MMC_CMD_DATA)
|
|
cmdat |= MMC_CMD_DCLR;
|
|
|
|
IO_MMC_COMMAND = cmdat;
|
|
|
|
if (cmdat & MMC_CMD_DATA)
|
|
{
|
|
/* Command requires data - do not wait for RSPDNE */
|
|
ret = EC_OK;
|
|
}
|
|
else
|
|
{
|
|
ret = sd_poll_status(0, MMC_ST0_RSPDNE);
|
|
}
|
|
|
|
if (ret != EC_OK)
|
|
{
|
|
dbgprintf("Command failed (ret %d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (response == NULL)
|
|
{
|
|
/* discard response */
|
|
}
|
|
else if ((cmdat & SDHC_RESP_FMT_1) || (cmdat & SDHC_RESP_FMT_3))
|
|
{
|
|
response[0] = (IO_MMC_RESPONSE7 << 16) | IO_MMC_RESPONSE6;
|
|
}
|
|
else if (cmdat & SDHC_RESP_FMT_2)
|
|
{
|
|
response[0] = (IO_MMC_RESPONSE7 << 16) | IO_MMC_RESPONSE6;
|
|
response[1] = (IO_MMC_RESPONSE5 << 16) | IO_MMC_RESPONSE4;
|
|
response[2] = (IO_MMC_RESPONSE3 << 16) | IO_MMC_RESPONSE2;
|
|
response[3] = (IO_MMC_RESPONSE1 << 16) | IO_MMC_RESPONSE0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sd_init_card(const int card_no)
|
|
{
|
|
bool sdhc = false;
|
|
unsigned long response[4];
|
|
int ret;
|
|
int i;
|
|
|
|
memset(currcard, 0, sizeof(*currcard));
|
|
sd_card_mux(card_no);
|
|
|
|
/* Set data bus width to 1 bit */
|
|
bitclr16(&IO_MMC_CONTROL, MMC_CTRL_WIDTH);
|
|
sd_set_clock_rate(INITIAL_CLK);
|
|
|
|
/* Prevent controller lock */
|
|
udelay(100);
|
|
|
|
ret = sd_command(SD_GO_IDLE_STATE, 0, MMC_CMD_INITCLK, NULL);
|
|
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
ret = sd_command(SD_SEND_IF_COND, 0x1AA,
|
|
SDHC_RESP_FMT_3, response);
|
|
if ((response[0] & 0xFFF) == 0x1AA)
|
|
{
|
|
sdhc = true;
|
|
dbgprintf("found sdhc card");
|
|
}
|
|
|
|
while ((currcard->ocr & (1 << 31)) == 0) /* until card is powered up */
|
|
{
|
|
ret = sd_command(SD_APP_CMD, currcard->rca,
|
|
SDHC_RESP_FMT_1, NULL);
|
|
if (ret < 0)
|
|
{
|
|
dbgprintf("SD_APP_CMD failed");
|
|
return -1;
|
|
}
|
|
|
|
ret = sd_command(SD_APP_OP_COND,
|
|
(1 << 20) /* 3.2-3.3V */ |
|
|
(1 << 21) /* 3.3-3.4V */ |
|
|
(sdhc ? (1 << 30) : 0),
|
|
SDHC_RESP_FMT_3, &currcard->ocr);
|
|
|
|
if (ret < 0)
|
|
{
|
|
dbgprintf("SD_APP_OP_COND failed");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
dbgprintf("Card powered up");
|
|
|
|
ret = sd_command(SD_ALL_SEND_CID, 0,
|
|
SDHC_RESP_FMT_2, response);
|
|
if (ret < 0)
|
|
{
|
|
dbgprintf("SD_ALL_SEND_CID failed");
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i<4; i++)
|
|
{
|
|
currcard->cid[i] = response[i];
|
|
}
|
|
|
|
ret = sd_command(SD_SEND_RELATIVE_ADDR, 0,
|
|
SDHC_RESP_FMT_1, &currcard->rca);
|
|
if (ret < 0)
|
|
{
|
|
dbgprintf("SD_SEND_RELATIVE_ADDR failed");
|
|
return -1;
|
|
}
|
|
|
|
ret = sd_command(SD_SEND_CSD, currcard->rca,
|
|
SDHC_RESP_FMT_2, response);
|
|
if (ret < 0)
|
|
{
|
|
dbgprintf("SD_SEND_CSD failed");
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i<4; i++)
|
|
{
|
|
currcard->csd[i] = response[i];
|
|
}
|
|
|
|
sd_parse_csd(currcard);
|
|
|
|
sd_set_clock_rate(currcard->speed);
|
|
|
|
/* Prevent controller lock */
|
|
udelay(100);
|
|
|
|
ret = sd_command(SD_SELECT_CARD, currcard->rca,
|
|
SDHC_RESP_FMT_1, NULL);
|
|
if (ret < 0)
|
|
{
|
|
dbgprintf("SD_SELECT_CARD failed");
|
|
return -1;
|
|
}
|
|
|
|
ret = sd_command(SD_APP_CMD, currcard->rca,
|
|
SDHC_RESP_FMT_1, NULL);
|
|
if (ret < 0)
|
|
{
|
|
dbgprintf("SD_APP_CMD failed");
|
|
return -1;
|
|
}
|
|
|
|
ret = sd_command(SD_SET_BUS_WIDTH, currcard->rca | 2,
|
|
SDHC_RESP_FMT_1, NULL); /* 4 bit */
|
|
if (ret < 0)
|
|
{
|
|
dbgprintf("SD_SET_BUS_WIDTH failed");
|
|
return -1;
|
|
}
|
|
|
|
/* Set data bus width to 4 bits */
|
|
bitset16(&IO_MMC_CONTROL, MMC_CTRL_WIDTH);
|
|
|
|
ret = sd_command(SD_SET_BLOCKLEN, currcard->blocksize,
|
|
SDHC_RESP_FMT_1, NULL);
|
|
if (ret < 0)
|
|
{
|
|
dbgprintf("SD_SET_BLOCKLEN failed");
|
|
return -1;
|
|
}
|
|
|
|
IO_MMC_BLOCK_LENGTH = currcard->blocksize;
|
|
|
|
dbgprintf("Card initialized");
|
|
currcard->initialized = 1;
|
|
|
|
return EC_OK;
|
|
}
|
|
|
|
/* lock must already by aquired */
|
|
static void sd_select_device(int card_no)
|
|
{
|
|
currcard = &card_info[card_no];
|
|
|
|
if (card_no == 0)
|
|
{
|
|
/* Main card always gets a chance */
|
|
sd_status[0].retry = 0;
|
|
}
|
|
|
|
if (currcard->initialized > 0)
|
|
{
|
|
/* This card is already initialized - switch to it */
|
|
sd_card_mux(card_no);
|
|
return;
|
|
}
|
|
|
|
if (currcard->initialized == 0)
|
|
{
|
|
/* Card needs (re)init */
|
|
sd_init_card(card_no);
|
|
}
|
|
}
|
|
|
|
static inline bool card_detect_target(void)
|
|
{
|
|
#ifdef SANSA_CONNECT
|
|
bool removed;
|
|
|
|
removed = IO_GIO_BITSET0 & (1 << 14);
|
|
|
|
return !removed;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
|
|
#ifdef HAVE_HOTSWAP
|
|
|
|
static int sd1_oneshot_callback(struct timeout *tmo)
|
|
{
|
|
(void)tmo;
|
|
|
|
/* This is called only if the state was stable for 300ms - check state
|
|
* and post appropriate event. */
|
|
if (card_detect_target())
|
|
{
|
|
queue_broadcast(SYS_HOTSWAP_INSERTED, 0);
|
|
}
|
|
else
|
|
queue_broadcast(SYS_HOTSWAP_EXTRACTED, 0);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef SANSA_CONNECT
|
|
void GIO14(void) __attribute__ ((section(".icode")));
|
|
void GIO14(void)
|
|
{
|
|
static struct timeout sd1_oneshot;
|
|
|
|
/* clear interrupt */
|
|
IO_INTC_IRQ2 = (1<<3);
|
|
|
|
timeout_register(&sd1_oneshot, sd1_oneshot_callback, (3*HZ/10), 0);
|
|
}
|
|
#endif
|
|
|
|
bool sd_removable(IF_MD_NONVOID(int card_no))
|
|
{
|
|
#ifndef HAVE_MULTIDRIVE
|
|
const int card_no = 0;
|
|
#endif
|
|
|
|
return (card_no == CARD_NUM_SLOT);
|
|
}
|
|
|
|
bool sd_present(IF_MD_NONVOID(int card_no))
|
|
{
|
|
#ifndef HAVE_MULTIDRIVE
|
|
const int card_no = 0;
|
|
#endif
|
|
|
|
return (card_no == CARD_NUM_SLOT) ? card_detect_target() :
|
|
#ifdef SANSA_CONNECT
|
|
true; /* iNAND is always present */
|
|
#else
|
|
false;
|
|
#endif
|
|
}
|
|
|
|
#else /* no hotswap */
|
|
|
|
bool sd_removable(IF_MD_NONVOID(int card_no))
|
|
{
|
|
#ifdef HAVE_MULTIDRIVE
|
|
(void)card_no;
|
|
#endif
|
|
|
|
/* not applicable */
|
|
return false;
|
|
}
|
|
|
|
#endif /* HAVE_HOTSWAP */
|
|
|
|
static void sd_thread(void) NORETURN_ATTR;
|
|
static void sd_thread(void)
|
|
{
|
|
struct queue_event ev;
|
|
bool idle_notified = false;
|
|
|
|
while (1)
|
|
{
|
|
queue_wait_w_tmo(&sd_queue, &ev, HZ);
|
|
switch ( ev.id )
|
|
{
|
|
#ifdef HAVE_HOTSWAP
|
|
case SYS_HOTSWAP_INSERTED:
|
|
case SYS_HOTSWAP_EXTRACTED:
|
|
{
|
|
int success = 1;
|
|
fat_lock(); /* lock-out FAT activity first -
|
|
prevent deadlocking via disk_mount that
|
|
would cause a reverse-order attempt with
|
|
another thread */
|
|
mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
|
|
into driver that bypass the fat cache */
|
|
|
|
/* We now have exclusive control of fat cache and ata */
|
|
|
|
disk_unmount(0); /* release "by force", ensure file
|
|
descriptors aren't leaked and any busy
|
|
ones are invalid if mounting */
|
|
|
|
/* 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[0].initialized = 0;
|
|
|
|
if (ev.id == SYS_HOTSWAP_INSERTED)
|
|
{
|
|
/* FIXME: once sd_enabled is implement properly,
|
|
* reinitializing the controllers might be needed */
|
|
sd_enable(true);
|
|
if (success < 0) /* initialisation failed */
|
|
panicf("SD init failed : %d", success);
|
|
success = disk_mount(0); /* 0 if fail */
|
|
}
|
|
|
|
/* notify the system about the changed filesystems
|
|
*/
|
|
if (success)
|
|
queue_broadcast(SYS_FS_CHANGED, 0);
|
|
|
|
/* Access is now safe */
|
|
mutex_unlock(&sd_mtx);
|
|
fat_unlock();
|
|
sd_enable(false);
|
|
}
|
|
break;
|
|
#endif
|
|
case SYS_TIMEOUT:
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int sd_wait_for_state(unsigned int state)
|
|
{
|
|
unsigned long response = 0;
|
|
unsigned int timeout = HZ; /* ticks */
|
|
long t = current_tick;
|
|
|
|
while (1)
|
|
{
|
|
long tick;
|
|
int ret = sd_command(SD_SEND_STATUS, currcard->rca,
|
|
SDHC_RESP_FMT_1, &response);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if ((SD_R1_CURRENT_STATE(response) == state))
|
|
{
|
|
return EC_OK;
|
|
}
|
|
|
|
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 *buffer, bool write)
|
|
{
|
|
int ret;
|
|
unsigned long start_addr;
|
|
int dma_channel = -1;
|
|
bool use_direct_dma;
|
|
int count_per_dma;
|
|
unsigned long rel_addr;
|
|
|
|
dbgprintf("transfer %d %d %d", card_no, start, count);
|
|
mutex_lock(&sd_mtx);
|
|
sd_enable(true);
|
|
|
|
sd_transfer_retry:
|
|
if (card_no == CARD_NUM_SLOT && !card_detect_target())
|
|
{
|
|
/* no external sd-card inserted */
|
|
ret = -EC_NOCARD;
|
|
goto sd_transfer_error;
|
|
}
|
|
|
|
sd_select_device(card_no);
|
|
|
|
if (currcard->initialized < 0)
|
|
{
|
|
ret = currcard->initialized;
|
|
goto sd_transfer_error;
|
|
}
|
|
|
|
last_disk_activity = current_tick;
|
|
|
|
ret = sd_wait_for_state(SD_TRAN);
|
|
if (ret < EC_OK)
|
|
{
|
|
goto sd_transfer_error;
|
|
}
|
|
|
|
IO_MMC_BLOCK_LENGTH = currcard->blocksize;
|
|
|
|
start_addr = start;
|
|
|
|
do
|
|
{
|
|
count_per_dma = count;
|
|
|
|
if (((unsigned long)buffer) & 0x1F)
|
|
{
|
|
/* MMC/SD interface requires 32-byte alignment of buffer */
|
|
use_direct_dma = false;
|
|
if (count > UNALIGNED_NUM_SECTORS)
|
|
{
|
|
count_per_dma = UNALIGNED_NUM_SECTORS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
use_direct_dma = true;
|
|
}
|
|
|
|
if (write == true)
|
|
{
|
|
if (use_direct_dma == false)
|
|
{
|
|
memcpy(aligned_buffer, buffer, count_per_dma*SD_BLOCK_SIZE);
|
|
}
|
|
commit_dcache_range(use_direct_dma ? buffer : aligned_buffer,
|
|
count_per_dma*SD_BLOCK_SIZE);
|
|
}
|
|
|
|
IO_MMC_NR_BLOCKS = count_per_dma;
|
|
|
|
/* Set start_addr to the correct unit (blocks or bytes) */
|
|
if (!(card_info[card_no].ocr & SD_OCR_CARD_CAPACITY_STATUS))
|
|
start_addr *= SD_BLOCK_SIZE; /* not SDHC */
|
|
|
|
ret = sd_command(write ? SD_WRITE_MULTIPLE_BLOCK : SD_READ_MULTIPLE_BLOCK,
|
|
start_addr, MMC_CMD_DCLR | MMC_CMD_DATA |
|
|
SDHC_RESP_FMT_1 | (write ? MMC_CMD_WRITE : 0),
|
|
NULL);
|
|
|
|
if (ret < 0)
|
|
goto sd_transfer_error;
|
|
|
|
/* other burst modes are not supported for this peripheral */
|
|
dma_channel = dma_request_channel(DMA_PERIPHERAL_MMCSD,
|
|
DMA_MODE_8_BURST);
|
|
|
|
if (use_direct_dma == true)
|
|
{
|
|
rel_addr = ((unsigned long)buffer)-CONFIG_SDRAM_START;
|
|
}
|
|
else
|
|
{
|
|
rel_addr = ((unsigned long)aligned_buffer)-CONFIG_SDRAM_START;
|
|
}
|
|
|
|
IO_MMC_SD_DMA_ADDR_LOW = rel_addr & 0xFFFF;
|
|
IO_MMC_SD_DMA_ADDR_HI = (rel_addr & 0xFFFF0000) >> 16;
|
|
|
|
IO_MMC_SD_DMA_MODE |= MMC_DMAMODE_ENABLE;
|
|
if (write == true)
|
|
{
|
|
IO_MMC_SD_DMA_MODE |= MMC_DMAMODE_WRITE;
|
|
}
|
|
|
|
IO_MMC_SD_DMA_TRIGGER = 1;
|
|
|
|
dbgprintf("SD DMA transfer in progress");
|
|
|
|
ret = dma_wait_for_completion();
|
|
dma_release_channel(dma_channel);
|
|
|
|
dbgprintf("SD DMA transfer complete");
|
|
|
|
if (ret != EC_OK)
|
|
{
|
|
goto sd_transfer_error;
|
|
}
|
|
|
|
count -= count_per_dma;
|
|
|
|
if (write == false)
|
|
{
|
|
discard_dcache_range(use_direct_dma ? buffer : aligned_buffer,
|
|
count_per_dma*SD_BLOCK_SIZE);
|
|
|
|
if (use_direct_dma == false)
|
|
{
|
|
memcpy(buffer, aligned_buffer, count_per_dma*SD_BLOCK_SIZE);
|
|
}
|
|
}
|
|
|
|
buffer += count_per_dma*SD_BLOCK_SIZE;
|
|
start_addr += count_per_dma;
|
|
|
|
last_disk_activity = current_tick;
|
|
|
|
ret = sd_command(SD_STOP_TRANSMISSION, 0, SDHC_RESP_FMT_1, NULL);
|
|
if (ret < 0)
|
|
{
|
|
goto sd_transfer_error;
|
|
}
|
|
|
|
ret = sd_wait_for_state(SD_TRAN);
|
|
if (ret < 0)
|
|
{
|
|
goto sd_transfer_error;
|
|
}
|
|
} while (count > 0);
|
|
|
|
while (1)
|
|
{
|
|
sd_enable(false);
|
|
mutex_unlock(&sd_mtx);
|
|
|
|
return ret;
|
|
|
|
sd_transfer_error:
|
|
if (sd_status[card_no].retry < sd_status[card_no].retry_max
|
|
&& ret != -EC_NOCARD)
|
|
{
|
|
sd_status[card_no].retry++;
|
|
currcard->initialized = 0;
|
|
goto sd_transfer_retry;
|
|
}
|
|
}
|
|
}
|
|
|
|
int sd_read_sectors(IF_MD(int card_no,) unsigned long start, int incount,
|
|
void* inbuf)
|
|
{
|
|
#ifndef HAVE_MULTIDRIVE
|
|
const int card_no = 0;
|
|
#endif
|
|
return sd_transfer_sectors(card_no, start, incount, inbuf, false);
|
|
}
|
|
|
|
int sd_write_sectors(IF_MD(int card_no,) unsigned long start, int count,
|
|
const void* outbuf)
|
|
{
|
|
#ifndef BOOTLOADER
|
|
#ifndef HAVE_MULTIDRIVE
|
|
const int card_no = 0;
|
|
#endif
|
|
return sd_transfer_sectors(card_no, start, count, (void*)outbuf, true);
|
|
#else /* we don't need write support in bootloader */
|
|
#ifdef HAVE_MULTIDRIVE
|
|
(void)card_no;
|
|
#endif
|
|
(void)start;
|
|
(void)count;
|
|
(void)outbuf;
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
int sd_init(void)
|
|
{
|
|
int ret = EC_OK;
|
|
|
|
#ifndef BOOTLOADER
|
|
sd_enabled = true;
|
|
sd_enable(false);
|
|
#endif
|
|
mutex_init(&sd_mtx);
|
|
|
|
mutex_lock(&sd_mtx);
|
|
initialized = true;
|
|
|
|
/* based on linux/drivers/mmc/dm320mmc.c
|
|
Copyright (C) 2006 ZSI, All Rights Reserved.
|
|
Written by: Ben Bostwick */
|
|
|
|
bitclr16(&IO_CLK_MOD2, CLK_MOD2_MMC);
|
|
bitset16(&IO_CLK_INV, CLK_INV_MMC);
|
|
|
|
/* mmc module clock: 75 Mhz (AHB) / 2 = ~37.5 Mhz
|
|
* (Frequencies above are taken from Sansa Connect's OF source code) */
|
|
IO_CLK_DIV3 = (IO_CLK_DIV3 & 0xFF00) | 0x02; /* OF uses 1 */
|
|
|
|
bitset16(&IO_CLK_MOD2, CLK_MOD2_MMC);
|
|
|
|
/* set mmc module into reset */
|
|
bitset16(&IO_MMC_CONTROL, (MMC_CTRL_DATRST | MMC_CTRL_CMDRST));
|
|
|
|
/* set resp timeout to max */
|
|
IO_MMC_RESPONSE_TIMEOUT |= 0x1FFF;
|
|
IO_MMC_READ_TIMEOUT = 0xFFFF;
|
|
|
|
/* all done, take mmc module out of reset */
|
|
bitclr16(&IO_MMC_CONTROL, (MMC_CTRL_DATRST | MMC_CTRL_CMDRST));
|
|
|
|
#ifdef SANSA_CONNECT
|
|
/* GIO37 - Power Card; GIO38 - Power iNAND (both active low) */
|
|
IO_GIO_DIR2 &= ~((1 << 5) /* GIO37 */ | (1 << 6) /* GIO38 */);
|
|
IO_GIO_INV2 &= ~((1 << 5) /* GIO37 */ | (1 << 6) /* GIO38 */);
|
|
IO_GIO_BITCLR2 = (1 << 5) | (1 << 6);
|
|
|
|
/* GIO6 - select Card; GIO5 - select iNAND (both active low) */
|
|
IO_GIO_DIR0 &= ~((1 << 6) /* GIO6 */ | (1 << 5) /* GIO5 */);
|
|
IO_GIO_INV0 &= ~((1 << 6) /* GIO6 */ | (1 << 5) /* GIO5 */);
|
|
IO_GIO_BITSET0 = (1 << 6) | (1 << 5);
|
|
|
|
#ifdef HAVE_HOTSWAP
|
|
/* GIO14 is card detect */
|
|
IO_GIO_DIR0 |= (1 << 14); /* Set GIO14 as input */
|
|
IO_GIO_INV0 &= ~(1 << 14); /* GIO14 not inverted */
|
|
IO_GIO_IRQPORT |= (1 << 14); /* Enable GIO14 external interrupt */
|
|
IO_GIO_IRQEDGE |= (1 << 14); /* Any edge detection */
|
|
|
|
/* Enable GIO14 interrupt */
|
|
IO_INTC_EINT2 |= INTR_EINT2_EXT14;
|
|
#endif
|
|
#endif
|
|
|
|
sd_select_device(1);
|
|
|
|
/* Disable Memory Card CLK - it is enabled on demand by TMS320DM320 */
|
|
bitclr16(&IO_MMC_MEM_CLK_CONTROL, (1 << 8));
|
|
|
|
queue_init(&sd_queue, true);
|
|
sd_thread_id = create_thread(sd_thread, sd_stack, sizeof(sd_stack),
|
|
0, sd_thread_name IF_PRIO(, PRIORITY_USER_INTERFACE)
|
|
IF_COP(, CPU));
|
|
|
|
mutex_unlock(&sd_mtx);
|
|
|
|
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];
|
|
}
|
|
|
|
void sd_sleepnow(void)
|
|
{
|
|
}
|
|
|