/* * PC-9801-86 PCM driver for FreeBSD(98). * * Copyright (c) 1995 NAGAO Tadaaki (ABTK) * All rights reserved. * * 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 AND 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. * * $FreeBSD$ */ /* * !! NOTE !! : * This file DOES NOT belong to the VoxWare distribution though it works * as part of the VoxWare drivers. It is FreeBSD(98) original. * -- Nagao (nagao@cs.titech.ac.jp) */ #include #ifdef CONFIGURE_SOUNDCARD #if !defined(EXCLUDE_NSS) && !defined(EXCLUDE_AUDIO) /* * Constants */ #define YES 1 #define NO 0 #define IMODE_NONE 0 #define IMODE_INPUT 1 #define IMODE_OUTPUT 2 /* PC-9801-86 specific constants */ #define PCM86_IOBASE 0xa460 /* PCM I/O ports */ #define PCM86_FIFOSIZE 32768 /* There is a 32kB FIFO buffer on 86-board */ /* XXX -- These values should be chosen appropriately. */ #define PCM86_INTRSIZE_OUT 1024 #define PCM86_INTRSIZE_IN (PCM86_FIFOSIZE / 2 - 128) #define DEFAULT_VOLUME 15 /* 0(min) - 15(max) */ /* * Switches for debugging and experiments */ /* #define NSS_DEBUG */ #ifdef NSS_DEBUG # ifdef DEB # undef DEB # endif # define DEB(x) x #endif /* * Private variables and types */ typedef unsigned char pcm_data; enum board_type { NO_SUPPORTED_BOARD = 0, PC980186_FAMILY = 1, PC980173_FAMILY = 2 }; static char *board_name[] = { /* Each must be of the length less than 32 bytes. */ "No supported board", "PC-9801-86 soundboard", "PC-9801-73 soundboard" }; /* Current status of the driver */ static struct { int iobase; int irq; enum board_type board_type; int opened; int format; int bytes; int chipspeedno; int chipspeed; int speed; int stereo; int volume; int intr_busy; int intr_size; int intr_mode; int intr_last; int intr_trailer; pcm_data * pdma_buf; int pdma_count; int pdma_chunkcount; int acc; int last_l; int last_r; sound_os_info *osp; } pcm_s; static struct { pcm_data buff[4]; int size; } tmpbuf; static int my_dev = 0; static char nss_initialized = NO; /* 86-board supports only the following rates. */ static int rates_tbl[8] = { #ifndef WAVEMASTER_FREQ 44100, 33075, 22050, 16538, 11025, 8269, 5513, 4134 #else /* * It is said that Q-Vision's WaveMaster of some earlier lot(s?) has * sampling rates incompatible with PC-9801-86. * But I'm not sure whether the following rates are correct, especially * 4000Hz. */ 44100, 33075, 22050, 16000, 11025, 8000, 5510, 4000 #endif }; /* u-law to linear translation table */ static pcm_data ulaw2linear[256] = { 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 240, 241, 241, 242, 242, 243, 243, 244, 244, 245, 245, 246, 246, 247, 247, 248, 248, 248, 249, 249, 249, 249, 250, 250, 250, 250, 251, 251, 251, 251, 252, 252, 252, 252, 252, 252, 253, 253, 253, 253, 253, 253, 253, 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 125, 121, 117, 113, 109, 105, 101, 97, 93, 89, 85, 81, 77, 73, 69, 65, 62, 60, 58, 56, 54, 52, 50, 48, 46, 44, 42, 40, 38, 36, 34, 32, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 15, 14, 14, 13, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, 8, 7, 7, 7, 6, 6, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; /* linear to u-law translation table */ static pcm_data linear2ulaw[256] = { 255, 231, 219, 211, 205, 201, 197, 193, 190, 188, 186, 184, 182, 180, 178, 176, 175, 174, 173, 172, 171, 170, 169, 168, 167, 166, 165, 164, 163, 162, 161, 160, 159, 159, 158, 158, 157, 157, 156, 156, 155, 155, 154, 154, 153, 153, 152, 152, 151, 151, 150, 150, 149, 149, 148, 148, 147, 147, 146, 146, 145, 145, 144, 144, 143, 143, 143, 143, 142, 142, 142, 142, 141, 141, 141, 141, 140, 140, 140, 140, 139, 139, 139, 139, 138, 138, 138, 138, 137, 137, 137, 137, 136, 136, 136, 136, 135, 135, 135, 135, 134, 134, 134, 134, 133, 133, 133, 133, 132, 132, 132, 132, 131, 131, 131, 131, 130, 130, 130, 130, 129, 129, 129, 129, 128, 128, 128, 128, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 50, 52, 54, 56, 58, 60, 62, 65, 69, 73, 77, 83, 91, 103 }; /* * Prototypes */ static int nss_detect(struct address_info *); static int nss_open(int, int); static void nss_close(int); static void nss_output_block(int, unsigned long, int, int, int); static void nss_start_input(int, unsigned long, int, int, int); static int nss_ioctl(int, u_int, ioctl_arg, int); static int nss_prepare_for_input(int, int, int); static int nss_prepare_for_output(int, int, int); static void nss_reset(int); static void nss_halt_xfer(int); static void dsp73_send_command(unsigned char); static void dsp73_send_data(unsigned char); static void dsp73_init(void); static int set_format(int); static int set_speed(int); static int set_stereo(int); static void set_volume(int); static void fifo_start(int); static void fifo_stop(void); static void fifo_reset(void); static void fifo_output_block(void); static int fifo_send(pcm_data *, int); static void fifo_sendtrailer(int); static void fifo_send_stereo(pcm_data *, int); static void fifo_send_monoral(pcm_data *, int); static void fifo_send_stereo_ulaw(pcm_data *, int); static void fifo_send_stereo_8(pcm_data *, int, int); static void fifo_send_stereo_16le(pcm_data *, int, int); static void fifo_send_stereo_16be(pcm_data *, int, int); static void fifo_send_mono_ulaw(pcm_data *, int); static void fifo_send_mono_8(pcm_data *, int, int); static void fifo_send_mono_16le(pcm_data *, int, int); static void fifo_send_mono_16be(pcm_data *, int, int); static void fifo_input_block(void); static void fifo_recv(pcm_data *, int); static void fifo_recv_stereo(pcm_data *, int); static void fifo_recv_monoral(pcm_data *, int); static void fifo_recv_stereo_ulaw(pcm_data *, int); static void fifo_recv_stereo_8(pcm_data *, int, int); static void fifo_recv_stereo_16le(pcm_data *, int, int); static void fifo_recv_stereo_16be(pcm_data *, int, int); static void fifo_recv_mono_ulaw(pcm_data *, int); static void fifo_recv_mono_8(pcm_data *, int, int); static void fifo_recv_mono_16le(pcm_data *, int, int); static void fifo_recv_mono_16be(pcm_data *, int, int); static void nss_stop(void); static void nss_init(void); /* * Identity */ static struct audio_operations nss_operations = { "PC-9801-86 SoundBoard", /* filled in properly by auto configuration */ DMA_DISABLE, ( AFMT_MU_LAW | AFMT_U8 | AFMT_S16_LE | AFMT_S16_BE | AFMT_S8 | AFMT_U16_LE | AFMT_U16_BE ), NULL, nss_open, nss_close, nss_output_block, nss_start_input, nss_ioctl, nss_prepare_for_input, nss_prepare_for_output, nss_reset, nss_halt_xfer, NULL, NULL }; /* * Codes for internal use */ static void dsp73_send_command(unsigned char command) { /* wait for RDY */ while ((inb(pcm_s.iobase + 2) & 0x48) != 8); /* command mode */ outb(pcm_s.iobase + 2, (inb(pcm_s.iobase + 2) & 0x20) | 3); /* wait for RDY */ while ((inb(pcm_s.iobase + 2) & 0x48) != 8); /* send command */ outb(pcm_s.iobase + 4, command); } static void dsp73_send_data(unsigned char data) { /* wait for RDY */ while ((inb(pcm_s.iobase + 2) & 0x48) != 8); /* data mode */ outb(pcm_s.iobase + 2, (inb(pcm_s.iobase + 2) & 0x20) | 0x83); /* wait for RDY */ while ((inb(pcm_s.iobase + 2) & 0x48) != 8); /* send command */ outb(pcm_s.iobase + 4, data); } static void dsp73_init(void) { const unsigned char dspinst[15] = { 0x00, 0x00, 0x27, 0x3f, 0xe0, 0x01, 0x00, 0x00, 0x27, 0x36, 0x5a, 0x0d, 0x3e, 0x60, 0x04 }; unsigned char t; int i; /* reset DSP */ t = inb(pcm_s.iobase + 2); outb(pcm_s.iobase + 2, (t & 0x80) | 0x23); /* mute on */ dsp73_send_command(0x04); dsp73_send_data(0x6f); dsp73_send_data(0x3c); /* write DSP instructions */ dsp73_send_command(0x01); dsp73_send_data(0x00); for (i = 0; i < 16; i++) dsp73_send_data(dspinst[i]); /* mute off */ dsp73_send_command(0x04); dsp73_send_data(0x6f); dsp73_send_data(0x30); /* wait for RDY */ while ((inb(pcm_s.iobase + 2) & 0x48) != 8); outb(pcm_s.iobase + 2, 3); } static int set_format(int format) { switch (format) { case AFMT_MU_LAW: case AFMT_S8: case AFMT_U8: pcm_s.format = format; pcm_s.bytes = 1; /* 8bit */ break; case AFMT_S16_LE: case AFMT_U16_LE: case AFMT_S16_BE: case AFMT_U16_BE: pcm_s.format = format; pcm_s.bytes = 2; /* 16bit */ break; case AFMT_QUERY: break; default: return -1; } return pcm_s.format; } static int set_speed(int speed) { int i; if (speed < 4000) /* Minimum 4000Hz */ speed = 4000; if (speed > 44100) /* Maximum 44100Hz */ speed = 44100; for (i = 7; i >= 0; i--) { if (speed <= rates_tbl[i]) { pcm_s.chipspeedno = i; pcm_s.chipspeed = rates_tbl[i]; break; } } pcm_s.speed = speed; return speed; } static int set_stereo(int stereo) { pcm_s.stereo = stereo ? YES : NO; return pcm_s.stereo; } static void set_volume(int volume) { if (volume < 0) volume = 0; if (volume > 15) volume = 15; pcm_s.volume = volume; outb(pcm_s.iobase + 6, 0xaf - volume); /* D/A -> LINE OUT */ outb(0x5f,0); outb(0x5f,0); outb(0x5f,0); outb(0x5f,0); outb(pcm_s.iobase + 6, 0x20); /* FM -> A/D */ outb(0x5f,0); outb(0x5f,0); outb(0x5f,0); outb(0x5f,0); outb(pcm_s.iobase + 6, 0x60); /* LINE IN -> A/D */ outb(0x5f,0); outb(0x5f,0); outb(0x5f,0); outb(0x5f,0); } static void fifo_start(int mode) { unsigned char tmp; /* Set frame length & panpot(LR). */ tmp = inb(pcm_s.iobase + 10) & 0x88; outb(pcm_s.iobase + 10, tmp | ((pcm_s.bytes == 1) ? 0x72 : 0x32)); tmp = pcm_s.chipspeedno; if (mode == IMODE_INPUT) tmp |= 0x40; /* Reset intr. flag. */ outb(pcm_s.iobase + 8, tmp); outb(pcm_s.iobase + 8, tmp | 0x10); /* Enable FIFO intr. */ outb(pcm_s.iobase + 8, tmp | 0x30); /* Set intr. interval. */ outb(pcm_s.iobase + 10, pcm_s.intr_size / 128 - 1); /* Start intr. */ outb(pcm_s.iobase + 8, tmp | 0xb0); } static void fifo_stop(void) { unsigned char tmp; /* Reset intr. flag, and disable FIFO intr. */ tmp = inb(pcm_s.iobase + 8) & 0x0f; outb(pcm_s.iobase + 8, tmp); } static void fifo_reset(void) { unsigned char tmp; /* Reset FIFO. */ tmp = inb(pcm_s.iobase + 8) & 0x77; outb(pcm_s.iobase + 8, tmp | 0x8); outb(pcm_s.iobase + 8, tmp); } static void fifo_output_block(void) { int chunksize, count; if (pcm_s.pdma_chunkcount) { /* Update chunksize and then send the next chunk to FIFO. */ chunksize = pcm_s.pdma_count / pcm_s.pdma_chunkcount--; count = fifo_send(pcm_s.pdma_buf, chunksize); } else { /* ??? something wrong... */ printf("nss0: chunkcount overrun\n"); chunksize = count = 0; } if (((audio_devs[my_dev]->dmap_out->qlen < 2) && (pcm_s.pdma_chunkcount == 0)) || (count < pcm_s.intr_size)) { /* The sent chunk seems to be the last one. */ fifo_sendtrailer(pcm_s.intr_size); pcm_s.intr_last = YES; } pcm_s.pdma_buf += chunksize; pcm_s.pdma_count -= chunksize; } static int fifo_send(pcm_data *buf, int count) { int i, length, r, cnt, rslt; pcm_data *p; /* Calculate the length of PCM frames. */ cnt = count + tmpbuf.size; length = pcm_s.bytes << pcm_s.stereo; r = cnt % length; cnt -= r; if (cnt > 0) { if (pcm_s.stereo) fifo_send_stereo(buf, cnt); else fifo_send_monoral(buf, cnt); /* Carry over extra data which doesn't seem to be a full PCM frame. */ p = (pcm_data *)buf + count - r; for (i = 0; i < r; i++) tmpbuf.buff[i] = *p++; } else { /* Carry over extra data which doesn't seem to be a full PCM frame. */ p = (pcm_data *)buf; for (i = tmpbuf.size; i < r; i++) tmpbuf.buff[i] = *p++; } tmpbuf.size = r; rslt = ((cnt / length) * pcm_s.chipspeed / pcm_s.speed) * pcm_s.bytes * 2; #ifdef NSS_DEBUG printf("fifo_send(): %d bytes sent\n", rslt); #endif return rslt; } static void fifo_sendtrailer(int count) { /* Send trailing zeros to the FIFO buffer. */ int i; for (i = 0; i < count; i++) outb(pcm_s.iobase + 12, 0); pcm_s.intr_trailer = YES; #ifdef NSS_DEBUG printf("fifo_sendtrailer(): %d bytes sent\n", count); #endif } static void fifo_send_stereo(pcm_data *buf, int count) { /* Convert format and sampling speed. */ switch (pcm_s.format) { case AFMT_MU_LAW: fifo_send_stereo_ulaw(buf, count); break; case AFMT_S8: fifo_send_stereo_8(buf, count, NO); break; case AFMT_U8: fifo_send_stereo_8(buf, count, YES); break; case AFMT_S16_LE: fifo_send_stereo_16le(buf, count, NO); break; case AFMT_U16_LE: fifo_send_stereo_16le(buf, count, YES); break; case AFMT_S16_BE: fifo_send_stereo_16be(buf, count, NO); break; case AFMT_U16_BE: fifo_send_stereo_16be(buf, count, YES); break; } } static void fifo_send_monoral(pcm_data *buf, int count) { /* Convert format and sampling speed. */ switch (pcm_s.format) { case AFMT_MU_LAW: fifo_send_mono_ulaw(buf, count); break; case AFMT_S8: fifo_send_mono_8(buf, count, NO); break; case AFMT_U8: fifo_send_mono_8(buf, count, YES); break; case AFMT_S16_LE: fifo_send_mono_16le(buf, count, NO); break; case AFMT_U16_LE: fifo_send_mono_16le(buf, count, YES); break; case AFMT_S16_BE: fifo_send_mono_16be(buf, count, NO); break; case AFMT_U16_BE: fifo_send_mono_16be(buf, count, YES); break; } } static void fifo_send_stereo_ulaw(pcm_data *buf, int count) { int i; signed char dl, dl0, dl1, dr, dr0, dr1; pcm_data t[2]; if (tmpbuf.size > 0) t[0] = ulaw2linear[tmpbuf.buff[0]]; else t[0] = ulaw2linear[*buf++]; t[1] = ulaw2linear[*buf++]; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ outb(pcm_s.iobase + 12, t[0]); outb(pcm_s.iobase + 12, t[1]); count -= 2; for (i = 0; i < count; i++) outb(pcm_s.iobase + 12, ulaw2linear[*buf++]); } else { /* Speed conversion with linear interpolation method. */ dl0 = pcm_s.last_l; dr0 = pcm_s.last_r; dl1 = t[0]; dr1 = t[1]; i = 0; count /= 2; while (i < count) { while (pcm_s.acc >= pcm_s.chipspeed) { pcm_s.acc -= pcm_s.chipspeed; i++; dl0 = dl1; dr0 = dr1; if (i < count) { dl1 = ulaw2linear[*buf++]; dr1 = ulaw2linear[*buf++]; } else dl1 = dr1 = 0; } dl = ((dl0 * (pcm_s.chipspeed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.chipspeed; dr = ((dr0 * (pcm_s.chipspeed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.chipspeed; outb(pcm_s.iobase + 12, dl); outb(pcm_s.iobase + 12, dr); pcm_s.acc += pcm_s.speed; } pcm_s.last_l = dl0; pcm_s.last_r = dr0; } } static void fifo_send_stereo_8(pcm_data *buf, int count, int uflag) { int i; signed char dl, dl0, dl1, dr, dr0, dr1, zlev; pcm_data t[2]; zlev = uflag ? -128 : 0; if (tmpbuf.size > 0) t[0] = tmpbuf.buff[0] + zlev; else t[0] = *buf++ + zlev; t[1] = *buf++ + zlev; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ outb(pcm_s.iobase + 12, t[0]); outb(pcm_s.iobase + 12, t[1]); count -= 2; for (i = 0; i < count; i++) outb(pcm_s.iobase + 12, *buf++ + zlev); } else { /* Speed conversion with linear interpolation method. */ dl0 = pcm_s.last_l; dr0 = pcm_s.last_r; dl1 = t[0]; dr1 = t[1]; i = 0; count /= 2; while (i < count) { while (pcm_s.acc >= pcm_s.chipspeed) { pcm_s.acc -= pcm_s.chipspeed; i++; dl0 = dl1; dr0 = dr1; if (i < count) { dl1 = *buf++ + zlev; dr1 = *buf++ + zlev; } else dl1 = dr1 = 0; } dl = ((dl0 * (pcm_s.chipspeed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.chipspeed; dr = ((dr0 * (pcm_s.chipspeed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.chipspeed; outb(pcm_s.iobase + 12, dl); outb(pcm_s.iobase + 12, dr); pcm_s.acc += pcm_s.speed; } pcm_s.last_l = dl0; pcm_s.last_r = dr0; } } static void fifo_send_stereo_16le(pcm_data *buf, int count, int uflag) { int i; short dl, dl0, dl1, dr, dr0, dr1, zlev; pcm_data t[4]; zlev = uflag ? -128 : 0; for (i = 0; i < 4; i++) t[i] = (tmpbuf.size > i) ? tmpbuf.buff[i] : *buf++; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ outb(pcm_s.iobase + 12, t[1] + zlev); outb(pcm_s.iobase + 12, t[0]); outb(pcm_s.iobase + 12, t[3] + zlev); outb(pcm_s.iobase + 12, t[2]); count = count / 2 - 2; for (i = 0; i < count; i++) { outb(pcm_s.iobase + 12, *(buf + 1) + zlev); outb(pcm_s.iobase + 12, *buf); buf += 2; } } else { /* Speed conversion with linear interpolation method. */ dl0 = pcm_s.last_l; dr0 = pcm_s.last_r; dl1 = t[0] + ((t[1] + zlev) << 8); dr1 = t[2] + ((t[3] + zlev) << 8); i = 0; count /= 4; while (i < count) { while (pcm_s.acc >= pcm_s.chipspeed) { pcm_s.acc -= pcm_s.chipspeed; i++; dl0 = dl1; dr0 = dr1; if (i < count) { dl1 = *buf + ((*(buf + 1) + zlev) << 8); buf += 2; dr1 = *buf + ((*(buf + 1) + zlev) << 8); buf += 2; } else dl1 = dr1 = 0; } dl = ((dl0 * (pcm_s.chipspeed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.chipspeed; dr = ((dr0 * (pcm_s.chipspeed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.chipspeed; outb(pcm_s.iobase + 12, (dl >> 8) & 0xff); outb(pcm_s.iobase + 12, dl & 0xff); outb(pcm_s.iobase + 12, (dr >> 8) & 0xff); outb(pcm_s.iobase + 12, dr & 0xff); pcm_s.acc += pcm_s.speed; } pcm_s.last_l = dl0; pcm_s.last_r = dr0; } } static void fifo_send_stereo_16be(pcm_data *buf, int count, int uflag) { int i; short dl, dl0, dl1, dr, dr0, dr1, zlev; pcm_data t[4]; zlev = uflag ? -128 : 0; for (i = 0; i < 4; i++) t[i] = (tmpbuf.size > i) ? tmpbuf.buff[i] : *buf++; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ outb(pcm_s.iobase + 12, t[0] + zlev); outb(pcm_s.iobase + 12, t[1]); outb(pcm_s.iobase + 12, t[2] + zlev); outb(pcm_s.iobase + 12, t[3]); count = count / 2 - 2; for (i = 0; i < count; i++) { outb(pcm_s.iobase + 12, *buf + zlev); outb(pcm_s.iobase + 12, *(buf + 1)); buf += 2; } } else { /* Speed conversion with linear interpolation method. */ dl0 = pcm_s.last_l; dr0 = pcm_s.last_r; dl1 = ((t[0] + zlev) << 8) + t[1]; dr1 = ((t[2] + zlev) << 8) + t[3]; i = 0; count /= 4; while (i < count) { while (pcm_s.acc >= pcm_s.chipspeed) { pcm_s.acc -= pcm_s.chipspeed; i++; dl0 = dl1; dr0 = dr1; if (i < count) { dl1 = ((*buf + zlev) << 8) + *(buf + 1); buf += 2; dr1 = ((*buf + zlev) << 8) + *(buf + 1); buf += 2; } else dl1 = dr1 = 0; } dl = ((dl0 * (pcm_s.chipspeed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.chipspeed; dr = ((dr0 * (pcm_s.chipspeed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.chipspeed; outb(pcm_s.iobase + 12, (dl >> 8) & 0xff); outb(pcm_s.iobase + 12, dl & 0xff); outb(pcm_s.iobase + 12, (dr >> 8) & 0xff); outb(pcm_s.iobase + 12, dr & 0xff); pcm_s.acc += pcm_s.speed; } pcm_s.last_l = dl0; pcm_s.last_r = dr0; } } static void fifo_send_mono_ulaw(pcm_data *buf, int count) { int i; signed char d, d0, d1; if (pcm_s.speed == pcm_s.chipspeed) /* No reason to convert the pcm speed. */ for (i = 0; i < count; i++) { d = ulaw2linear[*buf++]; outb(pcm_s.iobase + 12, d); outb(pcm_s.iobase + 12, d); } else { /* Speed conversion with linear interpolation method. */ d0 = pcm_s.last_l; d1 = ulaw2linear[*buf++]; i = 0; while (i < count) { while (pcm_s.acc >= pcm_s.chipspeed) { pcm_s.acc -= pcm_s.chipspeed; i++; d0 = d1; d1 = (i < count) ? ulaw2linear[*buf++] : 0; } d = ((d0 * (pcm_s.chipspeed - pcm_s.acc)) + (d1 * pcm_s.acc)) / pcm_s.chipspeed; outb(pcm_s.iobase + 12, d); outb(pcm_s.iobase + 12, d); pcm_s.acc += pcm_s.speed; } pcm_s.last_l = d0; } } static void fifo_send_mono_8(pcm_data *buf, int count, int uflag) { int i; signed char d, d0, d1, zlev; zlev = uflag ? -128 : 0; if (pcm_s.speed == pcm_s.chipspeed) /* No reason to convert the pcm speed. */ for (i = 0; i < count; i++) { d = *buf++ + zlev; outb(pcm_s.iobase + 12, d); outb(pcm_s.iobase + 12, d); } else { /* Speed conversion with linear interpolation method. */ d0 = pcm_s.last_l; d1 = *buf++ + zlev; i = 0; while (i < count) { while (pcm_s.acc >= pcm_s.chipspeed) { pcm_s.acc -= pcm_s.chipspeed; i++; d0 = d1; d1 = (i < count) ? *buf++ + zlev : 0; } d = ((d0 * (pcm_s.chipspeed - pcm_s.acc)) + (d1 * pcm_s.acc)) / pcm_s.chipspeed; outb(pcm_s.iobase + 12, d); outb(pcm_s.iobase + 12, d); pcm_s.acc += pcm_s.speed; } pcm_s.last_l = d0; } } static void fifo_send_mono_16le(pcm_data *buf, int count, int uflag) { int i; short d, d0, d1, zlev; pcm_data t[2]; zlev = uflag ? -128 : 0; for (i = 0; i < 2; i++) t[i] = (tmpbuf.size > i) ? tmpbuf.buff[i] : *buf++; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ outb(pcm_s.iobase + 12, t[1] + zlev); outb(pcm_s.iobase + 12, t[0]); outb(pcm_s.iobase + 12, t[1] + zlev); outb(pcm_s.iobase + 12, t[0]); count = count / 2 - 1; for (i = 0; i < count; i++) { outb(pcm_s.iobase + 12, *(buf + 1) + zlev); outb(pcm_s.iobase + 12, *buf); outb(pcm_s.iobase + 12, *(buf + 1) + zlev); outb(pcm_s.iobase + 12, *buf); buf += 2; } } else { /* Speed conversion with linear interpolation method. */ d0 = pcm_s.last_l; d1 = t[0] + ((t[1] + zlev) << 8); i = 0; count /= 2; while (i < count) { while (pcm_s.acc >= pcm_s.chipspeed) { pcm_s.acc -= pcm_s.chipspeed; i++; d0 = d1; if (i < count) { d1 = *buf + ((*(buf + 1) + zlev) << 8); buf += 2; } else d1 = 0; } d = ((d0 * (pcm_s.chipspeed - pcm_s.acc)) + (d1 * pcm_s.acc)) / pcm_s.chipspeed; outb(pcm_s.iobase + 12, (d >> 8) & 0xff); outb(pcm_s.iobase + 12, d & 0xff); outb(pcm_s.iobase + 12, (d >> 8) & 0xff); outb(pcm_s.iobase + 12, d & 0xff); pcm_s.acc += pcm_s.speed; } pcm_s.last_l = d0; } } static void fifo_send_mono_16be(pcm_data *buf, int count, int uflag) { int i; short d, d0, d1, zlev; pcm_data t[2]; zlev = uflag ? -128 : 0; for (i = 0; i < 2; i++) t[i] = (tmpbuf.size > i) ? tmpbuf.buff[i] : *buf++; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ outb(pcm_s.iobase + 12, t[0] + zlev); outb(pcm_s.iobase + 12, t[1]); outb(pcm_s.iobase + 12, t[0] + zlev); outb(pcm_s.iobase + 12, t[1]); count = count / 2 - 1; for (i = 0; i < count; i++) { outb(pcm_s.iobase + 12, *buf + zlev); outb(pcm_s.iobase + 12, *(buf + 1)); outb(pcm_s.iobase + 12, *buf + zlev); outb(pcm_s.iobase + 12, *(buf + 1)); buf += 2; } } else { /* Speed conversion with linear interpolation method. */ d0 = pcm_s.last_l; d1 = ((t[0] + zlev) << 8) + t[1]; i = 0; count /= 2; while (i < count) { while (pcm_s.acc >= pcm_s.chipspeed) { pcm_s.acc -= pcm_s.chipspeed; i++; d0 = d1; if (i < count) { d1 = ((*buf + zlev) << 8) + *(buf + 1); buf += 2; } else d1 = 0; } d = ((d0 * (pcm_s.chipspeed - pcm_s.acc)) + (d1 * pcm_s.acc)) / pcm_s.chipspeed; /* outb(pcm_s.iobase + 12, d & 0xff); outb(pcm_s.iobase + 12, (d >> 8) & 0xff); outb(pcm_s.iobase + 12, d & 0xff); outb(pcm_s.iobase + 12, (d >> 8) & 0xff); */ outb(pcm_s.iobase + 12, (d >> 8) & 0xff); outb(pcm_s.iobase + 12, d & 0xff); outb(pcm_s.iobase + 12, (d >> 8) & 0xff); outb(pcm_s.iobase + 12, d & 0xff); pcm_s.acc += pcm_s.speed; } pcm_s.last_l = d0; } } static void fifo_input_block(void) { int chunksize; if (pcm_s.pdma_chunkcount) { /* Update chunksize and then receive the next chunk from FIFO. */ chunksize = pcm_s.pdma_count / pcm_s.pdma_chunkcount--; fifo_recv(pcm_s.pdma_buf, chunksize); pcm_s.pdma_buf += chunksize; pcm_s.pdma_count -= chunksize; } else /* ??? something wrong... */ printf("nss0: chunkcount overrun\n"); } static void fifo_recv(pcm_data *buf, int count) { int i; if (count > tmpbuf.size) { for (i = 0; i < tmpbuf.size; i++) *buf++ = tmpbuf.buff[i]; count -= tmpbuf.size; tmpbuf.size = 0; if (pcm_s.stereo) fifo_recv_stereo(buf, count); else fifo_recv_monoral(buf, count); } else { for (i = 0; i < count; i++) *buf++ = tmpbuf.buff[i]; for (i = 0; i < tmpbuf.size - count; i++) tmpbuf.buff[i] = tmpbuf.buff[i + count]; tmpbuf.size -= count; } #ifdef NSS_DEBUG printf("fifo_recv(): %d bytes received\n", ((count / (pcm_s.bytes << pcm_s.stereo)) * pcm_s.chipspeed / pcm_s.speed) * pcm_s.bytes * 2); #endif } static void fifo_recv_stereo(pcm_data *buf, int count) { /* Convert format and sampling speed. */ switch (pcm_s.format) { case AFMT_MU_LAW: fifo_recv_stereo_ulaw(buf, count); break; case AFMT_S8: fifo_recv_stereo_8(buf, count, NO); break; case AFMT_U8: fifo_recv_stereo_8(buf, count, YES); break; case AFMT_S16_LE: fifo_recv_stereo_16le(buf, count, NO); break; case AFMT_U16_LE: fifo_recv_stereo_16le(buf, count, YES); break; case AFMT_S16_BE: fifo_recv_stereo_16be(buf, count, NO); break; case AFMT_U16_BE: fifo_recv_stereo_16be(buf, count, YES); break; } } static void fifo_recv_monoral(pcm_data *buf, int count) { /* Convert format and sampling speed. */ switch (pcm_s.format) { case AFMT_MU_LAW: fifo_recv_mono_ulaw(buf, count); break; case AFMT_S8: fifo_recv_mono_8(buf, count, NO); break; case AFMT_U8: fifo_recv_mono_8(buf, count, YES); break; case AFMT_S16_LE: fifo_recv_mono_16le(buf, count, NO); break; case AFMT_U16_LE: fifo_recv_mono_16le(buf, count, YES); break; case AFMT_S16_BE: fifo_recv_mono_16be(buf, count, NO); break; case AFMT_U16_BE: fifo_recv_mono_16be(buf, count, YES); break; } } static void fifo_recv_stereo_ulaw(pcm_data *buf, int count) { int i, cnt; signed char dl, dl0, dl1, dr, dr0, dr1; cnt = count / 2; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ for (i = 0; i < cnt; i++) { *buf++ = linear2ulaw[inb(pcm_s.iobase + 12)]; *buf++ = linear2ulaw[inb(pcm_s.iobase + 12)]; } if (count % 2) { *buf++ = linear2ulaw[inb(pcm_s.iobase + 12)]; tmpbuf.buff[0] = linear2ulaw[inb(pcm_s.iobase + 12)]; tmpbuf.size = 1; } } else { /* Speed conversion with linear interpolation method. */ dl0 = pcm_s.last_l; dr0 = pcm_s.last_r; dl1 = inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12); for (i = 0; i < cnt; i++) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; dl0 = dl1; dr0 = dr1; dl1 = inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12); } dl = ((dl0 * (pcm_s.speed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.speed; dr = ((dr0 * (pcm_s.speed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.speed; *buf++ = linear2ulaw[dl & 0xff]; *buf++ = linear2ulaw[dr & 0xff]; pcm_s.acc += pcm_s.chipspeed; } if (count % 2) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; dl0 = dl1; dr0 = dr1; dl1 = inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12); } dl = ((dl0 * (pcm_s.speed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.speed; dr = ((dr0 * (pcm_s.speed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.speed; *buf++ = linear2ulaw[dl & 0xff]; tmpbuf.buff[0] = linear2ulaw[dr & 0xff]; tmpbuf.size = 1; } pcm_s.last_l = dl0; pcm_s.last_r = dr0; } } static void fifo_recv_stereo_8(pcm_data *buf, int count, int uflag) { int i, cnt; signed char dl, dl0, dl1, dr, dr0, dr1, zlev; zlev = uflag ? -128 : 0; cnt = count / 2; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ for (i = 0; i < cnt; i++) { *buf++ = inb(pcm_s.iobase + 12) + zlev; *buf++ = inb(pcm_s.iobase + 12) + zlev; } if (count % 2) { *buf++ = inb(pcm_s.iobase + 12) + zlev; tmpbuf.buff[0] = inb(pcm_s.iobase + 12) + zlev; tmpbuf.size = 1; } } else { /* Speed conversion with linear interpolation method. */ dl0 = pcm_s.last_l; dr0 = pcm_s.last_r; dl1 = inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12); for (i = 0; i < cnt; i++) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; dl0 = dl1; dr0 = dr1; dl1 = inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12); } dl = ((dl0 * (pcm_s.speed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.speed; dr = ((dr0 * (pcm_s.speed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.speed; *buf++ = dl + zlev; *buf++ = dr + zlev; pcm_s.acc += pcm_s.chipspeed; } if (count % 2) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; dl0 = dl1; dr0 = dr1; dl1 = inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12); } dl = ((dl0 * (pcm_s.speed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.speed; dr = ((dr0 * (pcm_s.speed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.speed; *buf++ = dl + zlev; tmpbuf.buff[0] = dr + zlev; tmpbuf.size = 1; } pcm_s.last_l = dl0; pcm_s.last_r = dr0; } } static void fifo_recv_stereo_16le(pcm_data *buf, int count, int uflag) { int i, cnt; short dl, dl0, dl1, dr, dr0, dr1, zlev; pcm_data t[4]; zlev = uflag ? -128 : 0; cnt = count / 4; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ for (i = 0; i < cnt; i++) { *(buf + 1) = inb(pcm_s.iobase + 12) + zlev; *buf = inb(pcm_s.iobase + 12); *(buf + 3) = inb(pcm_s.iobase + 12) + zlev; *(buf + 2) = inb(pcm_s.iobase + 12); buf += 4; } if (count % 4) { t[1] = inb(pcm_s.iobase + 12) + zlev; t[0] = inb(pcm_s.iobase + 12); t[3] = inb(pcm_s.iobase + 12) + zlev; t[2] = inb(pcm_s.iobase + 12); tmpbuf.size = 0; for (i = 0; i < count % 4; i++) *buf++ = t[i]; for (i = count % 4; i < 4; i++) tmpbuf.buff[tmpbuf.size++] = t[i]; } } else { /* Speed conversion with linear interpolation method. */ dl0 = pcm_s.last_l; dr0 = pcm_s.last_r; dl1 = inb(pcm_s.iobase + 12) << 8; dl1 |= inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12) << 8; dr1 |= inb(pcm_s.iobase + 12); for (i = 0; i < cnt; i++) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; dl0 = dl1; dr0 = dr1; dl1 = inb(pcm_s.iobase + 12) << 8; dl1 |= inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12) << 8; dr1 |= inb(pcm_s.iobase + 12); } dl = ((dl0 * (pcm_s.speed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.speed; dr = ((dr0 * (pcm_s.speed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.speed; *buf++ = dl & 0xff; *buf++ = ((dl >> 8) & 0xff) + zlev; *buf++ = dr & 0xff; *buf++ = ((dr >> 8) & 0xff) + zlev; pcm_s.acc += pcm_s.chipspeed; } if (count % 4) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; dl0 = dl1; dr0 = dr1; dl1 = inb(pcm_s.iobase + 12) << 8; dl1 |= inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12) << 8; dr1 |= inb(pcm_s.iobase + 12); } dl = ((dl0 * (pcm_s.speed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.speed; dr = ((dr0 * (pcm_s.speed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.speed; t[0] = dl & 0xff; t[1] = ((dl >> 8) & 0xff) + zlev; t[2] = dr & 0xff; t[3] = ((dr >> 8) & 0xff) + zlev; tmpbuf.size = 0; for (i = 0; i < count % 4; i++) *buf++ = t[i]; for (i = count % 4; i < 4; i++) tmpbuf.buff[tmpbuf.size++] = t[i]; } pcm_s.last_l = dl0; pcm_s.last_r = dr0; } } static void fifo_recv_stereo_16be(pcm_data *buf, int count, int uflag) { int i, cnt; short dl, dl0, dl1, dr, dr0, dr1, zlev; pcm_data t[4]; zlev = uflag ? -128 : 0; cnt = count / 4; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ for (i = 0; i < cnt; i++) { *buf++ = inb(pcm_s.iobase + 12) + zlev; *buf++ = inb(pcm_s.iobase + 12); *buf++ = inb(pcm_s.iobase + 12) + zlev; *buf++ = inb(pcm_s.iobase + 12); } if (count % 4) { t[0] = inb(pcm_s.iobase + 12) + zlev; t[1] = inb(pcm_s.iobase + 12); t[2] = inb(pcm_s.iobase + 12) + zlev; t[3] = inb(pcm_s.iobase + 12); tmpbuf.size = 0; for (i = 0; i < count % 4; i++) *buf++ = t[i]; for (i = count % 4; i < 4; i++) tmpbuf.buff[tmpbuf.size++] = t[i]; } } else { /* Speed conversion with linear interpolation method. */ dl0 = pcm_s.last_l; dr0 = pcm_s.last_r; dl1 = inb(pcm_s.iobase + 12) << 8; dl1 |= inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12) << 8; dr1 |= inb(pcm_s.iobase + 12); for (i = 0; i < cnt; i++) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; dl0 = dl1; dr0 = dr1; dl1 = inb(pcm_s.iobase + 12) << 8; dl1 |= inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12) << 8; dr1 |= inb(pcm_s.iobase + 12); } dl = ((dl0 * (pcm_s.speed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.speed; dr = ((dr0 * (pcm_s.speed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.speed; *buf++ = ((dl >> 8) & 0xff) + zlev; *buf++ = dl & 0xff; *buf++ = ((dr >> 8) & 0xff) + zlev; *buf++ = dr & 0xff; pcm_s.acc += pcm_s.chipspeed; } if (count % 4) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; dl0 = dl1; dr0 = dr1; dl1 = inb(pcm_s.iobase + 12) << 8; dl1 |= inb(pcm_s.iobase + 12); dr1 = inb(pcm_s.iobase + 12) << 8; dr1 |= inb(pcm_s.iobase + 12); } dl = ((dl0 * (pcm_s.speed - pcm_s.acc)) + (dl1 * pcm_s.acc)) / pcm_s.speed; dr = ((dr0 * (pcm_s.speed - pcm_s.acc)) + (dr1 * pcm_s.acc)) / pcm_s.speed; t[0] = ((dl >> 8) & 0xff) + zlev; t[1] = dl & 0xff; t[2] = ((dr >> 8) & 0xff) + zlev; t[3] = dr & 0xff; tmpbuf.size = 0; for (i = 0; i < count % 4; i++) *buf++ = t[i]; for (i = count % 4; i < 4; i++) tmpbuf.buff[tmpbuf.size++] = t[i]; } pcm_s.last_l = dl0; pcm_s.last_r = dr0; } } static void fifo_recv_mono_ulaw(pcm_data *buf, int count) { int i; signed char d, d0, d1; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ for (i = 0; i < count; i++) { d = ((signed char)inb(pcm_s.iobase + 12) + (signed char)inb(pcm_s.iobase + 12)) >> 1; *buf++ = linear2ulaw[d & 0xff]; } } else { /* Speed conversion with linear interpolation method. */ d0 = pcm_s.last_l; d1 = ((signed char)inb(pcm_s.iobase + 12) + (signed char)inb(pcm_s.iobase + 12)) >> 1; for (i = 0; i < count; i++) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; d0 = d1; d1 = ((signed char)inb(pcm_s.iobase + 12) + (signed char)inb(pcm_s.iobase + 12)) >> 1; } d = ((d0 * (pcm_s.speed - pcm_s.acc)) + (d1 * pcm_s.acc)) / pcm_s.speed; *buf++ = linear2ulaw[d & 0xff]; pcm_s.acc += pcm_s.chipspeed; } pcm_s.last_l = d0; } } static void fifo_recv_mono_8(pcm_data *buf, int count, int uflag) { int i; signed char d, d0, d1, zlev; zlev = uflag ? -128 : 0; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ for (i = 0; i < count; i++) { d = ((signed char)inb(pcm_s.iobase + 12) + (signed char)inb(pcm_s.iobase + 12)) >> 1; *buf++ = d + zlev; } } else { /* Speed conversion with linear interpolation method. */ d0 = pcm_s.last_l; d1 = ((signed char)inb(pcm_s.iobase + 12) + (signed char)inb(pcm_s.iobase + 12)) >> 1; for (i = 0; i < count; i++) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; d0 = d1; d1 = ((signed char)inb(pcm_s.iobase + 12) + (signed char)inb(pcm_s.iobase + 12)) >> 1; } d = ((d0 * (pcm_s.speed - pcm_s.acc)) + (d1 * pcm_s.acc)) / pcm_s.speed; *buf++ = d + zlev; pcm_s.acc += pcm_s.chipspeed; } pcm_s.last_l = d0; } } static void fifo_recv_mono_16le(pcm_data *buf, int count, int uflag) { int i, cnt; short d, d0, d1, el, er, zlev; zlev = uflag ? -128 : 0; cnt = count / 2; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ for (i = 0; i < cnt; i++) { el = inb(pcm_s.iobase + 12) << 8; el |= inb(pcm_s.iobase + 12); er = inb(pcm_s.iobase + 12) << 8; er |= inb(pcm_s.iobase + 12); d = (el + er) >> 1; *buf++ = d & 0xff; *buf++ = ((d >> 8) & 0xff) + zlev; } if (count % 2) { el = inb(pcm_s.iobase + 12) << 8; el |= inb(pcm_s.iobase + 12); er = inb(pcm_s.iobase + 12) << 8; er |= inb(pcm_s.iobase + 12); d = (el + er) >> 1; *buf++ = d & 0xff; tmpbuf.buff[0] = ((d >> 8) & 0xff) + zlev; tmpbuf.size = 1; } } else { /* Speed conversion with linear interpolation method. */ d0 = pcm_s.last_l; el = inb(pcm_s.iobase + 12) << 8; el |= inb(pcm_s.iobase + 12); er = inb(pcm_s.iobase + 12) << 8; er |= inb(pcm_s.iobase + 12); d1 = (el + er) >> 1; for (i = 0; i < cnt; i++) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; d0 = d1; el = inb(pcm_s.iobase + 12) << 8; el |= inb(pcm_s.iobase + 12); er = inb(pcm_s.iobase + 12) << 8; er |= inb(pcm_s.iobase + 12); d1 = (el + er) >> 1; } d = ((d0 * (pcm_s.speed - pcm_s.acc)) + (d1 * pcm_s.acc)) / pcm_s.speed; *buf++ = d & 0xff; *buf++ = ((d >> 8) & 0xff) + zlev; pcm_s.acc += pcm_s.chipspeed; } if (count % 2) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; d0 = d1; el = inb(pcm_s.iobase + 12) << 8; el |= inb(pcm_s.iobase + 12); er = inb(pcm_s.iobase + 12) << 8; er |= inb(pcm_s.iobase + 12); d1 = (el + er) >> 1; } d = ((d0 * (pcm_s.speed - pcm_s.acc)) + (d1 * pcm_s.acc)) / pcm_s.speed; *buf++ = d & 0xff; tmpbuf.buff[0] = ((d >> 8) & 0xff) + zlev; tmpbuf.size = 1; } pcm_s.last_l = d0; } } static void fifo_recv_mono_16be(pcm_data *buf, int count, int uflag) { int i, cnt; short d, d0, d1, el, er, zlev; zlev = uflag ? -128 : 0; cnt = count / 2; if (pcm_s.speed == pcm_s.chipspeed) { /* No reason to convert the pcm speed. */ for (i = 0; i < cnt; i++) { el = inb(pcm_s.iobase + 12) << 8; el |= inb(pcm_s.iobase + 12); er = inb(pcm_s.iobase + 12) << 8; er |= inb(pcm_s.iobase + 12); d = (el + er) >> 1; *buf++ = ((d >> 8) & 0xff) + zlev; *buf++ = d & 0xff; } if (count % 2) { el = inb(pcm_s.iobase + 12) << 8; el |= inb(pcm_s.iobase + 12); er = inb(pcm_s.iobase + 12) << 8; er |= inb(pcm_s.iobase + 12); d = (el + er) >> 1; *buf++ = ((d >> 8) & 0xff) + zlev; tmpbuf.buff[0] = d & 0xff; tmpbuf.size = 1; } } else { /* Speed conversion with linear interpolation method. */ d0 = pcm_s.last_l; el = inb(pcm_s.iobase + 12) << 8; el |= inb(pcm_s.iobase + 12); er = inb(pcm_s.iobase + 12) << 8; er |= inb(pcm_s.iobase + 12); d1 = (el + er) >> 1; for (i = 0; i < cnt; i++) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; d0 = d1; el = inb(pcm_s.iobase + 12) << 8; el |= inb(pcm_s.iobase + 12); er = inb(pcm_s.iobase + 12) << 8; er |= inb(pcm_s.iobase + 12); d1 = (el + er) >> 1; } d = ((d0 * (pcm_s.speed - pcm_s.acc)) + (d1 * pcm_s.acc)) / pcm_s.speed; *buf++ = ((d >> 8) & 0xff) + zlev; *buf++ = d & 0xff; pcm_s.acc += pcm_s.chipspeed; } if (count % 2) { while (pcm_s.acc >= pcm_s.speed) { pcm_s.acc -= pcm_s.speed; d0 = d1; el = inb(pcm_s.iobase + 12) << 8; el |= inb(pcm_s.iobase + 12); er = inb(pcm_s.iobase + 12) << 8; er |= inb(pcm_s.iobase + 12); d1 = (el + er) >> 1; } d = ((d0 * (pcm_s.speed - pcm_s.acc)) + (d1 * pcm_s.acc)) / pcm_s.speed; *buf++ = ((d >> 8) & 0xff) + zlev; tmpbuf.buff[0] = d & 0xff; tmpbuf.size = 1; } pcm_s.last_l = d0; } } static void nss_stop(void) { fifo_stop(); /* stop FIFO */ fifo_reset(); /* reset FIFO buffer */ /* Reset driver's status. */ pcm_s.intr_busy = NO; pcm_s.intr_last = NO; pcm_s.intr_trailer = NO; pcm_s.acc = 0; pcm_s.last_l = 0; pcm_s.last_r = 0; DEB(printf("nss_stop\n")); } static void nss_init(void) { /* Initialize registers on the board. */ nss_stop(); if (pcm_s.board_type == PC980173_FAMILY) dsp73_init(); /* Set default volume. */ set_volume(DEFAULT_VOLUME); /* Initialize driver's status. */ pcm_s.opened = NO; nss_initialized = YES; } /* * Codes for global use */ int probe_nss(struct address_info *hw_config) { return nss_detect(hw_config); } void attach_nss(struct address_info *hw_config) { if (pcm_s.board_type == NO_SUPPORTED_BOARD) return ; /* Initialize the board. */ nss_init(); conf_printf(nss_operations.name, hw_config); if (num_audiodevs < MAX_AUDIO_DEV) { my_dev = num_audiodevs++; audio_devs[my_dev] = &nss_operations; /* audio_devs[my_dev]->buffcount = DSP_BUFFCOUNT; */ audio_devs[my_dev]->buffsize = DSP_BUFFSIZE; #ifdef NSS_DEBUG printf("\nbuffsize = %d", DSP_BUFFSIZE); #endif } else printf("nss0: Too many PCM devices available"); return ; } static int nss_detect(struct address_info *hw_config) { int opna_iobase = 0x188, irq = 12, i; unsigned char tmp; if (hw_config->io_base == -1) { printf("nss0: iobase not specified. Assume default port(0x%x)\n", PCM86_IOBASE); hw_config->io_base = PCM86_IOBASE; } pcm_s.iobase = hw_config->io_base; /* auto configuration */ tmp = (inb(pcm_s.iobase) & 0xf0) >> 4; if (tmp == 0x07) { /* * Remap MATE-X PCM Sound ID register (0xA460 -> 0xB460) * to avoid corrision with 86 Sound System. */ /* printf("nss0: Found MATE-X PCM Sound ID\n"); printf("nss0: Remaped 0xa460 to 0xb460\n"); */ outb(0xc24, 0xe1); outb(0xc2b, 0x60); outb(0xc2d, 0xb4); } tmp = inb(pcm_s.iobase) & 0xfc; switch ((tmp & 0xf0) >> 4) { case 2: opna_iobase = 0x188; pcm_s.board_type = PC980173_FAMILY; break; case 3: opna_iobase = 0x288; pcm_s.board_type = PC980173_FAMILY; break; case 4: opna_iobase = 0x188; pcm_s.board_type = PC980186_FAMILY; break; case 5: opna_iobase = 0x288; pcm_s.board_type = PC980186_FAMILY; break; default: pcm_s.board_type = NO_SUPPORTED_BOARD; return NO; } /* Enable OPNA(YM2608) facilities. */ outb(pcm_s.iobase, tmp | 0x01); /* Wait for OPNA to be ready. */ i = 100000; /* Some large value */ while((inb(opna_iobase) & 0x80) && (i-- > 0)); /* Make IOA/IOB port ready (IOA:input, IOB:output) */ outb(opna_iobase, 0x07); outb(0x5f, 0); /* Because OPNA ports are comparatively slow(?), */ outb(0x5f, 0); /* we'd better wait a moment. */ outb(0x5f, 0); outb(0x5f, 0); tmp = inb(opna_iobase + 2) & 0x3f; outb(opna_iobase + 2, tmp | 0x80); /* Wait for OPNA to be ready. */ i = 100000; /* Some large value */ while((inb(opna_iobase) & 0x80) && (i-- > 0)); /* Get irq number from IOA port. */ outb(opna_iobase, 0x0e); outb(0x5f, 0); outb(0x5f, 0); outb(0x5f, 0); outb(0x5f, 0); tmp = inb(opna_iobase + 2) & 0xc0; switch (tmp >> 6) { case 0: /* INT0 (IRQ3)*/ irq = 3; break; case 1: /* INT6 (IRQ13)*/ irq = 13; break; case 2: /* INT4 (IRQ10)*/ irq = 10; break; case 3: /* INT5 (IRQ12)*/ irq = 12; break; default: /* error */ return NO; } /* Wait for OPNA to be ready. */ i = 100000; /* Some large value */ while((inb(opna_iobase) & 0x80) && (i-- > 0)); /* Reset OPNA timer register. */ outb(opna_iobase, 0x27); outb(0x5f, 0); outb(0x5f, 0); outb(0x5f, 0); outb(0x5f, 0); outb(opna_iobase + 2, 0x30); /* Ok. Detection finished. */ snprintf(nss_operations.name, sizeof(nss_operations.name), "%s", board_name[pcm_s.board_type]); nss_initialized = NO; pcm_s.irq = irq; if ((hw_config->irq > 0) && (hw_config->irq != irq)) printf("nss0: change irq %d -> %d\n", hw_config->irq, irq); hw_config->irq = irq; pcm_s.osp = hw_config->osp; return YES; } static int nss_open(int dev, int mode) { int err; if (!nss_initialized) return -(ENXIO); if (pcm_s.intr_busy || pcm_s.opened) return -(EBUSY); if ((err = snd_set_irq_handler(pcm_s.irq, nssintr, pcm_s.osp)) < 0) return err; nss_stop(); tmpbuf.size = 0; pcm_s.intr_mode = IMODE_NONE; pcm_s.opened = YES; return 0; } static void nss_close(int dev) { /* snd_release_irq(pcm_s.irq); */ pcm_s.opened = NO; } static void nss_output_block(int dev, unsigned long buf, int count, int intrflag, int dma_restart) { unsigned long flags, cnt; int maxchunksize; #ifdef NSS_DEBUG printf("nss_output_block():"); if (audio_devs[dev]->dmap_out->flags & DMA_BUSY) printf(" DMA_BUSY"); if (audio_devs[dev]->dmap_out->flags & DMA_RESTART) printf(" DMA_RESTART"); if (audio_devs[dev]->dmap_out->flags & DMA_ACTIVE) printf(" DMA_ACTIVE"); if (audio_devs[dev]->dmap_out->flags & DMA_STARTED) printf(" DMA_STARTED"); if (audio_devs[dev]->dmap_out->flags & DMA_ALLOC_DONE) printf(" DMA_ALLOC_DONE"); printf("\n"); #endif #if 0 DISABLE_INTR(flags); #endif #ifdef NSS_DEBUG printf("nss_output_block(): count = %d, intrsize= %d\n", count, pcm_s.intr_size); #endif pcm_s.pdma_buf = (pcm_data *)buf; pcm_s.pdma_count = count; pcm_s.pdma_chunkcount = 1; maxchunksize = (((PCM86_FIFOSIZE - pcm_s.intr_size * 2) / (pcm_s.bytes * 2)) * pcm_s.speed / pcm_s.chipspeed) * (pcm_s.bytes << pcm_s.stereo); if (count > maxchunksize) pcm_s.pdma_chunkcount = 2 * count / maxchunksize; /* * Let chunksize = (float)count / (float)pcm_s.pdma_chunkcount. * Data of size chunksize is sent to the FIFO buffer on the 86-board * on every occuring of interrupt. * By assuming that pcm_s.intr_size < PCM86_FIFOSIZE / 2, we can conclude * that the FIFO buffer never overflows from the following lemma. * * Lemma: * maxchunksize / 2 <= chunksize <= maxchunksize. * (Though pcm_s.pdma_chunkcount is obtained through the flooring * function, this inequality holds.) * Proof) Omitted. */ fifo_output_block(); pcm_s.intr_last = NO; pcm_s.intr_mode = IMODE_OUTPUT; if (!pcm_s.intr_busy) fifo_start(IMODE_OUTPUT); pcm_s.intr_busy = YES; #if 0 RESTORE_INTR(flags); #endif } static void nss_start_input(int dev, unsigned long buf, int count, int intrflag, int dma_restart) { unsigned long flags, cnt; int maxchunksize; #ifdef NSS_DEBUG printf("nss_start_input():"); if (audio_devs[dev]->dmap_in->flags & DMA_BUSY) printf(" DMA_BUSY"); if (audio_devs[dev]->dmap_in->flags & DMA_RESTART) printf(" DMA_RESTART"); if (audio_devs[dev]->dmap_in->flags & DMA_ACTIVE) printf(" DMA_ACTIVE"); if (audio_devs[dev]->dmap_in->flags & DMA_STARTED) printf(" DMA_STARTED"); if (audio_devs[dev]->dmap_in->flags & DMA_ALLOC_DONE) printf(" DMA_ALLOC_DONE"); printf("\n"); #endif #if 0 DISABLE_INTR(flags); #endif pcm_s.intr_size = PCM86_INTRSIZE_IN; #ifdef NSS_DEBUG printf("nss_start_input(): count = %d, intrsize= %d\n", count, pcm_s.intr_size); #endif pcm_s.pdma_buf = (pcm_data *)buf; pcm_s.pdma_count = count; pcm_s.pdma_chunkcount = 1; maxchunksize = ((pcm_s.intr_size / (pcm_s.bytes * 2)) * pcm_s.speed / pcm_s.chipspeed) * (pcm_s.bytes << pcm_s.stereo); if (count > maxchunksize) pcm_s.pdma_chunkcount = 2 * count / maxchunksize; pcm_s.intr_mode = IMODE_INPUT; if (!pcm_s.intr_busy) fifo_start(IMODE_INPUT); pcm_s.intr_busy = YES; #if 0 RESTORE_INTR(flags); #endif } static int nss_ioctl(int dev, u_int cmd, ioctl_arg arg, int local) { switch (cmd) { case SOUND_PCM_WRITE_RATE: if (local) return set_speed((int) arg); return *(int *) arg = set_speed((*(int *) arg)); case SOUND_PCM_READ_RATE: if (local) return pcm_s.speed; return *(int *) arg = pcm_s.speed; case SNDCTL_DSP_STEREO: if (local) return set_stereo((int) arg); return *(int *) arg = set_stereo((*(int *) arg)); case SOUND_PCM_WRITE_CHANNELS: if (local) return set_stereo((int) arg - 1) + 1; return *(int *) arg = set_stereo((*(int *) arg) - 1) + 1; case SOUND_PCM_READ_CHANNELS: if (local) return pcm_s.stereo + 1; return *(int *) arg = pcm_s.stereo + 1; case SNDCTL_DSP_SETFMT: if (local) return set_format((int) arg); return *(int *) arg = set_format((*(int *) arg)); case SOUND_PCM_READ_BITS: if (local) return pcm_s.bytes * 8; return *(int *) arg = pcm_s.bytes * 8; } /* Invalid ioctl request */ return -(EINVAL); } static int nss_prepare_for_input(int dev, int bufsize, int nbufs) { pcm_s.intr_size = PCM86_INTRSIZE_IN; pcm_s.intr_mode = IMODE_NONE; pcm_s.acc = 0; pcm_s.last_l = 0; pcm_s.last_r = 0; DEB(printf("nss_prepare_for_input\n")); return 0; } static int nss_prepare_for_output(int dev, int bufsize, int nbufs) { pcm_s.intr_size = PCM86_INTRSIZE_OUT; pcm_s.intr_mode = IMODE_NONE; pcm_s.acc = 0; pcm_s.last_l = 0; pcm_s.last_r = 0; DEB(printf("nss_prepare_for_output\n")); return 0; } static void nss_reset(int dev) { nss_stop(); } static void nss_halt_xfer(int dev) { nss_stop(); DEB(printf("nss_halt_xfer\n")); } void nssintr(int unit) { unsigned char tmp; if ((inb(pcm_s.iobase + 8) & 0x10) == 0) return; /* not FIFO intr. */ switch(pcm_s.intr_mode) { case IMODE_OUTPUT: if (pcm_s.intr_trailer) { DEB(printf("nssintr(): fifo_reset\n")); fifo_reset(); pcm_s.intr_trailer = NO; pcm_s.intr_busy = NO; } if (pcm_s.pdma_count > 0) fifo_output_block(); else DMAbuf_outputintr(my_dev, 1); /* Reset intr. flag. */ tmp = inb(pcm_s.iobase + 8); outb(pcm_s.iobase + 8, tmp & ~0x10); outb(pcm_s.iobase + 8, tmp | 0x10); break; case IMODE_INPUT: fifo_input_block(); if (pcm_s.pdma_count == 0) DMAbuf_inputintr(my_dev); /* Reset intr. flag. */ tmp = inb(pcm_s.iobase + 8); outb(pcm_s.iobase + 8, tmp & ~0x10); outb(pcm_s.iobase + 8, tmp | 0x10); break; default: nss_stop(); printf("nss0: unexpected interrupt\n"); } } #endif /* EXCLUDE_NSS, EXCLUDE_AUDIO */ #endif /* CONFIGURE_SOUNDCARD */