3e7a09cb0d
What works: - LCD: 16-bit RGB565 - all buttons, including scrollwheel - SD Card - Battery level and charging/not charging status - USB - audio - sample rate switching - HP / LO detect, with "safe" fixed LO volume - LO volume will only be put to user-defined max volume if headphones are not present. - rtc - Plugins build, tried a couple and they seem OK - Bootloader, installable to nand via usbboot What doesn't work: - Dual Boot - power on/off has intermittent, low volume audio click (sometimes it's completely silent, sometimes there's a click) - Audio uses 16-bit volume scaling, so clicking/popping is pretty bad at lower volumes - need 32 bit volume scaling, 24 bit I2S data - USB HID keys not yet defined - no jztool support Unknowns: - Stereo Switch pins: Direction select, AC_DC (probably not even hooked up) - What is the actual purpose of the Stereo Swtich? - How does the bluetooth module connect? "Someday" stuff: - get LCD working at higher bit depth - Bluetooth Change-Id: I70dda8fc092c6e3f4352f2245e4164193f803c33
917 lines
25 KiB
C
917 lines
25 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2021 Aidan MacDonald
|
|
*
|
|
* 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 "system.h"
|
|
#include "panic.h"
|
|
#include "msc-x1000.h"
|
|
#include "gpio-x1000.h"
|
|
#include "irq-x1000.h"
|
|
#include "clk-x1000.h"
|
|
#include "x1000/msc.h"
|
|
#include "x1000/cpm.h"
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
|
|
/* #define LOGF_ENABLE */
|
|
#include "logf.h"
|
|
|
|
/* TODO - this needs some auditing to better handle errors
|
|
*
|
|
* There should be a clearer code path involving errors. Especially we should
|
|
* ensure that removing the card always resets the driver to a sane state.
|
|
*/
|
|
|
|
#define DEBOUNCE_TIME (HZ/10)
|
|
|
|
static const msc_config msc_configs[] = {
|
|
#if defined(FIIO_M3K)
|
|
#define MSC_CLOCK_SOURCE X1000_CLK_SCLK_A
|
|
{
|
|
.msc_nr = 0,
|
|
.msc_type = MSC_TYPE_SD,
|
|
.bus_width = 4,
|
|
.label = "microSD",
|
|
.cd_gpio = GPIO_MSC0_CD,
|
|
.cd_active_level = 0,
|
|
},
|
|
#elif defined(SHANLING_Q1)
|
|
#define MSC_CLOCK_SOURCE X1000_CLK_MPLL
|
|
{
|
|
.msc_nr = 0,
|
|
.msc_type = MSC_TYPE_SD,
|
|
.bus_width = 4,
|
|
.label = "microSD",
|
|
.cd_gpio = GPIO_MSC0_CD,
|
|
.cd_active_level = 0,
|
|
},
|
|
/* NOTE: SDIO wifi card is on msc1 */
|
|
#elif defined(EROS_QN)
|
|
#define MSC_CLOCK_SOURCE X1000_CLK_SCLK_A
|
|
{
|
|
.msc_nr = 0,
|
|
.msc_type = MSC_TYPE_SD,
|
|
.bus_width = 4,
|
|
.label = "microSD",
|
|
.cd_gpio = GPIO_MSC0_CD,
|
|
.cd_active_level = 0,
|
|
},
|
|
#else
|
|
# error "Please add X1000 MSC config"
|
|
#endif
|
|
{.msc_nr = -1},
|
|
};
|
|
|
|
static const msc_config* msc_lookup_config(int msc)
|
|
{
|
|
for(int i = 0; i < MSC_COUNT; ++i)
|
|
if(msc_configs[i].msc_nr == msc)
|
|
return &msc_configs[i];
|
|
return NULL;
|
|
}
|
|
|
|
static msc_drv msc_drivers[MSC_COUNT];
|
|
|
|
static void msc0_cd_interrupt(void);
|
|
static void msc1_cd_interrupt(void);
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* Initialization
|
|
*/
|
|
|
|
static void msc_gate_clock(int msc, bool gate)
|
|
{
|
|
int bit;
|
|
if(msc == 0)
|
|
bit = BM_CPM_CLKGR_MSC0;
|
|
else
|
|
bit = BM_CPM_CLKGR_MSC1;
|
|
|
|
if(gate)
|
|
REG_CPM_CLKGR |= bit;
|
|
else
|
|
REG_CPM_CLKGR &= ~bit;
|
|
}
|
|
|
|
static void msc_init_one(msc_drv* d, int msc)
|
|
{
|
|
/* Lookup config */
|
|
d->drive_nr = -1;
|
|
d->config = msc_lookup_config(msc);
|
|
if(!d->config) {
|
|
d->msc_nr = -1;
|
|
return;
|
|
}
|
|
|
|
/* Initialize driver state */
|
|
d->msc_nr = msc;
|
|
d->driver_flags = 0;
|
|
d->clk_status = 0;
|
|
d->cmdat_def = jz_orf(MSC_CMDAT, RTRG_V(GE32), TTRG_V(LE32));
|
|
d->req = NULL;
|
|
d->iflag_done = 0;
|
|
d->card_present = 1;
|
|
d->card_present_last = 1;
|
|
d->req_running = 0;
|
|
mutex_init(&d->lock);
|
|
semaphore_init(&d->cmd_done, 1, 0);
|
|
|
|
/* Ensure correct clock source */
|
|
jz_writef(CPM_MSC0CDR, CE(1), CLKDIV(0),
|
|
CLKSRC(MSC_CLOCK_SOURCE == X1000_CLK_MPLL ? 1 : 0));
|
|
while(jz_readf(CPM_MSC0CDR, BUSY));
|
|
jz_writef(CPM_MSC0CDR, CE(0));
|
|
|
|
/* Initialize the hardware */
|
|
msc_gate_clock(msc, false);
|
|
msc_full_reset(d);
|
|
system_enable_irq(msc == 0 ? IRQ_MSC0 : IRQ_MSC1);
|
|
|
|
/* Setup the card detect IRQ */
|
|
if(d->config->cd_gpio != GPIO_NONE) {
|
|
if(gpio_get_level(d->config->cd_gpio) != d->config->cd_active_level) {
|
|
d->card_present = 0;
|
|
d->card_present_last = 0;
|
|
}
|
|
|
|
system_set_irq_handler(GPIO_TO_IRQ(d->config->cd_gpio),
|
|
msc == 0 ? msc0_cd_interrupt : msc1_cd_interrupt);
|
|
gpio_set_function(d->config->cd_gpio, GPIOF_IRQ_EDGE(1));
|
|
gpio_flip_edge_irq(d->config->cd_gpio);
|
|
gpio_enable_irq(d->config->cd_gpio);
|
|
}
|
|
}
|
|
|
|
void msc_init(void)
|
|
{
|
|
/* Only do this once -- each storage subsystem calls us in its init */
|
|
static bool done = false;
|
|
if(done)
|
|
return;
|
|
done = true;
|
|
|
|
/* Set up each MSC driver according to msc_configs */
|
|
for(int i = 0; i < MSC_COUNT; ++i)
|
|
msc_init_one(&msc_drivers[i], i);
|
|
}
|
|
|
|
msc_drv* msc_get(int type, int index)
|
|
{
|
|
for(int i = 0, m = 0; i < MSC_COUNT; ++i) {
|
|
if(msc_drivers[i].config == NULL)
|
|
continue;
|
|
if(type == MSC_TYPE_ANY || msc_drivers[i].config->msc_type == type)
|
|
if(index == m++)
|
|
return &msc_drivers[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
msc_drv* msc_get_by_drive(int drive_nr)
|
|
{
|
|
for(int i = 0; i < MSC_COUNT; ++i)
|
|
if(msc_drivers[i].drive_nr == drive_nr)
|
|
return &msc_drivers[i];
|
|
return NULL;
|
|
}
|
|
|
|
void msc_lock(msc_drv* d)
|
|
{
|
|
mutex_lock(&d->lock);
|
|
}
|
|
|
|
void msc_unlock(msc_drv* d)
|
|
{
|
|
mutex_unlock(&d->lock);
|
|
}
|
|
|
|
void msc_full_reset(msc_drv* d)
|
|
{
|
|
msc_lock(d);
|
|
msc_set_clock_mode(d, MSC_CLK_AUTOMATIC);
|
|
msc_set_speed(d, MSC_SPEED_INIT);
|
|
msc_set_width(d, 1);
|
|
msc_ctl_reset(d);
|
|
d->driver_flags = 0;
|
|
memset(&d->cardinfo, 0, sizeof(tCardInfo));
|
|
msc_unlock(d);
|
|
}
|
|
|
|
bool msc_card_detect(msc_drv* d)
|
|
{
|
|
if(d->config->cd_gpio == GPIO_NONE)
|
|
return true;
|
|
|
|
return gpio_get_level(d->config->cd_gpio) == d->config->cd_active_level;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* Controller API
|
|
*/
|
|
|
|
void msc_ctl_reset(msc_drv* d)
|
|
{
|
|
/* Ingenic code suggests a reset changes clkrt */
|
|
int clkrt = REG_MSC_CLKRT(d->msc_nr);
|
|
|
|
/* Send reset -- bit is NOT self clearing */
|
|
jz_overwritef(MSC_CTRL(d->msc_nr), RESET(1));
|
|
udelay(100);
|
|
jz_writef(MSC_CTRL(d->msc_nr), RESET(0));
|
|
|
|
/* Verify reset in the status register */
|
|
long deadline = current_tick + HZ;
|
|
while(jz_readf(MSC_STAT(d->msc_nr), IS_RESETTING) &&
|
|
current_tick < deadline) {
|
|
sleep(1);
|
|
}
|
|
|
|
/* Ensure the clock state is as expected */
|
|
if(d->clk_status & MSC_CLKST_AUTO)
|
|
jz_writef(MSC_LPM(d->msc_nr), ENABLE(1));
|
|
else if(d->clk_status & MSC_CLKST_ENABLE)
|
|
jz_overwritef(MSC_CTRL(d->msc_nr), CLOCK_V(START));
|
|
else
|
|
jz_overwritef(MSC_CTRL(d->msc_nr), CLOCK_V(STOP));
|
|
|
|
/* Clear and mask interrupts */
|
|
REG_MSC_IMASK(d->msc_nr) = 0xffffffff;
|
|
REG_MSC_IFLAG(d->msc_nr) = 0xffffffff;
|
|
|
|
/* Restore clkrt */
|
|
REG_MSC_CLKRT(d->msc_nr) = clkrt;
|
|
}
|
|
|
|
void msc_set_clock_mode(msc_drv* d, int mode)
|
|
{
|
|
int cur_mode = (d->clk_status & MSC_CLKST_AUTO) ? MSC_CLK_AUTOMATIC
|
|
: MSC_CLK_MANUAL;
|
|
if(mode == cur_mode)
|
|
return;
|
|
|
|
d->clk_status &= ~MSC_CLKST_ENABLE;
|
|
if(mode == MSC_CLK_AUTOMATIC) {
|
|
d->clk_status |= MSC_CLKST_AUTO;
|
|
jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(STOP));
|
|
jz_writef(MSC_LPM(d->msc_nr), ENABLE(1));
|
|
} else {
|
|
d->clk_status &= ~MSC_CLKST_AUTO;
|
|
jz_writef(MSC_LPM(d->msc_nr), ENABLE(0));
|
|
jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(STOP));
|
|
}
|
|
}
|
|
|
|
void msc_enable_clock(msc_drv* d, bool enable)
|
|
{
|
|
if(d->clk_status & MSC_CLKST_AUTO)
|
|
return;
|
|
|
|
bool is_enabled = (d->clk_status & MSC_CLKST_ENABLE);
|
|
if(enable == is_enabled)
|
|
return;
|
|
|
|
if(enable) {
|
|
jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(START));
|
|
d->clk_status |= MSC_CLKST_ENABLE;
|
|
} else {
|
|
jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(STOP));
|
|
d->clk_status &= ~MSC_CLKST_ENABLE;
|
|
}
|
|
}
|
|
|
|
void msc_set_speed(msc_drv* d, int rate)
|
|
{
|
|
/* Shut down clock while we change frequencies */
|
|
if(d->clk_status & MSC_CLKST_ENABLE)
|
|
jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(STOP));
|
|
|
|
/* Wait for clock to go idle */
|
|
while(jz_readf(MSC_STAT(d->msc_nr), CLOCK_EN))
|
|
sleep(1);
|
|
|
|
/* freq1 is output by MSCxDIV; freq2 is output by MSC_CLKRT */
|
|
uint32_t freq1 = rate;
|
|
uint32_t freq2 = rate;
|
|
if(freq1 < MSC_SPEED_FAST)
|
|
freq1 = MSC_SPEED_FAST;
|
|
|
|
/* Handle MSCxDIV */
|
|
uint32_t src_freq = clk_get(MSC_CLOCK_SOURCE) / 2;
|
|
uint32_t div = clk_calc_div(src_freq, freq1);
|
|
if(d->msc_nr == 0) {
|
|
jz_writef(CPM_MSC0CDR, CE(1), CLKDIV(div - 1));
|
|
while(jz_readf(CPM_MSC0CDR, BUSY));
|
|
jz_writef(CPM_MSC0CDR, CE(0));
|
|
} else {
|
|
jz_writef(CPM_MSC1CDR, CE(1), CLKDIV(div - 1));
|
|
while(jz_readf(CPM_MSC1CDR, BUSY));
|
|
jz_writef(CPM_MSC1CDR, CE(0));
|
|
}
|
|
|
|
/* Handle MSC_CLKRT */
|
|
uint32_t clkrt = clk_calc_shift(src_freq/div, freq2);
|
|
REG_MSC_CLKRT(d->msc_nr) = clkrt;
|
|
|
|
/* Handle frequency dependent timing settings
|
|
* TODO - these settings might be SD specific...
|
|
*/
|
|
uint32_t out_freq = (src_freq/div) >> clkrt;
|
|
if(out_freq > MSC_SPEED_FAST) {
|
|
jz_writef(MSC_LPM(d->msc_nr),
|
|
DRV_SEL_V(RISE_EDGE_DELAY_QTR_PHASE),
|
|
SMP_SEL_V(RISE_EDGE_DELAYED));
|
|
jz_writef(MSC_CTRL2(d->msc_nr), SPEED_V(HIGHSPEED));
|
|
} else {
|
|
jz_writef(MSC_LPM(d->msc_nr),
|
|
DRV_SEL_V(FALL_EDGE),
|
|
SMP_SEL_V(RISE_EDGE));
|
|
jz_writef(MSC_CTRL2(d->msc_nr), SPEED_V(DEFAULT));
|
|
}
|
|
|
|
/* Restart clock if it was running before */
|
|
if(d->clk_status & MSC_CLKST_ENABLE)
|
|
jz_writef(MSC_CTRL(d->msc_nr), CLOCK_V(START));
|
|
}
|
|
|
|
void msc_set_width(msc_drv* d, int width)
|
|
{
|
|
/* Bus width is controlled per command with MSC_CMDAT. */
|
|
if(width == 8)
|
|
jz_vwritef(d->cmdat_def, MSC_CMDAT, BUS_WIDTH_V(8BIT));
|
|
else if(width == 4)
|
|
jz_vwritef(d->cmdat_def, MSC_CMDAT, BUS_WIDTH_V(4BIT));
|
|
else
|
|
jz_vwritef(d->cmdat_def, MSC_CMDAT, BUS_WIDTH_V(1BIT));
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* Request API
|
|
*/
|
|
|
|
/* Note -- this must only be called with IRQs disabled */
|
|
static void msc_finish_request(msc_drv* d, int status)
|
|
{
|
|
REG_MSC_IMASK(d->msc_nr) = 0xffffffff;
|
|
REG_MSC_IFLAG(d->msc_nr) = 0xffffffff;
|
|
if(d->req->flags & MSC_RF_DATA)
|
|
jz_writef(MSC_DMAC(d->msc_nr), ENABLE(0));
|
|
|
|
d->req->status = status;
|
|
d->req_running = 0;
|
|
d->iflag_done = 0;
|
|
timeout_cancel(&d->cmd_tmo);
|
|
semaphore_release(&d->cmd_done);
|
|
}
|
|
|
|
static int msc_req_timeout(struct timeout* tmo)
|
|
{
|
|
msc_drv* d = (msc_drv*)tmo->data;
|
|
msc_async_abort(d, MSC_REQ_LOCKUP);
|
|
return 0;
|
|
}
|
|
|
|
void msc_async_start(msc_drv* d, msc_req* r)
|
|
{
|
|
/* Determined needed cmdat and interrupts */
|
|
unsigned cmdat = d->cmdat_def;
|
|
d->iflag_done = jz_orm(MSC_IFLAG, END_CMD_RES);
|
|
|
|
cmdat |= jz_orf(MSC_CMDAT, RESP_FMT(r->resptype & ~MSC_RESP_BUSY));
|
|
if(r->resptype & MSC_RESP_BUSY)
|
|
cmdat |= jz_orm(MSC_CMDAT, BUSY);
|
|
|
|
if(r->flags & MSC_RF_INIT)
|
|
cmdat |= jz_orm(MSC_CMDAT, INIT);
|
|
|
|
if(r->flags & MSC_RF_DATA) {
|
|
cmdat |= jz_orm(MSC_CMDAT, DATA_EN);
|
|
if(r->flags & MSC_RF_PROG)
|
|
d->iflag_done = jz_orm(MSC_IFLAG, WR_ALL_DONE);
|
|
else
|
|
d->iflag_done = jz_orm(MSC_IFLAG, DMA_DATA_DONE);
|
|
}
|
|
|
|
if(r->flags & MSC_RF_WRITE)
|
|
cmdat |= jz_orm(MSC_CMDAT, WRITE_READ);
|
|
|
|
if(r->flags & MSC_RF_AUTO_CMD12)
|
|
cmdat |= jz_orm(MSC_CMDAT, AUTO_CMD12);
|
|
|
|
if(r->flags & MSC_RF_ABORT)
|
|
cmdat |= jz_orm(MSC_CMDAT, IO_ABORT);
|
|
|
|
unsigned imask = jz_orm(MSC_IMASK,
|
|
CRC_RES_ERROR, CRC_READ_ERROR, CRC_WRITE_ERROR,
|
|
TIME_OUT_RES, TIME_OUT_READ, END_CMD_RES);
|
|
imask |= d->iflag_done;
|
|
|
|
/* Program the controller */
|
|
if(r->flags & MSC_RF_DATA) {
|
|
REG_MSC_NOB(d->msc_nr) = r->nr_blocks;
|
|
REG_MSC_BLKLEN(d->msc_nr) = r->block_len;
|
|
}
|
|
|
|
REG_MSC_CMD(d->msc_nr) = r->command;
|
|
REG_MSC_ARG(d->msc_nr) = r->argument;
|
|
REG_MSC_CMDAT(d->msc_nr) = cmdat;
|
|
|
|
REG_MSC_IFLAG(d->msc_nr) = imask;
|
|
REG_MSC_IMASK(d->msc_nr) &= ~imask;
|
|
|
|
if(r->flags & MSC_RF_DATA) {
|
|
d->dma_desc.nda = 0;
|
|
d->dma_desc.mem = PHYSADDR(r->data);
|
|
d->dma_desc.len = r->nr_blocks * r->block_len;
|
|
d->dma_desc.cmd = 2; /* ID=0, ENDI=1, LINK=0 */
|
|
commit_dcache_range(&d->dma_desc, sizeof(d->dma_desc));
|
|
|
|
if(r->flags & MSC_RF_WRITE)
|
|
commit_dcache_range(r->data, d->dma_desc.len);
|
|
else
|
|
discard_dcache_range(r->data, d->dma_desc.len);
|
|
|
|
/* Unaligned address for DMA doesn't seem to work correctly.
|
|
* FAT FS driver should ensure proper alignment of all buffers,
|
|
* so in practice this panic should not occur, but if it does
|
|
* I want to hear about it. */
|
|
if(UNLIKELY(d->dma_desc.mem & 3)) {
|
|
panicf("msc%d bad align: %08x", d->msc_nr,
|
|
(unsigned)d->dma_desc.mem);
|
|
}
|
|
|
|
jz_writef(MSC_DMAC(d->msc_nr), MODE_SEL(0), INCR(0), DMASEL(0));
|
|
REG_MSC_DMANDA(d->msc_nr) = PHYSADDR(&d->dma_desc);
|
|
}
|
|
|
|
/* Begin processing */
|
|
d->req = r;
|
|
d->req_running = 1;
|
|
jz_writef(MSC_CTRL(d->msc_nr), START_OP(1));
|
|
if(r->flags & MSC_RF_DATA)
|
|
jz_writef(MSC_DMAC(d->msc_nr), ENABLE(1));
|
|
|
|
/* TODO: calculate a suitable lower value for the lockup timeout.
|
|
*
|
|
* The SD spec defines timings based on the number of blocks transferred,
|
|
* see sec. 4.6.2 "Read, write, and erase timeout conditions". This should
|
|
* reduce the long delays which happen if errors occur.
|
|
*
|
|
* Also need to check if registers MSC_RDTO / MSC_RESTO are correctly set.
|
|
*/
|
|
timeout_register(&d->cmd_tmo, msc_req_timeout, 10*HZ, (intptr_t)d);
|
|
}
|
|
|
|
void msc_async_abort(msc_drv* d, int status)
|
|
{
|
|
int irq = disable_irq_save();
|
|
if(d->req_running) {
|
|
logf("msc%d: async abort status:%d", d->msc_nr, status);
|
|
msc_finish_request(d, status);
|
|
}
|
|
|
|
restore_irq(irq);
|
|
}
|
|
|
|
int msc_async_wait(msc_drv* d, int timeout)
|
|
{
|
|
if(semaphore_wait(&d->cmd_done, timeout) == OBJ_WAIT_TIMEDOUT)
|
|
return MSC_REQ_INCOMPLETE;
|
|
|
|
return d->req->status;
|
|
}
|
|
|
|
int msc_request(msc_drv* d, msc_req* r)
|
|
{
|
|
msc_async_start(d, r);
|
|
return msc_async_wait(d, TIMEOUT_BLOCK);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* Command response handling
|
|
*/
|
|
|
|
static void msc_read_response(msc_drv* d)
|
|
{
|
|
unsigned res = REG_MSC_RES(d->msc_nr);
|
|
unsigned dat;
|
|
switch(d->req->resptype) {
|
|
case MSC_RESP_R1:
|
|
case MSC_RESP_R1B:
|
|
case MSC_RESP_R3:
|
|
case MSC_RESP_R6:
|
|
case MSC_RESP_R7:
|
|
dat = res << 24;
|
|
res = REG_MSC_RES(d->msc_nr);
|
|
dat |= res << 8;
|
|
res = REG_MSC_RES(d->msc_nr);
|
|
dat |= res & 0xff;
|
|
d->req->response[0] = dat;
|
|
break;
|
|
|
|
case MSC_RESP_R2:
|
|
for(int i = 0; i < 4; ++i) {
|
|
dat = res << 24;
|
|
res = REG_MSC_RES(d->msc_nr);
|
|
dat |= res << 8;
|
|
res = REG_MSC_RES(d->msc_nr);
|
|
dat |= res >> 8;
|
|
d->req->response[i] = dat;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int msc_check_sd_response(msc_drv* d)
|
|
{
|
|
if(d->req->resptype == MSC_RESP_R1 ||
|
|
d->req->resptype == MSC_RESP_R1B) {
|
|
if(d->req->response[0] & SD_R1_CARD_ERROR) {
|
|
logf("msc%d: R1 card error: %08x", d->msc_nr, d->req->response[0]);
|
|
return MSC_REQ_CARD_ERR;
|
|
}
|
|
}
|
|
|
|
return MSC_REQ_SUCCESS;
|
|
}
|
|
|
|
static int msc_check_response(msc_drv* d)
|
|
{
|
|
switch(d->config->msc_type) {
|
|
case MSC_TYPE_SD:
|
|
return msc_check_sd_response(d);
|
|
default:
|
|
/* TODO - implement msc_check_response for MMC and CE-ATA */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* Interrupt handlers
|
|
*/
|
|
|
|
static void msc_interrupt(msc_drv* d)
|
|
{
|
|
const unsigned tmo_bits = jz_orm(MSC_IFLAG, TIME_OUT_READ, TIME_OUT_RES);
|
|
const unsigned crc_bits = jz_orm(MSC_IFLAG, CRC_RES_ERROR,
|
|
CRC_READ_ERROR, CRC_WRITE_ERROR);
|
|
const unsigned err_bits = tmo_bits | crc_bits;
|
|
|
|
unsigned iflag = REG_MSC_IFLAG(d->msc_nr) & ~REG_MSC_IMASK(d->msc_nr);
|
|
bool handled = false;
|
|
|
|
/* In case card was removed */
|
|
if(!msc_card_detect(d)) {
|
|
msc_finish_request(d, MSC_REQ_EXTRACTED);
|
|
return;
|
|
}
|
|
|
|
/* Check for errors */
|
|
if(iflag & err_bits) {
|
|
int st;
|
|
if(iflag & crc_bits)
|
|
st = MSC_REQ_CRC_ERR;
|
|
else if(iflag & tmo_bits)
|
|
st = MSC_REQ_TIMEOUT;
|
|
else
|
|
st = MSC_REQ_ERROR;
|
|
|
|
msc_finish_request(d, st);
|
|
return;
|
|
}
|
|
|
|
/* Read and check the command response */
|
|
if(iflag & BM_MSC_IFLAG_END_CMD_RES) {
|
|
msc_read_response(d);
|
|
int st = msc_check_response(d);
|
|
if(st == MSC_REQ_SUCCESS) {
|
|
jz_writef(MSC_IMASK(d->msc_nr), END_CMD_RES(1));
|
|
jz_overwritef(MSC_IFLAG(d->msc_nr), END_CMD_RES(1));
|
|
handled = true;
|
|
} else {
|
|
msc_finish_request(d, st);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Check if the "done" interrupt is signaled */
|
|
if(iflag & d->iflag_done) {
|
|
/* Discard after DMA in case of hardware cache prefetching.
|
|
* Only needed for read operations.
|
|
*/
|
|
if((d->req->flags & MSC_RF_DATA) != 0 &&
|
|
(d->req->flags & MSC_RF_WRITE) == 0) {
|
|
discard_dcache_range(d->req->data,
|
|
d->req->block_len * d->req->nr_blocks);
|
|
}
|
|
|
|
msc_finish_request(d, MSC_REQ_SUCCESS);
|
|
return;
|
|
}
|
|
|
|
if(!handled) {
|
|
panicf("msc%d: irq bug! iflag:%08x raw_iflag:%08lx imask:%08lx",
|
|
d->msc_nr, iflag, REG_MSC_IFLAG(d->msc_nr), REG_MSC_IMASK(d->msc_nr));
|
|
}
|
|
}
|
|
|
|
static int msc_cd_callback(struct timeout* tmo)
|
|
{
|
|
msc_drv* d = (msc_drv*)tmo->data;
|
|
int now_present = msc_card_detect(d) ? 1 : 0;
|
|
|
|
/* If the CD pin level changed during the timeout interval, then the
|
|
* signal is not yet stable and we need to wait longer. */
|
|
if(now_present != d->card_present_last) {
|
|
d->card_present_last = now_present;
|
|
return DEBOUNCE_TIME;
|
|
}
|
|
|
|
/* If there is a change, then broadcast the hotswap event */
|
|
if(now_present != d->card_present) {
|
|
if(now_present) {
|
|
d->card_present = 1;
|
|
queue_broadcast(SYS_HOTSWAP_INSERTED, d->drive_nr);
|
|
} else {
|
|
msc_async_abort(d, MSC_REQ_EXTRACTED);
|
|
d->card_present = 0;
|
|
queue_broadcast(SYS_HOTSWAP_EXTRACTED, d->drive_nr);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void msc_cd_interrupt(msc_drv* d)
|
|
{
|
|
/* Timer to debounce input */
|
|
d->card_present_last = msc_card_detect(d) ? 1 : 0;
|
|
timeout_register(&d->cd_tmo, msc_cd_callback, DEBOUNCE_TIME, (intptr_t)d);
|
|
|
|
/* Invert the IRQ */
|
|
gpio_flip_edge_irq(d->config->cd_gpio);
|
|
}
|
|
|
|
void MSC0(void)
|
|
{
|
|
msc_interrupt(&msc_drivers[0]);
|
|
}
|
|
|
|
void MSC1(void)
|
|
{
|
|
msc_interrupt(&msc_drivers[1]);
|
|
}
|
|
|
|
static void msc0_cd_interrupt(void)
|
|
{
|
|
msc_cd_interrupt(&msc_drivers[0]);
|
|
}
|
|
|
|
static void msc1_cd_interrupt(void)
|
|
{
|
|
msc_cd_interrupt(&msc_drivers[1]);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* SD command helpers
|
|
*/
|
|
|
|
int msc_cmd_exec(msc_drv* d, msc_req* r)
|
|
{
|
|
int status = msc_request(d, r);
|
|
if(status == MSC_REQ_SUCCESS)
|
|
return status;
|
|
else if(status == MSC_REQ_LOCKUP || status == MSC_REQ_EXTRACTED)
|
|
d->driver_flags |= MSC_DF_ERRSTATE;
|
|
else if(r->flags & (MSC_RF_ERR_CMD12|MSC_RF_AUTO_CMD12)) {
|
|
/* After an error, the controller does not automatically issue CMD12,
|
|
* so we need to send it if it's needed, as required by the SD spec.
|
|
*/
|
|
msc_req nreq = {0};
|
|
nreq.command = SD_STOP_TRANSMISSION;
|
|
nreq.resptype = MSC_RESP_R1B;
|
|
nreq.flags = MSC_RF_ABORT;
|
|
logf("msc%d: cmd%d error, sending cmd12", d->msc_nr, r->command);
|
|
if(msc_cmd_exec(d, &nreq))
|
|
d->driver_flags |= MSC_DF_ERRSTATE;
|
|
}
|
|
|
|
logf("msc%d: err:%d, cmd%d, arg:%x", d->msc_nr, status,
|
|
r->command, r->argument);
|
|
return status;
|
|
}
|
|
|
|
int msc_app_cmd_exec(msc_drv* d, msc_req* r)
|
|
{
|
|
msc_req areq = {0};
|
|
areq.command = SD_APP_CMD;
|
|
areq.argument = d->cardinfo.rca;
|
|
areq.resptype = MSC_RESP_R1;
|
|
if(msc_cmd_exec(d, &areq))
|
|
return areq.status;
|
|
|
|
/* Verify that CMD55 was accepted */
|
|
if((areq.response[0] & (1 << 5)) == 0)
|
|
return MSC_REQ_ERROR;
|
|
|
|
return msc_cmd_exec(d, r);
|
|
}
|
|
|
|
int msc_cmd_go_idle_state(msc_drv* d)
|
|
{
|
|
msc_req req = {0};
|
|
req.command = SD_GO_IDLE_STATE;
|
|
req.resptype = MSC_RESP_NONE;
|
|
req.flags = MSC_RF_INIT;
|
|
return msc_cmd_exec(d, &req);
|
|
}
|
|
|
|
int msc_cmd_send_if_cond(msc_drv* d)
|
|
{
|
|
msc_req req = {0};
|
|
req.command = SD_SEND_IF_COND;
|
|
req.argument = 0x1aa;
|
|
req.resptype = MSC_RESP_R7;
|
|
|
|
/* TODO - Check if SEND_IF_COND timeout is really an error
|
|
* IIRC, this can occur if the card isn't HCS (old cards < 2 GiB).
|
|
*/
|
|
if(msc_cmd_exec(d, &req))
|
|
return req.status;
|
|
|
|
/* Set HCS bit if the card responds correctly */
|
|
if((req.response[0] & 0xff) == 0xaa)
|
|
d->driver_flags |= MSC_DF_HCS_CARD;
|
|
|
|
return MSC_REQ_SUCCESS;
|
|
}
|
|
|
|
int msc_cmd_app_op_cond(msc_drv* d)
|
|
{
|
|
msc_req req = {0};
|
|
req.command = SD_APP_OP_COND;
|
|
req.argument = 0x00300000; /* 3.4 - 3.6 V */
|
|
req.resptype = MSC_RESP_R3;
|
|
if(d->driver_flags & MSC_DF_HCS_CARD)
|
|
req.argument |= (1 << 30);
|
|
|
|
int timeout = 2 * HZ;
|
|
do {
|
|
if(msc_app_cmd_exec(d, &req))
|
|
return req.status;
|
|
if(req.response[0] & (1 << 31))
|
|
break;
|
|
sleep(1);
|
|
} while(--timeout > 0);
|
|
|
|
if(timeout == 0)
|
|
return MSC_REQ_TIMEOUT;
|
|
|
|
return MSC_REQ_SUCCESS;
|
|
}
|
|
|
|
int msc_cmd_all_send_cid(msc_drv* d)
|
|
{
|
|
msc_req req = {0};
|
|
req.command = SD_ALL_SEND_CID;
|
|
req.resptype = MSC_RESP_R2;
|
|
if(msc_cmd_exec(d, &req))
|
|
return req.status;
|
|
|
|
for(int i = 0; i < 4; ++i)
|
|
d->cardinfo.cid[i] = req.response[i];
|
|
|
|
return MSC_REQ_SUCCESS;
|
|
}
|
|
|
|
int msc_cmd_send_rca(msc_drv* d)
|
|
{
|
|
msc_req req = {0};
|
|
req.command = SD_SEND_RELATIVE_ADDR;
|
|
req.resptype = MSC_RESP_R6;
|
|
if(msc_cmd_exec(d, &req))
|
|
return req.status;
|
|
|
|
d->cardinfo.rca = req.response[0] & 0xffff0000;
|
|
return MSC_REQ_SUCCESS;
|
|
}
|
|
|
|
int msc_cmd_send_csd(msc_drv* d)
|
|
{
|
|
msc_req req = {0};
|
|
req.command = SD_SEND_CSD;
|
|
req.argument = d->cardinfo.rca;
|
|
req.resptype = MSC_RESP_R2;
|
|
if(msc_cmd_exec(d, &req))
|
|
return req.status;
|
|
|
|
for(int i = 0; i < 4; ++i)
|
|
d->cardinfo.csd[i] = req.response[i];
|
|
sd_parse_csd(&d->cardinfo);
|
|
|
|
if((req.response[0] >> 30) == 1)
|
|
d->driver_flags |= MSC_DF_V2_CARD;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int msc_cmd_select_card(msc_drv* d)
|
|
{
|
|
msc_req req = {0};
|
|
req.command = SD_SELECT_CARD;
|
|
req.argument = d->cardinfo.rca;
|
|
req.resptype = MSC_RESP_R1B;
|
|
return msc_cmd_exec(d, &req);
|
|
}
|
|
|
|
int msc_cmd_set_bus_width(msc_drv* d, int width)
|
|
{
|
|
/* TODO - must we check bus width is supported in the cardinfo? */
|
|
msc_req req = {0};
|
|
req.command = SD_SET_BUS_WIDTH;
|
|
req.resptype = MSC_RESP_R1;
|
|
switch(width) {
|
|
case 1: req.argument = 0; break;
|
|
case 4: req.argument = 2; break;
|
|
default: return MSC_REQ_ERROR;
|
|
}
|
|
|
|
if(msc_app_cmd_exec(d, &req))
|
|
return req.status;
|
|
|
|
msc_set_width(d, width);
|
|
return MSC_REQ_SUCCESS;
|
|
}
|
|
|
|
int msc_cmd_set_clr_card_detect(msc_drv* d, int arg)
|
|
{
|
|
msc_req req = {0};
|
|
req.command = SD_SET_CLR_CARD_DETECT;
|
|
req.argument = arg;
|
|
req.resptype = MSC_RESP_R1;
|
|
return msc_app_cmd_exec(d, &req);
|
|
}
|
|
|
|
int msc_cmd_switch_freq(msc_drv* d)
|
|
{
|
|
/* If card doesn't support High Speed, we don't need to send a command */
|
|
if((d->driver_flags & MSC_DF_V2_CARD) == 0) {
|
|
msc_set_speed(d, MSC_SPEED_FAST);
|
|
return MSC_REQ_SUCCESS;
|
|
}
|
|
|
|
/* Try switching to High Speed (50 MHz) */
|
|
char buffer[64] CACHEALIGN_ATTR;
|
|
msc_req req = {0};
|
|
req.command = SD_SWITCH_FUNC;
|
|
req.argument = 0x80fffff1;
|
|
req.resptype = MSC_RESP_R1;
|
|
req.flags = MSC_RF_DATA;
|
|
req.data = &buffer[0];
|
|
req.block_len = 64;
|
|
req.nr_blocks = 1;
|
|
if(msc_cmd_exec(d, &req))
|
|
return req.status;
|
|
|
|
msc_set_speed(d, MSC_SPEED_HIGH);
|
|
return MSC_REQ_SUCCESS;
|
|
}
|
|
|
|
int msc_cmd_send_status(msc_drv* d)
|
|
{
|
|
msc_req req = {0};
|
|
req.command = SD_SEND_STATUS;
|
|
req.argument = d->cardinfo.rca;
|
|
req.resptype = MSC_RESP_R1;
|
|
return msc_cmd_exec(d, &req);
|
|
}
|
|
|
|
int msc_cmd_set_block_len(msc_drv* d, unsigned len)
|
|
{
|
|
msc_req req = {0};
|
|
req.command = SD_SET_BLOCKLEN;
|
|
req.argument = len;
|
|
req.resptype = MSC_RESP_R1;
|
|
return msc_cmd_exec(d, &req);
|
|
}
|