/* * * (C) Samsung Electronics 2004 * * Philips UDA1341 Audio Device Driver for SMDK board * * 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. * * * 2004-04-28 Kwanghyun La <[email protected]> * - modified for sharing module device driver of samsung arch * * 2004-07: SW.LEE * comment : Originally made by MIZI Research For S3C2410 * ported to S3C2440A */ #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/delay.h> #include <linux/sched.h> #include <linux/poll.h> #include <linux/interrupt.h> #include <linux/errno.h> #include <linux/sound.h> #include <linux/soundcard.h> #include <linux/l3/l3.h> #include <linux/l3/uda1341.h> #include <linux/platform_device.h> #include <linux/wait.h> #include <linux/dma-mapping.h> #include <asm/uaccess.h> #include <asm/io.h> #include <asm/hardware.h> #include <asm/semaphore.h> #include <asm/dma.h> #include <asm/hardware/clock.h> #include <asm/arch/dma.h> #include <asm/arch/regs-iis.h> #include "utu2440-audio.h" #undef USE_SYSFS #define USE_SYSFS #ifdef GDEBUG # define dprintk( x... ) printk( x ) #else # define dprintk( x... ) #endif #define AUDIO_NAME "UTU2440_UDA1341" #define AUDIO_NAME_VERBOSE "UTU2440 UDA1341 audio driver" #define AUDIO_FMT_MASK (AFMT_S16_LE) #define AUDIO_FMT_DEFAULT (AFMT_S16_LE) /* the UDA1341 is stereo only */ #define AUDIO_CHANNELS_DEFAULT 2 #define AUDIO_RATE_DEFAULT 44100 #define AUDIO_NBFRAGS_DEFAULT 8 //#define AUDIO_FRAGSIZE_DEFAULT 8192 #define AUDIO_FRAGSIZE_DEFAULT 16384 typedef struct { int size; /* buffer size */ char *start; /* point to actual buffer */ dma_addr_t dma_addr; /* physical buffer address */ struct semaphore sem; /* down before touching the buffer */ int master; /* owner for buffer allocation, contain size when true */ } audio_buf_t; typedef struct { audio_buf_t *buffers; /* pointer to audio buffer structures */ audio_buf_t *buf; /* current buffer used by read/write */ u_int buf_idx; /* index for the pointer above */ u_int fragsize; /* fragment i.e. buffer size */ u_int nbfrags; /* nbr of fragments */ int bytecount; /* nbr of processed bytes */ //int fragcount; /* nbr of fragment transitions */ //u_int channels; /* audio channels 1:mono, 2:stereo */ //u_int rate; /* audio rate */ dmach_t dma_ch; /* DMA channel (channel2 for audio) */ //int active:1; /* actually in progress */ //int stopped:1; /* might be active but stopped */ //wait_queue_head_t frag_wq; /* for poll(), etc. */ //s3c2410_dma_client_t dmaclient; /* kernel 2.6 dma client */ } audio_stream_t; /* * Mixer (UDA1341) interface */ static struct l3_client uda1341; static audio_stream_t output_stream; static audio_stream_t input_stream; static int ao_dcon = 0, ai_dcon = 0; #define NEXT_BUF(_s_,_b_) { \ (_s_)->_b_##_idx++; \ (_s_)->_b_##_idx %= (_s_)->nbfrags; \ (_s_)->_b_ = (_s_)->buffers + (_s_)->_b_##_idx; } static u_int audio_rate; static int audio_channels; static int audio_fmt; //static u_int audio_fragsize; //static u_int audio_nbfrags; static int audio_rd_refcount; static int audio_wr_refcount; #define audio_active (audio_rd_refcount | audio_wr_refcount) static void start_utu2440_iis_bus_tx(void); static void start_utu2440_iis_bus_rx(void); static void audio_clear_buf(audio_stream_t * s) { dprintk("\n"); s->active = 0; s->stopped = 0; /* * ensure DMA won't run anymore */ //utu2440_dma_flush_all(s->dma_ch); s3c2410_dma_ctrl(s->dma_ch, S3C2410_DMAOP_FLUSH); if (s->buffers) { int frag; for (frag = 0; frag < s->nbfrags; frag++) { if (!s->buffers[frag].master) continue; dma_free_coherent( NULL, s->buffers[frag].master, s->buffers[frag].start, 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; //printk("audio_setup_buf:=================\n"); 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); 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(NULL, dmasize, &dmaphys, GFP_KERNEL); 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 %ld\n", frag, b->start, (unsigned long)b->dma_addr); dmabuf += s->fragsize; dmaphys += s->fragsize; dmasize -= s->fragsize; } s->buf_idx = 0; s->buf = &s->buffers[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; s3c2410_dma_ctrl(s->dma_ch, S3C2410_DMAOP_FLUSH); 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; } static void audio_dmaout_done_callback(s3c2410_dma_chan_t *chp, void *buf, int size, s3c2410_dma_buffresult_t result) { audio_buf_t *b = (audio_buf_t *) buf; up(&b->sem); wake_up(&output_stream.frag_wq); if( result != S3C2410_RES_OK ) { dprintk("%s: tranter error\n", __FUNCTION__); } } static void audio_dmain_done_callback(s3c2410_dma_chan_t *chp, void *buf, int size, s3c2410_dma_buffresult_t result) { audio_buf_t *b = (audio_buf_t *) buf; b->size = size; up(&b->sem); wake_up(&input_stream.frag_wq); if( result != S3C2410_RES_OK ) { dprintk("%s: tranter error\n", __FUNCTION__); } } static int audio_sync(struct file *file) { audio_stream_t *s = &output_stream; audio_buf_t *b = s->buf; dprintk("audio_sync\n"); if (!s->buffers) return 0; if (b->size != 0) { down(&b->sem); s3c2410_dma_enqueue(s->dma_ch, (void *) b, b->dma_addr, b->size); b->size = 0; NEXT_BUF(s, buf); } b = s->buffers + ((s->nbfrags + s->buf_idx - 1) % s->nbfrags); if (down_interruptible(&b->sem)) return -EINTR; up(&b->sem); return 0; } static inline int copy_from_user_mono_stereo(char *to, const char *from, int count) { u_int *dst = (u_int *) to; const char *end = from + count; if( !access_ok(VERIFY_READ, from, count) ) return -EFAULT; if ((int) from & 0x2) { u_int v; __get_user(v, (const u_short *) from); from += 2; *dst++ = v | (v << 16); } while (from < end - 2) { u_int v, x, y; __get_user(v, (const u_int *) from); from += 4; x = v << 16; x |= x >> 16; y = v >> 16; y |= y << 16; *dst++ = x; *dst++ = y; } if (from < end) { u_int v; __get_user(v, (const u_short *) from); *dst = v | (v << 16); } return 0; } static ssize_t utu2440_audio_write(struct file *file, const char *buffer, size_t count, loff_t * ppos) { const char *buffer0 = buffer; audio_stream_t *s = &output_stream; int chunksize, ret = 0; dprintk("audio_write : start count=%d\n", count); switch (file->f_flags & O_ACCMODE) { case O_WRONLY: case O_RDWR: break; default: return -EPERM; } if (!s->buffers && audio_setup_buf(s)) return -ENOMEM; count &= ~0x03; while (count > 0) { audio_buf_t *b = s->buf; if (file->f_flags & O_NONBLOCK) { ret = -EAGAIN; if (down_trylock(&b->sem)) break; } else { ret = -ERESTARTSYS; if (down_interruptible(&b->sem)) break; } if (s->channels == 2) { 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; } else { chunksize = (s->fragsize - b->size) >> 1; if (chunksize > count) chunksize = count; dprintk("write %d to %d\n", chunksize * 2, s->buf_idx); if (copy_from_user_mono_stereo(b->start + b->size, buffer, chunksize)) { up(&b->sem); return -EFAULT; } b->size += chunksize * 2; } buffer += chunksize; count -= chunksize; if (b->size < s->fragsize) { up(&b->sem); break; } s->active = 1; // ghcstop add s3c2410_dma_enqueue(s->dma_ch, (void *) b, b->dma_addr, b->size); b->size = 0; NEXT_BUF(s, buf); } if ((buffer - buffer0)) ret = buffer - buffer0; dprintk("audio_write : end count=%d\n\n", ret); return ret; } static ssize_t utu2440_audio_read(struct file *file, char *buffer, size_t count, loff_t * ppos) { const char *buffer0 = buffer; audio_stream_t *s = &input_stream; int chunksize, ret = 0; dprintk("audio_read: count=%d\n", count); if (ppos != &file->f_pos) return -ESPIPE; if (!s->buffers) { int i; if (audio_setup_buf(s)) return -ENOMEM; for (i = 0; i < s->nbfrags; i++) { audio_buf_t *b = s->buf; down(&b->sem); s3c2410_dma_enqueue(s->dma_ch, (void *) b, b->dma_addr, s->fragsize); NEXT_BUF(s, buf); } } 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; } 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 */ s3c2410_dma_enqueue(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 unsigned int utu2440_audio_poll(struct file *file, struct poll_table_struct *wait) { unsigned int mask = 0; int i; dprintk("audio_poll(): mode=%s\n", (file->f_mode & FMODE_WRITE) ? "w" : ""); if (file->f_mode & FMODE_READ) { if (!input_stream.active) { if (!input_stream.buffers && audio_setup_buf(&input_stream)) return -ENOMEM; } poll_wait(file, &input_stream.frag_wq, wait); for (i = 0; i < input_stream.nbfrags; i++) { if (atomic_read(&input_stream.buffers[i].sem.count) > 0) mask |= POLLIN | POLLWRNORM; break; } } if (file->f_mode & FMODE_WRITE) { if (!output_stream.active) { if (!output_stream.buffers && audio_setup_buf(&output_stream)) return -ENOMEM; poll_wait(file, &output_stream.frag_wq, wait); } for (i = 0; i < output_stream.nbfrags; i++) { if (atomic_read(&output_stream.buffers[i].sem.count) > 0) mask |= POLLOUT | POLLWRNORM; break; } } dprintk("audio_poll() returned mask of %s\n", (mask & POLLOUT) ? "w" : ""); return mask; } static loff_t utu2440_audio_llseek(struct file *file, loff_t offset, int origin) { return -ESPIPE; } static int utu2440_mixer_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) { /* * We only accept mixer (type 'M') ioctls. */ if (_IOC_TYPE(cmd) != 'M') return -EINVAL; return l3_command(&uda1341, cmd, (void *) arg); } static int iispsr_value(int sample_rate) { int i; unsigned long fact0 = clk_get_rate(clk_get(NULL, "pclk")) / S_CLOCK_FREQ; unsigned long r0_sample_rate, r1_sample_rate = 0, r2_sample_rate; int prescaler = 0; dprintk("requested sample_rate = %d\n", sample_rate); for (i = 1; i < 32; i++) { r1_sample_rate = fact0 / i; if (r1_sample_rate < sample_rate) break; } r0_sample_rate = fact0 / (i + 1); r2_sample_rate = fact0 / (i - 1); dprintk("calculated (%d-1) freq = %ld, error = %d\n", i + 1, r0_sample_rate, abs(r0_sample_rate - sample_rate)); dprintk("calculated (%d-1) freq = %ld, error = %d\n", i, r1_sample_rate, abs(r1_sample_rate - sample_rate)); dprintk("calculated (%d-1) freq = %ld, error = %d\n", i - 1, r2_sample_rate, abs(r2_sample_rate - sample_rate)); prescaler = i; if (abs(r0_sample_rate - sample_rate) < abs(r1_sample_rate - sample_rate)) prescaler = i + 1; if (abs(r2_sample_rate - sample_rate) < abs(r1_sample_rate - sample_rate)) prescaler = i - 1; prescaler = max_t(int, 0, (prescaler - 1)); dprintk("selected prescale value = %d, freq = %ld, error = %d\n", prescaler, fact0 / (prescaler + 1), abs((fact0 / (prescaler + 1)) - sample_rate)); return prescaler; } static long audio_set_dsp_speed(long val) { unsigned long tmp; int prescaler = 0; tmp = clk_get_rate(clk_get(NULL, "pclk")) / S_CLOCK_FREQ; dprintk("requested = %ld, limit = %ld\n", val, tmp); if (val > (tmp >> 1)) return -1; prescaler = iispsr_value(val); IISPSR = IISPSR_A(prescaler) | IISPSR_B(prescaler); audio_rate = val; output_stream.rate = input_stream.rate = audio_rate; // ghcstop fix dprintk("return audio_rate = %ld\n", (unsigned long) audio_rate); return audio_rate; } static int audio_set_fragments(audio_stream_t * s, int val) { if (s->active) return -EBUSY; if (s->buffers) audio_clear_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 utu2440_audio_ioctl(struct inode *inode, struct file *file, uint cmd, ulong arg) { long val; //printk("a_ioctl = 0x%08x, SNDCTL_DSP_GETOSPACE=0x%08x\n", cmd, SNDCTL_DSP_GETOSPACE); switch (cmd) { case SNDCTL_DSP_SETFMT: get_user(val, (long *) arg); if (val & AUDIO_FMT_MASK) { audio_fmt = val; break; } else return -EINVAL; case SNDCTL_DSP_CHANNELS: case SNDCTL_DSP_STEREO: get_user(val, (long *) arg); if (cmd == SNDCTL_DSP_STEREO) val = val ? 2 : 1; if (val != 1 && val != 2) return -EINVAL; audio_channels = val; break; case SOUND_PCM_READ_CHANNELS: put_user(audio_channels, (long *) arg); break; case SNDCTL_DSP_SPEED: get_user(val, (long *) arg); val = audio_set_dsp_speed(val); if (val < 0) return -EINVAL; put_user(val, (long *) arg); break; case SOUND_PCM_READ_RATE: put_user(audio_rate, (long *) arg); break; case SNDCTL_DSP_GETFMTS: put_user(AUDIO_FMT_MASK, (long *) arg); break; case SNDCTL_DSP_GETBLKSIZE: if (file->f_mode & FMODE_WRITE) return put_user(output_stream.fragsize, (long *) arg); else return put_user(input_stream.fragsize, (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(&input_stream, 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(&output_stream, 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_GETOSPACE: { audio_stream_t *s = &output_stream; audio_buf_info *inf = (audio_buf_info *) arg; int err = !access_ok(VERIFY_WRITE, inf, sizeof(*inf)); int i; int frags = 0, bytes = 0, dma_send_bytes = 0; if (!(file->f_mode & FMODE_WRITE)) return -EINVAL; if (err) return err; if (!s->buffers && audio_setup_buf(s)) return -ENOMEM; for (i = 0; i < s->nbfrags; i++) { if (atomic_read(&s->buffers[i].sem.count) > 0) { if (s->buffers[i].size == 0) frags++; bytes += s->fragsize - s->buffers[i].size; } } put_user(frags, &inf->fragments); put_user(s->nbfrags, &inf->fragstotal); put_user(s->fragsize, &inf->fragsize); put_user(bytes, &inf->bytes); break; } case SNDCTL_DSP_GETISPACE: { audio_stream_t *s = &input_stream; audio_buf_info *inf = (audio_buf_info *) arg; int err = !access_ok(VERIFY_WRITE, inf, sizeof(*inf)); int i; int frags = 0, bytes = 0; if (!(file->f_mode & FMODE_READ)) return -EINVAL; if (err) return err; if (!s->buffers && audio_setup_buf(s)) return -ENOMEM; for (i = 0; i < s->nbfrags; i++) { if (atomic_read(&s->buffers[i].sem.count) > 0) { if (s->buffers[i].size == s->fragsize) frags++; bytes += s->buffers[i].size; } } put_user(frags, &inf->fragments); put_user(s->nbfrags, &inf->fragstotal); put_user(s->fragsize, &inf->fragsize); put_user(bytes, &inf->bytes); break; } case SNDCTL_DSP_RESET: if (file->f_mode & FMODE_READ) { audio_reset_buf(&input_stream); } if (file->f_mode & FMODE_WRITE) { audio_reset_buf(&output_stream); } return 0; case SNDCTL_DSP_NONBLOCK: file->f_flags |= O_NONBLOCK; return 0; case SNDCTL_DSP_POST: case SNDCTL_DSP_SUBDIVIDE: case SNDCTL_DSP_GETCAPS: case SNDCTL_DSP_GETTRIGGER: case SNDCTL_DSP_SETTRIGGER: case SNDCTL_DSP_GETIPTR: case SNDCTL_DSP_GETOPTR: case SNDCTL_DSP_MAPINBUF: case SNDCTL_DSP_MAPOUTBUF: case SNDCTL_DSP_SETSYNCRO: case SNDCTL_DSP_SETDUPLEX: printk("request IOCTL %d \n", cmd); return -ENOSYS; default: return utu2440_mixer_ioctl(inode, file, cmd, arg); } return 0; } static int utu2440_audio_open(struct inode *inode, struct file *file) { int cold = !audio_active; dprintk("audio_open\n"); if ((file->f_flags & O_ACCMODE) == O_RDONLY) { if (audio_rd_refcount || audio_wr_refcount) return -EBUSY; audio_rd_refcount++; } else if ((file->f_flags & O_ACCMODE) == O_WRONLY) { if (audio_wr_refcount) return -EBUSY; audio_wr_refcount++; } else if ((file->f_flags & O_ACCMODE) == O_RDWR) { if (audio_rd_refcount || audio_wr_refcount) return -EBUSY; audio_rd_refcount++; audio_wr_refcount++; } else return -EINVAL; if (cold) { audio_rate = AUDIO_RATE_DEFAULT; audio_channels = AUDIO_CHANNELS_DEFAULT; /* * the UDA1341 is stereo only ==> 2 channels */ if ((file->f_mode & FMODE_WRITE)) { output_stream.fragsize = AUDIO_FRAGSIZE_DEFAULT; output_stream.nbfrags = AUDIO_NBFRAGS_DEFAULT; output_stream.channels = audio_channels; start_utu2440_iis_bus_tx(); audio_clear_buf(&output_stream); init_waitqueue_head(&output_stream.frag_wq); } if ((file->f_mode & FMODE_READ)) { input_stream.fragsize = AUDIO_FRAGSIZE_DEFAULT; input_stream.nbfrags = AUDIO_NBFRAGS_DEFAULT; input_stream.channels = audio_channels; start_utu2440_iis_bus_rx(); audio_clear_buf(&input_stream); init_waitqueue_head(&input_stream.frag_wq); } } return 0; } static int utu2440_audio_release(struct inode *inode, struct file *file) { dprintk("audio_release\n"); if (file->f_mode & FMODE_READ) { if (audio_rd_refcount == 1) audio_clear_buf(&input_stream); audio_rd_refcount = 0; } if (file->f_mode & FMODE_WRITE) { if (audio_wr_refcount == 1) { audio_sync(file); audio_clear_buf(&output_stream); audio_wr_refcount = 0; } } return 0; } static void start_uda1341(void) { #if 0 struct uda1341_cfg cfg; cfg.format = FMT_MSB; cfg.fs = S_CLOCK_FREQ; l3_command(&uda1341, L3_UDA1341_CONFIGURE, &cfg); #endif } static void start_utu2440_iis_bus_rx(void) { IISCON = 0; IISMOD = 0; IISFIFOC = 0; /* * 44 KHz , 384fs */ IISPSR = (IISPSR_A(iispsr_value(AUDIO_RATE_DEFAULT)) | IISPSR_B(iispsr_value(AUDIO_RATE_DEFAULT))); IISCON = (IISCON_RX_DMA /* Transmit DMA service request */ | IISCON_TX_IDLE /* Receive Channel idle */ | IISCON_PRESCALE); /* IIS Prescaler Enable */ IISMOD = (IISMOD_SEL_MA /* Master mode */ | IISMOD_SEL_RX | IISMOD_CH_RIGHT /* Low for left channel */ | IISMOD_FMT_MSB /* MSB-justified format */ | IISMOD_BIT_16 /* Serial data bit/channel is 16 bit */ #if (S_CLOCK_FREQ == 384) | IISMOD_FREQ_384 /* Master clock freq = 384 fs */ #else | IISMOD_FREQ_256 /* Master clock freq = 256 fs */ #endif | IISMOD_SFREQ_32); /* 32 fs */ IISFIFOC = (IISFCON_RX_DMA /* Transmit FIFO access mode: DMA */ | IISFCON_RX_EN); /* Transmit FIFO enable */ IISCON |= IISCON_EN; /* IIS enable(start) */ } void start_utu2440_iis_bus_tx(void) { IISCON = 0; IISMOD = 0; IISFIFOC = 0; IISPSR = (IISPSR_A(iispsr_value(AUDIO_RATE_DEFAULT)) | IISPSR_B(iispsr_value(AUDIO_RATE_DEFAULT))); IISCON = (IISCON_TX_DMA /* Transmit DMA service request */ | IISCON_RX_IDLE /* Receive Channel idle */ | IISCON_PRESCALE); /* IIS Prescaler Enable */ IISMOD = (IISMOD_SEL_MA /* Master mode */ | IISMOD_SEL_TX /* Transmit */ | IISMOD_CH_RIGHT /* Low for left channel */ | IISMOD_FMT_MSB /* MSB-justified format */ | IISMOD_BIT_16 /* Serial data bit/channel is 16 bit */ #if (S_CLOCK_FREQ == 384) | IISMOD_FREQ_384 /* Master clock freq = 384 fs */ #else | IISMOD_FREQ_256 /* Master clock freq = 256 fs */ #endif | IISMOD_SFREQ_32); /* 32 fs */ IISFIFOC = (IISFCON_TX_DMA /* Transmit FIFO access mode: DMA */ | IISFCON_TX_EN); /* Transmit FIFO enable */ IISCON |= IISCON_EN; /* IIS enable(start) */ } #ifdef USE_SYSFS // sysfs锟斤拷锟斤拷锟斤拷 锟斤拷锟斤拷. static int audio_init_dma(audio_stream_t * s, char *desc) #else static int __init audio_init_dma(audio_stream_t * s, char *desc) #endif { int ret; if (s->dma_ch == S3C2410_DMA_CH2) { ret = s3c2410_dma_request(s->dma_ch, &(s->dmaclient), NULL); if( ret ) { dprintk("%s: dma request err\n", __FUNCTION__ ); return ret; } ao_dcon = S3C2410_DCON_HANDSHAKE|S3C2410_DCON_SYNC_PCLK|S3C2410_DCON_TSZUNIT|S3C2410_DCON_SSERVE|S3C2410_DCON_CH2_I2SSDO|S3C2410_DCON_NORELOAD| s3c2410_dma_config(s->dma_ch, 2, ao_dcon); // a out, halfword s3c2410_dma_setflags(s->dma_ch, S3C2410_DMAF_AUTOSTART); // a out s3c2410_dma_set_buffdone_fn(s->dma_ch, audio_dmaout_done_callback); s3c2410_dma_devconfig(s->dma_ch, S3C2410_DMASRC_MEM, BUF_ON_APB, 0x55000010); dprintk("%s: dma request done audio out channel\n", __FUNCTION__ ); return 0; } else if (s->dma_ch == S3C2410_DMA_CH1) { ret = s3c2410_dma_request(s->dma_ch, &(s->dmaclient), NULL); if( ret ) { dprintk("%s: dma request err\n", __FUNCTION__ ); return ret; } ai_dcon = S3C2410_DCON_HANDSHAKE|S3C2410_DCON_SYNC_PCLK|S3C2410_DCON_TSZUNIT|S3C2410_DCON_SSERVE|S3C2410_DCON_CH1_I2SSDI|S3C2410_DCON_NORELOAD| s3c2410_dma_config(s->dma_ch, 2, ai_dcon); // a in, halfword s3c2410_dma_setflags(s->dma_ch, S3C2410_DMAF_AUTOSTART); // a in s3c2410_dma_set_buffdone_fn(s->dma_ch, audio_dmain_done_callback); s3c2410_dma_devconfig(s->dma_ch, S3C2410_DMASRC_HW, BUF_ON_APB, 0x55000010); dprintk("%s: dma request done audio in channel\n", __FUNCTION__ ); return 0; } else return 1; } static int audio_clear_dma(audio_stream_t * s) { //extern int s3c2410_dma_free(dmach_t channel, s3c2410_dma_client_t *); s3c2410_dma_free(s->dma_ch, &(s->dmaclient) ); //utu2440_free_dma(s->dma_ch); return 0; } // ghcstop: audio driver common routine ==> device driver register routine =================== static struct file_operations utu2440_audio_fops = { llseek: utu2440_audio_llseek, write: utu2440_audio_write, read: utu2440_audio_read, poll: utu2440_audio_poll, ioctl: utu2440_audio_ioctl, open: utu2440_audio_open, release: utu2440_audio_release, owner: THIS_MODULE }; static struct file_operations utu2440_mixer_fops = { ioctl: utu2440_mixer_ioctl, owner: THIS_MODULE }; #if 0 // if use sa1100-audio driver style, todo this static int h3600_audio_open(struct inode *inode, struct file *file) { return sa1100_audio_attach(inode, file, &audio_state); } /* * Missing fields of this structure will be patched with the call * to sa1100_audio_attach(). */ static struct file_operations h3600_audio_fops = { open: h3600_audio_open, owner: THIS_MODULE }; #endif static inline void utu2440_uda1341_enable(void) { start_uda1341(); start_utu2440_iis_bus_tx(); } #ifdef CONFIG_PM static int utu2440_audio_suspend(struct device *dev, u32 state, u32 level) { switch (level) { case SUSPEND_POWER_DOWN: break; } return 0; } static int utu2440_audio_resume(struct device *dev, u32 level) { switch (level) { case RESUME_POWER_ON: printk("%s\n", __FUNCTION__); utu2440_uda1341_enable(); break; } return 0; } #else #define utu2440_audio_suspend NULL #define utu2440_audio_resume NULL #endif static int audio_dev_dsp, audio_dev_mixer; static int utu2440_audio_probe(struct device *_dev) { int ret = 0; printk("UTU2440 SOUND driver probe!\n"); ret = l3_attach_client(&uda1341, "l3-bit-24x0-gpio", "uda1341"); if (ret) { printk("l3_attach_client() failed.\n"); return ret; } l3_open(&uda1341); start_uda1341(); output_stream.dma_ch = S3C2410_DMA_CH2; output_stream.dmaclient.name = "audio_out"; if (audio_init_dma(&output_stream, "UDA1341 out")) { audio_clear_dma(&output_stream); printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels\n"); return -EBUSY; } input_stream.dma_ch = S3C2410_DMA_CH1; input_stream.dmaclient.name = "audio_in"; if (audio_init_dma(&input_stream, "UDA1341 in")) { audio_clear_dma(&input_stream); printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels\n"); return -EBUSY; } audio_dev_dsp = register_sound_dsp(&utu2440_audio_fops, -1); audio_dev_mixer = register_sound_mixer(&utu2440_mixer_fops, -1); printk(AUDIO_NAME_VERBOSE " initialized\n"); return 0; } static int utu2440_audio_remove(struct device *_dev) { unregister_sound_dsp(audio_dev_dsp); unregister_sound_mixer(audio_dev_mixer); audio_clear_dma(&output_stream); audio_clear_dma(&input_stream); l3_close(&uda1341); l3_detach_client(&uda1341); printk(AUDIO_NAME_VERBOSE " unloaded\n"); return 0; } static struct device_driver utu2440_audio_driver = { .name = "s3c2440-sound", .bus = &platform_bus_type, .probe = utu2440_audio_probe, .remove = utu2440_audio_remove, .suspend = utu2440_audio_suspend, .resume = utu2440_audio_resume, }; #ifdef USE_SYSFS static int __init utu2440_uda1341_init(void) { int ret = -ENODEV; printk("UTU2440 SOUND driver register\n"); //if (!machine_is_h3600() || machine_is_h3100() || machine_is_h3800()) ret = driver_register(&utu2440_audio_driver); if( ret ) { printk("S3C2440 SOUND driver un registered, %d\n", ret); } return ret; } static void __exit utu2440_uda1341_exit(void) { driver_unregister(&utu2440_audio_driver); } #else int __init utu2440_uda1341_init(void) { int ret = 0; //printk("ghcstop.........probe\n"); ret = l3_attach_client(&uda1341, "l3-bit-24x0-gpio", "uda1341"); if (ret) { printk("l3_attach_client() failed.\n"); return ret; } l3_open(&uda1341); start_uda1341(); output_stream.dma_ch = S3C2410_DMA_CH2; output_stream.dmaclient.name = "audio_out"; if (audio_init_dma(&output_stream, "UDA1341 out")) { audio_clear_dma(&output_stream); printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels\n"); return -EBUSY; } input_stream.dma_ch = S3C2410_DMA_CH1; input_stream.dmaclient.name = "audio_in"; if (audio_init_dma(&input_stream, "UDA1341 in")) { audio_clear_dma(&input_stream); printk(KERN_WARNING AUDIO_NAME_VERBOSE ": unable to get DMA channels\n"); return -EBUSY; } audio_dev_dsp = register_sound_dsp(&utu2440_audio_fops, -1); audio_dev_mixer = register_sound_mixer(&utu2440_mixer_fops, -1); printk(AUDIO_NAME_VERBOSE " initialized\n"); return 0; } void __exit utu2440_uda1341_exit(void) { unregister_sound_dsp(audio_dev_dsp); unregister_sound_mixer(audio_dev_mixer); audio_clear_dma(&output_stream); audio_clear_dma(&input_stream); l3_close(&uda1341); l3_detach_client(&uda1341); printk(AUDIO_NAME_VERBOSE " unloaded\n"); //return 0; } #endif module_init(utu2440_uda1341_init); module_exit(utu2440_uda1341_exit); MODULE_AUTHOR("Kwanghyun La <[email protected]>"); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("IIS sound driver for S3C2440");
test
/* * sound.c */ #include #include #include #include #include #include #include #define LENGTH 3 /* 存储秒数 */ #define RATE 8000 /* 采样频率 */ #define SIZE 8 /* 量化位数 */ #define CHANNELS 1 /* 声道数目 */ /* 用于保存数字音频数据的内存缓冲区 */ unsigned char buf[LENGTH*RATE*SIZE*CHANNELS/8]; int main() { int fd; /* 声音设备的文件描述符 */ int arg; /* 用于ioctl调用的参数 */ int status; /* 系统调用的返回值 */ /* 打开声音设备 */ fd = open("/dev/dsp", O_RDWR); if (fd < 0) { perror("open of /dev/dsp failed"); exit(1); } /* 设置采样时的量化位数 */ arg = SIZE; status = ioctl(fd, SOUND_PCM_WRITE_BITS, &arg); if (status == -1) perror("SOUND_PCM_WRITE_BITS ioctl failed"); if (arg != SIZE) perror("unable to set sample size"); /* 设置采样时的声道数目 */ arg = CHANNELS; status = ioctl(fd, SOUND_PCM_WRITE_CHANNELS, &arg); if (status == -1) perror("SOUND_PCM_WRITE_CHANNELS ioctl failed"); if (arg != CHANNELS) perror("unable to set number of channels"); /* 设置采样时的采样频率 */ arg = RATE; status = ioctl(fd, SOUND_PCM_WRITE_RATE, &arg); if (status == -1) perror("SOUND_PCM_WRITE_WRITE ioctl failed"); /* 循环,直到按下Control-C */ while (1) { printf("Say something:\n"); status = read(fd, buf, sizeof(buf)); /* 录音 */ if (status != sizeof(buf)) perror("read wrong number of bytes"); printf("You said:\n"); status = write(fd, buf, sizeof(buf)); /* 回放 */ if (status != sizeof(buf)) perror("wrote wrong number of bytes"); /* 在继续录音前等待回放结束 */ status = ioctl(fd, SOUND_PCM_SYNC, 0); if (status == -1) perror("SOUND_PCM_SYNC ioctl failed"); } }
/* * mixer.c */ #include #include #include #include #include #include /* 用来存储所有可用混音设备的名称 */ const char *sound_device_names[] = SOUND_DEVICE_NAMES; int fd; /* 混音设备所对应的文件描述符 */ int devmask, stereodevs; /* 混音器信息对应的位图掩码 */ char *name; /* 显示命令的使用方法及所有可用的混音设备 */ void usage() { int i; fprintf(stderr, "usage: %s " %s "Where is one of:\n", name, name); for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++) if ((1 << i) & devmask) /* 只显示有效的混音设备 */ fprintf(stderr, "%s ", sound_device_names[i]); fprintf(stderr, "\n"); exit(1); } int main(int argc, char *argv[]) { int left, right, level; /* 增益设置 */ int status; /* 系统调用的返回值 */ int device; /* 选用的混音设备 */ char *dev; /* 混音设备的名称 */ int i; name = argv[0]; /* 以只读方式打开混音设备 */ fd = open("/dev/mixer", O_RDONLY); if (fd == -1) { perror("unable to open /dev/mixer"); exit(1); } /* 获得所需要的信息 */ status = ioctl(fd, SOUND_MIXER_READ_DEVMASK, &devmask); if (status == -1) perror("SOUND_MIXER_READ_DEVMASK ioctl failed"); status = ioctl(fd, SOUND_MIXER_READ_STEREODEVS, &stereodevs); if (status == -1) perror("SOUND_MIXER_READ_STEREODEVS ioctl failed"); /* 检查用户输入 */ if (argc != 3 && argc != 4) usage(); /* 保存用户输入的混音器名称 */ dev = argv[1]; /* 确定即将用到的混音设备 */ for (i = 0 ; i < SOUND_MIXER_NRDEVICES ; i++) if (((1 << i) & devmask) && !strcmp(dev, sound_device_names[i])) break; if (i == SOUND_MIXER_NRDEVICES) { /* 没有找到匹配项 */ fprintf(stderr, "%s is not a valid mixer device\n", dev); usage(); } /* 查找到有效的混音设备 */ device = i; /* 获取增益值 */ if (argc == 4) { /* 左、右声道均给定 */ left = atoi(argv[2]); right = atoi(argv[3]); } else { /* 左、右声道设为相等 */ left = atoi(argv[2]); right = atoi(argv[2]); } /* 对非立体声设备给出警告信息 */ if ((left != right) && !((1 << i) & stereodevs)) { fprintf(stderr, "warning: %s is not a stereo device\n", dev); } /* 将两个声道的值合到同一变量中 */ level = (right << 8) + left; /* 设置增益 */ status = ioctl(fd, MIXER_WRITE(device), &level); if (status == -1) { perror("MIXER_WRITE ioctl failed"); exit(1); } /* 获得从驱动返回的左右声道的增益 */ left = level & 0xff; right = (level & 0xff00) >> 8; /* 显示实际设置的增益 */ fprintf(stderr, "%s gain set to %d%% / %d%%\n", dev, left, right); /* 关闭混音设备 */ close(fd); return 0; } /*编译好上面的程序之后,先不带任何参数执行一遍,此时会列出声卡上所有可用的混音通道: [xiaowp@linuxgam sound]$ ./mixer usage: ./mixer ./mixer Where is one of: vol pcm speaker line mic cd igain line1 phin video 之后就可以很方便地设置各个混音通道的增益大小了,例如下面的命令就能够将CD输入的左、右声道的增益分别设置为80%和90%: [xiaowp@linuxgam sound]$ ./mixer cd 80 90 cd gain set to 80% / 90% */ /*SOUND_MIXER_VOLUME 主音量调节 SOUND_MIXER_BASS 低音控制 SOUND_MIXER_TREBLE 高音控制 SOUND_MIXER_SYNTH FM合成器 SOUND_MIXER_PCM 主D/A转换器 SOUND_MIXER_SPEAKER PC喇叭 SOUND_MIXER_LINE 音频线输入 SOUND_MIXER_MIC 麦克风输入 SOUND_MIXER_CD CD输入 SOUND_MIXER_IMIX 回放音量 SOUND_MIXER_ALTPCM 从D/A 转换器 SOUND_MIXER_RECLEV 录音音量 SOUND_MIXER_IGAIN 输入增益 SOUND_MIXER_OGAIN 输出增益 SOUND_MIXER_LINE1 声卡的第1输入 SOUND_MIXER_LINE2 声卡的第2输入 SOUND_MIXER_LINE3 声卡的第3输入 */