/*
*
* (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 <nala.la@samsung.com>
* - 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 <nala.la@samsung.com>");
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输入
*/