📄 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 <sound/core.h>#include "pmac.h"#include <sound/pcm_params.h>#ifdef CONFIG_PPC_HAS_FEATURE_CALLS#include <asm/pmac_feature.h>#else#include <asm/feature.h>#endif#if defined(CONFIG_PM) && defined(CONFIG_PMAC_PBOOK)static int snd_pmac_register_sleep_notifier(pmac_t *chip);static int snd_pmac_unregister_sleep_notifier(pmac_t *chip);static int snd_pmac_suspend(snd_card_t *card, unsigned int state);static int snd_pmac_resume(snd_card_t *card, unsigned int state);#endif/* 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(pmac_dbdma_t *rec, int size){ rec->space = kmalloc(sizeof(struct dbdma_cmd) * (size + 1), GFP_KERNEL); if (rec->space == NULL) return -ENOMEM; rec->size = size; memset(rec->space, 0, sizeof(struct dbdma_cmd) * (size + 1)); rec->cmds = (void*)DBDMA_ALIGN(rec->space); rec->addr = virt_to_bus(rec->cmds); return 0;}static void snd_pmac_dbdma_free(pmac_dbdma_t *rec){ if (rec && rec->space) kfree(rec->space);}/* * pcm stuff *//* * look up frequency table */unsigned int snd_pmac_rate_index(pmac_t *chip, pmac_stream_t *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(snd_pcm_substream_t *subs, snd_pcm_hw_params_t *hw_params){ return snd_pcm_lib_malloc_pages(subs, params_buffer_bytes(hw_params));}/* * release buffers */static int snd_pmac_pcm_hw_free(snd_pcm_substream_t *subs){ snd_pcm_lib_free_pages(subs); return 0;}/* * get a stream of the opposite direction */static pmac_stream_t *snd_pmac_get_stream(pmac_t *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 */inline static voidsnd_pmac_wait_ack(pmac_stream_t *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(pmac_t *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 */inline static void snd_pmac_dma_stop(pmac_stream_t *rec){ out_le32(&rec->dma->control, (RUN|WAKE|FLUSH|PAUSE) << 16); snd_pmac_wait_ack(rec);}/* * set the command pointer address */inline static void snd_pmac_dma_set_command(pmac_stream_t *rec, pmac_dbdma_t *cmd){ out_le32(&rec->dma->cmdptr, cmd->addr);}/* * start the DMA */inline static void snd_pmac_dma_run(pmac_stream_t *rec, int status){ out_le32(&rec->dma->control, status | (status << 16));}/* * prepare playback/capture stream */static int snd_pmac_pcm_prepare(pmac_t *chip, pmac_stream_t *rec, snd_pcm_substream_t *subs){ int i; volatile struct dbdma_cmd *cp; snd_pcm_runtime_t *runtime = subs->runtime; int rate_index; long offset; pmac_stream_t *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)); snd_runtime_check(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 = virt_to_bus(runtime->dma_area); 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(pmac_t *chip, pmac_stream_t *rec, snd_pcm_substream_t *subs, int cmd){ volatile struct dbdma_cmd *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(pmac_t *chip, pmac_stream_t *rec, snd_pcm_substream_t *subs){ int count = 0;#if 1 /* hmm.. how can we get the current dma pointer?? */ int stat; volatile struct dbdma_cmd *cp = &rec->cmd.cmds[rec->cur_period]; stat = ld_le16(&cp->xfer_status); if (stat & (ACTIVE|DEAD)) { count = in_le16(&cp->res_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(snd_pcm_substream_t *subs){ pmac_t *chip = snd_pcm_substream_chip(subs); return snd_pmac_pcm_prepare(chip, &chip->playback, subs);}static int snd_pmac_playback_trigger(snd_pcm_substream_t *subs, int cmd){ pmac_t *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(snd_pcm_substream_t *subs){ pmac_t *chip = snd_pcm_substream_chip(subs); return snd_pmac_pcm_pointer(chip, &chip->playback, subs);}/* * capture */static int snd_pmac_capture_prepare(snd_pcm_substream_t *subs){ pmac_t *chip = snd_pcm_substream_chip(subs); return snd_pmac_pcm_prepare(chip, &chip->capture, subs);}static int snd_pmac_capture_trigger(snd_pcm_substream_t *subs, int cmd){ pmac_t *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(snd_pcm_substream_t *subs){ pmac_t *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(pmac_t *chip, pmac_stream_t *rec){ volatile struct dbdma_cmd *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 snd_pcm_hardware_t 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 = 32768, .period_bytes_min = 256, .period_bytes_max = 16384, .periods_min = 1, .periods_max = PMAC_MAX_FRAGS,};static snd_pcm_hardware_t snd_pmac_capture ={ .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,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -