summaryrefslogtreecommitdiffstats
path: root/uClinux-2.4.20-uc1/drivers/sound/w90n745_audio.c
diff options
context:
space:
mode:
Diffstat (limited to 'uClinux-2.4.20-uc1/drivers/sound/w90n745_audio.c')
-rw-r--r--uClinux-2.4.20-uc1/drivers/sound/w90n745_audio.c1049
1 files changed, 1049 insertions, 0 deletions
diff --git a/uClinux-2.4.20-uc1/drivers/sound/w90n745_audio.c b/uClinux-2.4.20-uc1/drivers/sound/w90n745_audio.c
new file mode 100644
index 0000000..48067c7
--- /dev/null
+++ b/uClinux-2.4.20-uc1/drivers/sound/w90n745_audio.c
@@ -0,0 +1,1049 @@
+/****************************************************************************
+ *
+ * Copyright (c) 2004 - 2007 Winbond Electronics Corp. All rights reserved.
+ *
+ *
+ * FILENAME
+ * w90n745_Audio.c
+ *
+ * VERSION
+ * 1.0
+ *
+ * DESCRIPTION
+ * Winbond w90n745 Audio Driver for Linux 2.4.x with OSS frame
+ *
+ * DATA STRUCTURES
+ * None
+ *
+ * FUNCTIONS
+ *
+ *
+ * HISTORY
+ * 2005.11.24 Created by PC34 QFu
+ *
+ * REMARK
+ * None
+ *
+ **************************************************************************/
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/poll.h>
+#include <linux/soundcard.h>
+#include <linux/sound.h>
+
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/arch/irqs.h>
+
+#include "w90n745_audio_regs.h"
+#include "w90n745_audio.h"
+
+#define AUDIO_BUFFER_ORDER 4 /* 64K x 2 (play + record) */
+
+#define FRAG_SIZE (( PAGE_SIZE << AUDIO_BUFFER_ORDER ) / 2)
+
+#define WB_AUDIO_DEFAULT_SAMPLERATE AU_SAMPLE_RATE_44100
+#define WB_AUDIO_DEFAULT_CHANNEL 2
+#define WB_AUDIO_DEFAULT_VOLUME 30
+
+//#define WB_AU_DEBUG
+//#define WB_AU_DEBUG_ENTER_LEAVE
+//#define WB_AU_DEBUG_MSG
+//#define WB_AU_DEBUG_MSG2
+
+#ifdef WB_AU_DEBUG
+#define DBG(fmt, arg...) printk(fmt, ##arg)
+#else
+#define DBG(fmt, arg...)
+#endif
+
+#ifdef WB_AU_DEBUG_ENTER_LEAVE
+#define ENTER() DBG("[%-10s] : Enter\n", __FUNCTION__)
+#define LEAVE() DBG("[%-10s] : Leave\n", __FUNCTION__)
+#else
+#define ENTER()
+#define LEAVE()
+#endif
+
+#ifdef WB_AU_DEBUG_MSG
+#define MSG(fmt) DBG("[%-10s] : "fmt, __FUNCTION__)
+#else
+#define MSG(fmt)
+#endif
+
+#ifdef WB_AU_DEBUG_MSG2
+#define MSG2(fmt, arg...) DBG("[%-10s] : "fmt, __FUNCTION__, ##arg)
+#else
+#define MSG2(fmt, arg...)
+#endif
+
+
+static WB_AUDIO_T audio_dev;
+static WB_AUDIO_CODEC_T *all_codecs[2] = {&wb_i2s_codec, &wb_ac97_codec};
+static int dev_dsp[2] = {-1, -1}, dev_mixer[2] = {-1, -1};
+static int nSamplingRate[]={AU_SAMPLE_RATE_8000,
+ AU_SAMPLE_RATE_11025,
+ AU_SAMPLE_RATE_16000,
+ AU_SAMPLE_RATE_22050,
+ AU_SAMPLE_RATE_24000,
+ AU_SAMPLE_RATE_32000,
+ AU_SAMPLE_RATE_44100,
+ AU_SAMPLE_RATE_48000};
+
+static void wb_audio_stop_play(void)
+{
+ MSG("Stop Play! \n");
+
+ if ( audio_dev.state & AU_STATE_PLAYING){
+ audio_dev.codec->set_play_volume(0, 0);
+ audio_dev.codec->stop_play();
+ audio_dev.state &= ~AU_STATE_PLAYING;
+ wake_up_interruptible(&audio_dev.write_wait_queue);
+ MSG2("Buf[0] : %x, Buf[1] : %x\n",
+ audio_dev.play_half_buf[0].ptr,
+ audio_dev.play_half_buf[1].ptr);
+
+ }
+}
+
+static void wb_audio_stop_record(void)
+{
+ MSG("Stop Record!\n");
+
+ if ( audio_dev.state & AU_STATE_RECORDING){
+ audio_dev.codec->set_record_volume(0, 0);
+ audio_dev.codec->stop_record();
+ audio_dev.state &= ~AU_STATE_RECORDING;
+ wake_up_interruptible(&audio_dev.read_wait_queue);
+ }
+}
+
+static int play_callback(UINT32 uAddr, UINT32 uLen)
+{
+ int i = 1;
+
+ ENTER();
+
+ if(uAddr == audio_dev.play_buf_addr){
+ MSG("<");
+ i = 0;
+ }
+ else
+ MSG(">");
+
+ audio_dev.play_half_buf[i].ptr = 0; /* set buffer empty flag */
+
+ wake_up_interruptible(&audio_dev.write_wait_queue); /* wake up all block write system call */
+
+ if(audio_dev.play_half_buf[i^1].ptr /*== FRAG_SIZE*/ > 0){ /* check whether next buffer is full ? */
+ LEAVE();
+ return 0;
+ }
+ else{
+ wb_audio_stop_play();
+ return 1;
+ }
+}
+
+static int record_callback(UINT32 uAddr, UINT32 uLen)
+{
+ int i = 1;
+
+ if(uAddr == audio_dev.record_buf_addr)
+ i = 0;
+
+ audio_dev.record_half_buf[i].ptr =0; /* indicate read from buffer[0] */
+
+ wake_up_interruptible(&audio_dev.read_wait_queue); /* wake up all blocked read system call */
+
+ if ( audio_dev.record_half_buf[i ^ 1].ptr == 0) { /* last block wan't take off , user may stop record */
+ wb_audio_stop_record();
+ return 1;
+ }
+ else
+ return 0;
+}
+
+static int wb_audio_start_play(void)
+{
+ int ret = 0;
+
+ MSG("Start Playing ... \n");
+
+ if ( audio_dev.state & AU_STATE_PLAYING) /* playing? */
+ return 0;
+
+ if ( audio_dev.state & AU_STATE_RECORDING ) { /* recording? */
+ if (!(audio_dev.codec->get_capacity() & DSP_CAP_DUPLEX)) /* not support full duplex */
+ wb_audio_stop_record();
+ }
+
+ audio_dev.state |= AU_STATE_PLAYING;
+
+ MSG2("Buf[0] : %x, Buf[1] : %x\n",
+ audio_dev.play_half_buf[0].ptr,
+ audio_dev.play_half_buf[1].ptr);
+
+ if ( audio_dev.codec->start_play(play_callback,
+ audio_dev.nSamplingRate,
+ audio_dev.nChannels)) {
+ audio_dev.state &= ~AU_STATE_PLAYING;
+ MSG("Play error\n");
+
+ ret = -EIO;
+ }
+ else{
+ audio_dev.codec->set_play_volume(audio_dev.nPlayVolumeLeft, audio_dev.nPlayVolumeRight);
+ }
+
+ return ret;
+}
+
+static int wb_audio_start_record(void)
+{
+ int ret=0;
+
+ MSG("Start Recording ... \n");
+
+ if (audio_dev.state & AU_STATE_RECORDING)
+ return 0;
+
+ if (audio_dev.state & AU_STATE_PLAYING) {
+ if (!(audio_dev.codec->get_capacity() & DSP_CAP_DUPLEX))
+ wb_audio_stop_play();
+ }
+
+ audio_dev.record_half_buf[0].ptr =FRAG_SIZE;
+ audio_dev.record_half_buf[1].ptr = FRAG_SIZE;
+ audio_dev.state |= AU_STATE_RECORDING;
+
+ if ( audio_dev.codec->start_record((AU_CB_FUN_T)record_callback,
+ audio_dev.nSamplingRate,
+ audio_dev.nChannels)) {
+ audio_dev.state &= ~AU_STATE_RECORDING;
+ ret = -EIO;
+ }
+ else{
+ audio_dev.codec->set_record_volume(audio_dev.nRecordVolumeLeft, audio_dev.nRecordVolumeRight);
+ }
+
+ return ret;
+}
+
+static int wb_mixer_open(struct inode *inode, struct file *file)
+{
+ int retval;
+
+ int minor = MINOR(inode->i_rdev);
+
+ ENTER();
+
+ if(minor != 0 && minor != 16)
+ return -ENODEV;
+
+ if(minor == 16)
+ minor = 1;
+
+ down(&audio_dev.mixer_sem);
+
+ retval = -EBUSY;
+
+ if(audio_dev.open_flags != 0){
+ if(audio_dev.mixer_openflag != 0)
+ goto quit;
+ else{
+ if(audio_dev.dsp_openflag != 0 && audio_dev.dsp_dev != minor)
+ goto quit;
+ }
+ }
+
+ audio_dev.open_flags = 1;
+ audio_dev.mixer_openflag = 1;
+ audio_dev.mixer_dev = minor;
+
+ audio_dev.codec = all_codecs[minor];
+
+ MSG2("Mixer[%d] opened\n", minor);
+
+ retval = 0;
+
+ MOD_INC_USE_COUNT;
+
+quit:
+ up(&audio_dev.mixer_sem);
+
+ LEAVE();
+
+ return retval;
+}
+
+static int wb_mixer_release(struct inode *inode, struct file *file)
+{
+ audio_dev.mixer_openflag = 0;
+ if(audio_dev.dsp_openflag == 0)
+ audio_dev.open_flags = 0;
+
+ MOD_DEC_USE_COUNT;
+
+ return 0;
+}
+
+static int wb_mixer_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int ret = 0, val=0, err = 0;
+ int tmpVolumeLeft, tmpVolumeRight;
+
+ if (cmd == SOUND_MIXER_INFO) {
+ mixer_info info;
+ memset(&info,0,sizeof(info));
+ strncpy(info.id,"w90n745",sizeof(info.id)-1);
+ strncpy(info.name,"Winbond w90n745 Audio",sizeof(info.name)-1);
+ info.modify_counter = 0;
+ if (copy_to_user((void *)arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+ if (cmd == SOUND_OLD_MIXER_INFO) {
+ _old_mixer_info info;
+ memset(&info,0,sizeof(info));
+ strncpy(info.id,"w90n745",sizeof(info.id)-1);
+ strncpy(info.name,"Winbond w90n745 Audio",sizeof(info.name)-1);
+ if (copy_to_user((void *)arg, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+ if (cmd == OSS_GETVERSION)
+ return put_user(SOUND_VERSION, (int *)arg);
+
+ /* read */
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+ if (get_user(val, (int *)arg))
+ return -EFAULT;
+
+ switch (cmd) {
+ case MIXER_READ(SOUND_MIXER_CAPS):
+ ret = SOUND_CAP_EXCL_INPUT; /* only one input can be selected */
+ break;
+ case MIXER_READ(SOUND_MIXER_STEREODEVS):
+ ret = 1; /* check whether support stereo */
+ break;
+
+ case MIXER_READ(SOUND_MIXER_RECMASK): /* get input channels mask */
+ ret = SOUND_MASK_MIC;
+
+ case MIXER_READ(SOUND_MIXER_DEVMASK): /* get all channels mask */
+ ret |= SOUND_MASK_PCM + SOUND_MASK_VOLUME;
+ break;
+
+ case MIXER_WRITE(SOUND_MIXER_RECSRC):
+ if ( val != SOUND_MASK_MIC )
+ err = -EPERM;
+
+ case MIXER_READ(SOUND_MIXER_RECSRC):
+ ret = SOUND_MASK_MIC;
+ break;
+
+ case MIXER_WRITE(SOUND_MIXER_VOLUME):
+ case MIXER_WRITE(SOUND_MIXER_PCM):
+ case MIXER_WRITE(SOUND_MIXER_MIC):
+ tmpVolumeLeft = (val & 0xff) * 31 / 100;
+ tmpVolumeRight = ((val >> 8) & 0xff) * 31 / 100;
+ if (cmd == MIXER_WRITE(SOUND_MIXER_MIC)){ /* set mic volume */
+ audio_dev.codec->set_record_volume(tmpVolumeLeft, tmpVolumeRight);
+ audio_dev.nRecordVolumeLeft = tmpVolumeLeft;
+ audio_dev.nRecordVolumeRight = tmpVolumeRight;
+ }
+ else{
+ audio_dev.codec->set_play_volume(tmpVolumeLeft, tmpVolumeRight); /* set play volume */
+ audio_dev.nPlayVolumeLeft = tmpVolumeLeft;
+ audio_dev.nPlayVolumeRight = tmpVolumeRight;
+ }
+
+ ret = (val & 0xffff);
+
+ break;
+
+ case MIXER_READ(SOUND_MIXER_VOLUME):
+ case MIXER_READ(SOUND_MIXER_PCM):
+ ret = ((audio_dev.nPlayVolumeLeft * 100 / 31) |
+ ((audio_dev.nPlayVolumeRight * 100 / 31 ) << 8));
+ break;
+
+ case MIXER_READ(SOUND_MIXER_MIC):
+ ret =((audio_dev.nRecordVolumeLeft * 100 / 31) |
+ ((audio_dev.nRecordVolumeRight * 100 / 31 ) << 8));
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if (put_user(ret, (int *)arg))
+ return -EFAULT;
+
+ return err;
+}
+
+
+static struct file_operations wb_mixer_fops = {
+ owner: THIS_MODULE,
+ ioctl: wb_mixer_ioctl,
+ open: wb_mixer_open,
+ release: wb_mixer_release,
+};
+
+static void wb_dsp_irq(int irq, void *dev_id, struct pt_regs * regs)
+{
+ ENTER();
+
+ if( audio_dev.state & AU_STATE_PLAYING)
+ audio_dev.codec->play_interrupt();
+ if(audio_dev.state & AU_STATE_RECORDING)
+ audio_dev.codec->record_interrupt();
+
+ LEAVE();
+}
+
+static int wb_dsp_open(struct inode *inode, struct file *file)
+{
+ int minor = MINOR(inode->i_rdev);
+ int retval = -EBUSY;
+
+ ENTER();
+
+ if(minor != 3 && minor != 19){
+ MSG2("Minor error : %d\n", minor);
+ return -ENODEV;
+ }
+
+ if(minor == 3)
+ minor = 0;
+ else
+ minor = 1;
+
+ if(audio_dev.open_flags != 0){
+ if(audio_dev.dsp_openflag != 0)
+ goto quit;
+ else{
+ if(audio_dev.mixer_openflag != 0 && audio_dev.mixer_dev != minor)
+ goto quit;
+ }
+ }
+
+ if((retval = request_irq(INT_AC97, wb_dsp_irq, SA_INTERRUPT, "wb audio", NULL))){
+ printk("wb_audio_init : Request IRQ error\n");
+ goto quit;
+ }
+
+ //enable_irq(INT_AC97);
+
+ audio_dev.open_flags = 1;
+ audio_dev.dsp_openflag = 1;
+ audio_dev.dsp_dev = minor;
+ audio_dev.state = AU_STATE_NOP;
+
+ audio_dev.play_buf_addr = __get_free_pages(GFP_KERNEL, AUDIO_BUFFER_ORDER);
+ if(audio_dev.play_buf_addr == NULL){
+ free_irq(INT_AC97, NULL);
+ MSG("Not enough memory\n");
+ return -ENOMEM;
+ }
+
+ audio_dev.record_buf_addr = __get_free_pages(GFP_KERNEL, AUDIO_BUFFER_ORDER);
+ if(audio_dev.record_buf_addr == NULL){
+ free_pages(audio_dev.play_buf_addr, AUDIO_BUFFER_ORDER);
+ free_irq(INT_AC97, NULL);
+ MSG("Not enough memory\n");
+ return -ENOMEM;
+ }
+
+ audio_dev.play_buf_addr |= 0x80000000; // none - cache
+ audio_dev.play_buf_length = ( PAGE_SIZE << AUDIO_BUFFER_ORDER );
+ audio_dev.record_buf_addr |= 0x80000000; // none - cache
+ audio_dev.record_buf_length = ( PAGE_SIZE << AUDIO_BUFFER_ORDER );
+
+ MSG2("Audio_Dev.play_buf_addr : %x, Length: %x\n",
+ audio_dev.play_buf_addr, audio_dev.play_buf_length);
+
+ init_waitqueue_head(&audio_dev.read_wait_queue);
+ init_waitqueue_head(&audio_dev.write_wait_queue);
+
+ memset(&audio_dev.play_half_buf, 0, sizeof(audio_dev.play_half_buf));
+ memset(&audio_dev.record_half_buf, 0, sizeof(audio_dev.record_half_buf));
+
+ audio_dev.nSamplingRate = WB_AUDIO_DEFAULT_SAMPLERATE;
+ audio_dev.nChannels = WB_AUDIO_DEFAULT_CHANNEL;
+ audio_dev.nPlayVolumeLeft = WB_AUDIO_DEFAULT_VOLUME;
+ audio_dev.nPlayVolumeRight = WB_AUDIO_DEFAULT_VOLUME;
+ audio_dev.nRecordVolumeLeft = WB_AUDIO_DEFAULT_VOLUME;
+ audio_dev.nRecordVolumeRight = WB_AUDIO_DEFAULT_VOLUME;
+
+ audio_dev.codec = all_codecs[minor];
+
+ /* set dma buffer */
+ audio_dev.codec->set_play_buffer(audio_dev.play_buf_addr,
+ audio_dev.play_buf_length);
+ audio_dev.codec->set_record_buffer(audio_dev.record_buf_addr,
+ audio_dev.record_buf_length);
+
+ audio_dev.codec->reset();
+
+ MOD_INC_USE_COUNT;
+
+ LEAVE();
+
+ return 0;
+
+quit:
+
+ MSG2("Open failed : %d\n", retval);
+
+ return retval;
+}
+
+static int wb_dsp_release(struct inode *inode, struct file *file)
+{
+ ENTER();
+
+ if(audio_dev.state & AU_STATE_PLAYING){
+ /* wait until stop playing */
+ wait_event_interruptible(audio_dev.write_wait_queue,
+ (audio_dev.state & AU_STATE_PLAYING) == 0 ||
+ (audio_dev.play_half_buf[0].ptr == 0 &&
+ audio_dev.play_half_buf[1].ptr == 0));
+ }
+ wb_audio_stop_play();
+ wb_audio_stop_record();
+
+ free_irq(INT_AC97, NULL);
+
+ free_pages(audio_dev.play_buf_addr & 0x7fffffff, AUDIO_BUFFER_ORDER);
+ free_pages(audio_dev.record_buf_addr & 0x7fffffff, AUDIO_BUFFER_ORDER);
+
+ audio_dev.dsp_openflag = 0;
+ if(audio_dev.mixer_openflag == 0)
+ audio_dev.open_flags = 0;
+
+ MOD_DEC_USE_COUNT;
+
+ LEAVE();
+
+ return 0;
+}
+
+static ssize_t wb_dsp_read(struct file *file, char *buffer,
+ size_t swcount, loff_t *ppos)
+{
+ int i, tmp, length, block_len, retval, bpos;
+ char *dma_buf = (char *)audio_dev.record_buf_addr;
+
+ ENTER();
+
+ if(swcount == 0)
+ return 0;
+
+ if(down_interruptible(&audio_dev.dsp_read_sem))
+ return -ERESTARTSYS;
+
+again:
+
+ if((audio_dev.state & AU_STATE_RECORDING) == 0) { /* if record not start, then start it */
+ retval = wb_audio_start_record();
+ if ( retval )
+ goto quit;
+ }
+
+ length = swcount;
+ block_len = FRAG_SIZE;
+ retval = -EFAULT;
+
+ if(audio_dev.record_half_buf[0].ptr == FRAG_SIZE &&
+ audio_dev.record_half_buf[1].ptr == FRAG_SIZE){ /* buffer empty */
+ if( file->f_flags & O_NONBLOCK){
+ retval = -EAGAIN;
+ goto quit;
+ }
+ else {
+ wait_event_interruptible(audio_dev.read_wait_queue,
+ (audio_dev.state & AU_STATE_RECORDING) == 0 ||
+ audio_dev.record_half_buf[0].ptr == 0 ||
+ audio_dev.record_half_buf[1].ptr == 0 );
+ if ( (audio_dev.state & AU_STATE_RECORDING) == 0){
+ retval = 0;
+ goto quit;
+ }
+ }
+
+ }
+
+ retval = 0;
+ bpos = 0;
+ for(i = 0; i < 2; i++){
+ tmp = block_len - audio_dev.record_half_buf[i].ptr;
+
+ if(swcount < tmp)
+ tmp = swcount;
+
+ if(tmp){
+ retval = -EFAULT;
+ if(copy_to_user(buffer + bpos,
+ dma_buf + i * block_len + audio_dev.record_half_buf[i].ptr , tmp))
+ goto quit;
+ }
+ else
+ continue;
+
+ swcount -= tmp;
+ audio_dev.record_half_buf[i].ptr += tmp;
+ bpos += tmp;
+
+ if(swcount == 0)
+ break;
+
+ }
+
+
+ retval = length - swcount;
+
+ if(swcount != 0){
+ if( file->f_flags & O_NONBLOCK
+ && audio_dev.play_half_buf[0].ptr == FRAG_SIZE
+ && audio_dev.play_half_buf[1].ptr == FRAG_SIZE)
+ goto quit;
+
+ buffer += retval;
+ goto again;
+ }
+
+quit:
+ up(&audio_dev.dsp_read_sem);
+
+ LEAVE();
+
+ return retval;
+}
+
+static ssize_t wb_dsp_write(struct file *file, const char *buffer,
+ size_t count, loff_t *ppos)
+{
+ int tmp, retval, length, i;
+ char *dma_buf = (char *)audio_dev.play_buf_addr;
+
+ ENTER();
+
+ MSG2("DSP Write : Buffer : %08x Count : %x\n", buffer, count);
+
+ if(count == 0)
+ return 0;
+
+ if(down_interruptible(&audio_dev.dsp_write_sem))
+ return -ERESTARTSYS;
+
+
+again:
+
+ length = count;
+
+ if(audio_dev.state & AU_STATE_PLAYING){
+ if(audio_dev.play_half_buf[0].ptr == FRAG_SIZE &&
+ audio_dev.play_half_buf[1].ptr == FRAG_SIZE){
+ if( file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto quit;
+ }
+ else{
+ MSG("Write block ...\n");
+ wait_event_interruptible(audio_dev.write_wait_queue,
+ (audio_dev.state & AU_STATE_PLAYING) == 0 ||
+ audio_dev.play_half_buf[0].ptr == 0 ||
+ audio_dev.play_half_buf[1].ptr == 0);
+ }
+ }
+ }
+
+
+ retval = -EFAULT;
+
+
+ for(i = 0; i < 2; i ++){
+ tmp = FRAG_SIZE - audio_dev.play_half_buf[i].ptr;
+
+ if(tmp > count)
+ tmp = count;
+
+ if(tmp){
+ if(copy_from_user(dma_buf + audio_dev.play_half_buf[i].ptr + i * FRAG_SIZE,
+ buffer + length - count, tmp))
+ goto quit;
+ }
+
+ count -= tmp;
+ audio_dev.play_half_buf[i].ptr += tmp;
+ if( count == 0)
+ break;
+ }
+
+ retval = length - count;
+
+ if((audio_dev.state & AU_STATE_PLAYING ) == 0
+ && audio_dev.play_half_buf[0].ptr == FRAG_SIZE
+ && audio_dev.play_half_buf[1].ptr == FRAG_SIZE){
+
+ if (wb_audio_start_play()){
+ retval = -EIO;
+ goto quit;
+ }
+ }
+
+ if(count != 0){
+ if( file->f_flags & O_NONBLOCK
+ && audio_dev.play_half_buf[0].ptr == FRAG_SIZE
+ && audio_dev.play_half_buf[1].ptr == FRAG_SIZE)
+ goto quit;
+
+ buffer += retval;
+ goto again;
+ }
+
+quit:
+
+ up(&audio_dev.dsp_write_sem);
+
+ DBG("W");
+
+ LEAVE();
+
+ return retval;
+}
+
+static int wb_dsp_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int val = 0, i, err = 0;
+ audio_buf_info info;
+
+ ENTER();
+
+ switch (cmd) {
+
+ case OSS_GETVERSION:
+ val = SOUND_VERSION;
+
+ break;
+
+ case SNDCTL_DSP_GETCAPS:
+
+ if (audio_dev.codec->get_capacity() & DSP_CAP_DUPLEX)
+ val |= DSP_CAP_DUPLEX;
+
+ val |= DSP_CAP_TRIGGER;
+
+ break;
+
+ case SNDCTL_DSP_SPEED:
+ if (get_user(val, (int*)arg))
+ return -EFAULT;
+
+ for(i = 0; i < sizeof(nSamplingRate)/sizeof(unsigned int); i++)
+ if(val == nSamplingRate[i])
+ break;
+
+ if(i >= sizeof(nSamplingRate)/sizeof(unsigned int)){ /* not supported */
+ val = audio_dev.nSamplingRate;
+ err = -EPERM;
+ }
+ else
+ audio_dev.nSamplingRate = val;
+
+ break;
+
+
+ case SOUND_PCM_READ_RATE:
+ val = audio_dev.nSamplingRate;
+ break;
+
+ case SNDCTL_DSP_STEREO:
+ if (get_user(val, (int*)arg))
+ return -EFAULT;
+
+ audio_dev.nChannels = val ? 2:1;
+
+ break;
+
+ case SNDCTL_DSP_CHANNELS:
+ if (get_user(val, (int*)arg))
+ return -EFAULT;
+
+ if(val != 1 && val != 2){
+ val = audio_dev.nChannels;
+ err = -EPERM;
+ }
+
+ audio_dev.nChannels = val;
+ break;
+
+ case SOUND_PCM_READ_CHANNELS:
+ val = audio_dev.nChannels;
+
+ break;
+
+ case SNDCTL_DSP_SETFMT:
+ if (get_user(val, (int*)arg))
+ return -EFAULT;
+
+ if ( (val & (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE |AFMT_U16_BE)) == 0)
+ err = -EPERM;
+
+ case SNDCTL_DSP_GETFMTS:
+
+ val = (AFMT_S16_LE | AFMT_S16_BE | AFMT_U16_LE |AFMT_U16_BE);
+ break;
+
+ case SOUND_PCM_READ_BITS:
+ val = 16;
+ break;
+
+ case SNDCTL_DSP_NONBLOCK:
+ file->f_flags |= O_NONBLOCK;
+ break;
+
+ case SNDCTL_DSP_RESET:
+ wb_audio_stop_play();
+ wb_audio_stop_record();
+
+ return 0;
+
+ case SNDCTL_DSP_GETBLKSIZE:
+ val =FRAG_SIZE;
+ break;
+
+ case SNDCTL_DSP_GETISPACE:
+
+ info.fragsize = FRAG_SIZE;
+ info.fragstotal = 2;
+ info.bytes = audio_dev.record_buf_length - audio_dev.record_half_buf[0].ptr - audio_dev.record_half_buf[1].ptr;
+ info.fragments = info.bytes / info.fragsize;
+
+ MSG2("SNDCTL_DSP_GETISPACE returns %d/%d/%d/%d\n",
+ info.fragsize, info.fragstotal,
+ info.bytes, info.fragments);
+
+ if (copy_to_user((void *)arg, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+
+ case SNDCTL_DSP_GETOSPACE:
+
+ info.fragsize = FRAG_SIZE;
+ info.fragstotal = 2;
+ info.bytes = audio_dev.play_buf_length - audio_dev.play_half_buf[0].ptr - audio_dev.play_half_buf[1].ptr;
+ info.fragments = info.bytes / info.fragsize;
+
+ MSG2("SNDCTL_DSP_GETISPACE returns %d/%d/%d/%d\n",
+ info.fragsize, info.fragstotal,
+ info.bytes, info.fragments);
+
+ if (copy_to_user((void *)arg, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+
+ case SNDCTL_DSP_SYNC:
+ /* no data */
+ if ( audio_dev.play_half_buf[0].ptr == 0 &&
+ audio_dev.play_half_buf[1].ptr == 0)
+ return 0;
+
+ wb_audio_start_play();
+
+ /* wait until stop playing */
+ wait_event_interruptible(audio_dev.write_wait_queue,
+ (audio_dev.state & AU_STATE_PLAYING) == 0 ||
+ (audio_dev.play_half_buf[0].ptr == 0 &&
+ audio_dev.play_half_buf[1].ptr == 0));
+ wb_audio_stop_play();
+
+ return 0;
+
+ case SNDCTL_DSP_GETTRIGGER:
+ val = 0;
+ if ( audio_dev.state & AU_STATE_PLAYING )
+ val |= PCM_ENABLE_OUTPUT;
+ if ( audio_dev.state & AU_STATE_RECORDING)
+ val |= PCM_ENABLE_INPUT;
+ break;
+
+ case SNDCTL_DSP_SETTRIGGER:
+ if (get_user(val, (int*)arg))
+ return -EFAULT;
+ if ( val & PCM_ENABLE_OUTPUT){
+ if ( wb_audio_start_play()){
+ val &= ~PCM_ENABLE_OUTPUT;
+ err = -EPERM ;
+ }
+ }
+ else{
+ wb_audio_stop_play();
+ }
+
+ if ( val & PCM_ENABLE_INPUT){
+ if ( wb_audio_start_record()){
+ val &= ~PCM_ENABLE_INPUT;
+ err = -EPERM ;
+ }
+ }
+ else{
+ wb_audio_stop_record();
+ }
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ LEAVE();
+
+ return err?-EPERM:put_user(val, (int *)arg);
+
+}
+
+static unsigned int wb_dsp_poll(struct file *file, struct poll_table_struct *wait)
+{
+ int mask = 0;
+
+ ENTER();
+
+ if (file->f_mode & FMODE_WRITE){
+ poll_wait(file, &audio_dev.write_wait_queue, wait);
+
+ /* check if has data */
+ if(audio_dev.play_half_buf[0].ptr != FRAG_SIZE ||
+ audio_dev.play_half_buf[1].ptr != FRAG_SIZE )
+ mask |= POLLOUT | POLLWRNORM;
+ }
+
+ if (file->f_mode & FMODE_READ){
+ poll_wait(file, &audio_dev.read_wait_queue, wait);
+
+ /* check if can read */
+ if(audio_dev.record_half_buf[0].ptr != FRAG_SIZE ||
+ audio_dev.record_half_buf[1].ptr != FRAG_SIZE )
+ mask |= POLLIN | POLLRDNORM;
+ }
+
+ LEAVE();
+
+ return mask;
+}
+
+static struct file_operations wb_dsp_fops = {
+ owner: THIS_MODULE,
+ llseek: no_llseek,
+ read: wb_dsp_read,
+ write: wb_dsp_write,
+ poll: wb_dsp_poll,
+ ioctl: wb_dsp_ioctl,
+ open: wb_dsp_open,
+ release: wb_dsp_release,
+};
+
+static int __init wb_dsp_init(void)
+{
+ int i;
+
+ for(i=0;i<2;i++){
+ dev_dsp[i] = register_sound_dsp(&wb_dsp_fops, i);
+ MSG2("Dsp Device No : %d\n", dev_dsp[i]);
+ if(dev_dsp[i]< 0)
+ goto quit;
+ }
+
+ return 0;
+
+quit:
+ printk("Register DSP device failed\n");
+ return -1;
+}
+
+
+static int __init wb_mixer_init(void)
+{
+ int i;
+
+ for(i=0;i<2;i++){
+ dev_mixer[i] = register_sound_mixer(&wb_mixer_fops, i);
+ MSG2("Mixer Device No : %d\n", dev_mixer[i]);
+ if(dev_mixer[i]< 0)
+ goto quit;
+ }
+
+ return 0;
+
+quit:
+ printk("Register Mixer device failed\n");
+ return -1;
+}
+
+static void wb_dsp_unload(void)
+{
+ int i;
+ for(i=0;i<2;i++)
+ unregister_sound_dsp(dev_dsp[i]);
+}
+
+static void wb_mixer_unload(void)
+{
+ int i;
+ for(i=0;i<2;i++)
+ unregister_sound_mixer(dev_mixer[i]);
+}
+
+
+MODULE_AUTHOR("QFu");
+MODULE_DESCRIPTION("Winbond w90n745 Audio Driver");
+MODULE_LICENSE("GPL");
+
+static void wb_audio_exit (void)
+{
+ wb_mixer_unload();
+ wb_dsp_unload();
+ free_irq(INT_AC97, NULL);
+}
+extern struct semaphore ac97_sem; // move here to ensure it's initialized before accesign it.
+static int __init wb_audio_init(void)
+{
+
+ Disable_Int(INT_AC97);
+
+ memset(&audio_dev, 0, sizeof(audio_dev));
+ sema_init(& audio_dev.dsp_read_sem, 1);
+ sema_init(&audio_dev.dsp_write_sem, 1);
+ sema_init(&audio_dev.mixer_sem, 1);
+ sema_init(&ac97_sem, 1);
+
+ if(wb_dsp_init())
+ goto quit;
+
+ if(wb_mixer_init())
+ goto quit;
+
+ printk("Winbond Audio Driver v1.0 Initialization successfully.\n");
+
+ return 0;
+
+quit:
+ printk("Winbond Audio Driver Initialization failed\n");
+ wb_audio_exit();
+ return -1;
+}
+
+module_init(wb_audio_init);
+module_exit(wb_audio_exit);
+