a0f70ac053
deltas, but it is possible since I had a few merge conflicts over the last few days while this has been sitting ready to go. (Part 1 was committed to the config files, but cvs aborted grrr..) Approved by: core
450 lines
10 KiB
C
450 lines
10 KiB
C
#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
|