299 lines
7.4 KiB
C
299 lines
7.4 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 "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"
|
||
|
|
||
|
#ifndef BOOTLOADER_SPL
|
||
|
/* DMA only works once the system is properly booted */
|
||
|
# define NEED_SFC_DMA
|
||
|
#endif
|
||
|
|
||
|
#if defined(BOOTLOADER_SPL)
|
||
|
# if X1000_EXCLK_FREQ == 24000000
|
||
|
# define FIXED_CLK_FREQ 600000000
|
||
|
# define FIXED_CLK_SRC X1000_CLK_MPLL
|
||
|
# elif X1000_EXCLK_FREQ == 26000000
|
||
|
# define FIXED_CLK_FREQ 598000000
|
||
|
# define FIXED_CLK_SRC X1000_CLK_MPLL
|
||
|
# else
|
||
|
# error "bad EXCLK freq"
|
||
|
# endif
|
||
|
#endif
|
||
|
|
||
|
#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(x1000_clk_t clksrc, uint32_t freq)
|
||
|
{
|
||
|
uint32_t in_freq;
|
||
|
#ifdef FIXED_CLK_FREQ
|
||
|
/* Small optimization to save code space in SPL by not polling clock */
|
||
|
clksrc = FIXED_CLK_SRC;
|
||
|
in_freq = FIXED_CLK_FREQ;
|
||
|
#else
|
||
|
in_freq = clk_get(clksrc);
|
||
|
#endif
|
||
|
|
||
|
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;
|
||
|
}
|