Skip to content

Commit

Permalink
[ALSA] protect Dreamcast PCM driver (AICA) from G2 bus effects
Browse files Browse the repository at this point in the history
The G2 bus on the SEGA Dreamcast connects both the maple peripheral
bus and the AICA sound memory. DMA requests on one can cause the other
to timeout on memory operations.
This patch prevents maple interrupts from causing hiccoughs in the
AICA sound (maple bus code will land in 2.6.24).
There are other cleanups for this (AICA) code - but this is in effect
a regression fix rather than a cleanup.

Signed-off-by: Adrian McMenamin <adrian@mcmen.demon.co.uk>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
  • Loading branch information
Adrian McMenamin authored and Jaroslav Kysela committed Oct 23, 2007
1 parent e5ab3a7 commit 44e0b68
Showing 1 changed file with 29 additions and 2 deletions.
31 changes: 29 additions & 2 deletions sound/sh/aica.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,22 @@ static void spu_write_wait(void)
static void spu_memset(u32 toi, u32 what, int length)
{
int i;
unsigned long flags;
snd_assert(length % 4 == 0, return);
for (i = 0; i < length; i++) {
if (!(i % 8))
spu_write_wait();
local_irq_save(flags);
writel(what, toi + SPU_MEMORY_BASE);
local_irq_restore(flags);
toi++;
}
}

/* spu_memload - write to SPU address space */
static void spu_memload(u32 toi, void *from, int length)
{
unsigned long flags;
u32 *froml = from;
u32 __iomem *to = (u32 __iomem *) (SPU_MEMORY_BASE + toi);
int i;
Expand All @@ -128,7 +132,9 @@ static void spu_memload(u32 toi, void *from, int length)
if (!(i % 8))
spu_write_wait();
val = *froml;
local_irq_save(flags);
writel(val, to);
local_irq_restore(flags);
froml++;
to++;
}
Expand All @@ -138,28 +144,36 @@ static void spu_memload(u32 toi, void *from, int length)
static void spu_disable(void)
{
int i;
unsigned long flags;
u32 regval;
spu_write_wait();
regval = readl(ARM_RESET_REGISTER);
regval |= 1;
spu_write_wait();
local_irq_save(flags);
writel(regval, ARM_RESET_REGISTER);
local_irq_restore(flags);
for (i = 0; i < 64; i++) {
spu_write_wait();
regval = readl(SPU_REGISTER_BASE + (i * 0x80));
regval = (regval & ~0x4000) | 0x8000;
spu_write_wait();
local_irq_save(flags);
writel(regval, SPU_REGISTER_BASE + (i * 0x80));
local_irq_restore(flags);
}
}

/* spu_enable - set spu registers to enable sound output */
static void spu_enable(void)
{
unsigned long flags;
u32 regval = readl(ARM_RESET_REGISTER);
regval &= ~1;
spu_write_wait();
local_irq_save(flags);
writel(regval, ARM_RESET_REGISTER);
local_irq_restore(flags);
}

/*
Expand All @@ -168,25 +182,34 @@ static void spu_enable(void)
*/
static void spu_reset(void)
{
unsigned long flags;
spu_disable();
spu_memset(0, 0, 0x200000 / 4);
/* Put ARM7 in endless loop */
local_irq_save(flags);
ctrl_outl(0xea000002, SPU_MEMORY_BASE);
local_irq_restore(flags);
spu_enable();
}

/* aica_chn_start - write to spu to start playback */
static void aica_chn_start(void)
{
unsigned long flags;
spu_write_wait();
local_irq_save(flags);
writel(AICA_CMD_KICK | AICA_CMD_START, (u32 *) AICA_CONTROL_POINT);
local_irq_restore(flags);
}

/* aica_chn_halt - write to spu to halt playback */
static void aica_chn_halt(void)
{
unsigned long flags;
spu_write_wait();
local_irq_save(flags);
writel(AICA_CMD_KICK | AICA_CMD_STOP, (u32 *) AICA_CONTROL_POINT);
local_irq_restore(flags);
}

/* ALSA code below */
Expand All @@ -213,12 +236,13 @@ static int aica_dma_transfer(int channels, int buffer_size,
int q, err, period_offset;
struct snd_card_aica *dreamcastcard;
struct snd_pcm_runtime *runtime;
err = 0;
unsigned long flags;
dreamcastcard = substream->pcm->private_data;
period_offset = dreamcastcard->clicks;
period_offset %= (AICA_PERIOD_NUMBER / channels);
runtime = substream->runtime;
for (q = 0; q < channels; q++) {
local_irq_save(flags);
err = dma_xfer(AICA_DMA_CHANNEL,
(unsigned long) (runtime->dma_area +
(AICA_BUFFER_SIZE * q) /
Expand All @@ -228,9 +252,12 @@ static int aica_dma_transfer(int channels, int buffer_size,
AICA_CHANNEL0_OFFSET + q * CHANNEL_OFFSET +
AICA_PERIOD_SIZE * period_offset,
buffer_size / channels, AICA_DMA_MODE);
if (unlikely(err < 0))
if (unlikely(err < 0)) {
local_irq_restore(flags);
break;
}
dma_wait_for_completion(AICA_DMA_CHANNEL);
local_irq_restore(flags);
}
return err;
}
Expand Down

0 comments on commit 44e0b68

Please sign in to comment.