diff options
Diffstat (limited to 'sys/i386/isa/sound/pas2_pcm.c')
-rw-r--r-- | sys/i386/isa/sound/pas2_pcm.c | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/sys/i386/isa/sound/pas2_pcm.c b/sys/i386/isa/sound/pas2_pcm.c new file mode 100644 index 0000000..f2fc95c --- /dev/null +++ b/sys/i386/isa/sound/pas2_pcm.c @@ -0,0 +1,449 @@ +#define _PAS2_PCM_C_ +/* + * sound/pas2_pcm.c + * + * The low level driver for the Pro Audio Spectrum ADC/DAC. + * + * Copyright by Hannu Savolainen 1993 + * + * 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. + * + */ + +#include <i386/isa/sound/sound_config.h> + +#if defined(CONFIG_PAS) && defined(CONFIG_AUDIO) +#include <i386/isa/sound/pas_hw.h> + + +#define TRACE(WHAT) /* * * (WHAT) */ + +#define PAS_PCM_INTRBITS (0x08) +/* + * Sample buffer timer interrupt enable + */ + +#define PCM_NON 0 +#define PCM_DAC 1 +#define PCM_ADC 2 + +static u_long pcm_speed = 0; /* sampling rate */ +static u_char pcm_channels = 1; /* channels (1 or 2) */ +static u_char pcm_bits = 8; /* bits/sample (8 or 16) */ +static u_char pcm_filter = 0; /* filter FLAG */ +static u_char pcm_mode = PCM_NON; +static u_long pcm_count = 0; +static u_short pcm_bitsok = 8; /* mask of OK bits */ +static int my_devnum = 0; +static int open_mode = 0; + +static int +pcm_set_speed(int arg) +{ + int foo, tmp; + u_long flags; + + if (arg > 44100) + arg = 44100; + if (arg < 5000) + arg = 5000; + + foo = (1193180 + (arg / 2)) / arg; + arg = 1193180 / foo; + + if (pcm_channels & 2) + foo = foo >> 1; + + pcm_speed = arg; + + tmp = pas_read(FILTER_FREQUENCY); + + /* + * Set anti-aliasing filters according to sample rate. You reall + * *NEED* to enable this feature for all normal recording unless you + * want to experiment with aliasing effects. These filters apply to + * the selected "recording" source. I (pfw) don't know the encoding + * of these 5 bits. The values shown come from the SDK found on + * ftp.uwp.edu:/pub/msdos/proaudio/. + */ +#if !defined NO_AUTO_FILTER_SET + tmp &= 0xe0; + if (pcm_speed >= 2 * 17897) + tmp |= 0x21; + else if (pcm_speed >= 2 * 15909) + tmp |= 0x22; + else if (pcm_speed >= 2 * 11931) + tmp |= 0x29; + else if (pcm_speed >= 2 * 8948) + tmp |= 0x31; + else if (pcm_speed >= 2 * 5965) + tmp |= 0x39; + else if (pcm_speed >= 2 * 2982) + tmp |= 0x24; + pcm_filter = tmp; +#endif + + flags = splhigh(); + + pas_write(tmp & ~(F_F_PCM_RATE_COUNTER | F_F_PCM_BUFFER_COUNTER), FILTER_FREQUENCY); + pas_write(S_C_C_SAMPLE_RATE | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL); + pas_write(foo & 0xff, SAMPLE_RATE_TIMER); + pas_write((foo >> 8) & 0xff, SAMPLE_RATE_TIMER); + pas_write(tmp, FILTER_FREQUENCY); + + splx(flags); + + return pcm_speed; +} + +static int +pcm_set_channels(int arg) +{ + + if ((arg != 1) && (arg != 2)) + return pcm_channels; + + if (arg != pcm_channels) { + pas_write(pas_read(PCM_CONTROL) ^ P_C_PCM_MONO, PCM_CONTROL); + + pcm_channels = arg; + pcm_set_speed(pcm_speed); /* The speed must be + * reinitialized */ + } + return pcm_channels; +} + +static int +pcm_set_bits(int arg) +{ + if ((arg & pcm_bitsok) != arg) + return pcm_bits; + + if (arg != pcm_bits) { + pas_write(pas_read(SYSTEM_CONFIGURATION_2) ^ S_C_2_PCM_16_BIT, SYSTEM_CONFIGURATION_2); + + pcm_bits = arg; + } + return pcm_bits; +} + +static int +pas_pcm_ioctl(int dev, u_int cmd, ioctl_arg arg, int local) +{ + TRACE(printf("pas2_pcm.c: static int pas_pcm_ioctl(u_int cmd = %X, u_int arg = %X)\n", cmd, arg)); + + switch (cmd) { + case SOUND_PCM_WRITE_RATE: + if (local) + return pcm_set_speed((int) arg); + return *(int *) arg = pcm_set_speed((*(int *) arg)); + break; + + case SOUND_PCM_READ_RATE: + if (local) + return pcm_speed; + return *(int *) arg = pcm_speed; + break; + + case SNDCTL_DSP_STEREO: + if (local) + return pcm_set_channels((int) arg + 1) - 1; + return *(int *) arg = pcm_set_channels((*(int *) arg) + 1) - 1; + break; + + case SOUND_PCM_WRITE_CHANNELS: + if (local) + return pcm_set_channels((int) arg); + return *(int *) arg = pcm_set_channels((*(int *) arg)); + break; + + case SOUND_PCM_READ_CHANNELS: + if (local) + return pcm_channels; + return *(int *) arg = pcm_channels; + break; + + case SNDCTL_DSP_SETFMT: + if (local) + return pcm_set_bits((int) arg); + return *(int *) arg = pcm_set_bits((*(int *) arg)); + break; + + case SOUND_PCM_READ_BITS: + if (local) + return pcm_bits; + return *(int *) arg = pcm_bits; + + case SOUND_PCM_WRITE_FILTER: /* NOT YET IMPLEMENTED */ + if ((*(int *) arg) > 1) + return -(EINVAL); + pcm_filter = (*(int *) arg); + break; + + case SOUND_PCM_READ_FILTER: + return *(int *) arg = pcm_filter; + break; + + default: + return -(EINVAL); + } + + return -(EINVAL); +} + +static void +pas_pcm_reset(int dev) +{ + TRACE(printf("pas2_pcm.c: static void pas_pcm_reset(void)\n")); + + pas_write(pas_read(PCM_CONTROL) & ~P_C_PCM_ENABLE, PCM_CONTROL); +} + +static int +pas_pcm_open(int dev, int mode) +{ + int err; + + TRACE(printf("pas2_pcm.c: static int pas_pcm_open(int mode = %X)\n", mode)); + + if ((err = pas_set_intr(PAS_PCM_INTRBITS)) < 0) + return err; + + + pcm_count = 0; + open_mode = mode; + + return 0; +} + +static void +pas_pcm_close(int dev) +{ + u_long flags; + + TRACE(printf("pas2_pcm.c: static void pas_pcm_close(void)\n")); + + flags = splhigh(); + + pas_pcm_reset(dev); + pas_remove_intr(PAS_PCM_INTRBITS); + pcm_mode = PCM_NON; + + open_mode = 0; + + splx(flags); +} + +static void +pas_pcm_output_block(int dev, u_long buf, int count, + int intrflag, int restart_dma) +{ + u_long flags, cnt; + + TRACE(printf("pas2_pcm.c: static void pas_pcm_output_block(char *buf = %P, int count = %X)\n", buf, count)); + + cnt = count; + if (audio_devs[dev]->dmachan1 > 3) + cnt >>= 1; + + if (audio_devs[dev]->flags & DMA_AUTOMODE && + intrflag && + cnt == pcm_count) + return; /* Auto mode on. No need to react */ + + flags = splhigh(); + + pas_write(pas_read(PCM_CONTROL) & ~P_C_PCM_ENABLE, + PCM_CONTROL); + + if (restart_dma) + DMAbuf_start_dma(dev, buf, count, 1); + + if (audio_devs[dev]->dmachan1 > 3) + count >>= 1; + + if (count != pcm_count) { + pas_write(pas_read(FILTER_FREQUENCY) & ~F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + pas_write(S_C_C_SAMPLE_BUFFER | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL); + pas_write(count & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write((count >> 8) & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write(pas_read(FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + + pcm_count = count; + } + pas_write(pas_read(FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER, FILTER_FREQUENCY); +#ifdef NO_TRIGGER + pas_write(pas_read(PCM_CONTROL) | P_C_PCM_ENABLE | P_C_PCM_DAC_MODE, PCM_CONTROL); +#endif + + pcm_mode = PCM_DAC; + + splx(flags); +} + +static void +pas_pcm_start_input(int dev, u_long buf, int count, + int intrflag, int restart_dma) +{ + u_long flags; + int cnt; + + TRACE(printf("pas2_pcm.c: static void pas_pcm_start_input(char *buf = %P, int count = %X)\n", buf, count)); + + cnt = count; + if (audio_devs[dev]->dmachan1 > 3) + cnt >>= 1; + + if (audio_devs[my_devnum]->flags & DMA_AUTOMODE && + intrflag && + cnt == pcm_count) + return; /* Auto mode on. No need to react */ + + flags = splhigh(); + + if (restart_dma) + DMAbuf_start_dma(dev, buf, count, 0); + + if (audio_devs[dev]->dmachan1 > 3) + count >>= 1; + + if (count != pcm_count) { + pas_write(pas_read(FILTER_FREQUENCY) & ~F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + pas_write(S_C_C_SAMPLE_BUFFER | S_C_C_LSB_THEN_MSB | S_C_C_SQUARE_WAVE, SAMPLE_COUNTER_CONTROL); + pas_write(count & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write((count >> 8) & 0xff, SAMPLE_BUFFER_COUNTER); + pas_write(pas_read(FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER, FILTER_FREQUENCY); + + pcm_count = count; + } + pas_write(pas_read(FILTER_FREQUENCY) | F_F_PCM_BUFFER_COUNTER | F_F_PCM_RATE_COUNTER, FILTER_FREQUENCY); +#ifdef NO_TRIGGER + pas_write((pas_read(PCM_CONTROL) | P_C_PCM_ENABLE) & ~P_C_PCM_DAC_MODE, PCM_CONTROL); +#endif + + pcm_mode = PCM_ADC; + + splx(flags); +} +#ifndef NO_TRIGGER +static void +pas_audio_trigger (int dev, int state) +{ + unsigned long flags; + + flags = splhigh(); + + state &= open_mode; + + if (state & PCM_ENABLE_OUTPUT) + pas_write (pas_read (0xF8A) | 0x40 | 0x10, 0xF8A); + else if (state & PCM_ENABLE_INPUT) + pas_write ((pas_read (0xF8A) | 0x40) & ~0x10, 0xF8A); + else + pas_write (pas_read (0xF8A) & ~0x40, 0xF8A); + + splx(flags); +} +#endif + +static int +pas_pcm_prepare_for_input(int dev, int bsize, int bcount) +{ + return 0; +} +static int +pas_pcm_prepare_for_output(int dev, int bsize, int bcount) +{ + return 0; +} + +static struct audio_operations pas_pcm_operations = +{ + "Pro Audio Spectrum", + DMA_AUTOMODE, + AFMT_U8 | AFMT_S16_LE, + NULL, + pas_pcm_open, + pas_pcm_close, + pas_pcm_output_block, + pas_pcm_start_input, + pas_pcm_ioctl, + pas_pcm_prepare_for_input, + pas_pcm_prepare_for_output, + pas_pcm_reset, + pas_pcm_reset, + NULL, + NULL, + NULL, + NULL, + pas_audio_trigger +}; + +void +pas_pcm_init(struct address_info * hw_config) +{ + pcm_bitsok = 8; + if (pas_read(OPERATION_MODE_1) & O_M_1_PCM_TYPE) + pcm_bitsok |= 16; + + pcm_set_speed(DSP_DEFAULT_SPEED); + + if (num_audiodevs < MAX_AUDIO_DEV) { + audio_devs[my_devnum = num_audiodevs++] = &pas_pcm_operations; + audio_devs[my_devnum]->dmachan1 = hw_config->dma; + audio_devs[my_devnum]->buffsize = DSP_BUFFSIZE; + } else + printf("PAS2: Too many PCM devices available\n"); + + return; +} + +void +pas_pcm_interrupt(u_char status, int cause) +{ + if (cause == 1) { /* PCM buffer done */ + /* + * Halt the PCM first. Otherwise we don't have time to start + * a new block before the PCM chip proceeds to the next + * sample + */ + + if (!(audio_devs[my_devnum]->flags & DMA_AUTOMODE)) { + pas_write(pas_read(PCM_CONTROL) & ~P_C_PCM_ENABLE, + PCM_CONTROL); + } + switch (pcm_mode) { + + case PCM_DAC: + DMAbuf_outputintr(my_devnum, 1); + break; + + case PCM_ADC: + DMAbuf_inputintr(my_devnum); + break; + + default: + printf("PAS: Unexpected PCM interrupt\n"); + } + } +} + +#endif |