/*
 * 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.
 *
 * $Id: pcm86.c,v 1.4 1997/06/03 10:25:41 kato Exp $
 */

/*
 * !! 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 <i386/isa/sound/sound_config.h>

#ifdef CONFIGURE_SOUNDCARD

#if !defined(EXCLUDE_PCM86) && !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 PCM86_DEBUG */

#ifdef PCM86_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;
} pcm_s;

static struct {
    pcm_data		buff[4];
    int			size;
} tmpbuf;

static int my_dev = 0;
static char pcm_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 pcm86_detect(struct address_info *);

static int pcm86_open(int, int);
static void pcm86_close(int);
static void pcm86_output_block(int, unsigned long, int, int, int);
static void pcm86_start_input(int, unsigned long, int, int, int);
static int pcm86_ioctl(int, unsigned int, unsigned int, int);
static int pcm86_prepare_for_input(int, int, int);
static int pcm86_prepare_for_output(int, int, int);
static void pcm86_reset(int);
static void pcm86_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 pcm_stop(void);
static void pcm_init(void);


/*
 * Identity
 */

static struct audio_operations pcm86_operations =
{
    "PC-9801-86 SoundBoard", /* filled in properly by auto configuration */
    NOTHING_SPECIAL,
    ( AFMT_MU_LAW |
      AFMT_U8 | AFMT_S16_LE | AFMT_S16_BE |
      AFMT_S8 | AFMT_U16_LE | AFMT_U16_BE ),
    NULL,
    pcm86_open,
    pcm86_close,
    pcm86_output_block,
    pcm86_start_input,
    pcm86_ioctl,
    pcm86_prepare_for_input,
    pcm86_prepare_for_output,
    pcm86_reset,
    pcm86_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... */
	printk("pcm0: chunkcount overrun\n");
	chunksize = count = 0;
    }

    if (((audio_devs[my_dev]->dmap->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 PCM86_DEBUG
    printk("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 PCM86_DEBUG
    printk("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... */
	printk("pcm0: 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 PCM86_DEBUG
    printk("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
pcm_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(printk("pcm_stop\n"));
}


static void
pcm_init(void)
{
    /* Initialize registers on the board. */
    pcm_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;
    pcm_initialized = YES;
}


/*
 * Codes for global use
 */

int
probe_pcm86(struct address_info *hw_config)
{
    return pcm86_detect(hw_config);
}


long
attach_pcm86(long mem_start, struct address_info *hw_config)
{
    if (pcm_s.board_type == NO_SUPPORTED_BOARD)
	return mem_start;

    /* Initialize the board. */
    pcm_init();

    printk("pcm0: <%s>", pcm86_operations.name);

    if (num_audiodevs < MAX_AUDIO_DEV) {
	my_dev = num_audiodevs++;
	audio_devs[my_dev] = &pcm86_operations;
	audio_devs[my_dev]->buffcount = DSP_BUFFCOUNT;
	audio_devs[my_dev]->buffsize = DSP_BUFFSIZE;
#ifdef PCM86_DEBUG
	printk("\nbuffsize = %d", DSP_BUFFSIZE);
#endif
    } else
	printk("pcm0: Too many PCM devices available");

    return mem_start;
}


static int
pcm86_detect(struct address_info *hw_config)
{
    int opna_iobase = 0x188, irq = 12, i;
    unsigned char tmp;

    if (hw_config->io_base == -1) {
	printf("pcm0: 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) & 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. */
    sprintf(pcm86_operations.name, board_name[pcm_s.board_type]);
    pcm_initialized = NO;
    pcm_s.irq = irq;

    if ((hw_config->irq > 0) && (hw_config->irq != irq))
	printf("pcm0: change irq %d -> %d\n", hw_config->irq, irq);
    hw_config->irq = irq;

    return YES;
}


static int
pcm86_open(int dev, int mode)
{
    int err;

    if (!pcm_initialized)
	return RET_ERROR(ENXIO);

    if (pcm_s.intr_busy || pcm_s.opened)
	return RET_ERROR(EBUSY);

    if ((err = snd_set_irq_handler(pcm_s.irq, pcmintr, "PC-9801-73/86")) < 0)
	return err;

    pcm_stop();

    tmpbuf.size = 0;
    pcm_s.intr_mode = IMODE_NONE;
    pcm_s.opened = YES;

    return 0;
}


static void
pcm86_close(int dev)
{
    snd_release_irq(pcm_s.irq);

    pcm_s.opened = NO;
}


static void
pcm86_output_block(int dev, unsigned long buf, int count, int intrflag,
		   int dma_restart)
{
    unsigned long flags, cnt;
    int maxchunksize;

#ifdef PCM86_DEBUG
    printk("pcm86_output_block():");
    if (audio_devs[dev]->dmap->flags & DMA_BUSY)
	printk(" DMA_BUSY");
    if (audio_devs[dev]->dmap->flags & DMA_RESTART)
	printk(" DMA_RESTART");
    if (audio_devs[dev]->dmap->flags & DMA_ACTIVE)
	printk(" DMA_ACTIVE");
    if (audio_devs[dev]->dmap->flags & DMA_STARTED)
	printk(" DMA_STARTED");
    if (audio_devs[dev]->dmap->flags & DMA_ALLOC_DONE)
	printk(" DMA_ALLOC_DONE");
    printk("\n");
#endif

#if 0
    DISABLE_INTR(flags);
#endif

#ifdef PCM86_DEBUG
    printk("pcm86_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
pcm86_start_input(int dev, unsigned long buf, int count, int intrflag,
		  int dma_restart)
{
    unsigned long flags, cnt;
    int maxchunksize;

#ifdef PCM86_DEBUG
    printk("pcm86_start_input():");
    if (audio_devs[dev]->dmap->flags & DMA_BUSY)
	printk(" DMA_BUSY");
    if (audio_devs[dev]->dmap->flags & DMA_RESTART)
	printk(" DMA_RESTART");
    if (audio_devs[dev]->dmap->flags & DMA_ACTIVE)
	printk(" DMA_ACTIVE");
    if (audio_devs[dev]->dmap->flags & DMA_STARTED)
	printk(" DMA_STARTED");
    if (audio_devs[dev]->dmap->flags & DMA_ALLOC_DONE)
	printk(" DMA_ALLOC_DONE");
    printk("\n");
#endif

#if 0
    DISABLE_INTR(flags);
#endif

    pcm_s.intr_size = PCM86_INTRSIZE_IN;

#ifdef PCM86_DEBUG
    printk("pcm86_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
pcm86_ioctl(int dev, unsigned int cmd, unsigned int arg, int local)
{
    switch (cmd) {
    case SOUND_PCM_WRITE_RATE:
	if (local)
	    return set_speed(arg);
	return IOCTL_OUT(arg, set_speed(IOCTL_IN(arg)));

    case SOUND_PCM_READ_RATE:
	if (local)
	    return pcm_s.speed;
	return IOCTL_OUT(arg, pcm_s.speed);

    case SNDCTL_DSP_STEREO:
	if (local)
	    return set_stereo(arg);
	return IOCTL_OUT(arg, set_stereo(IOCTL_IN(arg)));

    case SOUND_PCM_WRITE_CHANNELS:
	if (local)
	    return set_stereo(arg - 1) + 1;
	return IOCTL_OUT(arg, set_stereo(IOCTL_IN(arg) - 1) + 1);

    case SOUND_PCM_READ_CHANNELS:
	if (local)
	    return pcm_s.stereo + 1;
	return IOCTL_OUT(arg, pcm_s.stereo + 1);

    case SNDCTL_DSP_SETFMT:
	if (local)
	    return set_format(arg);
	return IOCTL_OUT(arg, set_format(IOCTL_IN(arg)));

    case SOUND_PCM_READ_BITS:
	if (local)
	    return pcm_s.bytes * 8;
	return IOCTL_OUT(arg, pcm_s.bytes * 8);
    }

    /* Invalid ioctl request */
    return RET_ERROR(EINVAL);
}


static int
pcm86_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(printk("pcm86_prepare_for_input\n"));

    return 0;
}


static int
pcm86_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(printk("pcm86_prepare_for_output\n"));

    return 0;
}


static void
pcm86_reset(int dev)
{
    pcm_stop();
}


static void
pcm86_halt_xfer(int dev)
{
    pcm_stop();

    DEB(printk("pcm86_halt_xfer\n"));
}


void
pcmintr(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(printk("pcmintr(): 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:
	pcm_stop();
	printk("pcm0: unexpected interrupt\n");
    }
}


#endif	/* EXCLUDE_PCM86, EXCLUDE_AUDIO */

#endif	/* CONFIGURE_SOUNDCARD */
