?? harmony.c
字號:
?
+
/* Hewlett-Packard Harmony audio driver * * This is a driver for the Harmony audio chipset found * on the LASI ASIC of various early HP PA-RISC workstations. * * Copyright (C) 2004, Kyle McMartin <kyle@{debian.org,parisc-linux.org}> * * Based on the previous Harmony incarnations by, * Copyright 2000 (c) Linuxcare Canada, Alex deVries * Copyright 2000-2003 (c) Helge Deller * Copyright 2001 (c) Matthieu Delahaye * Copyright 2001 (c) Jean-Christophe Vaugeois * Copyright 2003 (c) Laurent Canet * Copyright 2004 (c) Stuart Brady * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2, 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * Notes: * - graveyard and silence buffers last for lifetime of * the driver. playback and capture buffers are allocated * per _open()/_close(). * * TODO: * */#include <linux/init.h>#include <linux/slab.h>#include <linux/time.h>#include <linux/wait.h>#include <linux/delay.h>#include <linux/module.h>#include <linux/interrupt.h>#include <linux/spinlock.h>#include <linux/dma-mapping.h>#include <sound/driver.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/control.h>#include <sound/rawmidi.h>#include <sound/initval.h>#include <sound/info.h>#include <asm/io.h>#include <asm/hardware.h>#include <asm/parisc-device.h>#include "harmony.h"static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */module_param(index, int, 0444);MODULE_PARM_DESC(index, "Index value for Harmony driver.");module_param(id, charp, 0444);MODULE_PARM_DESC(id, "ID string for Harmony driver.");static struct parisc_device_id snd_harmony_devtable[] = { /* bushmaster / flounder */ { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007A }, /* 712 / 715 */ { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007B }, /* pace */ { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007E }, /* outfield / coral II */ { HPHW_FIO, HVERSION_REV_ANY_ID, HVERSION_ANY_ID, 0x0007F }, { 0, }};MODULE_DEVICE_TABLE(parisc, snd_harmony_devtable);#define NAME "harmony"#define PFX NAME ": "static unsigned int snd_harmony_rates[] = { 5512, 6615, 8000, 9600, 11025, 16000, 18900, 22050, 27428, 32000, 33075, 37800, 44100, 48000};static unsigned int rate_bits[14] = { HARMONY_SR_5KHZ, HARMONY_SR_6KHZ, HARMONY_SR_8KHZ, HARMONY_SR_9KHZ, HARMONY_SR_11KHZ, HARMONY_SR_16KHZ, HARMONY_SR_18KHZ, HARMONY_SR_22KHZ, HARMONY_SR_27KHZ, HARMONY_SR_32KHZ, HARMONY_SR_33KHZ, HARMONY_SR_37KHZ, HARMONY_SR_44KHZ, HARMONY_SR_48KHZ};static struct snd_pcm_hw_constraint_list hw_constraint_rates = { .count = ARRAY_SIZE(snd_harmony_rates), .list = snd_harmony_rates, .mask = 0,};static inline unsigned longharmony_read(struct snd_harmony *h, unsigned r){ return __raw_readl(h->iobase + r);}static inline voidharmony_write(struct snd_harmony *h, unsigned r, unsigned long v){ __raw_writel(v, h->iobase + r);}static inline voidharmony_wait_for_control(struct snd_harmony *h){ while (harmony_read(h, HARMONY_CNTL) & HARMONY_CNTL_C) ;}static inline voidharmony_reset(struct snd_harmony *h){ harmony_write(h, HARMONY_RESET, 1); mdelay(50); harmony_write(h, HARMONY_RESET, 0);}static voidharmony_disable_interrupts(struct snd_harmony *h){ u32 dstatus; harmony_wait_for_control(h); dstatus = harmony_read(h, HARMONY_DSTATUS); dstatus &= ~HARMONY_DSTATUS_IE; harmony_write(h, HARMONY_DSTATUS, dstatus);}static voidharmony_enable_interrupts(struct snd_harmony *h){ u32 dstatus; harmony_wait_for_control(h); dstatus = harmony_read(h, HARMONY_DSTATUS); dstatus |= HARMONY_DSTATUS_IE; harmony_write(h, HARMONY_DSTATUS, dstatus);}static voidharmony_mute(struct snd_harmony *h){ unsigned long flags; spin_lock_irqsave(&h->mixer_lock, flags); harmony_wait_for_control(h); harmony_write(h, HARMONY_GAINCTL, HARMONY_GAIN_SILENCE); spin_unlock_irqrestore(&h->mixer_lock, flags);}static voidharmony_unmute(struct snd_harmony *h){ unsigned long flags; spin_lock_irqsave(&h->mixer_lock, flags); harmony_wait_for_control(h); harmony_write(h, HARMONY_GAINCTL, h->st.gain); spin_unlock_irqrestore(&h->mixer_lock, flags);}static voidharmony_set_control(struct snd_harmony *h){ u32 ctrl; unsigned long flags; spin_lock_irqsave(&h->lock, flags); ctrl = (HARMONY_CNTL_C | (h->st.format << 6) | (h->st.stereo << 5) | (h->st.rate)); harmony_wait_for_control(h); harmony_write(h, HARMONY_CNTL, ctrl); spin_unlock_irqrestore(&h->lock, flags);}static irqreturn_tsnd_harmony_interrupt(int irq, void *dev){ u32 dstatus; struct snd_harmony *h = dev; spin_lock(&h->lock); harmony_disable_interrupts(h); harmony_wait_for_control(h); dstatus = harmony_read(h, HARMONY_DSTATUS); spin_unlock(&h->lock); if (dstatus & HARMONY_DSTATUS_PN) { if (h->psubs && h->st.playing) { spin_lock(&h->lock); h->pbuf.buf += h->pbuf.count; /* PAGE_SIZE */ h->pbuf.buf %= h->pbuf.size; /* MAX_BUFS*PAGE_SIZE */ harmony_write(h, HARMONY_PNXTADD, h->pbuf.addr + h->pbuf.buf); h->stats.play_intr++; spin_unlock(&h->lock); snd_pcm_period_elapsed(h->psubs); } else { spin_lock(&h->lock); harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); h->stats.silence_intr++; spin_unlock(&h->lock); } } if (dstatus & HARMONY_DSTATUS_RN) { if (h->csubs && h->st.capturing) { spin_lock(&h->lock); h->cbuf.buf += h->cbuf.count; h->cbuf.buf %= h->cbuf.size; harmony_write(h, HARMONY_RNXTADD, h->cbuf.addr + h->cbuf.buf); h->stats.rec_intr++; spin_unlock(&h->lock); snd_pcm_period_elapsed(h->csubs); } else { spin_lock(&h->lock); harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); h->stats.graveyard_intr++; spin_unlock(&h->lock); } } spin_lock(&h->lock); harmony_enable_interrupts(h); spin_unlock(&h->lock); return IRQ_HANDLED;}static unsigned int snd_harmony_rate_bits(int rate){ unsigned int i; for (i = 0; i < ARRAY_SIZE(snd_harmony_rates); i++) if (snd_harmony_rates[i] == rate) return rate_bits[i]; return HARMONY_SR_44KHZ;}static struct snd_pcm_hardware snd_harmony_playback ={ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER), .formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW), .rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT), .rate_min = 5512, .rate_max = 48000, .channels_min = 1, .channels_max = 2, .buffer_bytes_max = MAX_BUF_SIZE, .period_bytes_min = BUF_SIZE, .period_bytes_max = BUF_SIZE, .periods_min = 1, .periods_max = MAX_BUFS, .fifo_size = 0,};static struct snd_pcm_hardware snd_harmony_capture ={ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_JOINT_DUPLEX | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BLOCK_TRANSFER), .formats = (SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW), .rates = (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT), .rate_min = 5512, .rate_max = 48000, .channels_min = 1, .channels_max = 2, .buffer_bytes_max = MAX_BUF_SIZE, .period_bytes_min = BUF_SIZE, .period_bytes_max = BUF_SIZE, .periods_min = 1, .periods_max = MAX_BUFS, .fifo_size = 0,};static intsnd_harmony_playback_trigger(struct snd_pcm_substream *ss, int cmd){ struct snd_harmony *h = snd_pcm_substream_chip(ss); if (h->st.capturing) return -EBUSY; spin_lock(&h->lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: h->st.playing = 1; harmony_write(h, HARMONY_PNXTADD, h->pbuf.addr); harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); harmony_unmute(h); harmony_enable_interrupts(h); break; case SNDRV_PCM_TRIGGER_STOP: h->st.playing = 0; harmony_mute(h); harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); harmony_disable_interrupts(h); break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_SUSPEND: default: spin_unlock(&h->lock); snd_BUG(); return -EINVAL; } spin_unlock(&h->lock); return 0;}static intsnd_harmony_capture_trigger(struct snd_pcm_substream *ss, int cmd){ struct snd_harmony *h = snd_pcm_substream_chip(ss); if (h->st.playing) return -EBUSY; spin_lock(&h->lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: h->st.capturing = 1; harmony_write(h, HARMONY_PNXTADD, h->sdma.addr); harmony_write(h, HARMONY_RNXTADD, h->cbuf.addr); harmony_unmute(h); harmony_enable_interrupts(h); break; case SNDRV_PCM_TRIGGER_STOP: h->st.capturing = 0; harmony_mute(h); harmony_write(h, HARMONY_RNXTADD, h->gdma.addr); harmony_disable_interrupts(h); break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: case SNDRV_PCM_TRIGGER_SUSPEND: default: spin_unlock(&h->lock); snd_BUG(); return -EINVAL; } spin_unlock(&h->lock); return 0;}static intsnd_harmony_set_data_format(struct snd_harmony *h, int fmt, int force){ int o = h->st.format; int n; switch(fmt) { case SNDRV_PCM_FORMAT_S16_BE: n = HARMONY_DF_16BIT_LINEAR; break; case SNDRV_PCM_FORMAT_A_LAW: n = HARMONY_DF_8BIT_ALAW; break; case SNDRV_PCM_FORMAT_MU_LAW: n = HARMONY_DF_8BIT_ULAW; break; default: n = HARMONY_DF_16BIT_LINEAR; break; } if (force || o != n) { snd_pcm_format_set_silence(fmt, h->sdma.area, SILENCE_BUFSZ / (snd_pcm_format_physical_width(fmt) / 8)); } return n;}static intsnd_harmony_playback_prepare(struct snd_pcm_substream *ss){ struct snd_harmony *h = snd_pcm_substream_chip(ss); struct snd_pcm_runtime *rt = ss->runtime; if (h->st.capturing) return -EBUSY; h->pbuf.size = snd_pcm_lib_buffer_bytes(ss); h->pbuf.count = snd_pcm_lib_period_bytes(ss); if (h->pbuf.buf >= h->pbuf.size) h->pbuf.buf = 0; h->st.playing = 0; h->st.rate = snd_harmony_rate_bits(rt->rate); h->st.format = snd_harmony_set_data_format(h, rt->format, 0); if (rt->channels == 2) h->st.stereo = HARMONY_SS_STEREO; else h->st.stereo = HARMONY_SS_MONO; harmony_set_control(h); h->pbuf.addr = rt->dma_addr; return 0;}static intsnd_harmony_capture_prepare(struct snd_pcm_substream *ss){ struct snd_harmony *h = snd_pcm_substream_chip(ss); struct snd_pcm_runtime *rt = ss->runtime; if (h->st.playing) return -EBUSY; h->cbuf.size = snd_pcm_lib_buffer_bytes(ss); h->cbuf.count = snd_pcm_lib_period_bytes(ss); if (h->cbuf.buf >= h->cbuf.size) h->cbuf.buf = 0; h->st.capturing = 0; h->st.rate = snd_harmony_rate_bits(rt->rate); h->st.format = snd_harmony_set_data_format(h, rt->format, 0); if (rt->channels == 2) h->st.stereo = HARMONY_SS_STEREO; else h->st.stereo = HARMONY_SS_MONO; harmony_set_control(h); h->cbuf.addr = rt->dma_addr; return 0;}static snd_pcm_uframes_t snd_harmony_playback_pointer(struct snd_pcm_substream *ss){ struct snd_pcm_runtime *rt = ss->runtime; struct snd_harmony *h = snd_pcm_substream_chip(ss); unsigned long pcuradd; unsigned long played; if (!(h->st.playing) || (h->psubs == NULL)) return 0; if ((h->pbuf.addr == 0) || (h->pbuf.size == 0)) return 0; pcuradd = harmony_read(h, HARMONY_PCURADD); played = pcuradd - h->pbuf.addr;#ifdef HARMONY_DEBUG printk(KERN_DEBUG PFX "playback_pointer is 0x%lx-0x%lx = %d bytes\n", pcuradd, h->pbuf.addr, played); #endif if (pcuradd > h->pbuf.addr + h->pbuf.size) { return 0; } return bytes_to_frames(rt, played);}static snd_pcm_uframes_tsnd_harmony_capture_pointer(struct snd_pcm_substream *ss){ struct snd_pcm_runtime *rt = ss->runtime; struct snd_harmony *h = snd_pcm_substream_chip(ss); unsigned long rcuradd; unsigned long caught; if (!(h->st.capturing) || (h->csubs == NULL)) return 0; if ((h->cbuf.addr == 0) || (h->cbuf.size == 0)) return 0; rcuradd = harmony_read(h, HARMONY_RCURADD); caught = rcuradd - h->cbuf.addr;#ifdef HARMONY_DEBUG printk(KERN_DEBUG PFX "capture_pointer is 0x%lx-0x%lx = %d bytes\n", rcuradd, h->cbuf.addr, caught);#endif if (rcuradd > h->cbuf.addr + h->cbuf.size) { return 0; }
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -