/*      $NetBSD: aucc.c,v 1.48 2020/02/29 06:03:55 isaki Exp $ */

/*
* Copyright (c) 1999 Bernardo Innocenti
* All rights reserved.
*
* Copyright (c) 1997 Stephan Thesing
* 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.
* 3. All advertising materials mentioning features or use of this software
*    must display the following acknowledgement:
*      This product includes software developed by Stephan Thesing.
* 4. The name of the author may not be used to endorse or promote products
*    derived from this software without specific prior written permission
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.
*/

/* TODO:
*
* - channel allocation is wrong for 14bit mono
* - perhaps use a calibration table for better 14bit output
* - set 31 kHz AGA video mode to allow 44.1 kHz even if grfcc is missing
*      in the kernel
* - 14bit output requires maximum volume
*/

#include "aucc.h"
#if NAUCC > 0

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: aucc.c,v 1.48 2020/02/29 06:03:55 isaki Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <sys/device.h>
#include <sys/proc.h>
#include <machine/cpu.h>

#include <sys/audioio.h>
#include <dev/audio/audio_if.h>
#include <dev/audio/audiovar.h> /* for AUDIO_MIN_FREQUENCY */

#include <amiga/amiga/cc.h>
#include <amiga/amiga/custom.h>
#include <amiga/amiga/device.h>
#include <amiga/dev/auccvar.h>

#include "opt_lev6_defer.h"


#ifdef LEV6_DEFER
#define AUCC_MAXINT 3
#define AUCC_ALLINTF (INTF_AUD0|INTF_AUD1|INTF_AUD2)
#else
#define AUCC_MAXINT 4
#define AUCC_ALLINTF (INTF_AUD0|INTF_AUD1|INTF_AUD2|INTF_AUD3)
#endif
/* this unconditionally; we may use AUD3 as slave channel with LEV6_DEFER */
#define AUCC_ALLDMAF (DMAF_AUD0|DMAF_AUD1|DMAF_AUD2|DMAF_AUD3)

#ifdef AUDIO_DEBUG
/*extern printf(const char *,...);*/
int     auccdebug = 1;
#define DPRINTF(x)      if (auccdebug) printf x
#else
#define DPRINTF(x)
#endif

/* clock frequency.. */
extern int eclockfreq;


/* hw audio ch */
extern struct audio_channel channel[4];


/*
* Software state.
*/
struct aucc_softc {
       aucc_data_t sc_channel[4];      /* per channel freq, ... */
       u_int   sc_encoding;            /* encoding AUDIO_ENCODING_.*/
       int     sc_channels;            /* # of channels used */
       int     sc_precision;           /* 8 or 16 bits */
       int     sc_14bit;               /* 14bit output enabled */

       int     sc_intrcnt;             /* interrupt count */
       int     sc_channelmask;         /* which channels are used ? */
       void (*sc_decodefunc)(u_char **, u_char *, int);
                               /* pointer to format conversion routine */

       kmutex_t sc_lock;
       kmutex_t sc_intr_lock;
};

/* interrupt interfaces */
void aucc_inthdl(int);

/* forward declarations */
static int init_aucc(struct aucc_softc *);
static u_int freqtoper(u_int);
static u_int pertofreq(u_int);

/* autoconfiguration driver */
void    auccattach(device_t, device_t, void *);
int     auccmatch(device_t, cfdata_t, void *);

CFATTACH_DECL_NEW(aucc, sizeof(struct aucc_softc),
   auccmatch, auccattach, NULL, NULL);

struct audio_device aucc_device = {
       "Amiga-audio",
       "2.0",
       "aucc"
};


struct aucc_softc *aucc = NULL;


/*
* Define our interface to the higher level audio driver.
*/
int     aucc_open(void *, int);
void    aucc_close(void *);
int     aucc_set_out_sr(void *, u_int);
int     aucc_query_format(void *, audio_format_query_t *);
int     aucc_round_blocksize(void *, int, int, const audio_params_t *);
int     aucc_commit_settings(void *);
int     aucc_start_output(void *, void *, int, void (*)(void *), void *);
int     aucc_start_input(void *, void *, int, void (*)(void *), void *);
int     aucc_halt_output(void *);
int     aucc_halt_input(void *);
int     aucc_getdev(void *, struct audio_device *);
int     aucc_set_port(void *, mixer_ctrl_t *);
int     aucc_get_port(void *, mixer_ctrl_t *);
int     aucc_query_devinfo(void *, mixer_devinfo_t *);
void    aucc_encode(int, int, int, int, u_char *, u_short **);
int     aucc_set_format(void *, int,
                       const audio_params_t *, const audio_params_t *,
                       audio_filter_reg_t *, audio_filter_reg_t *);
int     aucc_get_props(void *);
void    aucc_get_locks(void *, kmutex_t **, kmutex_t **);


static void aucc_decode_slinear16_1ch(u_char **, u_char *, int);
static void aucc_decode_slinear16_2ch(u_char **, u_char *, int);
static void aucc_decode_slinear16_3ch(u_char **, u_char *, int);
static void aucc_decode_slinear16_4ch(u_char **, u_char *, int);


const struct audio_hw_if sa_hw_if = {
       .open                   = aucc_open,
       .close                  = aucc_close,
       .query_format           = aucc_query_format,
       .set_format             = aucc_set_format,
       .round_blocksize        = aucc_round_blocksize,
       .commit_settings        = aucc_commit_settings,
       .start_output           = aucc_start_output,
       .start_input            = aucc_start_input,
       .halt_output            = aucc_halt_output,
       .halt_input             = aucc_halt_input,
       .getdev                 = aucc_getdev,
       .set_port               = aucc_set_port,
       .get_port               = aucc_get_port,
       .query_devinfo          = aucc_query_devinfo,
       .get_props              = aucc_get_props,
       .get_locks              = aucc_get_locks,
};

/*
* XXX *1 How lower limit of frequency should be?  same as audio(4)?
* XXX *2 Should avoid a magic number at the upper limit of frequency.
* XXX *3 In fact, there is a number in this range that have minimal errors.
*        It would be better if there is a mechanism which such frequency
*        is prioritized.
* XXX *4 3/4ch modes use 8bits, 1/2ch modes use 14bits,
*        so I imagined that 1/2ch modes are better.
*/
#define AUCC_FORMAT(prio, ch, chmask) \
       { \
               .mode           = AUMODE_PLAY, \
               .priority       = (prio), \
               .encoding       = AUDIO_ENCODING_SLINEAR_BE, \
               .validbits      = 16, \
               .precision      = 16, \
               .channels       = (ch), \
               .channel_mask   = (chmask), \
               .frequency_type = 0, \
               .frequency      = { AUDIO_MIN_FREQUENCY, 28867 }, \
       }
static const struct audio_format aucc_formats[] = {
       AUCC_FORMAT(1, 1, AUFMT_MONAURAL),
       AUCC_FORMAT(1, 2, AUFMT_STEREO),
       AUCC_FORMAT(0, 3, AUFMT_UNKNOWN_POSITION),
       AUCC_FORMAT(0, 4, AUFMT_UNKNOWN_POSITION),
};
#define AUCC_NFORMATS __arraycount(aucc_formats)

/* autoconfig routines */

int
auccmatch(device_t parent, cfdata_t cf, void *aux)
{
       static int aucc_matched = 0;

       if (!matchname((char *)aux, "aucc") ||
#ifdef DRACO
           is_draco() ||
#endif
           aucc_matched)
               return 0;

       aucc_matched = 1;
       return 1;
}

/*
* Audio chip found.
*/
void
auccattach(device_t parent, device_t self, void *args)
{
       struct aucc_softc *sc;
       int i;

       sc = device_private(self);
       printf("\n");

       if ((i=init_aucc(sc))) {
               printf("audio: no chipmem\n");
               return;
       }

       audio_attach_mi(&sa_hw_if, sc, self);
}


static int
init_aucc(struct aucc_softc *sc)
{
       int i, err;

       err = 0;
       /* init values per channel */
       for (i = 0; i < 4; i++) {
               sc->sc_channel[i].nd_freq = 8000;
               sc->sc_channel[i].nd_per = freqtoper(8000);
               sc->sc_channel[i].nd_busy = 0;
               sc->sc_channel[i].nd_dma = alloc_chipmem(AUDIO_BUF_SIZE*2);
               if (sc->sc_channel[i].nd_dma == NULL)
                       err = 1;
               sc->sc_channel[i].nd_dmalength = 0;
               sc->sc_channel[i].nd_volume = 64;
               sc->sc_channel[i].nd_intr = NULL;
               sc->sc_channel[i].nd_intrdata = NULL;
               sc->sc_channel[i].nd_doublebuf = 0;
               DPRINTF(("DMA buffer for channel %d is %p\n", i,
                   sc->sc_channel[i].nd_dma));
       }

       if (err) {
               for (i = 0; i < 4; i++)
                       if (sc->sc_channel[i].nd_dma)
                               free_chipmem(sc->sc_channel[i].nd_dma);
       }

       sc->sc_channels = 1;
       sc->sc_channelmask = 0xf;
       sc->sc_precision = 16;
       sc->sc_14bit = 1;
       sc->sc_encoding = AUDIO_ENCODING_SLINEAR_BE;
       sc->sc_decodefunc = aucc_decode_slinear16_2ch;

       /* clear interrupts and DMA: */
       custom.intena = AUCC_ALLINTF;
       custom.dmacon = AUCC_ALLDMAF;

       mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
       mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_SCHED);

       return err;
}

int
aucc_open(void *addr, int flags)
{
       struct aucc_softc *sc;
       int i;

       sc = addr;
       DPRINTF(("sa_open: unit %p\n",sc));

       for (i = 0; i < AUCC_MAXINT; i++) {
               sc->sc_channel[i].nd_intr = NULL;
               sc->sc_channel[i].nd_intrdata = NULL;
       }
       aucc = sc;
       sc->sc_channelmask = 0xf;

       DPRINTF(("saopen: ok -> sc=%p\n",sc));

       return 0;
}

void
aucc_close(void *addr)
{

       DPRINTF(("sa_close: closed.\n"));
}

int
aucc_set_out_sr(void *addr, u_int sr)
{
       struct aucc_softc *sc;
       u_long per;
       int i;

       sc = addr;
       per = freqtoper(sr);
       if (per > 0xffff)
               return EINVAL;
       sr = pertofreq(per);

       for (i = 0; i < 4; i++) {
               sc->sc_channel[i].nd_freq = sr;
               sc->sc_channel[i].nd_per = per;
       }

       return 0;
}

int
aucc_query_format(void *addr, audio_format_query_t *afp)
{

       return audio_query_format(aucc_formats, AUCC_NFORMATS, afp);
}

int
aucc_set_format(void *addr, int setmode,
       const audio_params_t *p, const audio_params_t *r,
       audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
{
       struct aucc_softc *sc;

       sc = addr;
       KASSERT((setmode & AUMODE_RECORD) == 0);

#ifdef AUCCDEBUG
       printf("%s(setmode 0x%x,"
           "enc %u bits %u, chn %u, sr %u)\n", setmode,
           p->encoding, p->precision, p->channels, p->sample_rate);
#endif

       switch (p->channels) {
       case 1:
               sc->sc_decodefunc = aucc_decode_slinear16_1ch;
               break;
       case 2:
               sc->sc_decodefunc = aucc_decode_slinear16_2ch;
               break;
       case 3:
               sc->sc_decodefunc = aucc_decode_slinear16_3ch;
               break;
       case 4:
               sc->sc_decodefunc = aucc_decode_slinear16_4ch;
               break;
       default:
               return EINVAL;
       }

       sc->sc_encoding = p->encoding;
       sc->sc_precision = p->precision;
       sc->sc_14bit = ((p->precision == 16) && (p->channels <= 2));
       sc->sc_channels = sc->sc_14bit ? (p->channels * 2) : p->channels;

       return aucc_set_out_sr(addr, p->sample_rate);
}

int
aucc_round_blocksize(void *addr, int blk,
                    int mode, const audio_params_t *param)
{

       if (blk > AUDIO_BUF_SIZE)
               blk = AUDIO_BUF_SIZE;

       blk = rounddown(blk, param->channels * param->precision / NBBY);
       return blk;
}

int
aucc_commit_settings(void *addr)
{
       struct aucc_softc *sc;
       int i;

       DPRINTF(("sa_commit.\n"));

       sc = addr;
       for (i = 0; i < 4; i++) {
               custom.aud[i].vol = sc->sc_channel[i].nd_volume;
               custom.aud[i].per = sc->sc_channel[i].nd_per;
       }

       DPRINTF(("commit done\n"));

       return 0;
}

static int masks[4] = {1,3,7,15}; /* masks for n first channels */
static int masks2[4] = {1,2,4,8};

int
aucc_start_output(void *addr, void *p, int cc, void (*intr)(void *), void *arg)
{
       struct aucc_softc *sc;
       int mask;
       int i, j, k, len;
       u_char *dmap[4];


       sc = addr;
       mask = sc->sc_channelmask;

       dmap[0] = dmap[1] = dmap[2] = dmap[3] = NULL;

       DPRINTF(("sa_start_output: cc=%d %p (%p)\n", cc, intr, arg));

       if (sc->sc_channels > 1)
               mask &= masks[sc->sc_channels - 1];
               /* we use first sc_channels channels */
       if (mask == 0) /* active and used channels are disjoint */
               return EINVAL;

       for (i = 0; i < 4; i++) {
               /* channels available ? */
               if ((masks2[i] & mask) && (sc->sc_channel[i].nd_busy))
                       return EBUSY; /* channel is busy */
               if (channel[i].isaudio == -1)
                       return EBUSY; /* system uses them */
       }

       /* enable interrupt on 1st channel */
       for (i = j = 0; i < AUCC_MAXINT; i++) {
               if (masks2[i] & mask) {
                       DPRINTF(("first channel is %d\n",i));
                       j = i;
                       sc->sc_channel[i].nd_intr = intr;
                       sc->sc_channel[i].nd_intrdata = arg;
                       break;
               }
       }

       DPRINTF(("dmap is %p %p %p %p, mask=0x%x\n", dmap[0], dmap[1],
                dmap[2], dmap[3], mask));

       /* disable ints, DMA for channels, until all parameters set */
       /* XXX dont disable DMA! custom.dmacon=mask;*/
       custom.intreq = mask << INTB_AUD0;
       custom.intena = mask << INTB_AUD0;

       /* copy data to DMA buffer */

       if (sc->sc_channels == 1) {
               dmap[0] =
               dmap[1] =
               dmap[2] =
               dmap[3] = (u_char *)sc->sc_channel[j].nd_dma;
       } else {
               for (k = 0; k < 4; k++) {
                       if (masks2[k+j] & mask)
                               dmap[k] = (u_char *)sc->sc_channel[k+j].nd_dma;
               }
       }

       sc->sc_channel[j].nd_doublebuf ^= 1;
       if (sc->sc_channel[j].nd_doublebuf) {
               dmap[0] += AUDIO_BUF_SIZE;
               dmap[1] += AUDIO_BUF_SIZE;
               dmap[2] += AUDIO_BUF_SIZE;
               dmap[3] += AUDIO_BUF_SIZE;
       }

       /*
        * compute output length in bytes per channel.
        * divide by two only for 16bit->8bit conversion.
        */
       len = cc / sc->sc_channels;
       if (!sc->sc_14bit && (sc->sc_precision == 16))
               len /= 2;

       /* call audio decoding routine */
       sc->sc_decodefunc (dmap, (u_char *)p, len);

       /* DMA buffers: we use same buffer 4 all channels
        * write DMA location and length
        */
       for (i = k = 0; i < 4; i++) {
               if (masks2[i] & mask) {
                       DPRINTF(("turning channel %d on\n",i));
                       /* sc->sc_channel[i].nd_busy=1; */
                       channel[i].isaudio = 1;
                       channel[i].play_count = 1;
                       channel[i].handler = NULL;
                       custom.aud[i].per = sc->sc_channel[i].nd_per;
                       if (sc->sc_14bit && (i > 1))
                               custom.aud[i].vol = 1;
                       else
                               custom.aud[i].vol = sc->sc_channel[i].nd_volume;
                       custom.aud[i].lc = PREP_DMA_MEM(dmap[k++]);
                       custom.aud[i].len = len / 2;
                       sc->sc_channel[i].nd_mask = mask;
                       DPRINTF(("per is %d, vol is %d, len is %d\n",\
                           sc->sc_channel[i].nd_per,
                           sc->sc_channel[i].nd_volume, len));
               }
       }

       channel[j].handler = aucc_inthdl;

       /* enable ints */
       custom.intena = INTF_SETCLR | INTF_INTEN | (masks2[j] << INTB_AUD0);

       DPRINTF(("enabled ints: 0x%x\n", (masks2[j] << INTB_AUD0)));

       /* enable DMA */
       custom.dmacon = DMAF_SETCLR | DMAF_MASTER | mask;

       DPRINTF(("enabled DMA, mask=0x%x\n",mask));

       return 0;
}

/* ARGSUSED */
int
aucc_start_input(void *addr, void *p, int cc, void (*intr)(void *), void *arg)
{

       return ENXIO; /* no input */
}

int
aucc_halt_output(void *addr)
{
       struct aucc_softc *sc;
       int i;

       /* XXX only halt, if input is also halted ?? */
       sc = addr;
       /* stop DMA, etc */
       custom.intena = AUCC_ALLINTF;
       custom.dmacon = AUCC_ALLDMAF;
       /* mark every busy unit idle */
       for (i = 0; i < 4; i++) {
               sc->sc_channel[i].nd_busy = sc->sc_channel[i].nd_mask = 0;
               channel[i].isaudio = 0;
               channel[i].play_count = 0;
       }

       return 0;
}

int
aucc_halt_input(void *addr)
{

       /* no input */
       return ENXIO;
}

int
aucc_getdev(void *addr, struct audio_device *retp)
{

       *retp = aucc_device;
       return 0;
}

int
aucc_set_port(void *addr, mixer_ctrl_t *cp)
{
       struct aucc_softc *sc;
       int i,j;

       DPRINTF(("aucc_set_port: port=%d", cp->dev));
       sc = addr;
       switch (cp->type) {
       case AUDIO_MIXER_SET:
               if (cp->dev != AUCC_CHANNELS)
                       return EINVAL;
               i = cp->un.mask;
               if ((i < 1) || (i > 15))
                       return EINVAL;

               sc->sc_channelmask = i;
               break;

       case AUDIO_MIXER_VALUE:
               i = cp->un.value.num_channels;
               if ((i < 1) || (i > 4))
                       return EINVAL;

#ifdef __XXXwhatsthat
               if (cp->dev != AUCC_VOLUME)
                       return EINVAL;
#endif

               /* set volume for channel 0..i-1 */

               /* evil workaround for xanim bug, IMO */
               if ((sc->sc_channels == 1) && (i == 2)) {
                       sc->sc_channel[0].nd_volume =
                           sc->sc_channel[3].nd_volume =
                           cp->un.value.level[0] >> 2;
                       sc->sc_channel[1].nd_volume =
                           sc->sc_channel[2].nd_volume =
                           cp->un.value.level[1] >> 2;
               } else if (i > 1) {
                       for (j = 0; j < i; j++)
                               sc->sc_channel[j].nd_volume =
                                   cp->un.value.level[j] >> 2;
               } else if (sc->sc_channels > 1)
                       for (j = 0; j < sc->sc_channels; j++)
                               sc->sc_channel[j].nd_volume =
                                   cp->un.value.level[0] >> 2;
               else
                       for (j = 0; j < 4; j++)
                               sc->sc_channel[j].nd_volume =
                                   cp->un.value.level[0] >> 2;
               break;

       default:
               return EINVAL;
               break;
       }
       return 0;
}


int
aucc_get_port(void *addr, mixer_ctrl_t *cp)
{
       struct aucc_softc *sc;
       int i,j;

       DPRINTF(("aucc_get_port: port=%d", cp->dev));
       sc = addr;
       switch (cp->type) {
       case AUDIO_MIXER_SET:
               if (cp->dev != AUCC_CHANNELS)
                       return EINVAL;
               cp->un.mask = sc->sc_channelmask;
               break;

       case AUDIO_MIXER_VALUE:
               i = cp->un.value.num_channels;
               if ((i < 1) || (i > 4))
                       return EINVAL;

               for (j = 0; j < i; j++)
                       cp->un.value.level[j] =
                           (sc->sc_channel[j].nd_volume << 2) +
                           (sc->sc_channel[j].nd_volume >> 4);
               break;

       default:
               return EINVAL;
       }
       return 0;
}


int
aucc_get_props(void *addr)
{

       return AUDIO_PROP_PLAYBACK;
}


void
aucc_get_locks(void *opaque, kmutex_t **intr, kmutex_t **thread)
{
       struct aucc_softc *sc = opaque;

       *intr = &sc->sc_intr_lock;
       *thread = &sc->sc_lock;
}

int
aucc_query_devinfo(void *addr, register mixer_devinfo_t *dip)
{
       int i;

       switch(dip->index) {
       case AUCC_CHANNELS:
               dip->type = AUDIO_MIXER_SET;
               dip->mixer_class = AUCC_OUTPUT_CLASS;
               dip->prev = dip->next = AUDIO_MIXER_LAST;
#define setname(a) strlcpy(dip->label.name, (a), sizeof(dip->label.name))
               setname(AudioNspeaker);
               for (i = 0; i < 16; i++) {
                       snprintf(dip->un.s.member[i].label.name,
                           sizeof(dip->un.s.member[i].label.name),
                           "channelmask%d", i);
                       dip->un.s.member[i].mask = i;
               }
               dip->un.s.num_mem = 16;
               break;

       case AUCC_VOLUME:
               dip->type = AUDIO_MIXER_VALUE;
               dip->mixer_class = AUCC_OUTPUT_CLASS;
               dip->prev = dip->next = AUDIO_MIXER_LAST;
               setname(AudioNmaster);
               dip->un.v.num_channels = 4;
               strcpy(dip->un.v.units.name, AudioNvolume);
               break;

       case AUCC_OUTPUT_CLASS:
               dip->type = AUDIO_MIXER_CLASS;
               dip->mixer_class = AUCC_OUTPUT_CLASS;
               dip->next = dip->prev = AUDIO_MIXER_LAST;
               setname(AudioCoutputs);
               break;

       default:
               return ENXIO;
       }

       DPRINTF(("AUDIO_MIXER_DEVINFO: name=%s\n", dip->label.name));

       return 0;
}

/* audio int handler */
void
aucc_inthdl(int ch)
{
       int i;
       int mask;

       mutex_spin_enter(&aucc->sc_intr_lock);
       mask = aucc->sc_channel[ch].nd_mask;
       /*
        * for all channels in this maskgroup:
        * disable DMA, int
        * mark idle
        */
       DPRINTF(("inthandler called, channel %d, mask 0x%x\n", ch, mask));

       custom.intreq = mask << INTB_AUD0; /* clear request */
       /*
        * XXX: maybe we can leave ints and/or DMA on,
        * if another sample has to be played?
        */
       custom.intena = mask << INTB_AUD0;
       /*
        * XXX custom.dmacon=mask; NO!!!
        */
       for (i = 0; i < 4; i++) {
               if (masks2[i] && mask) {
                       DPRINTF(("marking channel %d idle\n",i));
                       aucc->sc_channel[i].nd_busy = 0;
                       aucc->sc_channel[i].nd_mask = 0;
                       channel[i].isaudio = channel[i].play_count = 0;
               }
       }

       /* call handler */
       if (aucc->sc_channel[ch].nd_intr) {
               DPRINTF(("calling %p\n",aucc->sc_channel[ch].nd_intr));
               (*(aucc->sc_channel[ch].nd_intr))
                   (aucc->sc_channel[ch].nd_intrdata);
       } else
               DPRINTF(("zero int handler\n"));
       mutex_spin_exit(&aucc->sc_intr_lock);
       DPRINTF(("ints done\n"));
}

/* transform frequency to period, adjust bounds */
static u_int
freqtoper(u_int freq)
{
       u_int per;

       per = eclockfreq * 5 / freq;
       if (per < 124)
               per = 124;   /* must have at least 124 ticks between samples */

       return per;
}

/* transform period to frequency */
static u_int
pertofreq(u_int per)
{

       return eclockfreq * 5 / per;
}


/* 14bit output */
static void
aucc_decode_slinear16_1ch(u_char **dmap, u_char *p, int i)
{
       u_char *ch0;
       u_char *ch3;

       ch0 = dmap[0];
       ch3 = dmap[1];          /* XXX should be 3 */
       while (i--) {
               *ch0++ = *p++;
               *ch3++ = *p++ >> 2;
       }
}

/* 14bit stereo output */
static void
aucc_decode_slinear16_2ch(u_char **dmap, u_char *p, int i)
{
       u_char *ch0;
       u_char *ch1;
       u_char *ch2;
       u_char *ch3;

       ch0 = dmap[0];
       ch1 = dmap[1];
       ch2 = dmap[2];
       ch3 = dmap[3];
       while (i--) {
               *ch0++ = *p++;
               *ch3++ = *p++ >> 2;
               *ch1++ = *p++;
               *ch2++ = *p++ >> 2;
       }
}

static void
aucc_decode_slinear16_3ch(u_char **dmap, u_char *p, int i)
{
       u_char *ch0;
       u_char *ch1;
       u_char *ch2;

       ch0 = dmap[0];
       ch1 = dmap[1];
       ch2 = dmap[2];
       while (i--) {
               *ch0++ = *p++; p++;
               *ch1++ = *p++; p++;
               *ch2++ = *p++; p++;
       }
}

static void
aucc_decode_slinear16_4ch(u_char **dmap, u_char *p, int i)
{
       u_char *ch0;
       u_char *ch1;
       u_char *ch2;
       u_char *ch3;

       ch0 = dmap[0];
       ch1 = dmap[1];
       ch2 = dmap[2];
       ch3 = dmap[3];
       while (i--) {
               *ch0++ = *p++; p++;
               *ch1++ = *p++; p++;
               *ch2++ = *p++; p++;
               *ch3++ = *p++; p++;
       }
}

#endif /* NAUCC > 0 */