aica.c
字号:
/** This code is licenced under * the General Public Licence* version 2** Copyright Adrian McMenamin 2005, 2006, 2007* <adrian@mcmen.demon.co.uk>* Requires firmware (BSD licenced) available from:* http://linuxdc.cvs.sourceforge.net/linuxdc/linux-sh-dc/sound/oss/aica/firmware/* or the maintainer** This program is free software; you can redistribute it and/or modify* it under the terms of version 2 of the GNU General Public License as published by* the Free Software Foundation.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.** You should have received a copy of the GNU General Public License* along with this program; if not, write to the Free Software* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA**/#include <linux/init.h>#include <linux/jiffies.h>#include <linux/slab.h>#include <linux/time.h>#include <linux/wait.h>#include <linux/moduleparam.h>#include <linux/platform_device.h>#include <linux/firmware.h>#include <linux/timer.h>#include <linux/delay.h>#include <linux/workqueue.h>#include <sound/driver.h>#include <sound/core.h>#include <sound/control.h>#include <sound/pcm.h>#include <sound/initval.h>#include <sound/info.h>#include <asm/io.h>#include <asm/dma.h>#include <asm/dreamcast/sysasic.h>#include "aica.h"MODULE_AUTHOR("Adrian McMenamin <adrian@mcmen.demon.co.uk>");MODULE_DESCRIPTION("Dreamcast AICA sound (pcm) driver");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{Yamaha/SEGA, AICA}}");/* module parameters */#define CARD_NAME "AICA"static int index = -1;static char *id;static int enable = 1;module_param(index, int, 0444);MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");module_param(id, charp, 0444);MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");module_param(enable, bool, 0644);MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");/* Use workqueue */static struct workqueue_struct *aica_queue;/* Simple platform device */static struct platform_device *pd;static struct resource aica_memory_space[2] = { { .name = "AICA ARM CONTROL", .start = ARM_RESET_REGISTER, .flags = IORESOURCE_MEM, .end = ARM_RESET_REGISTER + 3, }, { .name = "AICA Sound RAM", .start = SPU_MEMORY_BASE, .flags = IORESOURCE_MEM, .end = SPU_MEMORY_BASE + 0x200000 - 1, },};/* SPU specific functions *//* spu_write_wait - wait for G2-SH FIFO to clear */static void spu_write_wait(void){ int time_count; time_count = 0; while (1) { if (!(readl(G2_FIFO) & 0x11)) break; /* To ensure hardware failure doesn't wedge kernel */ time_count++; if (time_count > 0x10000) { snd_printk ("WARNING: G2 FIFO appears to be blocked.\n"); break; } }}/* spu_memset - write to memory in SPU address space */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; u32 val; length = DIV_ROUND_UP(length, 4); spu_write_wait(); for (i = 0; i < length; i++) { if (!(i % 8)) spu_write_wait(); val = *froml; local_irq_save(flags); writel(val, to); local_irq_restore(flags); froml++; to++; }}/* spu_disable - set spu registers to stop sound output */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);}/* * Halt the sound processor, clear the memory, * load some default ARM7 code, and then restart ARM7*/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 */static struct snd_pcm_hardware snd_pcm_aica_playback_hw = { .info = (SNDRV_PCM_INFO_NONINTERLEAVED), .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_IMA_ADPCM), .rates = SNDRV_PCM_RATE_8000_48000, .rate_min = 8000, .rate_max = 48000, .channels_min = 1, .channels_max = 2, .buffer_bytes_max = AICA_BUFFER_SIZE, .period_bytes_min = AICA_PERIOD_SIZE, .period_bytes_max = AICA_PERIOD_SIZE, .periods_min = AICA_PERIOD_NUMBER, .periods_max = AICA_PERIOD_NUMBER,};static int aica_dma_transfer(int channels, int buffer_size, struct snd_pcm_substream *substream){ int q, err, period_offset; struct snd_card_aica *dreamcastcard; struct snd_pcm_runtime *runtime; 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) / channels + AICA_PERIOD_SIZE * period_offset), AICA_CHANNEL0_OFFSET + q * CHANNEL_OFFSET + AICA_PERIOD_SIZE * period_offset, buffer_size / channels, AICA_DMA_MODE); if (unlikely(err < 0)) { local_irq_restore(flags); break; } dma_wait_for_completion(AICA_DMA_CHANNEL); local_irq_restore(flags); } return err;}static void startup_aica(struct snd_card_aica *dreamcastcard){ spu_memload(AICA_CHANNEL0_CONTROL_OFFSET, dreamcastcard->channel, sizeof(struct aica_channel)); aica_chn_start();}static void run_spu_dma(struct work_struct *work){ int buffer_size; struct snd_pcm_runtime *runtime; struct snd_card_aica *dreamcastcard; dreamcastcard = container_of(work, struct snd_card_aica, spu_dma_work); runtime = dreamcastcard->substream->runtime; if (unlikely(dreamcastcard->dma_check == 0)) { buffer_size = frames_to_bytes(runtime, runtime->buffer_size); if (runtime->channels > 1) dreamcastcard->channel->flags |= 0x01; aica_dma_transfer(runtime->channels, buffer_size, dreamcastcard->substream); startup_aica(dreamcastcard); dreamcastcard->clicks = buffer_size / (AICA_PERIOD_SIZE * runtime->channels); return; } else { aica_dma_transfer(runtime->channels, AICA_PERIOD_SIZE * runtime->channels, dreamcastcard->substream); snd_pcm_period_elapsed(dreamcastcard->substream); dreamcastcard->clicks++; if (unlikely(dreamcastcard->clicks >= AICA_PERIOD_NUMBER)) dreamcastcard->clicks %= AICA_PERIOD_NUMBER; mod_timer(&dreamcastcard->timer, jiffies + 1); }}static void aica_period_elapsed(unsigned long timer_var){ /*timer function - so cannot sleep */ int play_period; struct snd_pcm_runtime *runtime; struct snd_pcm_substream *substream; struct snd_card_aica *dreamcastcard; substream = (struct snd_pcm_substream *) timer_var; runtime = substream->runtime; dreamcastcard = substream->pcm->private_data; /* Have we played out an additional period? */ play_period = frames_to_bytes(runtime, readl (AICA_CONTROL_CHANNEL_SAMPLE_NUMBER)) / AICA_PERIOD_SIZE; if (play_period == dreamcastcard->current_period) { /* reschedule the timer */ mod_timer(&(dreamcastcard->timer), jiffies + 1); return; } if (runtime->channels > 1) dreamcastcard->current_period = play_period; if (unlikely(dreamcastcard->dma_check == 0)) dreamcastcard->dma_check = 1; queue_work(aica_queue, &(dreamcastcard->spu_dma_work));}static void spu_begin_dma(struct snd_pcm_substream *substream){ struct snd_card_aica *dreamcastcard; struct snd_pcm_runtime *runtime; runtime = substream->runtime; dreamcastcard = substream->pcm->private_data; /*get the queue to do the work */ queue_work(aica_queue, &(dreamcastcard->spu_dma_work)); /* Timer may already be running */ if (unlikely(dreamcastcard->timer.data)) { mod_timer(&dreamcastcard->timer, jiffies + 4); return; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -