📄 sa1100-audio.c
字号:
/* * Common audio handling for the SA11x0 processor * * Copyright (C) 2000, 2001 Nicolas Pitre <nico@cam.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License. * * * This module handles the generic buffering/DMA/mmap audio interface for * codecs connected to the SA1100 chip. All features depending on specific * hardware implementations like supported audio formats or samplerates are * relegated to separate specific modules. * * * History: * * 2000-05-21 Nicolas Pitre Initial release. * * 2000-06-10 Erik Bunce Add initial poll support. * * 2000-08-22 Nicolas Pitre Removed all DMA stuff. Now using the * generic SA1100 DMA interface. * * 2000-11-30 Nicolas Pitre - Validation of opened instances; * - Power handling at open/release time instead * of driver load/unload; * * 2001-06-03 Nicolas Pitre Made this file a separate module, based on * the former sa1100-uda1341.c driver. * * 2001-07-22 Nicolas Pitre - added mmap() and realtime support * - corrected many details to better comply * with the OSS API * * 2001-10-19 Nicolas Pitre - brought DMA registration processing * into this module for better ressource * management. This also fixes a bug * with the suspend/resume logic. */#include <linux/module.h>#include <linux/init.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/mm.h>#include <linux/slab.h>#include <linux/sched.h>#include <linux/poll.h>#include <linux/pm.h>#include <linux/errno.h>#include <linux/sound.h>#include <linux/soundcard.h>#include <linux/sysrq.h>#include <asm/uaccess.h>#include <asm/io.h>#include <asm/hardware.h>#include <asm/semaphore.h>#include <asm/dma.h>#include "sa1100-audio.h"#undef DEBUG#ifdef DEBUG#define DPRINTK( x... ) printk( ##x )#else#define DPRINTK( x... )#endif#define AUDIO_NAME "sa1100-audio"#define AUDIO_NBFRAGS_DEFAULT 8#define AUDIO_FRAGSIZE_DEFAULT 8192#define NEXT_BUF(_s_,_b_) { \ (_s_)->_b_##_idx++; \ (_s_)->_b_##_idx %= (_s_)->nbfrags; \ (_s_)->_b_ = (_s_)->buffers + (_s_)->_b_##_idx; }#define AUDIO_ACTIVE(state) ((state)->rd_ref || (state)->wr_ref)/* * This function frees all buffers */static void audio_clear_buf(audio_stream_t * s){ DPRINTK("audio_clear_buf\n"); /* ensure DMA won't run anymore */ s->active = 0; s->stopped = 0; sa1100_dma_flush_all(s->dma_ch); if (s->buffers) { int frag; for (frag = 0; frag < s->nbfrags; frag++) { if (!s->buffers[frag].master) continue; consistent_free(s->buffers[frag].start, s->buffers[frag].master, s->buffers[frag].dma_addr); } kfree(s->buffers); s->buffers = NULL; } s->buf_idx = 0; s->buf = NULL;}/* * This function allocates the buffer structure array and buffer data space * according to the current number of fragments and fragment size. */static int audio_setup_buf(audio_stream_t * s){ int frag; int dmasize = 0; char *dmabuf = NULL; dma_addr_t dmaphys = 0; if (s->buffers) return -EBUSY; s->buffers = (audio_buf_t *) kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL); if (!s->buffers) goto err; memset(s->buffers, 0, sizeof(audio_buf_t) * s->nbfrags); 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 allocate more smaller buffers. */ if (!dmasize) { dmasize = (s->nbfrags - frag) * s->fragsize; do { dmabuf = consistent_alloc(GFP_KERNEL|GFP_DMA, dmasize, &dmaphys); if (!dmabuf) dmasize -= s->fragsize; } while (!dmabuf && dmasize); if (!dmabuf) goto err; b->master = dmasize; memzero(dmabuf, dmasize); } b->start = dmabuf; b->dma_addr = dmaphys; b->stream = s; sema_init(&b->sem, 1); DPRINTK("buf %d: start %p dma %p\n", frag, b->start, b->dma_addr); dmabuf += s->fragsize; dmaphys += s->fragsize; dmasize -= s->fragsize; } s->buf_idx = 0; s->buf = &s->buffers[0]; s->bytecount = 0; s->fragcount = 0; return 0;err: printk(AUDIO_NAME ": unable to allocate audio memory\n "); audio_clear_buf(s); return -ENOMEM;}/* * This function yanks all buffers from the DMA code's control and * resets them ready to be used again. */static void audio_reset_buf(audio_stream_t * s){ int frag; s->active = 0; s->stopped = 0; sa1100_dma_flush_all(s->dma_ch); if (s->buffers) { for (frag = 0; frag < s->nbfrags; frag++) { audio_buf_t *b = &s->buffers[frag]; b->size = 0; sema_init(&b->sem, 1); } } s->bytecount = 0; s->fragcount = 0;}/* * DMA callback functions */static void audio_dmaout_done_callback(void *buf_id, int size){ audio_buf_t *b = (audio_buf_t *) buf_id; audio_stream_t *s = b->stream; /* Accounting */ s->bytecount += size; s->fragcount++; /* Recycle buffer */ if (s->mapped) sa1100_dma_queue_buffer(s->dma_ch, buf_id, b->dma_addr, s->fragsize); else up(&b->sem); /* And any process polling on write. */ wake_up(&s->wq);}static void audio_dmain_done_callback(void *buf_id, int size){ audio_buf_t *b = (audio_buf_t *) buf_id; audio_stream_t *s = b->stream; /* Accounting */ s->bytecount += size; s->fragcount++; /* Recycle buffer */ if (s->mapped) { sa1100_dma_queue_buffer(s->dma_ch, buf_id, b->dma_addr, s->fragsize); } else { b->size = size; up(&b->sem); } /* And any process polling on write. */ wake_up(&s->wq);}static int audio_sync(struct file *file){ audio_state_t *state = (audio_state_t *)file->private_data; audio_stream_t *s = state->output_stream; audio_buf_t *b; DPRINTK("audio_sync\n"); if (!(file->f_mode & FMODE_WRITE) || !s->buffers || s->mapped) return 0; /* * Send current buffer if it contains data. Be sure to send * a full sample count. */ b = s->buf; if (b->size &= ~3) { down(&b->sem); sa1100_dma_queue_buffer(s->dma_ch, (void *) b, b->dma_addr, b->size); b->size = 0; NEXT_BUF(s, buf); } /* * Let's wait for the last buffer we sent i.e. the one before the * current buf_idx. When we acquire the semaphore, this means either: * - DMA on the buffer completed or * - the buffer was already free thus nothing else to sync. */ b = s->buffers + ((s->nbfrags + s->buf_idx - 1) % s->nbfrags); if (down_interruptible(&b->sem)) return -EINTR; up(&b->sem); return 0;}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; DPRINTK("audio_write: count=%d\n", count); 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->buf; /* Wait for a buffer to become free */ if (file->f_flags & O_NONBLOCK) { ret = -EAGAIN; if (down_trylock(&b->sem)) break; } else { ret = -ERESTARTSYS; if (down_interruptible(&b->sem)) break; } /* Feed the current buffer */ chunksize = s->fragsize - b->size; if (chunksize > count) chunksize = count; DPRINTK("write %d to %d\n", chunksize, s->buf_idx); if (copy_from_user(b->start + b->size, buffer, chunksize)) { up(&b->sem); return -EFAULT; } b->size += chunksize; buffer += chunksize; count -= chunksize; if (b->size < s->fragsize) { up(&b->sem); break; } /* Send current buffer to dma */ s->active = 1; sa1100_dma_queue_buffer(s->dma_ch, (void *) b, b->dma_addr, b->size); b->size = 0; /* indicate that the buffer has been sent */ NEXT_BUF(s, buf); } if ((buffer - buffer0)) ret = buffer - buffer0; DPRINTK("audio_write: return=%d\n", ret); return ret;}static inline void audio_check_tx_spin(audio_state_t *state){ /* * With some codecs like the Philips UDA1341 we must ensure * there is an output stream at any time while recording since * this is how the UDA1341 gets its clock from the SA1100. * So while there is no playback data to send, the output DMA * will spin with all zeroes. We use the cache flush special * area for that. */ if (state->need_tx_for_rx && !state->tx_spinning) { sa1100_dma_set_spin(state->output_stream->dma_ch, (dma_addr_t)FLUSH_BASE_PHYS, 2048); state->tx_spinning = 1; }}static void audio_prime_dma(audio_stream_t *s){ int i; s->active = 1; for (i = 0; i < s->nbfrags; i++) { audio_buf_t *b = s->buf; down(&b->sem); sa1100_dma_queue_buffer(s->dma_ch, (void *) b, b->dma_addr, s->fragsize); NEXT_BUF(s, buf); }}static int audio_read(struct file *file, char *buffer, size_t count, loff_t * ppos){ char *buffer0 = buffer; audio_state_t *state = (audio_state_t *)file->private_data; audio_stream_t *s = state->input_stream; int chunksize, ret = 0; DPRINTK("audio_read: count=%d\n", count); if (ppos != &file->f_pos) return -ESPIPE; if (s->mapped) return -ENXIO; if (!s->active) { if (!s->buffers && audio_setup_buf(s)) return -ENOMEM; audio_check_tx_spin(state); audio_prime_dma(s); } while (count > 0) { audio_buf_t *b = s->buf; /* Wait for a buffer to become full */ if (file->f_flags & O_NONBLOCK) { ret = -EAGAIN; if (down_trylock(&b->sem)) break; } else { ret = -ERESTARTSYS; if (down_interruptible(&b->sem)) break; } /* Grab data from the current buffer */ chunksize = b->size; if (chunksize > count) chunksize = count; DPRINTK("read %d from %d\n", chunksize, s->buf_idx); if (copy_to_user(buffer, b->start + s->fragsize - b->size, chunksize)) { up(&b->sem); return -EFAULT; } b->size -= chunksize; buffer += chunksize; count -= chunksize; if (b->size > 0) { up(&b->sem); break; } /* Make current buffer available for DMA again */ sa1100_dma_queue_buffer(s->dma_ch, (void *) b, b->dma_addr, s->fragsize); NEXT_BUF(s, buf); } if ((buffer - buffer0)) ret = buffer - buffer0; DPRINTK("audio_read: return=%d\n", ret); return ret;}static int audio_mmap(struct file *file, struct vm_area_struct *vma){ audio_state_t *state = (audio_state_t *)file->private_data; audio_stream_t *s; unsigned long size, vma_addr; int i, ret; if (vma->vm_pgoff != 0) return -EINVAL; if (vma->vm_flags & VM_WRITE) { if (!state->wr_ref) return -EINVAL;; s = state->output_stream; } else if (vma->vm_flags & VM_READ) { if (!state->rd_ref) return -EINVAL; s = state->input_stream; } else return -EINVAL; if (s->mapped) return -EINVAL; size = vma->vm_end - vma->vm_start; if (size != s->fragsize * s->nbfrags) return -EINVAL; if (!s->buffers && audio_setup_buf(s)) return -ENOMEM; vma_addr = vma->vm_start; for (i = 0; i < s->nbfrags; i++) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -