rockbox/firmware/target/arm/imx31/sdma-imx31.c

717 lines
19 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2009 by Michael Sevakis
*
* 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 "config.h"
#include "system.h"
#include <string.h>
#include "logf.h"
#include "panic.h"
#include "ccm-imx31.h"
#include "avic-imx31.h"
#include "sdma_struct.h"
#include "sdma-imx31.h"
#include "sdma_script_code.h"
#include "mmu-imx31.h"
/* Most of the code in here is based upon the Linux BSP provided by Freescale
* Copyright 2004-2008 Freescale Semiconductor, Inc. All Rights Reserved. */
/* Cut down to bare bones essentials */
/* Mask of channels with callback enabled */
static unsigned long sdma_enabled_ints = 0;
/* One channel control block per channel in physically mapped device RAM */
static struct channel_control_block ccb_array[CH_NUM] NOCACHEBSS_ATTR;
/* Channel 0 (command channel) data */
static struct buffer_descriptor_extd c0_buffer_desc NOCACHEBSS_ATTR;
/* All SDMA channel interrupts are handled here.
* Dispatches lower channel numbers first (prioritized by SDMA API callers
* who specify the desired channel number).
*/
static void __attribute__((interrupt("IRQ"))) SDMA_HANDLER(void)
{
unsigned long pending = SDMA_INTR;
SDMA_INTR = pending; /* Ack all ints */
pending &= sdma_enabled_ints; /* Only dispatch ints with callback */
while (pending)
{
unsigned int bit = pending & -pending; /* Isolate bottom bit */
pending &= ~bit; /* Clear it */
/* Call callback (required if using an interrupt). bit number = channel */
ccb_array[31 - __builtin_clz(bit)].channel_desc->callback();
}
}
/* Return pc of SDMA script in SDMA halfword space according to peripheral
* and transfer type */
static unsigned long get_script_pc(unsigned int peripheral_type,
unsigned int transfer_type)
{
unsigned long res = (unsigned short)-1;
switch (peripheral_type)
{
case SDMA_PER_MEMORY:
switch (transfer_type)
{
case SDMA_TRAN_EMI_2_INT:
case SDMA_TRAN_EMI_2_EMI:
case SDMA_TRAN_INT_2_EMI:
res = AP_2_AP_ADDR;
break;
}
break;
#if 0 /* Not using this */
case SDMA_PER_DSP:
switch (transfer_type)
{
case SDMA_TRAN_EMI_2_DSP:
res = AP_2_BP_ADDR;
break;
case SDMA_TRAN_DSP_2_EMI:
res = BP_2_AP_ADDR;
break;
case SDMA_TRAN_DSP_2_EMI_LOOP:
res = LOOPBACK_ON_DSP_SIDE_ADDR;
break;
case SDMA_TRAN_EMI_2_DSP_LOOP:
res = MCU_INTERRUPT_ONLY_ADDR;
break;
}
break;
#endif
#if 0 /* Not using this */
case SDMA_PER_FIRI:
switch (transfer_type)
{
case SDMA_TRAN_PER_2_INT:
res = FIRI_2_PER_ADDR;
break;
case SDMA_TRAN_PER_2_EMI:
res = FIRI_2_MCU_ADDR;
break;
case SDMA_TRAN_INT_2_PER:
res = PER_2_FIRI_ADDR;
break;
case SDMA_TRAN_EMI_2_PER:
res = MCU_2_FIRI_ADDR;
break;
}
break;
#endif
#if 0 /* Not using this */
case SDMA_PER_UART:
switch (transfer_type)
{
case SDMA_TRAN_PER_2_INT:
res = UART_2_PER_ADDR;
break;
case SDMA_TRAN_PER_2_EMI:
res = UART_2_MCU_ADDR;
break;
case SDMA_TRAN_INT_2_PER:
res = PER_2_APP_ADDR;
break;
case SDMA_TRAN_EMI_2_PER:
res = MCU_2_APP_ADDR;
break;
}
break;
#endif
#if 0 /* Not using this */
case SDMA_PER_UART_SHP:
switch (transfer_type)
{
case SDMA_TRAN_PER_2_INT:
res = UARTSH_2_PER_ADDR;
break;
case SDMA_TRAN_PER_2_EMI:
res = UARTSH_2_MCU_ADDR;
break;
case SDMA_TRAN_INT_2_PER:
res = PER_2_SHP_ADDR;
break;
case SDMA_TRAN_EMI_2_PER:
res = MCU_2_SHP_ADDR;
break;
}
break;
#endif
case SDMA_PER_ATA:
switch (transfer_type)
{
case SDMA_TRAN_PER_2_EMI:
res = ATA_2_MCU_ADDR;
break;
case SDMA_TRAN_EMI_2_PER:
res = MCU_2_ATA_ADDR;
break;
}
break;
case SDMA_PER_CSPI:
case SDMA_PER_EXT:
case SDMA_PER_SSI:
switch (transfer_type)
{
case SDMA_TRAN_PER_2_INT:
res = APP_2_PER_ADDR;
break;
case SDMA_TRAN_PER_2_EMI:
res = APP_2_MCU_ADDR;
break;
case SDMA_TRAN_INT_2_PER:
res = PER_2_APP_ADDR;
break;
case SDMA_TRAN_EMI_2_PER:
res = MCU_2_APP_ADDR;
break;
}
break;
#if 0 /* Not using this */
case SDMA_PER_MMC:
case SDMA_PER_SDHC:
#endif
case SDMA_PER_SSI_SHP:
case SDMA_PER_CSPI_SHP:
switch (transfer_type)
{
case SDMA_TRAN_PER_2_INT:
res = SHP_2_PER_ADDR;
break;
case SDMA_TRAN_PER_2_EMI:
res = SHP_2_MCU_ADDR;
break;
case SDMA_TRAN_INT_2_PER:
res = PER_2_SHP_ADDR;
break;
case SDMA_TRAN_EMI_2_PER:
res = MCU_2_SHP_ADDR;
break;
}
break;
case SDMA_PER_MSHC:
switch (transfer_type)
{
case SDMA_TRAN_PER_2_EMI:
res = MSHC_2_MCU_ADDR;
break;
case SDMA_TRAN_EMI_2_PER:
res = MCU_2_MSHC_ADDR;
break;
}
break;
case SDMA_PER_CCM:
switch (transfer_type)
{
case SDMA_TRAN_PER_2_EMI:
res = DPTC_DVFS_ADDR;
break;
}
break;
}
if (res == (unsigned short)-1)
{
logf("SDMA script not found\n");
}
return res;
}
static unsigned int get_config(unsigned int transfer_type)
{
unsigned int res = -1;
switch (transfer_type)
{
case SDMA_TRAN_PER_2_INT:
case SDMA_TRAN_PER_2_EMI:
case SDMA_TRAN_INT_2_PER:
case SDMA_TRAN_EMI_2_PER:
/*
* Peripheral <------> Memory
* evtOvr = 0 mcuOvr = 0 dspOvr = 1
*/
res = CH_OWNSHP_MCU | CH_OWNSHP_EVT;
break;
#if 0 /* Not using this */
case SDMA_TRAN_DSP_2_PER:
res = 0;
break;
case SDMA_TRAN_EMI_2_DSP:
case SDMA_TRAN_INT_2_DSP:
case SDMA_TRAN_DSP_2_INT:
case SDMA_TRAN_DSP_2_EMI:
case SDMA_TRAN_DSP_2_DSP:
/*
* DSP <-----------> Memory
* evtOvr = 1 mcuOvr = 0 dspOvr = 0
*/
res = CH_OWNSHP_MCU | CH_OWNSHP_DSP;
break;
#endif
case SDMA_TRAN_EMI_2_INT:
case SDMA_TRAN_EMI_2_EMI:
case SDMA_TRAN_INT_2_INT:
case SDMA_TRAN_INT_2_EMI:
#if 0 /* Not using this */
case SDMA_TRAN_DSP_2_EMI_LOOP:
case SDMA_TRAN_EMI_2_DSP_LOOP:
#endif
/* evtOvr = 1 mcuOvr = 0 dspOvr = 1 */
res = CH_OWNSHP_MCU;
break;
#if 0 /* Not using this */
case SDMA_TRAN_PER_2_DSP:
/* evtOvr = 0 mcuOvr = 1 dspOvr = 0 */
res = CH_OWNSHP_DSP | CH_OWNSHP_EVT;
break;
#endif
default:
break;
}
return res;
}
/* Fill the buffer descriptor with the values given in parameter.
* Expects physical addresses. */
static inline void set_buffer_descriptor(
struct buffer_descriptor *bd_p,
unsigned int command, /* C0_* command or transfer size */
unsigned int status, /* BD_* flags */
unsigned int count, /* Size of buffer to transfer */
void *buf_addr, /* Buffer to transfer */
void *buf_addr_ext)
{
bd_p->mode.command = command;
bd_p->mode.status = status;
bd_p->mode.count = count;
bd_p->buf_addr = buf_addr;
if (status & BD_EXTD)
((struct buffer_descriptor_extd *)bd_p)->buf_addr_ext = buf_addr_ext;
}
/* Configure channel ownership */
static void set_channel_ownership(unsigned int channel, unsigned int config)
{
unsigned long bit = 1ul << channel;
/* DSP side */
#if 0 /* Not using this */
bitmod32(&SDMA_DSPOVR, (config & CH_OWNSHP_DSP) ? 0 : bit, bit);
#endif
/* Event */
bitmod32(&SDMA_EVTOVR, (config & CH_OWNSHP_EVT) ? 0 : bit, bit);
/* MCU side */
bitmod32(&SDMA_HOSTOVR, (config & CH_OWNSHP_MCU) ? 0 : bit, bit);
}
static bool setup_channel(struct channel_control_block *ccb_p)
{
struct context_data context_buffer;
struct channel_descriptor *cd_p;
unsigned int channel_cfg;
unsigned int channel;
unsigned long pc;
memset(&context_buffer, 0x00, sizeof (context_buffer));
channel = ccb_p - ccb_array;
cd_p = ccb_p->channel_desc;
/* Obtain script start address for perihperal and transfer type */
pc = get_script_pc(cd_p->per_type, cd_p->tran_type);
if (pc == (unsigned short)-1)
return false; /* Failed to find a script */
context_buffer.channel_state.pc = pc;
if (cd_p->per_type != SDMA_PER_MEMORY && cd_p->per_type != SDMA_PER_DSP)
{
/* Set peripheral DMA request mask for this channel */
context_buffer.event_mask1 = 1ul << cd_p->event_id1;
if (cd_p->per_type == SDMA_PER_ATA)
{
/* ATA has two */
context_buffer.event_mask2 = 1ul << cd_p->event_id2;
}
context_buffer.shp_addr = cd_p->shp_addr;
context_buffer.wml = cd_p->wml;
}
else
{
context_buffer.wml = SDMA_PER_ADDR_SDRAM;
}
/* Send channel context to SDMA core */
commit_dcache_range(&context_buffer, sizeof (context_buffer));
sdma_write_words((unsigned long *)&context_buffer,
CHANNEL_CONTEXT_ADDR(channel),
sizeof (context_buffer)/4);
ccb_p->status.error = 0; /* Clear channel-wide error flag */
if (cd_p->is_setup != 0)
return true; /* No more to do */
/* Obtain channel ownership configuration */
channel_cfg = get_config(cd_p->tran_type);
if (channel_cfg == (unsigned int)-1)
return false;
/* Set who owns it and thus can activate it */
set_channel_ownership(channel, channel_cfg);
if (channel_cfg & CH_OWNSHP_EVT)
{
/* Set event ID to channel activation bitmapping */
bitset32(&SDMA_CHNENBL(cd_p->event_id1), 1ul << channel);
if (cd_p->per_type == SDMA_PER_ATA)
{
/* ATA has two */
bitset32(&SDMA_CHNENBL(cd_p->event_id2), 1ul << channel);
}
}
cd_p->is_setup = 1;
return true;
}
/** Public routines **/
void INIT_ATTR sdma_init(void)
{
int i;
unsigned long acr;
ccm_module_clock_gating(CG_SDMA, CGM_ON_RUN_WAIT);
/* Reset the controller */
SDMA_RESET |= SDMA_RESET_RESET;
while (SDMA_RESET & SDMA_RESET_RESET);
/* No channel enabled, all priorities 0 */
for (i = 0; i < CH_NUM; i++)
{
SDMA_CHNENBL(i) = 0;
SDMA_CHNPRI(i) = 0;
}
/* Ensure no ints pending */
SDMA_INTR = 0xffffffff;
/* Nobody owns any channel (yet) */
SDMA_HOSTOVR = 0xffffffff;
SDMA_DSPOVR = 0xffffffff;
SDMA_EVTOVR = 0xffffffff;
SDMA_MC0PTR = 0x00000000;
/* 32-word channel contexts, use default bootscript address */
SDMA_CHN0ADDR = SDMA_CHN0ADDR_SMSZ | 0x0050;
avic_enable_int(INT_SDMA, INT_TYPE_IRQ, INT_PRIO_SDMA, SDMA_HANDLER);
/* SDMA core must run at the proper frequency based upon the AHB/IPG
* ratio */
acr = (ccm_get_ahb_clk() / ccm_get_ipg_clk()) < 2 ? SDMA_CONFIG_ACR : 0;
/* No dsp, no debug
* Static context switching - TLSbo86520L SW Workaround for SDMA Chnl0
* access issue */
SDMA_CONFIG = acr;
/* Tell SDMA where the host channel table is */
SDMA_MC0PTR = (unsigned long)ccb_array;
ccb_array[0].status.opened_init = 1;
ccb_array[0].curr_bd_ptr = &c0_buffer_desc.bd;
ccb_array[0].base_bd_ptr = &c0_buffer_desc.bd;
ccb_array[0].channel_desc = NULL; /* No channel descriptor */
/* Command channel owned by AP */
set_channel_ownership(0, CH_OWNSHP_MCU);
sdma_channel_set_priority(0, 1);
/* Load SDMA script code */
set_buffer_descriptor(&c0_buffer_desc.bd,
C0_SETPM,
BD_DONE | BD_WRAP | BD_EXTD,
RAM_CODE_SIZE,
(void *)addr_virt_to_phys(MCU_START_ADDR),
(void *)RAM_CODE_START_ADDR);
SDMA_HSTART = 1ul;
sdma_channel_wait_nonblocking(0);
/* No dsp, no debug, dynamic context switching */
SDMA_CONFIG = acr | SDMA_CONFIG_CSM_DYNAMIC;
}
/* Busy wait for a channel to complete */
void sdma_channel_wait_nonblocking(unsigned int channel)
{
unsigned long mask;
if (channel >= CH_NUM)
return;
if (ccb_array[channel].status.opened_init == 0)
return;
mask = 1ul << channel;
while (SDMA_STOP_STAT & mask);
}
/* Set a new channel priority */
void sdma_channel_set_priority(unsigned int channel, unsigned int priority)
{
if (channel >= CH_NUM || priority > MAX_CH_PRIORITY)
return;
if (ccb_array[channel].status.opened_init == 0)
return;
SDMA_CHNPRI(channel) = priority;
}
/* Resets a channel to start of script next time it runs. */
bool sdma_channel_reset(unsigned int channel)
{
struct channel_control_block *ccb_p;
if (channel == 0 || channel >= CH_NUM)
return false;
ccb_p = &ccb_array[channel];
if (ccb_p->status.opened_init == 0)
return false;
if (!setup_channel(ccb_p))
return false;
return true;
}
/* Resume or start execution on a channel */
void sdma_channel_run(unsigned int channel)
{
if (channel == 0 || channel >= CH_NUM)
return;
if (ccb_array[channel].status.opened_init == 0)
return;
SDMA_HSTART = 1ul << channel;
}
/* Pause a running channel - can be resumed */
void sdma_channel_pause(unsigned int channel)
{
if (channel == 0 || channel >= CH_NUM)
return;
if (ccb_array[channel].status.opened_init == 0)
return;
SDMA_STOP_STAT = 1ul << channel;
}
/* Stop a channel from executing - cannot be resumed */
void sdma_channel_stop(unsigned int channel)
{
struct channel_control_block *ccb_p;
unsigned long chmsk;
unsigned long intmsk;
int oldstatus;
int i;
if (channel == 0 || channel >= CH_NUM)
return;
ccb_p = &ccb_array[channel];
if (ccb_p->status.opened_init == 0)
return;
chmsk = 1ul << channel;
/* Lock callback */
oldstatus = disable_irq_save();
intmsk = sdma_enabled_ints;
sdma_enabled_ints &= ~chmsk;
restore_irq(oldstatus);
/* Stop execution */
for (i = ccb_p->channel_desc->bd_count - 1; i >= 0; i--)
ccb_p->base_bd_ptr[i].mode.status &= ~BD_DONE;
SDMA_STOP_STAT = chmsk;
while (SDMA_STOP_STAT & chmsk);
/* Unlock callback if it was set */
if (intmsk & chmsk)
bitset32(&sdma_enabled_ints, chmsk);
logf("SDMA ch closed: %d", channel);
}
bool sdma_channel_init(unsigned int channel,
struct channel_descriptor *cd_p,
struct buffer_descriptor *base_bd_p)
{
struct channel_control_block *ccb_p;
if (channel == 0 || channel >= CH_NUM ||
cd_p == NULL || base_bd_p == NULL)
return false;
ccb_p = &ccb_array[channel];
/* If initialized already, should close first then init. */
if (ccb_p->status.opened_init != 0)
return false;
/* Initialize channel control block. */
ccb_p->curr_bd_ptr = base_bd_p;
ccb_p->base_bd_ptr = base_bd_p;
ccb_p->channel_desc = cd_p;
ccb_p->status.error = 0;
ccb_p->status.opened_init = 1;
ccb_p->status.state_direction = 0;
ccb_p->status.execute = 0;
/* Finish any channel descriptor inits. */
cd_p->ccb_ptr = ccb_p;
cd_p->is_setup = 0;
/* Do an initial setup now. */
if (!setup_channel(ccb_p))
{
logf("SDMA ch init failed: %d", channel);
cd_p->ccb_ptr = NULL;
memset(ccb_p, 0x00, sizeof (struct channel_control_block));
return false;
}
/* Enable interrupt if a callback is specified. */
if (cd_p->callback != NULL)
bitset32(&sdma_enabled_ints, 1ul << channel);
/* Minimum schedulable = 1 */
sdma_channel_set_priority(channel, 1);
logf("SDMA ch initialized: %d", channel);
return true;
}
void sdma_channel_close(unsigned int channel)
{
struct channel_control_block *ccb_p;
int i;
if (channel == 0 || channel >= CH_NUM)
return;
ccb_p = &ccb_array[channel];
/* Block callbacks (if not initialized, it won't be set). */
bitclr32(&sdma_enabled_ints, 1ul << channel);
if (ccb_p->status.opened_init == 0)
return;
/* Stop the channel if running */
for (i = ccb_p->channel_desc->bd_count - 1; i >= 0; i--)
ccb_p->base_bd_ptr[i].mode.status &= ~BD_DONE;
sdma_channel_stop(channel);
/* No ownership */
set_channel_ownership(channel, 0);
/* Cannot schedule it again */
sdma_channel_set_priority(channel, 0);
/* Reset channel control block entry */
memset(ccb_p, 0x00, sizeof (struct channel_control_block));
}
/* Check channel-wide error flag */
bool sdma_channel_is_error(unsigned int channel)
{
return channel < CH_NUM && ccb_array[channel].status.error;
}
/* Write 32-bit words to SDMA core memory. Host endian->SDMA endian. */
void sdma_write_words(const unsigned long *buf, unsigned long start, int count)
{
/* Setup buffer descriptor with channel 0 command */
set_buffer_descriptor(&c0_buffer_desc.bd,
C0_SETDM,
BD_DONE | BD_WRAP | BD_EXTD,
count,
(void *)addr_virt_to_phys((unsigned long)buf),
(void *)start);
SDMA_HSTART = 1ul;
sdma_channel_wait_nonblocking(0);
}
/* Read 32-bit words from SDMA core memory. SDMA endian->host endian. */
void sdma_read_words(unsigned long *buf, unsigned long start, int count)
{
/* Setup buffer descriptor with channel 0 command */
set_buffer_descriptor(&c0_buffer_desc.bd,
C0_GETDM,
BD_DONE | BD_WRAP | BD_EXTD,
count,
(void *)addr_virt_to_phys((unsigned long)buf),
(void *)start);
SDMA_HSTART = 1ul;
sdma_channel_wait_nonblocking(0);
}