📄 pmac.c
字号:
/* * PMac DBDMA lowlevel functions * * Copyright (c) by Takashi Iwai <tiwai@suse.de> * code based on dmasound.c. * * 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 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 <sound/driver.h>#include <asm/io.h>#include <asm/irq.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/slab.h>#include <linux/interrupt.h>#include <linux/pci.h>#include <linux/dma-mapping.h>#include <sound/core.h>#include "pmac.h"#include <sound/pcm_params.h>#include <asm/pmac_feature.h>#include <asm/pci-bridge.h>/* fixed frequency table for awacs, screamer, burgundy, DACA (44100 max) */static int awacs_freqs[8] = { 44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350};/* fixed frequency table for tumbler */static int tumbler_freqs[1] = { 44100};/* * allocate DBDMA command arrays */static int snd_pmac_dbdma_alloc(struct snd_pmac *chip, struct pmac_dbdma *rec, int size){ unsigned int rsize = sizeof(struct dbdma_cmd) * (size + 1); rec->space = dma_alloc_coherent(&chip->pdev->dev, rsize, &rec->dma_base, GFP_KERNEL); if (rec->space == NULL) return -ENOMEM; rec->size = size; memset(rec->space, 0, rsize); rec->cmds = (void __iomem *)DBDMA_ALIGN(rec->space); rec->addr = rec->dma_base + (unsigned long)((char *)rec->cmds - (char *)rec->space); return 0;}static void snd_pmac_dbdma_free(struct snd_pmac *chip, struct pmac_dbdma *rec){ if (rec->space) { unsigned int rsize = sizeof(struct dbdma_cmd) * (rec->size + 1); dma_free_coherent(&chip->pdev->dev, rsize, rec->space, rec->dma_base); }}/* * pcm stuff *//* * look up frequency table */unsigned int snd_pmac_rate_index(struct snd_pmac *chip, struct pmac_stream *rec, unsigned int rate){ int i, ok, found; ok = rec->cur_freqs; if (rate > chip->freq_table[0]) return 0; found = 0; for (i = 0; i < chip->num_freqs; i++, ok >>= 1) { if (! (ok & 1)) continue; found = i; if (rate >= chip->freq_table[i]) break; } return found;}/* * check whether another stream is active */static inline int another_stream(int stream){ return (stream == SNDRV_PCM_STREAM_PLAYBACK) ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;}/* * allocate buffers */static int snd_pmac_pcm_hw_params(struct snd_pcm_substream *subs, struct snd_pcm_hw_params *hw_params){ return snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw_params));}/* * release buffers */static int snd_pmac_pcm_hw_free(struct snd_pcm_substream *subs){ snd_pcm_lib_free_pages(subs); return 0;}/* * get a stream of the opposite direction */static struct pmac_stream *snd_pmac_get_stream(struct snd_pmac *chip, int stream){ switch (stream) { case SNDRV_PCM_STREAM_PLAYBACK: return &chip->playback; case SNDRV_PCM_STREAM_CAPTURE: return &chip->capture; default: snd_BUG(); return NULL; }}/* * wait while run status is on */static inline voidsnd_pmac_wait_ack(struct pmac_stream *rec){ int timeout = 50000; while ((in_le32(&rec->dma->status) & RUN) && timeout-- > 0) udelay(1);}/* * set the format and rate to the chip. * call the lowlevel function if defined (e.g. for AWACS). */static void snd_pmac_pcm_set_format(struct snd_pmac *chip){ /* set up frequency and format */ out_le32(&chip->awacs->control, chip->control_mask | (chip->rate_index << 8)); out_le32(&chip->awacs->byteswap, chip->format == SNDRV_PCM_FORMAT_S16_LE ? 1 : 0); if (chip->set_format) chip->set_format(chip);}/* * stop the DMA transfer */static inline void snd_pmac_dma_stop(struct pmac_stream *rec){ out_le32(&rec->dma->control, (RUN|WAKE|FLUSH|PAUSE) << 16); snd_pmac_wait_ack(rec);}/* * set the command pointer address */static inline void snd_pmac_dma_set_command(struct pmac_stream *rec, struct pmac_dbdma *cmd){ out_le32(&rec->dma->cmdptr, cmd->addr);}/* * start the DMA */static inline void snd_pmac_dma_run(struct pmac_stream *rec, int status){ out_le32(&rec->dma->control, status | (status << 16));}/* * prepare playback/capture stream */static int snd_pmac_pcm_prepare(struct snd_pmac *chip, struct pmac_stream *rec, struct snd_pcm_substream *subs){ int i; volatile struct dbdma_cmd __iomem *cp; struct snd_pcm_runtime *runtime = subs->runtime; int rate_index; long offset; struct pmac_stream *astr; rec->dma_size = snd_pcm_lib_buffer_bytes(subs); rec->period_size = snd_pcm_lib_period_bytes(subs); rec->nperiods = rec->dma_size / rec->period_size; rec->cur_period = 0; rate_index = snd_pmac_rate_index(chip, rec, runtime->rate); /* set up constraints */ astr = snd_pmac_get_stream(chip, another_stream(rec->stream)); if (! astr) return -EINVAL; astr->cur_freqs = 1 << rate_index; astr->cur_formats = 1 << runtime->format; chip->rate_index = rate_index; chip->format = runtime->format; /* We really want to execute a DMA stop command, after the AWACS * is initialized. * For reasons I don't understand, it stops the hissing noise * common to many PowerBook G3 systems and random noise otherwise * captured on iBook2's about every third time. -ReneR */ spin_lock_irq(&chip->reg_lock); snd_pmac_dma_stop(rec); st_le16(&chip->extra_dma.cmds->command, DBDMA_STOP); snd_pmac_dma_set_command(rec, &chip->extra_dma); snd_pmac_dma_run(rec, RUN); spin_unlock_irq(&chip->reg_lock); mdelay(5); spin_lock_irq(&chip->reg_lock); /* continuous DMA memory type doesn't provide the physical address, * so we need to resolve the address here... */ offset = runtime->dma_addr; for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) { st_le32(&cp->phy_addr, offset); st_le16(&cp->req_count, rec->period_size); /*st_le16(&cp->res_count, 0);*/ st_le16(&cp->xfer_status, 0); offset += rec->period_size; } /* make loop */ st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS); st_le32(&cp->cmd_dep, rec->cmd.addr); snd_pmac_dma_stop(rec); snd_pmac_dma_set_command(rec, &rec->cmd); spin_unlock_irq(&chip->reg_lock); return 0;}/* * PCM trigger/stop */static int snd_pmac_pcm_trigger(struct snd_pmac *chip, struct pmac_stream *rec, struct snd_pcm_substream *subs, int cmd){ volatile struct dbdma_cmd __iomem *cp; int i, command; switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: if (rec->running) return -EBUSY; command = (subs->stream == SNDRV_PCM_STREAM_PLAYBACK ? OUTPUT_MORE : INPUT_MORE) + INTR_ALWAYS; spin_lock(&chip->reg_lock); snd_pmac_beep_stop(chip); snd_pmac_pcm_set_format(chip); for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) out_le16(&cp->command, command); snd_pmac_dma_set_command(rec, &rec->cmd); (void)in_le32(&rec->dma->status); snd_pmac_dma_run(rec, RUN|WAKE); rec->running = 1; spin_unlock(&chip->reg_lock); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: spin_lock(&chip->reg_lock); rec->running = 0; /*printk("stopped!!\n");*/ snd_pmac_dma_stop(rec); for (i = 0, cp = rec->cmd.cmds; i < rec->nperiods; i++, cp++) out_le16(&cp->command, DBDMA_STOP); spin_unlock(&chip->reg_lock); break; default: return -EINVAL; } return 0;}/* * return the current pointer */inlinestatic snd_pcm_uframes_t snd_pmac_pcm_pointer(struct snd_pmac *chip, struct pmac_stream *rec, struct snd_pcm_substream *subs){ int count = 0;#if 1 /* hmm.. how can we get the current dma pointer?? */ int stat; volatile struct dbdma_cmd __iomem *cp = &rec->cmd.cmds[rec->cur_period]; stat = ld_le16(&cp->xfer_status); if (stat & (ACTIVE|DEAD)) { count = in_le16(&cp->res_count); if (count) count = rec->period_size - count; }#endif count += rec->cur_period * rec->period_size; /*printk("pointer=%d\n", count);*/ return bytes_to_frames(subs->runtime, count);}/* * playback */static int snd_pmac_playback_prepare(struct snd_pcm_substream *subs){ struct snd_pmac *chip = snd_pcm_substream_chip(subs); return snd_pmac_pcm_prepare(chip, &chip->playback, subs);}static int snd_pmac_playback_trigger(struct snd_pcm_substream *subs, int cmd){ struct snd_pmac *chip = snd_pcm_substream_chip(subs); return snd_pmac_pcm_trigger(chip, &chip->playback, subs, cmd);}static snd_pcm_uframes_t snd_pmac_playback_pointer(struct snd_pcm_substream *subs){ struct snd_pmac *chip = snd_pcm_substream_chip(subs); return snd_pmac_pcm_pointer(chip, &chip->playback, subs);}/* * capture */static int snd_pmac_capture_prepare(struct snd_pcm_substream *subs){ struct snd_pmac *chip = snd_pcm_substream_chip(subs); return snd_pmac_pcm_prepare(chip, &chip->capture, subs);}static int snd_pmac_capture_trigger(struct snd_pcm_substream *subs, int cmd){ struct snd_pmac *chip = snd_pcm_substream_chip(subs); return snd_pmac_pcm_trigger(chip, &chip->capture, subs, cmd);}static snd_pcm_uframes_t snd_pmac_capture_pointer(struct snd_pcm_substream *subs){ struct snd_pmac *chip = snd_pcm_substream_chip(subs); return snd_pmac_pcm_pointer(chip, &chip->capture, subs);}/* * update playback/capture pointer from interrupts */static void snd_pmac_pcm_update(struct snd_pmac *chip, struct pmac_stream *rec){ volatile struct dbdma_cmd __iomem *cp; int c; int stat; spin_lock(&chip->reg_lock); if (rec->running) { cp = &rec->cmd.cmds[rec->cur_period]; for (c = 0; c < rec->nperiods; c++) { /* at most all fragments */ stat = ld_le16(&cp->xfer_status); if (! (stat & ACTIVE)) break; /*printk("update frag %d\n", rec->cur_period);*/ st_le16(&cp->xfer_status, 0); st_le16(&cp->req_count, rec->period_size); /*st_le16(&cp->res_count, 0);*/ rec->cur_period++; if (rec->cur_period >= rec->nperiods) { rec->cur_period = 0; cp = rec->cmd.cmds; } else cp++; spin_unlock(&chip->reg_lock); snd_pcm_period_elapsed(rec->substream); spin_lock(&chip->reg_lock); } } spin_unlock(&chip->reg_lock);}/* * hw info */static struct snd_pcm_hardware snd_pmac_playback ={ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_RESUME), .formats = SNDRV_PCM_FMTBIT_S16_BE | SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_8000_44100, .rate_min = 7350, .rate_max = 44100, .channels_min = 2, .channels_max = 2, .buffer_bytes_max = 131072, .period_bytes_min = 256, .period_bytes_max = 16384, .periods_min = 3, .periods_max = PMAC_MAX_FRAGS,};static struct snd_pcm_hardware snd_pmac_capture ={ .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP |
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -