rockbox/firmware/target/mips/ingenic_x1000/sfc-x1000.c

287 lines
7.3 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* 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 "kernel.h"
#include "panic.h"
#include "sfc-x1000.h"
#include "gpio-x1000.h"
#include "irq-x1000.h"
#include "x1000/sfc.h"
#include "x1000/cpm.h"
/* DMA works, but not in the SPL due to some hardware not being set up right.
* Only the SPL and bootloader actually require flash access, so to keep it
* simple, DMA is unconditionally disabled. */
//#define NEED_SFC_DMA
#define FIFO_THRESH 31
#define SFC_STATUS_PENDING (-1)
#ifdef NEED_SFC_DMA
static struct mutex sfc_mutex;
static struct semaphore sfc_sema;
static struct timeout sfc_lockup_tmo;
static bool sfc_inited = false;
static volatile int sfc_status;
#else
# define sfc_status SFC_STATUS_OK
#endif
void sfc_init(void)
{
#ifdef NEED_SFC_DMA
if(sfc_inited)
return;
mutex_init(&sfc_mutex);
semaphore_init(&sfc_sema, 1, 0);
sfc_inited = true;
#endif
}
void sfc_lock(void)
{
#ifdef NEED_SFC_DMA
mutex_lock(&sfc_mutex);
#endif
}
void sfc_unlock(void)
{
#ifdef NEED_SFC_DMA
mutex_unlock(&sfc_mutex);
#endif
}
void sfc_open(void)
{
gpio_config(GPIO_A, 0x3f << 26, GPIO_DEVICE(1));
jz_writef(CPM_CLKGR, SFC(0));
jz_writef(SFC_GLB, OP_MODE_V(SLAVE), PHASE_NUM(1),
THRESHOLD(FIFO_THRESH), WP_EN(1));
REG_SFC_CGE = 0;
REG_SFC_INTC = 0x1f;
REG_SFC_MEM_ADDR = 0;
#ifdef NEED_SFC_DMA
jz_writef(SFC_GLB, OP_MODE_V(DMA), BURST_MD_V(INCR32));
system_enable_irq(IRQ_SFC);
#endif
}
void sfc_close(void)
{
#ifdef NEED_SFC_DMA
system_disable_irq(IRQ_SFC);
#endif
REG_SFC_CGE = 0x1f;
jz_writef(CPM_CLKGR, SFC(1));
}
void sfc_set_clock(uint32_t freq)
{
/* TODO: This is a hack so we can use MPLL in the SPL.
* There must be a better way to do this... */
x1000_clk_t clksrc = X1000_CLK_MPLL;
uint32_t in_freq = clk_get(clksrc);
if(in_freq < freq) {
clksrc = X1000_CLK_SCLK_A;
in_freq = clk_get(clksrc);
}
uint32_t div = clk_calc_div(in_freq, freq);
jz_writef(CPM_SSICDR, CE(1), CLKDIV(div - 1),
SFC_CS(clksrc == X1000_CLK_MPLL ? 1 : 0));
while(jz_readf(CPM_SSICDR, BUSY));
jz_writef(CPM_SSICDR, CE(0));
}
#ifdef NEED_SFC_DMA
static int sfc_lockup_tmo_cb(struct timeout* tmo)
{
(void)tmo;
int irq = disable_irq_save();
if(sfc_status == SFC_STATUS_PENDING) {
sfc_status = SFC_STATUS_LOCKUP;
jz_overwritef(SFC_TRIG, STOP(1));
semaphore_release(&sfc_sema);
}
restore_irq(irq);
return 0;
}
static void sfc_wait_end(void)
{
semaphore_wait(&sfc_sema, TIMEOUT_BLOCK);
}
void SFC(void)
{
unsigned sr = REG_SFC_SR & ~REG_SFC_INTC;
if(jz_vreadf(sr, SFC_SR, OVER)) {
jz_overwritef(SFC_SCR, CLR_OVER(1));
sfc_status = SFC_STATUS_OVERFLOW;
} else if(jz_vreadf(sr, SFC_SR, UNDER)) {
jz_overwritef(SFC_SCR, CLR_UNDER(1));
sfc_status = SFC_STATUS_UNDERFLOW;
} else if(jz_vreadf(sr, SFC_SR, END)) {
jz_overwritef(SFC_SCR, CLR_END(1));
sfc_status = SFC_STATUS_OK;
} else {
panicf("SFC IRQ bug");
return;
}
/* Not sure this is wholly correct */
if(sfc_status != SFC_STATUS_OK)
jz_overwritef(SFC_TRIG, STOP(1));
REG_SFC_INTC = 0x1f;
semaphore_release(&sfc_sema);
}
#else
/* Note the X1000 is *very* picky about how the SFC FIFOs are accessed
* so please do NOT try to rearrange the code without testing it first!
*/
void sfc_fifo_read(unsigned* buffer, int data_bytes)
{
int data_words = (data_bytes + 3) / 4;
while(data_words > 0) {
if(jz_readf(SFC_SR, RREQ)) {
jz_overwritef(SFC_SCR, CLR_RREQ(1));
int amount = data_words > FIFO_THRESH ? FIFO_THRESH : data_words;
data_words -= amount;
while(amount > 0) {
*buffer++ = REG_SFC_DATA;
amount -= 1;
}
}
}
}
void sfc_fifo_write(const unsigned* buffer, int data_bytes)
{
int data_words = (data_bytes + 3) / 4;
while(data_words > 0) {
if(jz_readf(SFC_SR, TREQ)) {
jz_overwritef(SFC_SCR, CLR_TREQ(1));
int amount = data_words > FIFO_THRESH ? FIFO_THRESH : data_words;
data_words -= amount;
while(amount > 0) {
REG_SFC_DATA = *buffer++;
amount -= 1;
}
}
}
}
static void sfc_wait_end(void)
{
while(jz_readf(SFC_SR, END) == 0);
jz_overwritef(SFC_SCR, CLR_TREQ(1));
}
#endif /* NEED_SFC_DMA */
int sfc_exec(const sfc_op* op)
{
#ifdef NEED_SFC_DMA
uint32_t intc_clear = jz_orm(SFC_INTC, MSK_END);
#endif
if(op->flags & (SFC_FLAG_READ|SFC_FLAG_WRITE)) {
jz_writef(SFC_TRAN_CONF(0), DATA_EN(1));
REG_SFC_TRAN_LENGTH = op->data_bytes;
#ifdef NEED_SFC_DMA
REG_SFC_MEM_ADDR = PHYSADDR(op->buffer);
#endif
if(op->flags & SFC_FLAG_READ)
{
jz_writef(SFC_GLB, TRAN_DIR_V(READ));
#ifdef NEED_SFC_DMA
discard_dcache_range(op->buffer, op->data_bytes);
intc_clear |= jz_orm(SFC_INTC, MSK_OVER);
#endif
}
else
{
jz_writef(SFC_GLB, TRAN_DIR_V(WRITE));
#ifdef NEED_SFC_DMA
commit_dcache_range(op->buffer, op->data_bytes);
intc_clear |= jz_orm(SFC_INTC, MSK_UNDER);
#endif
}
} else {
jz_writef(SFC_TRAN_CONF(0), DATA_EN(0));
REG_SFC_TRAN_LENGTH = 0;
#ifdef NEED_SFC_DMA
REG_SFC_MEM_ADDR = 0;
#endif
}
bool dummy_first = (op->flags & SFC_FLAG_DUMMYFIRST) != 0;
jz_writef(SFC_TRAN_CONF(0),
MODE(op->mode), POLL_EN(0),
ADDR_WIDTH(op->addr_bytes),
PHASE_FMT(dummy_first ? 1 : 0),
DUMMY_BITS(op->dummy_bits),
COMMAND(op->command), CMD_EN(1));
REG_SFC_DEV_ADDR(0) = op->addr_lo;
REG_SFC_DEV_PLUS(0) = op->addr_hi;
#ifdef NEED_SFC_DMA
sfc_status = SFC_STATUS_PENDING;
timeout_register(&sfc_lockup_tmo, sfc_lockup_tmo_cb, 10*HZ, 0);
REG_SFC_SCR = 0x1f;
REG_SFC_INTC &= ~intc_clear;
#endif
jz_overwritef(SFC_TRIG, FLUSH(1));
jz_overwritef(SFC_TRIG, START(1));
#ifndef NEED_SFC_DMA
if(op->flags & SFC_FLAG_READ)
sfc_fifo_read((unsigned*)op->buffer, op->data_bytes);
if(op->flags & SFC_FLAG_WRITE)
sfc_fifo_write((const unsigned*)op->buffer, op->data_bytes);
#endif
sfc_wait_end();
#ifdef NEED_SFC_DMA
if(op->flags & SFC_FLAG_READ)
discard_dcache_range(op->buffer, op->data_bytes);
#endif
return sfc_status;
}