gint/src/dma/dma.c
Lephe 31dcc6fd6c
usb: enable writing with DMA
Nothing particular to change, simply make sure that the DMA channels
have higher priority than the USB module, otherwise the BEMP interrupt
might be executed before the DMA frees the channel, resulting in the
transfer failing because the channel is still busy.

Also reduce BUSWAIT since it works even on high overclock levels, and
keeping it high won't help increase performance.
2021-05-12 09:17:25 +02:00

335 lines
8.1 KiB
C

#include <gint/hardware.h>
#include <gint/mpu/dma.h>
#include <gint/mpu/power.h>
#include <gint/mpu/intc.h>
#include <gint/intc.h>
#include <gint/dma.h>
#include <gint/drivers.h>
#include <gint/drivers/states.h>
#include <gint/clock.h>
#include <gint/exc.h>
#define DMA SH7305_DMA
#define POWER SH7305_POWER
typedef volatile sh7305_dma_channel_t channel_t;
/* Callbacks for all channels */
static gint_call_t dma_callbacks[6] = { 0 };
/* Sleep blocking flags for all channels */
static bool dma_sleep_blocking[6] = { 0 };
/* dma_channel(): Get address of a DMA channel */
static channel_t *dma_channel(int channel)
{
channel_t *addr[6] = {
&DMA.DMA0, &DMA.DMA1, &DMA.DMA2,
&DMA.DMA3, &DMA.DMA4, &DMA.DMA5,
};
return ((uint)channel >= 6 ? NULL : addr[channel]);
}
/* dma_translate(): Translate virtual address to DMA-suitable form */
static uint32_t dma_translate(void const *address)
{
uint32_t a = (uint32_t)address;
/* Preserve RS addresses (as of SH7724 Reference, 11.2.2) */
if(a >= 0xfd800000 && a < 0xfd800800)
return a;
/* Translate virtual addresses to IL memory to physical addresses; the
same address is used (as of SH7724 Reference, 10.3.3) */
if(a >= 0xe5200000 && a < 0xe5204000)
return a;
/* First additional on-chip memory area (XRAM) */
if(a >= 0xe5007000 && a < 0xe5009000)
return a;
/* Second on-chip memory area (YRAM) */
if(a >= 0xe5017000 && a < 0xe5019000)
return a;
/* Translate P1 and P2 addresses to ROM and RAM to physical form */
if(a >= 0x80000000 && a < 0xc0000000)
return a & 0x1fffffff;
/* By default: I don't know what this is, let's preserve it */
return a;
}
//---
// Driver interface
//---
/* dma_setup(): Setup the DMA in interrupt or no-interrupt mode.
The first parameters are as for dma_transfer() and dma_transfer_atomic().
The last parameter indicates whether interrupts should be used.
Returns non-zero if the DMA is busy or a configuration error occurs. */
static int dma_setup(int channel, dma_size_t size, uint blocks,
void const *src, dma_address_t src_mode,
void *dst, dma_address_t dst_mode,
int interrupts)
{
channel_t *ch = dma_channel(channel);
if(!ch) return 1;
/* Safety guard: only start a transfer if there's not one running */
if(ch->CHCR.DE) return 1;
/* Disable channel and disable the master DMA switch */
ch->CHCR.DE = 0;
DMA.OR.DME = 0;
/* Set DMA source and target address */
ch->SAR = dma_translate(src);
ch->DAR = dma_translate(dst);
/* Set the number of blocks to be transferred */
ch->TCR = blocks;
/* Fill in CHCR. Set RS=0100 (auto-request) and the user-provided
values for TS (transfer size), DM and SM (address modes) */
ch->CHCR.lword = 0x00000400;
ch->CHCR.TS_32 = (size >> 2);
ch->CHCR.TS_10 = (size & 3);
ch->CHCR.DM = dst_mode;
ch->CHCR.SM = src_mode;
ch->CHCR.IE = !!interrupts;
/* Prepare DMAOR by enabling the master switch and clearing the
blocking flags. */
DMA.OR.DME = 1;
DMA.OR.AE = 0;
DMA.OR.NMIF = 0;
/* Block sleep when the transfer involves on-chip memory */
dma_sleep_blocking[channel] = false;
if(ch->SAR >= 0xe5007000 && ch->SAR <= 0xe5204000)
dma_sleep_blocking[channel] = true;
if(ch->DAR >= 0xe5007000 && ch->DAR <= 0xe5204000)
dma_sleep_blocking[channel] = true;
return 0;
}
bool dma_transfer_async(int channel, dma_size_t size, uint blocks,
void const *src, dma_address_t src_mode, void *dst,
dma_address_t dst_mode, gint_call_t callback)
{
if(dma_setup(channel, size, blocks, src, src_mode, dst, dst_mode, 1))
return false;
dma_callbacks[channel] = callback;
if(dma_sleep_blocking[channel])
sleep_block();
/* Enable channel, starting the DMA transfer. */
channel_t *ch = dma_channel(channel);
ch->CHCR.DE = 1;
return true;
}
/* Interrupt handler for all finished DMA transfers */
static void dma_interrupt_transfer_ended(int channel)
{
channel_t *ch = dma_channel(channel);
ch->CHCR.DE = 0;
ch->CHCR.TE = 0;
DMA.OR.AE = 0;
DMA.OR.NMIF = 0;
if(dma_sleep_blocking[channel])
sleep_unblock();
if(dma_callbacks[channel].function)
{
gint_call(dma_callbacks[channel]);
dma_callbacks[channel] = GINT_CALL_NULL;
}
}
/* dma_transfer_wait(): Wait for a transfer to finish */
void dma_transfer_wait(int channel)
{
if(channel < 0)
{
for(int channel = 0; channel < 6; channel++)
dma_transfer_wait(channel);
return;
}
channel_t *ch = dma_channel(channel);
if(!ch) return;
while(ch->CHCR.DE && !ch->CHCR.TE)
{
/* Sleep only if the interrupt is enabled to wake us up */
if(ch->CHCR.IE) sleep();
}
}
bool dma_transfer_sync(int channel, dma_size_t size, uint length,
void const *src, dma_address_t src_mode, void *dst,
dma_address_t dst_mode)
{
if(!dma_transfer_async(channel, size, length, src, src_mode, dst,
dst_mode, GINT_CALL_NULL)) return false;
dma_transfer_wait(channel);
return true;
}
/* dma_transfer_atomic(): Perform a data transfer without interruptions */
void dma_transfer_atomic(int channel, dma_size_t size, uint blocks,
void const *src, dma_address_t src_mode,
void *dst, dma_address_t dst_mode)
{
if(dma_setup(channel, size, blocks, src, src_mode, dst, dst_mode, 0))
return;
/* Enable channel, starting the DMA transfer. */
channel_t *ch = dma_channel(channel);
ch->CHCR.DE = 1;
/* Actively wait until the transfer is finished */
while(!ch->CHCR.TE);
/* Disable the channel and clear the TE flag. Disable the channel first
as clearing the TE flag will allow the transfer to restart */
ch->CHCR.DE = 0;
ch->CHCR.TE = 0;
/* Clear the AE and NMIF status flags */
DMA.OR.AE = 0;
DMA.OR.NMIF = 0;
}
/* Deprecated version of dma_transfer_async() that did not have a callback */
void dma_transfer(int channel, dma_size_t size, uint length, void const *src,
dma_address_t src_mode, void *dst, dma_address_t dst_mode)
{
dma_transfer_async(channel, size, length, src, src_mode, dst, dst_mode,
GINT_CALL_NULL);
}
//---
// Initialization
//---
static void configure(void)
{
if(isSH3()) return;
/* Install the interrupt handler from dma/inth.s */
int codes[] = { 0x800, 0x820, 0x840, 0x860, 0xb80, 0xba0 };
extern void inth_dma_te(void);
for(int i = 0; i < 6; i++)
{
intc_handler_function(codes[i],
GINT_CALL(dma_interrupt_transfer_ended, i));
/* Disable the channel */
dma_channel(i)->CHCR.DE = 0;
}
/* Install the address error gate */
extern void inth_dma_ae(void);
intc_handler(0xbc0, inth_dma_ae, 32);
/* Set interrupt priority to 3, except 11 for the channels that are
used by the USB driver */
intc_priority(INTC_DMA_DEI0, 3);
intc_priority(INTC_DMA_DEI1, 3);
intc_priority(INTC_DMA_DEI2, 3);
intc_priority(INTC_DMA_DEI3, 9);
intc_priority(INTC_DMA_DEI4, 9);
intc_priority(INTC_DMA_DEI5, 3);
intc_priority(INTC_DMA_DADERR, 3);
/* Clear blocking flags and enable the master switch */
DMA.OR.AE = 0;
DMA.OR.NMIF = 0;
DMA.OR.DME = 1;
}
static void universal_unbind(void)
{
/* Make sure any DMA transfer is finished before leaving the app */
dma_transfer_wait(-1);
}
static bool hpowered(void)
{
if(isSH3()) return false;
return (POWER.MSTPCR0.DMAC0 == 0);
}
static void hpoweron(void)
{
if(isSH3()) return;
POWER.MSTPCR0.DMAC0 = 0;
}
static void hpoweroff(void)
{
if(isSH3()) return;
POWER.MSTPCR0.DMAC0 = 1;
}
//---
// State and driver metadata
//---
static void hsave(dma_state_t *s)
{
if(isSH3()) return;
for(int i = 0; i < 6; i++)
{
channel_t *ch = dma_channel(i);
s->ch[i].SAR = ch->SAR;
s->ch[i].DAR = ch->DAR;
s->ch[i].TCR = ch->TCR;
s->ch[i].CHCR.lword = ch->CHCR.lword;
}
s->OR = DMA.OR.word;
}
static void hrestore(dma_state_t const *s)
{
if(isSH3()) return;
/* Disable the DMA while editing */
DMA.OR.DME = 0;
for(int i = 0; i < 6; i++)
{
channel_t *ch = dma_channel(i);
ch->SAR = s->ch[i].SAR;
ch->DAR = s->ch[i].DAR;
ch->TCR = s->ch[i].TCR;
ch->CHCR.lword = s->ch[i].CHCR.lword;
}
DMA.OR.word = s->OR;
}
gint_driver_t drv_dma0 = {
.name = "DMA",
.configure = configure,
.funbind = universal_unbind,
.unbind = universal_unbind,
.hpowered = hpowered,
.hpoweron = hpoweron,
.hpoweroff = hpoweroff,
.hsave = (void *)hsave,
.hrestore = (void *)hrestore,
.state_size = sizeof(dma_state_t),
};
GINT_DECLARE_DRIVER(05, drv_dma0);