📄 pxa-audio.c
字号:
/* * linux/drivers/sound/pxa-audio.c -- audio interface for the Cotula chip * * Author: Nicolas Pitre * Created: Aug 15, 2001 * Copyright: MontaVista Software Inc. * * 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. */#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/slab.h>#include <linux/pci.h>#include <linux/poll.h>#include <linux/sound.h>#include <linux/soundcard.h>#include <asm/hardware.h>#include <asm/irq.h>#include <asm/uaccess.h>#include <asm/semaphore.h>#include <asm/dma.h>#include "pxa-audio.h"#define AUDIO_NBFRAGS_DEFAULT 8#define AUDIO_FRAGSIZE_DEFAULT 8192#define MAX_DMA_SIZE 4096#define DMA_DESC_SIZE sizeof(pxa_dma_desc)/* * This function frees all buffers */#define audio_clear_buf pxa_audio_clear_bufvoid pxa_audio_clear_buf(audio_stream_t * s){ DECLARE_WAITQUEUE(wait, current); int frag; if (!s->buffers) return; /* Ensure DMA isn't running */ set_current_state(TASK_UNINTERRUPTIBLE); add_wait_queue(&s->stop_wq, &wait); DCSR(s->dma_ch) = DCSR_STOPIRQEN; schedule(); remove_wait_queue(&s->stop_wq, &wait); /* free DMA buffers */ for (frag = 0; frag < s->nbfrags; frag++) { audio_buf_t *b = &s->buffers[frag]; if (!b->master) continue; consistent_free(b->data, b->master, b->dma_desc->dsadr); } /* free descriptor ring */ if (s->buffers->dma_desc) consistent_free(s->buffers->dma_desc, s->nbfrags * s->descs_per_frag * DMA_DESC_SIZE, s->dma_desc_phys); /* free buffer structure array */ kfree(s->buffers); s->buffers = NULL;}/* * This function allocates the DMA descriptor array and buffer data space * according to the current number of fragments and fragment size. */static int audio_setup_buf(audio_stream_t * s){ pxa_dma_desc *dma_desc; dma_addr_t dma_desc_phys; int nb_desc, frag, i, buf_size = 0; char *dma_buf = NULL; dma_addr_t dma_buf_phys = 0; if (s->buffers) return -EBUSY; /* Our buffer structure array */ s->buffers = kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL); if (!s->buffers) goto err; memzero(s->buffers, sizeof(audio_buf_t) * s->nbfrags); /* * Our DMA descriptor array: * for Each fragment we have one checkpoint descriptor plus one * descriptor per MAX_DMA_SIZE byte data blocks. */ nb_desc = (1 + (s->fragsize + MAX_DMA_SIZE - 1)/MAX_DMA_SIZE) * s->nbfrags; dma_desc = consistent_alloc(GFP_KERNEL, nb_desc * DMA_DESC_SIZE, &dma_desc_phys); if (!dma_desc) goto err; s->descs_per_frag = nb_desc / s->nbfrags; s->buffers->dma_desc = dma_desc; s->dma_desc_phys = dma_desc_phys; for (i = 0; i < nb_desc - 1; i++) dma_desc[i].ddadr = dma_desc_phys + (i + 1) * DMA_DESC_SIZE; dma_desc[i].ddadr = dma_desc_phys; /* Our actual DMA buffers */ for (frag = 0; frag < s->nbfrags; frag++) { audio_buf_t *b = &s->buffers[frag]; /* * Let's allocate non-cached memory for DMA buffers. * We try to allocate all memory at once. * If this fails (a common reason is memory fragmentation), * then we'll try allocating smaller buffers. */ if (!buf_size) { buf_size = (s->nbfrags - frag) * s->fragsize; do { dma_buf = consistent_alloc(GFP_KERNEL, buf_size, &dma_buf_phys); if (!dma_buf) buf_size -= s->fragsize; } while (!dma_buf && buf_size); if (!dma_buf) goto err; b->master = buf_size; memzero(dma_buf, buf_size); } /* * Set up our checkpoint descriptor. Since the count * is always zero, we'll abuse the dsadr and dtadr fields * just in case this one is picked up by the hardware * while processing SOUND_DSP_GETPTR. */ dma_desc->dsadr = dma_buf_phys; dma_desc->dtadr = dma_buf_phys; dma_desc->dcmd = DCMD_ENDIRQEN; if (s->output && !s->mapped) dma_desc->ddadr |= DDADR_STOP; b->dma_desc = dma_desc++; /* set up the actual data descriptors */ for (i = 0; (i * MAX_DMA_SIZE) < s->fragsize; i++) { dma_desc[i].dsadr = (s->output) ? (dma_buf_phys + i*MAX_DMA_SIZE) : s->dev_addr; dma_desc[i].dtadr = (s->output) ? s->dev_addr : (dma_buf_phys + i*MAX_DMA_SIZE); dma_desc[i].dcmd = s->dcmd | ((s->fragsize < MAX_DMA_SIZE) ? s->fragsize : MAX_DMA_SIZE); } dma_desc += i; /* handle buffer pointers */ b->data = dma_buf; dma_buf += s->fragsize; dma_buf_phys += s->fragsize; buf_size -= s->fragsize; } s->usr_frag = s->dma_frag = 0; s->bytecount = 0; s->fragcount = 0; sema_init(&s->sem, (s->output) ? s->nbfrags : 0); return 0;err: printk("pxa-audio: unable to allocate audio memory\n "); audio_clear_buf(s); return -ENOMEM;}/* * Our DMA interrupt handler */static void audio_dma_irq(int ch, void *dev_id, struct pt_regs *regs){ audio_stream_t *s = dev_id; u_int dcsr; dcsr = DCSR(ch); DCSR(ch) = dcsr & ~DCSR_STOPIRQEN; if (!s->buffers) { printk("AC97 DMA: wow... received IRQ for channel %d but no buffer exists\n", ch); return; } if (dcsr & DCSR_BUSERR) printk("AC97 DMA: bus error interrupt on channel %d\n", ch); if (dcsr & DCSR_ENDINTR) { u_long cur_dma_desc; u_int cur_dma_frag; /* * Find out which DMA desc is current. Note that DDADR * points to the next desc, not the current one. */ cur_dma_desc = DDADR(ch) - s->dma_desc_phys - DMA_DESC_SIZE; /* * Let the compiler nicely optimize constant divisors into * multiplications for the common cases which is much faster. * Common cases: x = 1 + (1 << y) for y = [0..3] */ switch (s->descs_per_frag) { case 2: cur_dma_frag = cur_dma_desc / (2*DMA_DESC_SIZE); break; case 3: cur_dma_frag = cur_dma_desc / (3*DMA_DESC_SIZE); break; case 5: cur_dma_frag = cur_dma_desc / (5*DMA_DESC_SIZE); break; case 9: cur_dma_frag = cur_dma_desc / (9*DMA_DESC_SIZE); break; default: cur_dma_frag = cur_dma_desc / (s->descs_per_frag * DMA_DESC_SIZE); } //printk(KERN_DEBUG"cur_dma_frag = %d, s->dma_frag = %d\n",cur_dma_frag,s->dma_frag); /* Account for possible wrap back of cur_dma_desc above */ if (cur_dma_frag >= s->nbfrags) cur_dma_frag = s->nbfrags - 1; while (s->dma_frag != cur_dma_frag) { //printk(KERN_DEBUG"over!\n"); if (!s->mapped) { /* * This fragment is done - set the checkpoint * descriptor to STOP until it is gets * processed by the read or write function. */ s->buffers[s->dma_frag].dma_desc->ddadr |= DDADR_STOP; up(&s->sem); } if (++s->dma_frag >= s->nbfrags) s->dma_frag = 0; /* Accounting */ s->bytecount += s->fragsize; s->fragcount++; } /* ... and for polling processes */ wake_up(&s->frag_wq); } if ((dcsr & DCSR_STOPIRQEN) && (dcsr & DCSR_STOPSTATE)) wake_up(&s->stop_wq);}/* * Validate and sets up buffer fragments, etc. */static int audio_set_fragments(audio_stream_t *s, int val){ if (s->mapped || DCSR(s->dma_ch) & DCSR_RUN) return -EBUSY; if (s->buffers) audio_clear_buf(s); s->nbfrags = (val >> 16) & 0x7FFF; val &= 0xffff; if (val < 5) val = 5; if (val > 15) val = 15; s->fragsize = 1 << val; if (s->nbfrags < 2) s->nbfrags = 2; if (s->nbfrags * s->fragsize > 256 * 1024) s->nbfrags = 256 * 1024 / s->fragsize; if (audio_setup_buf(s)) return -ENOMEM; return val|(s->nbfrags << 16);}/* * The fops functions */static int audio_write(struct file *file, const char *buffer, size_t count, loff_t * ppos){ const char *buffer0 = buffer; audio_state_t *state = (audio_state_t *)file->private_data; audio_stream_t *s = state->output_stream; int chunksize, ret = 0; if (ppos != &file->f_pos) return -ESPIPE; if (s->mapped) return -ENXIO; if (!s->buffers && audio_setup_buf(s)) return -ENOMEM; while (count > 0) { audio_buf_t *b = &s->buffers[s->usr_frag]; /* Grab a fragment */ if (file->f_flags & O_NONBLOCK) { ret = -EAGAIN; if (down_trylock(&s->sem)) break; } else { ret = -ERESTARTSYS; if (down_interruptible(&s->sem)) break; } /* Feed the current buffer */ chunksize = s->fragsize - b->offset; if (chunksize > count) chunksize = count; if (copy_from_user(b->data + b->offset, buffer, chunksize)) { up(&s->sem); return -EFAULT; } b->offset += chunksize; buffer += chunksize; count -= chunksize; if (b->offset < s->fragsize) { up(&s->sem); break; } /* * Activate DMA on current buffer. * We unlock this fragment's checkpoint descriptor and * kick DMA if it is idle. Using checkpoint descriptors * allows for control operations without the need for * stopping the DMA channel if it is already running. */ b->offset = 0; b->dma_desc->ddadr &= ~DDADR_STOP; if (DCSR(s->dma_ch) & DCSR_STOPSTATE) { DDADR(s->dma_ch) = b->dma_desc->ddadr; DCSR(s->dma_ch) = DCSR_RUN; } /* move the index to the next fragment */ if (++s->usr_frag >= s->nbfrags) s->usr_frag = 0; } if ((buffer - buffer0)) ret = buffer - buffer0; return ret;}static int audio_read(struct file *file, char *buffer, size_t count, loff_t * ppos){ char *buffer0 = buffer; audio_state_t *state = file->private_data; audio_stream_t *s = state->input_stream; int chunksize, ret = 0; if (ppos != &file->f_pos) return -ESPIPE; if (s->mapped) return -ENXIO; if (!s->buffers && audio_setup_buf(s)) return -ENOMEM; while (count > 0) { audio_buf_t *b = &s->buffers[s->usr_frag]; /* prime DMA */ if (DCSR(s->dma_ch) & DCSR_STOPSTATE) { DDADR(s->dma_ch) = s->buffers[s->dma_frag].dma_desc->ddadr; DCSR(s->dma_ch) = DCSR_RUN; } /* Wait for a buffer to become full */ if (file->f_flags & O_NONBLOCK) { ret = -EAGAIN; if (down_trylock(&s->sem)) break; } else { ret = -ERESTARTSYS; if (down_interruptible(&s->sem)) break; } /* Grab data from current buffer */ chunksize = s->fragsize - b->offset; if (chunksize > count) chunksize = count; if (copy_to_user(buffer, b->data + b->offset, chunksize)) { up(&s->sem); return -EFAULT; } b->offset += chunksize; buffer += chunksize; count -= chunksize; if (b->offset < s->fragsize) { up(&s->sem); break; } /* * Make this buffer available for DMA again. * We unlock this fragment's checkpoint descriptor and * kick DMA if it is idle. Using checkpoint descriptors * allows for control operations without the need for * stopping the DMA channel if it is already running. */ b->offset = 0; b->dma_desc->ddadr &= ~DDADR_STOP; /* move the index to the next fragment */ if (++s->usr_frag >= s->nbfrags) s->usr_frag = 0; } if ((buffer - buffer0))
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -