/* * Common audio handling for the SA11x0 processor * * Copyright (C) 2007, Ryu Euiyoul * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "s3c6400_pcm.h" #undef DEBUG //#define DEBUG #ifdef DEBUG #define DPRINTK printk #else #define DPRINTK( x... ) #endif #define AUDIO_NAME "s3c-audio" #define AUDIO_NBFRAGS_DEFAULT 8 //#define AUDIO_FRAGSIZE_DEFAULT 8192 #define AUDIO_FRAGSIZE_DEFAULT 0x1000 #define AUDIO_ACTIVE(state) ((state)->rd_ref || (state)->wr_ref) void audio_dma_callback(struct s3c2410_dma_chan *dma_ch, void *buf_id, int size, enum s3c2410_dma_buffresult result); /* * DMA processing */ static struct s3c2410_dma_client s3cplay_dma_client = { .name = "s3c-play", }; static struct s3c2410_dma_client s3cmic_dma_client = { .name = "s3c-recmic", }; static int s3c_iis_dma_init(audio_stream_t *s,int mode) { if(mode == 0) { /* play channel */ s3c2410_dma_devconfig(s->subchannel, S3C2410_DMASRC_MEM, 0, S3C_IIS0TXD_PHYS); } else if(mode ==1) { /* capture channel */ s3c2410_dma_devconfig(s->subchannel, S3C2410_DMASRC_HW, 0, S3C_IIS0RXD_PHYS); } else { printk("Error : mode set\n"); } s3c2410_dma_config(s->subchannel, 4, 0); s3c2410_dma_set_buffdone_fn(s->subchannel, audio_dma_callback); s3c2410_dma_setflags(s->subchannel, S3C2410_DMAF_AUTOSTART); return 0; } static u_int audio_get_dma_pos(audio_stream_t *s) { int dma_srcpos,dma_dstpos; audio_buf_t *b = &s->buffers[s->dma_tail]; u_int offset; if (b->dma_ref) { s3c2410_dma_getposition(s->subchannel, &dma_srcpos, &dma_dstpos); offset = dma_srcpos - b->dma_addr; if (offset >= s->fragsize) offset = s->fragsize - 4; } else if (s->pending_frags) { offset = b->offset; } else { offset = 0; } return offset; } static void audio_stop_dma(audio_stream_t *s) { u_int pos; unsigned long flags; audio_buf_t *b; if (!s->buffers) return; local_irq_save(flags); s->stopped = 1; pos = audio_get_dma_pos(s); s3c2410_dma_ctrl(s->subchannel, S3C2410_DMAOP_FLUSH); local_irq_restore(flags); /* back up pointers to be ready to restart from the same spot */ while (s->dma_head != s->dma_tail) { b = &s->buffers[s->dma_head]; if (b->dma_ref) { b->dma_ref = 0; b->offset = 0; } s->pending_frags++; if (s->dma_head == 0) s->dma_head = s->nbfrags; s->dma_head--; } b = &s->buffers[s->dma_head]; if (b->dma_ref) { b->offset = pos; b->dma_ref = 0; } } static void audio_reset(audio_stream_t *s) { if (s->buffers) { audio_stop_dma(s); s->buffers[s->dma_head].offset = 0; s->buffers[s->usr_head].offset = 0; s->usr_head = s->dma_head; s->pending_frags = 0; sema_init(&s->sem, s->nbfrags); } s->active = 0; s->stopped = 0; } static void audio_process_dma(audio_stream_t *s) { while (s->pending_frags) { audio_buf_t *b = &s->buffers[s->dma_head]; u_int dma_size = s->fragsize - b->offset; s3c2410_dma_enqueue(s->subchannel, (void *) s, b->dma_addr+b->offset,dma_size); b->dma_ref++; b->offset += dma_size; if (b->offset >= s->fragsize) { s->pending_frags--; if (++s->dma_head >= s->nbfrags) s->dma_head = 0; } } } void audio_dma_callback(struct s3c2410_dma_chan *dma_ch, void *buf_id, int size, enum s3c2410_dma_buffresult result) { audio_stream_t *s = (audio_stream_t *)buf_id; audio_buf_t *b = &s->buffers[s->dma_tail]; if (!s->buffers) { printk(KERN_CRIT "elfin: received DMA IRQ for non existent buffers!\n"); return; } else if (b->dma_ref && --b->dma_ref == 0 && b->offset >= s->fragsize) { /* This fragment is done */ b->offset = 0; s->bytecount += s->fragsize; s->fragcount++; if (++s->dma_tail >= s->nbfrags) s->dma_tail = 0; if (!s->mapped) up(&s->sem); else s->pending_frags++; wake_up(&s->wq); } audio_process_dma(s); } static int copy_to_user_stereo_mono(char *to, char *from, int count) { int ind, fact; if(!count) return 0; if(from[6] && from[7] && !from[5] && !from[4]) //check for the nonzero channel fact = 0; else fact = 1; ind = 0; while(ind < count){ if(ind%2){ *(from + ind) = *(from + 2*ind +1); *(from + ind) = *(from + 2*ind -1); }else{ *(from + ind) = *(from + 2*ind +2); *(from + ind) = *(from + 2*ind -0); } ind++; } return copy_to_user(to, from, count); } static int copy_from_user_mono_stereo(char *to, const char *from, int count) { int ind; if(copy_from_user(to, from, count)) return -EFAULT; ind = count - 1; while(ind){ if(ind%2){ *(to + 2*ind + 1) = *(to + ind); *(to + 2*ind - 1) = *(to + ind); // R; }else{ *(to + 2*ind + 2) = *(to + ind); *(to + 2*ind + 0) = *(to + ind); // R; } ind--; } return 0; } /* * Buffer creation/destruction */ static void audio_discard_buf(audio_stream_t * s) { DPRINTK("audio_discard_buf\n"); /* ensure DMA isn't using those buffers */ audio_reset(s); if (s->buffers) { int frag; for (frag = 0; frag < s->nbfrags; frag++) { if (!s->buffers[frag].master) continue; dma_free_coherent(s->dev, s->buffers[frag].master, s->buffers[frag].data, s->buffers[frag].dma_addr); } kfree(s->buffers); s->buffers = NULL; } } 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 = 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 = dma_alloc_coherent(s->dev, dmasize, &dmaphys, GFP_KERNEL); if (!dmabuf) dmasize -= s->fragsize; } while (!dmabuf && dmasize); if (!dmabuf) goto err; b->master = dmasize; memzero(dmabuf, dmasize); } b->data = dmabuf; b->dma_addr = dmaphys; DPRINTK("buf %d: start %p dma %#08x\n", frag, b->data, b->dma_addr); dmabuf += s->fragsize; dmaphys += s->fragsize; dmasize -= s->fragsize; } s->usr_head = s->dma_head = s->dma_tail = 0; s->bytecount = 0; s->fragcount = 0; sema_init(&s->sem, s->nbfrags); return 0; err: printk(AUDIO_NAME ": unable to allocate audio memory\n "); audio_discard_buf(s); return -ENOMEM; } /* * Driver interface 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 = file->private_data; audio_stream_t *s = state->output_stream; int chunksize, ret = 0; unsigned long flags; DPRINTK("audio_write: count=%d\n", count); 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_head]; /* Wait for a buffer to become free */ 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 */ if(state->sound_mode == MONO) { /* mono mode play back */ if(count > s->fragsize/2) chunksize = s->fragsize/2; else chunksize = count; if (copy_from_user_mono_stereo(b->data + b->offset, buffer, chunksize)) { up(&s->sem); return -EFAULT; } b->offset += chunksize*2; } else { /* stereo mode play back */ chunksize = s->fragsize - b->offset; if (chunksize > count) chunksize = count; DPRINTK("write %d to %d\n", chunksize, s->usr_head); if (copy_from_user(b->data + b->offset, buffer, chunksize)) { up(&s->sem); return -EFAULT; } b->offset += chunksize; } count -= chunksize; buffer += chunksize; if (b->offset < s->fragsize) { up(&s->sem); break; } /* Update pointers and send current fragment to DMA */ b->offset = 0; if (++s->usr_head >= s->nbfrags) s->usr_head = 0; local_irq_save(flags); s->pending_frags++; s->active = 1; audio_process_dma(s); local_irq_restore(flags); } if ((buffer - buffer0)) ret = buffer - buffer0; DPRINTK("audio_write: return=%d\n", ret); return ret; } static void audio_prime_rx(audio_state_t *state) { audio_stream_t *is = state->input_stream; unsigned long flags; local_irq_save(flags); is->pending_frags = is->nbfrags; sema_init(&is->sem, 0); is->active = 1; audio_process_dma(is); local_irq_restore(flags); } 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; unsigned long flags; DPRINTK("audio_read: count=%d\n", count); if (s->mapped) return -ENXIO; if (!s->active) { if (!s->buffers && audio_setup_buf(s)) return -ENOMEM; audio_prime_rx(state); } while (count > 0) { audio_buf_t *b = &s->buffers[s->usr_head]; /* 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 the current buffer */ if(state->sound_mode == MONO) { chunksize = (s->fragsize - b->offset)/2; if(chunksize > count) chunksize = count/2; if(copy_to_user_stereo_mono(buffer, b->data + b->offset, chunksize)) { up(&s->sem); return -EFAULT; } b->offset += chunksize * 2; } else { /* stereo mode play back */ chunksize = s->fragsize - b->offset; if (chunksize > count) chunksize = count; #if 0 DPRINTK("%s:%d buffer 0x%x data 0x%x offset 0x%x chunksize 0x%x, count 0x%x\n" ,__FUNCTION__, __LINE__ ,*buffer, *b->data, b->offset, chunksize, count); #endif if (copy_to_user(buffer, b->data + b->offset, chunksize)) { /* to , from , n */ up(&s->sem); return -EFAULT; } b->offset += chunksize; } buffer += chunksize; count -= chunksize; if (b->offset < s->fragsize) { up(&s->sem); break; } /* Update pointers and return current fragment to DMA */ b->offset = 0; if (++s->usr_head >= s->nbfrags) s->usr_head = 0; local_irq_save(flags); s->pending_frags++; audio_process_dma(s); local_irq_restore(flags); } if ((buffer - buffer0)) ret = buffer - buffer0; DPRINTK("audio_read: return=%d\n", ret); return ret; } static int audio_sync(struct file *file) { audio_state_t *state = file->private_data; audio_stream_t *s = state->output_stream; audio_buf_t *b; u_int shiftval = 0; unsigned long flags; DECLARE_WAITQUEUE(wait, current); 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->buffers[s->usr_head]; if (b->offset &= ~3) { down(&s->sem); /* * HACK ALERT ! * To avoid increased complexity in the rest of the code * where full fragment sizes are assumed, we cheat a little * with the start pointer here and don't forget to restore * it later. */ shiftval = s->fragsize - b->offset; b->offset = shiftval; b->dma_addr -= shiftval; s->bytecount -= shiftval; if (++s->usr_head >= s->nbfrags) s->usr_head = 0; local_irq_save(flags); s->pending_frags++; audio_process_dma(s); local_irq_restore(flags); } /* Let's wait for all buffers to complete */ set_current_state(TASK_INTERRUPTIBLE); add_wait_queue(&s->wq, &wait); while (s->pending_frags && s->dma_tail != s->usr_head && !signal_pending(current)) { schedule(); set_current_state(TASK_INTERRUPTIBLE); } set_current_state(TASK_RUNNING); remove_wait_queue(&s->wq, &wait); /* undo the pointer hack above */ if (shiftval) { local_irq_save(flags); b->dma_addr += shiftval; /* ensure sane DMA code behavior if not yet processed */ if (b->offset != 0) b->offset = s->fragsize; local_irq_restore(flags); } return 0; } static int audio_mmap(struct file *file, struct vm_area_struct *vma) { audio_state_t *state = 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++) { audio_buf_t *buf = &s->buffers[i]; if (!buf->master) continue; ret = remap_pfn_range(vma, vma_addr, buf->dma_addr, buf->master, vma->vm_page_prot); if (ret) return ret; vma_addr += buf->master; } s->mapped = 1; return 0; } static unsigned int audio_poll(struct file *file, struct poll_table_struct *wait) { audio_state_t *state = file->private_data; audio_stream_t *is = state->input_stream; audio_stream_t *os = state->output_stream; unsigned int mask = 0; DPRINTK("audio_poll(): mode=%s%s\n", (file->f_mode & FMODE_READ) ? "r" : "", (file->f_mode & FMODE_WRITE) ? "w" : ""); if (file->f_mode & FMODE_READ) { /* Start audio input if not already active */ if (!is->active) { if (!is->buffers && audio_setup_buf(is)) return -ENOMEM; audio_prime_rx(state); } poll_wait(file, &is->wq, wait); } if (file->f_mode & FMODE_WRITE) { if (!os->buffers && audio_setup_buf(os)) return -ENOMEM; poll_wait(file, &os->wq, wait); } if (file->f_mode & FMODE_READ) if (( is->mapped && is->bytecount > 0)) mask |= POLLIN | POLLRDNORM; if (file->f_mode & FMODE_WRITE) if (( os->mapped && os->bytecount > 0)) mask |= POLLOUT | POLLWRNORM; DPRINTK("audio_poll() returned mask of %s%s\n", (mask & POLLIN) ? "r" : "", (mask & POLLOUT) ? "w" : ""); return mask; } static loff_t audio_llseek(struct file *file, loff_t offset, int origin) { return -ESPIPE; } static int audio_set_fragments(audio_stream_t *s, int val) { if (s->active) return -EBUSY; if (s->buffers) audio_discard_buf(s); s->nbfrags = (val >> 16) & 0x7FFF; val &= 0xffff; if (val < 4) val = 4; if (val > 15) val = 15; s->fragsize = 1 << val; if (s->nbfrags < 2) s->nbfrags = 2; if (s->nbfrags * s->fragsize > 128 * 1024) s->nbfrags = 128 * 1024 / s->fragsize; if (audio_setup_buf(s)) return -ENOMEM; return val|(s->nbfrags << 16); } static int audio_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) { audio_state_t *state = file->private_data; audio_stream_t *os = state->output_stream; audio_stream_t *is = state->input_stream; audio_buf_info inf = { 0, }; long val; /* dispatch based on command */ switch (cmd) { case OSS_GETVERSION: return put_user(SOUND_VERSION, (int *)arg); case SNDCTL_DSP_GETBLKSIZE: if (file->f_mode & FMODE_WRITE) return put_user(os->fragsize, (int *)arg); else return put_user(is->fragsize, (int *)arg); case SNDCTL_DSP_GETCAPS: val = DSP_CAP_REALTIME|DSP_CAP_TRIGGER|DSP_CAP_MMAP; if (is && os) val |= DSP_CAP_DUPLEX; return put_user(val, (int *)arg); case SNDCTL_DSP_SETFRAGMENT: if (get_user(val, (long *) arg)) return -EFAULT; if (file->f_mode & FMODE_READ) { int ret = audio_set_fragments(is, val); if (ret < 0) return ret; ret = put_user(ret, (int *)arg); if (ret) return ret; } if (file->f_mode & FMODE_WRITE) { int ret = audio_set_fragments(os, val); if (ret < 0) return ret; ret = put_user(ret, (int *)arg); if (ret) return ret; } return 0; case SNDCTL_DSP_SYNC: return audio_sync(file); case SNDCTL_DSP_SETDUPLEX: return 0; case SNDCTL_DSP_POST: return 0; case SNDCTL_DSP_GETTRIGGER: val = 0; if (file->f_mode & FMODE_READ && is->active && !is->stopped) val |= PCM_ENABLE_INPUT; if (file->f_mode & FMODE_WRITE && os->active && !os->stopped) val |= PCM_ENABLE_OUTPUT; return put_user(val, (int *)arg); case SNDCTL_DSP_SETTRIGGER: if (get_user(val, (int *)arg)) return -EFAULT; if (file->f_mode & FMODE_READ) { if (val & PCM_ENABLE_INPUT) { unsigned long flags; if (!is->active) { if (!is->buffers && audio_setup_buf(is)) return -ENOMEM; audio_prime_rx(state); } local_irq_save(flags); is->stopped = 0; audio_process_dma(is); local_irq_restore(flags); } else { audio_stop_dma(is); } } if (file->f_mode & FMODE_WRITE) { if (val & PCM_ENABLE_OUTPUT) { unsigned long flags; if (!os->buffers && audio_setup_buf(os)) return -ENOMEM; local_irq_save(flags); if (os->mapped && !os->pending_frags) { os->pending_frags = os->nbfrags; sema_init(&os->sem, 0); os->active = 1; } os->stopped = 0; audio_process_dma(os); local_irq_restore(flags); } else { audio_stop_dma(os); } } return 0; case SNDCTL_DSP_GETOPTR: case SNDCTL_DSP_GETIPTR: { count_info inf = { 0, }; audio_stream_t *s = (cmd == SNDCTL_DSP_GETOPTR) ? os : is; int bytecount, offset; unsigned long flags; if ((s == is && !(file->f_mode & FMODE_READ)) || (s == os && !(file->f_mode & FMODE_WRITE))) return -EINVAL; if (s->active) { local_irq_save(flags); offset = audio_get_dma_pos(s); inf.ptr = s->dma_tail * s->fragsize + offset; bytecount = s->bytecount + offset; s->bytecount = -offset; inf.blocks = s->fragcount; s->fragcount = 0; local_irq_restore(flags); if (bytecount < 0) bytecount = 0; inf.bytes = bytecount; } return copy_to_user((void *)arg, &inf, sizeof(inf)); } case SNDCTL_DSP_GETOSPACE: { audio_stream_t *s = os; if (!(file->f_mode & FMODE_WRITE)) return -EINVAL; if (!s->buffers && audio_setup_buf(s)) return -ENOMEM; inf.bytes = s->fragsize * s->free_bufnum; inf.fragments = s->free_bufnum; inf.fragsize = s->fragsize; inf.fragstotal = s->nbfrags; return copy_to_user((void *) arg, &inf, sizeof(inf)); } case SNDCTL_DSP_GETISPACE: { audio_stream_t *s = is; if (!(file->f_mode & FMODE_READ)) return -EINVAL; if (!s->buffers && audio_setup_buf(s)) return -ENOMEM; inf.bytes = s->fragsize * s->free_bufnum; inf.fragments = s->free_bufnum; inf.fragsize = s->fragsize; inf.fragstotal = s->nbfrags; return copy_to_user((void *) arg, &inf, sizeof(inf)); } case SNDCTL_DSP_NONBLOCK: file->f_flags |= O_NONBLOCK; return 0; case SNDCTL_DSP_RESET: if (file->f_mode & FMODE_READ) { audio_reset(is); } if (file->f_mode & FMODE_WRITE) { audio_reset(os); } return 0; default: /* * Let the client of this module handle the * non generic ioctls */ return state->client_ioctl(inode, file, cmd, arg); } return 0; } static int audio_release(struct inode *inode, struct file *file) { audio_state_t *state = file->private_data; audio_stream_t *os = state->output_stream; audio_stream_t *is = state->input_stream; DPRINTK("audio_release\n"); down(&state->sem); if (file->f_mode & FMODE_READ) { audio_discard_buf(is); s3c2410_dma_free(is->subchannel, &s3cmic_dma_client); /* s3c2410_dma_free(is->subchannel, &s3cline_dma_client); */ state->rd_ref = 0; } if (file->f_mode & FMODE_WRITE) { audio_sync(file); audio_discard_buf(os); s3c2410_dma_free( os->subchannel, &s3cplay_dma_client); state->wr_ref = 0; } if (!AUDIO_ACTIVE(state)) { if (state->hw_shutdown) state->hw_shutdown(state->data); } up(&state->sem); return 0; } #ifdef CONFIG_PM int s3c64xx_audio_suspend(audio_state_t * s, u32 state, u32 level) { if (level == SUSPEND_DISABLE || level == SUSPEND_POWER_DOWN) { audio_stream_t *is = s->input_stream; audio_stream_t *os = s->output_stream; int stopstate; if (is) { stopstate = is->stopped; audio_stop_dma(is); s3c_dma_ctrl(is->dma, is->subchannel, S3C_DMAOP_FLUSH); is->stopped = stopstate; } if (os) { stopstate = os->stopped; audio_stop_dma(os); s3c_dma_ctrl(os->dma, os->subchannel, S3C_DMAOP_FLUSH); os->stopped = stopstate; } if (AUDIO_ACTIVE(s) && s->hw_shutdown) s->hw_shutdown(s->data); } return 0; } int s3c64xx_audio_resume(audio_state_t * s, u32 level) { printk("%s\n", __FUNCTION__); #if 0 if (level == RESUME_ENABLE) { audio_stream_t *is = s->input_stream; audio_stream_t *os = s->output_stream; if (AUDIO_ACTIVE(s) && s->hw_init) s->hw_init(s->data); if (os && os->dma_regs) { //DMA_RESET(os); audio_process_dma(os); } if (is && is->dma_regs) { //DMA_RESET(is); audio_process_dma(is); } } #endif return 0; } EXPORT_SYMBOL(s3c64xx_audio_suspend); EXPORT_SYMBOL(s3c64xx_audio_resume); #else #define s3c64xx_audio_suspend() NULL #define s3c64xx_audio_resume() NULL #endif /* structure file_operations changed to const. */ static const struct file_operations s3c_f_ops = { .release = audio_release, .write = audio_write, .read = audio_read, .mmap = audio_mmap, .poll = audio_poll, .ioctl = audio_ioctl, .llseek = audio_llseek }; int s3c_audio_attach(struct inode *inode, struct file *file, audio_state_t *state) { audio_stream_t *os = state->output_stream; audio_stream_t *is = state->input_stream; int err, need_tx_dma=0; DPRINTK("audio_open\n"); down(&state->sem); /* access control */ err = -ENODEV; if ((file->f_mode & FMODE_WRITE) && !os) goto out; if ((file->f_mode & FMODE_READ) && !is) goto out; err = -EBUSY; if ((file->f_mode & FMODE_WRITE) && state->wr_ref) goto out; if ((file->f_mode & FMODE_READ) && state->rd_ref) goto out; err = -EINVAL; if ((file->f_mode & FMODE_READ) && state->need_tx_for_rx && !os) goto out; if(file->f_mode & FMODE_READ) file->f_mode = FMODE_READ; DPRINTK("%s, f_mode = 0x%x\n", __FUNCTION__, file->f_mode); /* request DMA channels */ if (file->f_mode & FMODE_WRITE) { if(s3c2410_dma_request(os->subchannel, &s3cplay_dma_client, NULL)) { printk(KERN_WARNING "unable to get DMA channel.\n" ); err = -EBUSY; goto out; } need_tx_dma = 1; err = s3c_iis_dma_init(os,0); if(err) goto out; } else if (file->f_mode & FMODE_READ) { if(s3c2410_dma_request(is->subchannel, &s3cmic_dma_client, NULL)) { printk(KERN_WARNING "unable to get DMA channel.\n" ); err = -EBUSY; if (need_tx_dma) s3c2410_dma_free(os->subchannel, &s3cplay_dma_client); s3c2410_dma_free(is->subchannel, &s3cmic_dma_client); goto out; } err = s3c_iis_dma_init(is,1); if(err) goto out; } else { printk("Error : audio set\n"); goto out; } /* now complete initialisation */ if (!AUDIO_ACTIVE(state)) { if (state->hw_init) state->hw_init(state->data); } if ((file->f_mode & FMODE_WRITE)) { state->wr_ref = 1; audio_reset(os); os->fragsize = AUDIO_FRAGSIZE_DEFAULT; os->nbfrags = AUDIO_NBFRAGS_DEFAULT; os->free_bufnum = AUDIO_NBFRAGS_DEFAULT; os->mapped = 0; init_waitqueue_head(&os->wq); } else if (file->f_mode & FMODE_READ) { state->rd_ref = 1; audio_reset(is); is->fragsize = AUDIO_FRAGSIZE_DEFAULT; is->nbfrags = AUDIO_NBFRAGS_DEFAULT; is->free_bufnum = AUDIO_NBFRAGS_DEFAULT; is->mapped = 0; init_waitqueue_head(&is->wq); } else { printk("Error : audio set\n"); goto out; } file->private_data = state; file->f_op = &s3c_f_ops; err = 0; out: up(&state->sem); return err; } EXPORT_SYMBOL(s3c_audio_attach); MODULE_AUTHOR("Ryu Euiyoul"); MODULE_DESCRIPTION("Common audio handling for the Samsung AP"); MODULE_LICENSE("GPL");