/* * sound/sb16_dsp.c * * The low level driver for the SoundBlaster DSP chip. * * (C) 1993 J. Schubert (jsb@sth.ruhr-uni-bochum.de) * * based on SB-driver by (C) Hannu Savolainen * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. 2. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #define DEB(x) #define DEB1(x) /* #define DEB_DMARES */ #include "sound_config.h" #include "sb.h" #include "sb_mixer.h" #if defined(CONFIGURE_SOUNDCARD) && !defined(EXCLUDE_SB16) && !defined(EXCLUDE_SB) && !defined(EXCLUDE_AUDIO) && !defined(EXCLUDE_SBPRO) extern int sbc_base; static int sb16_dsp_ok = 0; /* Set to 1 after successful initialization */ static int dsp_16bit = 0; static int dsp_stereo = 0; static int dsp_current_speed = 8000;/*DSP_DEFAULT_SPEED;*/ static int dsp_busy = 0; static int dma16, dma8; static unsigned long dsp_count = 0; static int irq_mode = IMODE_NONE; /* IMODE_INPUT, IMODE_OUTPUT or IMODE_NONE */ static int my_dev = 0; static volatile int intr_active = 0; static int sb16_dsp_open (int dev, int mode); static void sb16_dsp_close (int dev); static void sb16_dsp_output_block (int dev, unsigned long buf, int count,int intrflag, int dma_restart); static void sb16_dsp_start_input (int dev, unsigned long buf, int count,int intrflag, int dma_restart); static int sb16_dsp_ioctl (int dev, unsigned int cmd, unsigned int arg,int local); static int sb16_dsp_prepare_for_input (int dev, int bsize, int bcount); static int sb16_dsp_prepare_for_output (int dev, int bsize, int bcount); static void sb16_dsp_reset (int dev); static void sb16_dsp_halt (int dev); static int dsp_set_speed (int); static int dsp_set_stereo (int); static void dsp_cleanup (void); int sb_reset_dsp (void); static struct audio_operations sb16_dsp_operations = { "SoundBlaster 16", sb16_dsp_open, sb16_dsp_close, sb16_dsp_output_block, sb16_dsp_start_input, sb16_dsp_ioctl, sb16_dsp_prepare_for_input, sb16_dsp_prepare_for_output, sb16_dsp_reset, sb16_dsp_halt, NULL, NULL }; static int sb_dsp_command01 (unsigned char val) { int i=1<<16; while(--i & (!INB (DSP_STATUS) & 0x80)); if(!i) printk("SB16 sb_dsp_command01 Timeout\n"); return sb_dsp_command(val); } static int wait_data_avail(int t) { int loopc=5000000; t+=GET_TIME(); do { if(INB(DSP_DATA_AVAIL) & 0x80) return 1; } while(--loopc && GET_TIME() 44100) mode = 44100; dsp_current_speed=mode; } return mode; } static int dsp_set_stereo(int mode) { DEB(printk("dsp_set_stereo(%d)\n",mode)); dsp_stereo=mode; return mode; } static int dsp_set_bits(int arg) { DEB(printk("dsp_set_bits(%d)\n",arg)); if (arg) switch(arg) { case 8: dsp_16bit=0; break; case 16: dsp_16bit=1; break; default: return RET_ERROR(EINVAL); } return dsp_16bit? 16:8; } static int sb16_dsp_ioctl (int dev, unsigned int cmd, unsigned int arg,int local) { switch (cmd) { case SOUND_PCM_WRITE_RATE: if(local) return dsp_set_speed(arg); return IOCTL_OUT (arg, dsp_set_speed (IOCTL_IN (arg))); case SOUND_PCM_READ_RATE: if(local) return dsp_current_speed; return IOCTL_OUT (arg, dsp_current_speed); case SNDCTL_DSP_STEREO: if (local) return dsp_set_stereo(arg); return IOCTL_OUT (arg, dsp_set_stereo(IOCTL_IN(arg))); case SOUND_PCM_WRITE_CHANNELS: if (local) return dsp_set_stereo(arg-1)+1; return IOCTL_OUT (arg, dsp_set_stereo (IOCTL_IN (arg) - 1) + 1); case SOUND_PCM_READ_CHANNELS: if (local) return dsp_stereo+1; return IOCTL_OUT (arg, dsp_stereo+1); case SNDCTL_DSP_SAMPLESIZE: if (local) return dsp_set_bits (arg); return IOCTL_OUT (arg, dsp_set_bits (IOCTL_IN (arg))); case SOUND_PCM_READ_BITS: if (local) return dsp_16bit?16:8; return IOCTL_OUT (arg, dsp_16bit?16:8); case SOUND_PCM_WRITE_FILTER: /* NOT YET IMPLEMENTED */ if (IOCTL_IN (arg) > 1) return IOCTL_OUT (arg, RET_ERROR (EINVAL)); default: return RET_ERROR (EINVAL); } return RET_ERROR (EINVAL); } static int sb16_dsp_open (int dev, int mode) { int retval; DEB(printk("sb16_dsp_open()\n")); if (!sb16_dsp_ok) { printk ("SB16 Error: SoundBlaster board not installed\n"); return RET_ERROR(ENXIO); } if (intr_active) return RET_ERROR(EBUSY); retval = sb_get_irq (); if (retval<0) return retval; if (ALLOC_DMA_CHN (dma8)) { printk ("SB16: Unable to grab DMA%d\n", dma8); sb_free_irq(); return RET_ERROR(EBUSY); } if (dma16 != dma8) if (ALLOC_DMA_CHN (dma16)) { printk ("SB16: Unable to grab DMA%d\n", dma16); sb_free_irq(); RELEASE_DMA_CHN (dma8); return RET_ERROR(EBUSY); } dsp_ini2(); irq_mode = IMODE_NONE; dsp_busy = 1; return 0; } static void sb16_dsp_close (int dev) { unsigned long flags; DEB(printk("sb16_dsp_close()\n")); sb_dsp_command01(0xd9); sb_dsp_command01(0xd5); DISABLE_INTR (flags); RELEASE_DMA_CHN (dma8); if (dma16 != dma8) RELEASE_DMA_CHN (dma16); sb_free_irq (); dsp_cleanup (); dsp_busy = 0; RESTORE_INTR (flags); } static void sb16_dsp_output_block (int dev, unsigned long buf, int count,int intrflag, int dma_restart) { unsigned long flags, cnt; cnt = count; if (dsp_16bit) cnt >>= 1; cnt--; #ifdef DEB_DMARES printk("output_block: %x %d %d\n",buf,count,intrflag); if(intrflag) { int pos,chan=sound_dsp_dmachan[dev]; DISABLE_INTR (flags); clear_dma_ff(chan); disable_dma(chan); pos=get_dma_residue(chan); enable_dma(chan); RESTORE_INTR (flags); printk("dmapos=%d %x\n",pos,pos); } #endif if (sound_dma_automode[dev] && intrflag && cnt == dsp_count) { irq_mode = IMODE_OUTPUT; intr_active = 1; return; /* Auto mode on. No need to react */ } DISABLE_INTR (flags); if (dma_restart) { sb16_dsp_halt(dev); DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); } sb_dsp_command (0x41); sb_dsp_command ((unsigned char)((dsp_current_speed >> 8) & 0xff)); sb_dsp_command ((unsigned char)(dsp_current_speed & 0xff)); sb_dsp_command ((unsigned char)(dsp_16bit ? 0xb6 : 0xc6)); sb_dsp_command ((unsigned char)((dsp_stereo ? 0x20 : 0) + (dsp_16bit ? 0x10:0))); sb_dsp_command01 ((unsigned char)(cnt&0xff)); sb_dsp_command ((unsigned char)(cnt>>8)); /* sb_dsp_command (0); sb_dsp_command (0); */ RESTORE_INTR (flags); dsp_count=cnt; irq_mode = IMODE_OUTPUT; intr_active = 1; } static void sb16_dsp_start_input (int dev, unsigned long buf, int count,int intrflag, int dma_restart) { unsigned long flags, cnt; cnt = count; if (dsp_16bit) cnt >>= 1; cnt--; #ifdef DEB_DMARES printk("start_input: %x %d %d\n",buf,count,intrflag); if(intrflag) { int pos,chan=sound_dsp_dmachan[dev]; DISABLE_INTR (flags); clear_dma_ff(chan); disable_dma(chan); pos=get_dma_residue(chan); enable_dma(chan); RESTORE_INTR (flags); printk("dmapos=%d %x\n",pos,pos); } #endif if (sound_dma_automode[dev] && intrflag && cnt == dsp_count) { irq_mode = IMODE_INPUT; intr_active = 1; return; /* Auto mode on. No need to react */ } DISABLE_INTR (flags); if (dma_restart) { sb16_dsp_halt(dev); DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); } sb_dsp_command (0x42); sb_dsp_command ((unsigned char)((dsp_current_speed >> 8) & 0xff)); sb_dsp_command ((unsigned char)(dsp_current_speed & 0xff)); sb_dsp_command ((unsigned char)(dsp_16bit ? 0xbe : 0xce)); sb_dsp_command ((unsigned char)((dsp_stereo ? 0x20 : 0) + (dsp_16bit ? 0x10:0))); sb_dsp_command01 ((unsigned char)(cnt&0xff)); sb_dsp_command ((unsigned char)(cnt>>8)); /* sb_dsp_command (0); sb_dsp_command (0); */ RESTORE_INTR (flags); dsp_count=cnt; irq_mode = IMODE_INPUT; intr_active = 1; } static int sb16_dsp_prepare_for_input (int dev, int bsize, int bcount) { sound_dsp_dmachan[my_dev] = dsp_16bit? dma16:dma8; dsp_count = 0; dsp_cleanup (); return 0; } static int sb16_dsp_prepare_for_output (int dev, int bsize, int bcount) { sound_dsp_dmachan[my_dev] = dsp_16bit? dma16:dma8; dsp_count = 0; dsp_cleanup (); return 0; } static void dsp_cleanup (void) { irq_mode = IMODE_NONE; intr_active = 0; } static void sb16_dsp_reset (int dev) { unsigned long flags; DISABLE_INTR (flags); sb_reset_dsp (); dsp_cleanup (); RESTORE_INTR (flags); } static void sb16_dsp_halt (int dev) { if (dsp_16bit) { sb_dsp_command01(0xd9); sb_dsp_command01(0xd5); } else { sb_dsp_command01(0xda); sb_dsp_command01(0xd0); } } static void set_irq_hw(int level) { int ival; switch(level) { case 5: ival=2; break; case 7: ival=4; break; case 10: ival=8; break; default: printk("SB16_IRQ_LEVEL %d does not exist\n",level); return; } sb_setmixer(IRQ_NR,ival); } long sb16_dsp_init (long mem_start, struct address_info *hw_config) { int i, major, minor; major = minor = 0; sb_dsp_command (0xe1); /* Get version */ for (i = 1000; i; i--) { if (INB (DSP_DATA_AVAIL) & 0x80) { /* wait for Data Ready */ if (major == 0) major = INB (DSP_READ); else { minor = INB (DSP_READ); break; } } } #ifndef SCO sprintf (sb16_dsp_operations.name, "SoundBlaster 16 %d.%d", major, minor); #endif printk (" <%s>", sb16_dsp_operations.name); if (num_dspdevs < MAX_DSP_DEV) { dsp_devs[my_dev = num_dspdevs++] = &sb16_dsp_operations; sound_dsp_dmachan[my_dev] = hw_config->dma; sound_buffcounts[my_dev] = 1; sound_buffsizes[my_dev] = DSP_BUFFSIZE; sound_dma_automode[my_dev] = 1; } else printk ("SB: Too many DSP devices available\n"); sb16_dsp_ok = 1; return mem_start; } int sb16_dsp_detect (struct address_info *hw_config) { struct address_info *sb_config; if (sb16_dsp_ok) return 1; /* Already initialized */ if (!(sb_config=sound_getconf(SNDCARD_SB))) { printk("SB16 Error: Plain SB not configured\n"); return 0; } if (sbc_base != hw_config->io_base) printk("Warning! SB16 I/O != SB I/O\n"); /* sb_setmixer(OPSW,0xf); if(sb_getmixer(OPSW)!=0xf) return 0; */ if (!sb_reset_dsp ()) return 0; if (hw_config->irq != sb_config->irq) { printk("SB16 Error: Invalid IRQ number %d/%d\n", sb_config->irq, hw_config->irq); return 0; } if (hw_config->dma < 4) if (hw_config->dma != sb_config->dma) { printk("SB16 Error: Invalid DMA channel %d/%d\n", sb_config->dma, hw_config->dma); return 0; } dma16 = hw_config->dma; dma8 = sb_config->dma; set_irq_hw(hw_config->irq); sb_setmixer(DMA_NR, (1<dma) | (1<dma)); DEB(printk ("SoundBlaster 16: IRQ %d DMA %d OK\n",hw_config->irq,hw_config->dma)); /* dsp_showmessage(0xe3,99); */ sb16_dsp_ok = 1; return 1; } void sb16_dsp_interrupt (int unused) { int data; data = INB (DSP_DATA_AVL16); /* Interrupt acknowledge */ if (intr_active) switch (irq_mode) { case IMODE_OUTPUT: intr_active = 0; DMAbuf_outputintr (my_dev, 1); break; case IMODE_INPUT: intr_active = 0; DMAbuf_inputintr (my_dev); break; default: printk ("SoundBlaster: Unexpected interrupt\n"); } } #endif