/*************************************************************************** * __________ __ ___. * 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 #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 */ clean_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); }