root/sys/arch/mac68k/obio/ascaudio.c
/* $NetBSD: ascaudio.c,v 1.18 2025/06/05 02:44:56 nat Exp $ */

/*-
 * Copyright (c) 2017, 2023, 2025 Nathanial Sloss <nathanialsloss@yahoo.com.au>
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/* Based on pad(4) and asc(4) */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ascaudio.c,v 1.18 2025/06/05 02:44:56 nat Exp $");

#include <sys/types.h>
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/buf.h>
#include <sys/kauth.h>
#include <sys/kmem.h>
#include <sys/kernel.h>
#include <sys/device.h>
#include <sys/proc.h>
#include <sys/audioio.h>
#include <sys/module.h>
#include <sys/atomic.h>

#include <uvm/uvm_extern.h>

#include <dev/audio/audio_if.h>
#include <dev/audio/audiovar.h>

#include <machine/autoconf.h>
#include <machine/cpu.h>
#include <machine/bus.h>
#include <machine/viareg.h>

#include <mac68k/dev/pm_direct.h>
#include <mac68k/obio/ascaudiovar.h>
#include <mac68k/obio/ascreg.h>
#include <mac68k/obio/obiovar.h>

#define MAC68K_ASCAUDIO_BASE            0x50f14000
#define MAC68K_IIFX_ASCAUDIO_BASE       0x50f10000
#define MAC68K_ASCAUDIO_LEN             0x2000

#define BUFSIZE                         32768
#define PLAYBLKSIZE                     8192
#define RECBLKSIZE                      1024

#define ASC_VIA_CLR_INTR()     via_reg(VIA2, vIFR) = V2IF_ASC

static int      ascaudiomatch(device_t, cfdata_t, void *);
static void     ascaudioattach(device_t, device_t, void *);

CFATTACH_DECL_NEW(ascaudio, sizeof(struct ascaudio_softc),
    ascaudiomatch, ascaudioattach, NULL, NULL);

extern struct cfdriver ascaudio_cd;

dev_type_open(ascaudioopen);
dev_type_close(ascaudioclose);
dev_type_read(ascaudioread);
dev_type_write(ascaudiowrite);
dev_type_ioctl(ascaudioioctl);

const struct cdevsw ascaudio_cdevsw = {
        .d_open = ascaudioopen,
        .d_close = ascaudioclose,
        .d_read = ascaudioread,
        .d_write = ascaudiowrite,
        .d_ioctl = ascaudioioctl,
        .d_stop = nostop,
        .d_tty = notty,
        .d_poll = nopoll,
        .d_mmap = nommap,
        .d_kqfilter = nokqfilter,
        .d_discard = nodiscard,
        .d_flag = 0
};

static int      ascaudio_query_format(void *, struct audio_format_query *);
static int      ascaudio_set_format(void *, int,
                    const audio_params_t *, const audio_params_t *,
                    audio_filter_reg_t *, audio_filter_reg_t *);
static int      ascaudio_start_output(void *, void *, int,
                                    void (*)(void *), void *);
static int      ascaudio_start_input(void *, void *, int,
                                   void (*)(void *), void *);
static int      ascaudio_halt_input(void *);
static int      ascaudio_halt_output(void *);
static int      ascaudio_set_port(void *, mixer_ctrl_t *);
static int      ascaudio_get_port(void *, mixer_ctrl_t *);
static int      ascaudio_getdev(void *, struct audio_device *);
static int      ascaudio_query_devinfo(void *, mixer_devinfo_t *);
static int      ascaudio_get_props(void *);
static int
            ascaudio_round_blocksize(void *, int, int, const audio_params_t *);
static void     ascaudio_get_locks(void *, kmutex_t **, kmutex_t **);
static void     ascaudio_intr(void *);
static int      ascaudio_intr_est(void *);
static void     ascaudio_intr_enable(void);
static void     ascaudio_done_output(void *);
static void     ascaudio_done_input(void *);
static void     configure_dfac(uint8_t);

static const struct audio_hw_if ascaudio_hw_if = {
        .query_format    = ascaudio_query_format,
        .set_format      = ascaudio_set_format,
        .start_output    = ascaudio_start_output,
        .start_input     = ascaudio_start_input,
        .halt_output     = ascaudio_halt_output,
        .halt_input      = ascaudio_halt_input,
        .set_port        = ascaudio_set_port,
        .get_port        = ascaudio_get_port,
        .getdev          = ascaudio_getdev,
        .query_devinfo   = ascaudio_query_devinfo,
        .get_props       = ascaudio_get_props,
        .round_blocksize = ascaudio_round_blocksize,
        .get_locks       = ascaudio_get_locks,
};

enum {
        ASC_OUTPUT_CLASS,
        ASC_INPUT_CLASS,
        ASC_OUTPUT_MASTER_VOLUME,
        ASC_INPUT_DAC_VOLUME,
        ASC_ENUM_LAST,
};

static int
ascaudiomatch(device_t parent, cfdata_t cf, void *aux)
{
        struct obio_attach_args *oa = (struct obio_attach_args *)aux;
        bus_addr_t addr;
        bus_space_handle_t bsh;
        int rval = 0;

        if (oa->oa_addr != (-1))
                addr = (bus_addr_t)oa->oa_addr;
        else if (current_mac_model->machineid == MACH_MACTV)
                return 0;
        else if (current_mac_model->machineid == MACH_MACIIFX)
                addr = (bus_addr_t)MAC68K_IIFX_ASCAUDIO_BASE;
        else
                addr = (bus_addr_t)MAC68K_ASCAUDIO_BASE;

        if (bus_space_map(oa->oa_tag, addr, MAC68K_ASCAUDIO_LEN, 0, &bsh))
                return (0);

        if (mac68k_bus_space_probe(oa->oa_tag, bsh, 0, 1)) {
                rval = 1;
        } else
                rval = 0;

        bus_space_unmap(oa->oa_tag, bsh, MAC68K_ASCAUDIO_LEN);

        return rval;
}

static void
ascaudioattach(device_t parent, device_t self, void *aux)
{
        struct ascaudio_softc *sc = device_private(self);
        struct obio_attach_args *oa = (struct obio_attach_args *)aux;
        bus_addr_t addr;
        uint8_t tmp;

        sc->sc_dev = self;
        sc->sc_tag = oa->oa_tag;

        if (oa->oa_addr != (-1))
                addr = (bus_addr_t)oa->oa_addr;
        else if (current_mac_model->machineid == MACH_MACIIFX)
                addr = (bus_addr_t)MAC68K_IIFX_ASCAUDIO_BASE;
        else
                addr = (bus_addr_t)MAC68K_ASCAUDIO_BASE;
        if (bus_space_map(sc->sc_tag, addr, MAC68K_ASCAUDIO_LEN, 0,
            &sc->sc_handle)) {
                printf(": can't map memory space\n");
                return;
        }

        /* Pull in the options flags. */
        sc->sc_options = ((device_cfdata(self)->cf_flags) &
                            ASCAUDIO_OPTIONS_MASK);

        sc->sc_playbuf = kmem_alloc(BUFSIZE, KM_SLEEP);
        sc->sc_recbuf = kmem_alloc(BUFSIZE, KM_SLEEP);
        sc->sc_rptr = sc->sc_recbuf;
        sc->sc_getptr = sc->sc_recbuf;
        sc->sc_wptr = sc->sc_playbuf;
        sc->sc_putptr = sc->sc_playbuf;

        bus_space_write_1(sc->sc_tag, sc->sc_handle, ASCMODE, MODESTOP);

        sc->sc_ver = bus_space_read_1(oa->oa_tag, sc->sc_handle, 0x800);

        if (sc->sc_options & HIGHQUALITY) {
                tmp = bus_space_read_1(sc->sc_tag, sc->sc_handle, ASCRATE);
                switch (tmp) {
                        case 2:
                                sc->sc_rate = 22050;
                                break;
                        case 3:
                                sc->sc_rate = 44100;
                                break;
                        default:
                                sc->sc_rate = 22254;
                                break;
                }

                tmp = bus_space_read_1(sc->sc_tag, sc->sc_handle, ASCTRL);
                if (tmp & STEREO)
                        sc->sc_speakers = 2;
                else
                        sc->sc_speakers = 1;

        } else {
                __USE(tmp);
                sc->sc_rate = 22254;
                sc->sc_speakers = 1;
        }

        if (sc->sc_options & LOWQUALITY) {
                sc->sc_slowcpu = true;
                if (sc->sc_slowcpu)
                        sc->sc_rate /= 2;
        }

        if (sc->sc_ver == EASC_VER || sc->sc_ver == EASC_VER2)
                printf(": Enhanced Apple Sound Chip");
        else
                printf(": Apple Sound Chip");

        if (oa->oa_addr != (-1))
                printf(" at %x", oa->oa_addr);
        printf("\n");

        bus_space_write_1(sc->sc_tag, sc->sc_handle, ASCMODE, MODESTOP);

        if (mac68k_machine.aux_interrupts) {
                intr_establish(ascaudio_intr_est, sc, ASCIRQ);
        } else {
                via2_register_irq(VIA2_ASC, ascaudio_intr, sc);
        }
        ascaudio_intr_enable();

        mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
        mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_AUDIO);
        callout_init(&sc->sc_pcallout, CALLOUT_MPSAFE);
        callout_setfunc(&sc->sc_pcallout, ascaudio_done_output, sc);
        callout_init(&sc->sc_rcallout, CALLOUT_MPSAFE);
        callout_setfunc(&sc->sc_rcallout, ascaudio_done_input, sc);

        sc->sc_vol = 180;
        sc->sc_recvol = 255;

        sc->sc_audiodev = audio_attach_mi(&ascaudio_hw_if, sc, sc->sc_dev);

        if (!pmf_device_register(sc->sc_dev, NULL, NULL))
                aprint_error_dev(sc->sc_dev,
                    "couldn't establish power handler\n");

        if (sc->sc_ver != EASC_VER && sc->sc_ver != EASC_VER2)
                return;

        if (sc->sc_options & HIGHQUALITY)
                tmp = CDQUALITY;
        else
                tmp = MACDEFAULTS;

        bus_space_write_1(sc->sc_tag, sc->sc_handle, FIFOCTRLA, tmp);
        bus_space_write_1(sc->sc_tag, sc->sc_handle, FIFOCTRLB, tmp);

}

int
ascaudioopen(dev_t dev, int flag, int mode, struct lwp *l)
{
        struct ascaudio_softc *sc;

        sc = device_lookup_private(&ascaudio_cd, ASCAUDIOUNIT(dev));
        if (sc == NULL)
                return (ENXIO);
        if (sc->sc_open)
                return (EBUSY);
        sc->sc_open = 1;

        return (0);
}

int
ascaudioclose(dev_t dev, int flag, int mode, struct lwp *l)
{
        struct ascaudio_softc *sc;

        sc = device_lookup_private(&ascaudio_cd, ASCAUDIOUNIT(dev));
        sc->sc_open = 0;

        return (0);
}

int
ascaudioread(dev_t dev, struct uio *uio, int ioflag)
{
        return (ENXIO);
}

int
ascaudiowrite(dev_t dev, struct uio *uio, int ioflag)
{
        return (ENXIO);
}

int
ascaudioioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
{
        int error;
#ifdef notyet
        struct ascaudio_softc *sc;
        int unit = ASCAUDIOUNIT(dev);

        sc = device_lookup_private(&ascaudio_cd, unit);
#endif
        error = 0;

        switch (cmd) {
        default:
                error = EINVAL;
                break;
        }
        return (error);
}

#define ASCAUDIO_NFORMATS       3
static int
ascaudio_query_format(void *opaque, struct audio_format_query *ae)
{
        struct ascaudio_softc *sc = opaque;

        const struct audio_format asc_formats[ASCAUDIO_NFORMATS] = {
              { .mode           = AUMODE_PLAY,
                .encoding       = AUDIO_ENCODING_SLINEAR_BE,
                .validbits      = 8,
                .precision      = 8,
                .channels       = sc->sc_speakers,
                .channel_mask   = sc->sc_speakers == 2 ? AUFMT_STEREO :
                    AUFMT_MONAURAL,
                .frequency_type = 1,
                .frequency      = { sc->sc_rate }, },
              { .mode           = AUMODE_RECORD,
                .encoding       = AUDIO_ENCODING_SLINEAR_BE,
                .validbits      = 8,
                .precision      = 8,
                .channels       = 1,
                .channel_mask   = AUFMT_MONAURAL,
                .frequency_type = 1,
                .frequency      = { 11025 }, },
              { .mode           = AUMODE_RECORD,
                .encoding       = AUDIO_ENCODING_SLINEAR_BE,
                .validbits      = 8,
                .precision      = 8,
                .channels       = 1,
                .channel_mask   = AUFMT_MONAURAL,
                .frequency_type = 1,
                .frequency      = { 22050 }, }
        };

        return audio_query_format(asc_formats, ASCAUDIO_NFORMATS, ae);
}

static int
ascaudio_set_format(void *opaque, int setmode,
    const audio_params_t *play, const audio_params_t *rec,
    audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
{
        struct ascaudio_softc *sc = opaque;

        KASSERT(mutex_owned(&sc->sc_lock));

        sc->sc_recfreq = rec->sample_rate;

        return 0;
}

static int
ascaudio_start_output(void *opaque, void *block, int blksize,
    void (*intr)(void *), void *intrarg)
{
        struct ascaudio_softc *sc;
        uint8_t *loc, tmp;
        int total;

        sc = (struct ascaudio_softc *)opaque;
        if (!sc)
                return (ENODEV);

        sc->sc_pintr = intr;
        sc->sc_pintrarg = intrarg;

        loc = block;
        if (bus_space_read_1(sc->sc_tag, sc->sc_handle, ASCMODE) !=
                                                                 MODEFIFO) {
                bus_space_write_1(sc->sc_tag, sc->sc_handle, ASCMODE, MODESTOP);
                bus_space_write_1(sc->sc_tag, sc->sc_handle, ASCTEST, 0);
                bus_space_write_1(sc->sc_tag, sc->sc_handle, FIFOPARAM,
                    NONCOMP);

        }

        /* set the volume. */
        if (sc->sc_ver == EASC_VER || sc->sc_ver == EASC_VER2) {
                /* DO NOT CHANGE THESE VALUES UNLESS TESTED.
                   CAN BE VERY LOUD!!!! */
                tmp = sc->sc_vol >> 5;
                KASSERT(tmp <= MACOS_HIGH_VOL);
                bus_space_write_1(sc->sc_tag, sc->sc_handle, B_LEFT_VOL, tmp);
                bus_space_write_1(sc->sc_tag, sc->sc_handle, B_RIGHT_VOL, tmp);
                if (sc->sc_rintr == NULL) {
                        bus_space_write_1(sc->sc_tag, sc->sc_handle,
                            A_LEFT_VOL, tmp);
                        bus_space_write_1(sc->sc_tag, sc->sc_handle,
                            A_RIGHT_VOL, tmp);
                }
        }
        bus_space_write_1(sc->sc_tag, sc->sc_handle, INTVOL, sc->sc_vol);

        total = blksize;
        if (sc->sc_putptr + blksize >= sc->sc_playbuf + BUFSIZE)
                total = sc->sc_playbuf + BUFSIZE - sc->sc_putptr;

        if (total) {
                memcpy(sc->sc_putptr, loc, total);
                sc->sc_putptr += total;
                loc += total;
        }

        total = blksize - total;
        if (total) {
                sc->sc_putptr = sc->sc_playbuf;
                memcpy(sc->sc_playbuf, loc, total);
                sc->sc_putptr += total;
        }

        sc->sc_avail += blksize;
        if (sc->sc_avail > BUFSIZE)
                sc->sc_avail = BUFSIZE;

        /* start fifo playback */
        if ((sc->sc_rintr == NULL) && bus_space_read_1(sc->sc_tag,
            sc->sc_handle, ASCMODE) != MODEFIFO)
                bus_space_write_1(sc->sc_tag, sc->sc_handle, ASCMODE, MODEFIFO);

        if (sc->sc_ver == EASC_VER || sc->sc_ver == EASC_VER2) {
                /* enable interrupts channel b */
                bus_space_write_1(sc->sc_tag, sc->sc_handle, IRQB, 0);
        }

        return 0;
}

static int
ascaudio_start_input(void *opaque, void *block, int blksize,
    void (*intr)(void *), void *intrarg)
{
        struct ascaudio_softc *sc;
        uint8_t tmp;
        int total;

        sc = (struct ascaudio_softc *)opaque;
        if (!sc)
                return (ENODEV);

        uint8_t *loc;
        loc = block;

        sc->sc_rintr = intr;
        sc->sc_rintrarg = intrarg;

        if (bus_space_read_1(sc->sc_tag, sc->sc_handle, ASCMODE) !=
                                                                 MODEFIFO) {
                bus_space_write_1(sc->sc_tag, sc->sc_handle, ASCMODE, MODESTOP);
                bus_space_write_1(sc->sc_tag, sc->sc_handle, ASCTEST, 0);
                bus_space_write_1(sc->sc_tag, sc->sc_handle, FIFOPARAM,
                    NONCOMP);
                memset(loc, 0x80, blksize);
        }

        if (sc->sc_ver == EASC_VER || sc->sc_ver == EASC_VER2) {
                /*
                 * Set up dfac for microphone.
                 * DO NOT SET BITS 5-7 due to loud feedback squeal.
                 */
                configure_dfac(DFAC_GAIN_HIGH);
        }

        tmp = RECORDA;
        if (sc->sc_recfreq == 22050)
                tmp |= REC22KHZ;
        bus_space_write_1(sc->sc_tag, sc->sc_handle, APLAYREC, tmp);

        if (sc->sc_ver == EASC_VER || sc->sc_ver == EASC_VER2) {
                /* enable interrupts channel a */
                bus_space_write_1(sc->sc_tag, sc->sc_handle, IRQA, 0);
        }

        /* start fifo playback */
        if ((sc->sc_pintr == NULL) && bus_space_read_1(sc->sc_tag,
            sc->sc_handle, ASCMODE) != MODEFIFO) {
                bus_space_write_1(sc->sc_tag, sc->sc_handle, ASCMODE, MODEFIFO);

                return 0;
        }

        /* set the volume. */
        if (sc->sc_ver == EASC_VER || sc->sc_ver == EASC_VER2) {
                /* DO NOT CHANGE THESE VALUES UNLESS TESTED.
                   CAN BE VERY LOUD!!!! */
                tmp = sc->sc_recvol >> 5;
                KASSERT(tmp <= MACOS_HIGH_VOL);
                bus_space_write_1(sc->sc_tag, sc->sc_handle, A_LEFT_VOL, tmp);
                bus_space_write_1(sc->sc_tag, sc->sc_handle, A_RIGHT_VOL, tmp);
                if (sc->sc_pintr == NULL) {
                        bus_space_write_1(sc->sc_tag, sc->sc_handle,
                            B_LEFT_VOL, tmp);
                        bus_space_write_1(sc->sc_tag, sc->sc_handle,
                            B_RIGHT_VOL, tmp);
                }
        }
        if (sc->sc_pintr == NULL) {
                bus_space_write_1(sc->sc_tag, sc->sc_handle, INTVOL,
                    sc->sc_recvol);
        }

        total = blksize;
        if (sc->sc_getptr + blksize >= sc->sc_recbuf + BUFSIZE)
                total = sc->sc_recbuf + BUFSIZE - sc->sc_getptr;

        if (total) {
                memcpy(loc, sc->sc_getptr, total);
                sc->sc_getptr += total;
                loc += total;
        }

        total = blksize - total;
        if (total) {
                sc->sc_getptr = sc->sc_recbuf;
                memcpy(loc, sc->sc_getptr, total);
                sc->sc_getptr += total;
        }

        sc->sc_recavail -= blksize;

        return 0;
}

static int
ascaudio_halt_input(void *opaque)
{
        ascaudio_softc_t *sc;

        sc = (ascaudio_softc_t *)opaque;

        KASSERT(mutex_owned(&sc->sc_lock));

        callout_halt(&sc->sc_rcallout, &sc->sc_intr_lock);

        sc->sc_rintr = NULL;
        sc->sc_rintrarg = NULL;
        sc->sc_recavail = 0;

        bus_space_write_1(sc->sc_tag, sc->sc_handle, APLAYREC, 0);

        if (sc->sc_pintr == NULL) {
                bus_space_write_1(sc->sc_tag, sc->sc_handle, ASCMODE, MODESTOP);
                bus_space_write_1(sc->sc_tag, sc->sc_handle, FIFOPARAM,
                    CLEARFIFO);
        }

        sc->sc_rptr = sc->sc_recbuf;
        sc->sc_getptr = sc->sc_recbuf;

        if (sc->sc_ver != EASC_VER && sc->sc_ver != EASC_VER2)
                return 0;

        configure_dfac(DFAC_DISABLE);

        /* disable half interrupts channel a */
        bus_space_write_1(sc->sc_tag, sc->sc_handle, IRQA, DISABLEHALFIRQ);

        return 0;
}

static int
ascaudio_halt_output(void *opaque)
{
        ascaudio_softc_t *sc;

        sc = (ascaudio_softc_t *)opaque;

        KASSERT(mutex_owned(&sc->sc_lock));

        callout_halt(&sc->sc_pcallout, &sc->sc_intr_lock);

        sc->sc_pintr = NULL;
        sc->sc_pintrarg = NULL;
        sc->sc_avail = 0;

        if (sc->sc_rintr == NULL) {
                bus_space_write_1(sc->sc_tag, sc->sc_handle, ASCMODE, MODESTOP);
                bus_space_write_1(sc->sc_tag, sc->sc_handle, FIFOPARAM,
                    CLEARFIFO);
        }

        sc->sc_wptr = sc->sc_playbuf;
        sc->sc_putptr = sc->sc_playbuf;

        if (sc->sc_ver != EASC_VER && sc->sc_ver != EASC_VER2)
                return 0;

        /* disable half interrupts channel b */
        bus_space_write_1(sc->sc_tag, sc->sc_handle, IRQB, DISABLEHALFIRQ);

        return 0;
}

static int
ascaudio_getdev(void *opaque, struct audio_device *ret)
{
        strlcpy(ret->name, "Apple ASC Audio", sizeof(ret->name));
        strlcpy(ret->version, osrelease, sizeof(ret->version));
        strlcpy(ret->config, "ascaudio", sizeof(ret->config));

        return 0;
}

static int
ascaudio_set_port(void *opaque, mixer_ctrl_t *mc)
{
        struct ascaudio_softc *sc = opaque;

        KASSERT(mutex_owned(&sc->sc_lock));

        switch (mc->dev) {
        case ASC_OUTPUT_MASTER_VOLUME:
                if (mc->un.value.num_channels != 1)
                        return EINVAL;
                sc->sc_vol = mc->un.value.level[AUDIO_MIXER_LEVEL_MONO];
                return 0;
        case ASC_INPUT_DAC_VOLUME:
                if (mc->un.value.num_channels != 1)
                        return EINVAL;
                sc->sc_recvol = mc->un.value.level[AUDIO_MIXER_LEVEL_MONO];
                return 0;
        }

        return ENXIO;
}

static int
ascaudio_get_port(void *opaque, mixer_ctrl_t *mc)
{
        struct ascaudio_softc *sc = opaque;

        KASSERT(mutex_owned(&sc->sc_lock));

        switch (mc->dev) {
        case ASC_OUTPUT_MASTER_VOLUME:
                if (mc->un.value.num_channels != 1)
                        return EINVAL;
                mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = sc->sc_vol;
                return 0;
        case ASC_INPUT_DAC_VOLUME:
                if (mc->un.value.num_channels != 1)
                        return EINVAL;
                mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = sc->sc_recvol;
                return 0;
        }

        return ENXIO;
}

static int
ascaudio_query_devinfo(void *opaque, mixer_devinfo_t *di)
{
        ascaudio_softc_t *sc __diagused;

        sc = (ascaudio_softc_t *)opaque;

        KASSERT(mutex_owned(&sc->sc_lock));

        switch (di->index) {
        case ASC_OUTPUT_CLASS:
                di->mixer_class = ASC_OUTPUT_CLASS;
                strcpy(di->label.name, AudioCoutputs);
                di->type = AUDIO_MIXER_CLASS;
                di->next = di->prev = AUDIO_MIXER_LAST;
                return 0;
        case ASC_INPUT_CLASS:
                di->mixer_class = ASC_INPUT_CLASS;
                strcpy(di->label.name, AudioCinputs);
                di->type = AUDIO_MIXER_CLASS;
                di->next = di->prev = AUDIO_MIXER_LAST;
                return 0;
        case ASC_OUTPUT_MASTER_VOLUME:
                di->mixer_class = ASC_OUTPUT_CLASS;
                strcpy(di->label.name, AudioNmaster);
                di->type = AUDIO_MIXER_VALUE;
                di->next = di->prev = AUDIO_MIXER_LAST;
                di->un.v.num_channels = 1;
                strcpy(di->un.v.units.name, AudioNvolume);
                return 0;
        case ASC_INPUT_DAC_VOLUME:
                di->mixer_class = ASC_INPUT_CLASS;
                strcpy(di->label.name, AudioNdac);
                di->type = AUDIO_MIXER_VALUE;
                di->next = di->prev = AUDIO_MIXER_LAST;
                di->un.v.num_channels = 1;
                strcpy(di->un.v.units.name, AudioNvolume);
                return 0;
        }

        return ENXIO;
}

static int
ascaudio_get_props(void *opaque)
{

        return AUDIO_PROP_PLAYBACK | AUDIO_PROP_CAPTURE |
            AUDIO_PROP_INDEPENDENT | AUDIO_PROP_FULLDUPLEX;
}

static int
ascaudio_round_blocksize(void *opaque, int blksize, int mode,
    const audio_params_t *p)
{
        ascaudio_softc_t *sc __diagused;

        sc = (ascaudio_softc_t *)opaque;
        KASSERT(mutex_owned(&sc->sc_lock));

        if (mode == AUMODE_PLAY)
                return PLAYBLKSIZE;
        else
                return RECBLKSIZE;
}

static void
ascaudio_get_locks(void *opaque, kmutex_t **intr, kmutex_t **thread)
{
        ascaudio_softc_t *sc;

        sc = (ascaudio_softc_t *)opaque;

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

static int
ascaudio_intr_est(void *arg)
{
        ascaudio_intr(arg);

        return 0;
}

static void
ascaudio_intr(void *arg)
{
        struct ascaudio_softc *sc = arg;
        uint8_t status;
        int8_t val;
        int loc_a, loc_b, total, count, i;

        if (!sc)
                return;

        if (!sc->sc_pintr && !sc->sc_rintr)
                return;

        mutex_enter(&sc->sc_intr_lock);

        status = bus_space_read_1(sc->sc_tag, sc->sc_handle, FIFOSTATUS);
        
        if (sc->sc_ver == EASC_VER || sc->sc_ver == EASC_VER2) {
                if (sc->sc_pintr) {
                        bus_space_write_1(sc->sc_tag, sc->sc_handle,
                            IRQB, DISABLEHALFIRQ);
                }
                if (sc->sc_rintr) {
                        bus_space_write_1(sc->sc_tag, sc->sc_handle,
                            IRQA, DISABLEHALFIRQ);
                }
        }

        if (sc->sc_ver == EASC_VER2 && (status & A_HALF))
                count = 0x200;
        else if (sc->sc_ver != EASC_VER2 && !(status & A_HALF))
                count = 0x200;
        else
                count = 0;

        if (count && sc->sc_rintr) {
                total = count;
                if (sc->sc_rptr + count >= sc->sc_recbuf + BUFSIZE)
                        count = sc->sc_recbuf + BUFSIZE - sc->sc_rptr;
                while (total) {
                        if (sc->sc_ver == EASC_VER2) {
                                loc_a = FIFO_A_ALT;
                                loc_b = FIFO_B_ALT;
                        } else {
                                loc_a = FIFO_A;
                                loc_b = 0;
                        }
                        for (i = 0; i < count; i++) {
                                val = bus_space_read_1(sc->sc_tag,
                                    sc->sc_handle, loc_a);
                                val ^= 0x80;
                                val = val * sc->sc_recvol / 64;
                                *sc->sc_rptr++ = val;
                                if (loc_b) {
                                        (void)bus_space_read_1
                                            (sc->sc_tag, sc->sc_handle, loc_b);
                                }
                        }
                        if (sc->sc_rptr >= sc->sc_recbuf + BUFSIZE)
                                sc->sc_rptr = sc->sc_recbuf;
                        total -= count;
                        sc->sc_recavail += count;
                        count = total;
                }

                if (sc->sc_recavail > BUFSIZE)
                        sc->sc_recavail = BUFSIZE;
        }
        
        if (sc->sc_pintr == NULL)
                goto more;

        if (status & B_HALF)
                count = 0x200;
        else
                count = 0;

        if (sc->sc_slowcpu)
                count /= 2;

        if (count && sc->sc_avail < count) {
                if (sc->sc_avail) {
                        count = sc->sc_avail;
                        goto fill_fifo;
                }
                if (sc->sc_pintr) {
                        for (i = 0; i < 0x200; i++) {
                                if (sc->sc_rintr == NULL ||
                                    sc->sc_ver == EASC_VER2) {
                                        bus_space_write_1(sc->sc_tag,
                                            sc->sc_handle, FIFO_A, 0x80);
                                }
                                bus_space_write_1(sc->sc_tag,
                                    sc->sc_handle, FIFO_B, 0x80);
                        }
                } else {
                        if (sc->sc_slowcpu)
                                count *= 2;
                        for (i = 0; i < count; i++) {
                                bus_space_write_1(sc->sc_tag,
                                    sc->sc_handle, FIFO_B, 0x80);
                        }
                }
                goto more;
        }

fill_fifo:
        total = count;
        if (sc->sc_wptr + count >= sc->sc_playbuf + BUFSIZE)
                count = sc->sc_playbuf + BUFSIZE - sc->sc_wptr;

        while (total) {
                if (sc->sc_slowcpu) {
                        for (i = 0; i < count; i++) {
                                val = *sc->sc_wptr++;
                                val ^= 0x80;
                                if (sc->sc_rintr == NULL ||
                                    sc->sc_ver == EASC_VER2) {
                                        bus_space_write_1(sc->sc_tag,
                                            sc->sc_handle, FIFO_A, val);
                                        bus_space_write_1(sc->sc_tag,
                                            sc->sc_handle, FIFO_A, val);
                                }
                                bus_space_write_1(sc->sc_tag,
                                    sc->sc_handle, FIFO_B, val);
                                bus_space_write_1(sc->sc_tag,
                                    sc->sc_handle, FIFO_B, val);
                        }
                } else {
                        for (i = 0; i < count; i++) {
                                val = *sc->sc_wptr++;
                                val ^= 0x80;
                                if (sc->sc_rintr == NULL ||
                                    sc->sc_ver == EASC_VER2) {
                                        bus_space_write_1(sc->sc_tag,
                                            sc->sc_handle, FIFO_A, val);
                                }
                                bus_space_write_1(sc->sc_tag,
                                    sc->sc_handle, FIFO_B, val);
                        }
                }
                if (sc->sc_wptr >= sc->sc_playbuf + BUFSIZE)
                        sc->sc_wptr = sc->sc_playbuf;
                total -= count;
                sc->sc_avail -= count;
                count = total;
        }

more:
        if (sc->sc_ver == EASC_VER || sc->sc_ver == EASC_VER2) {
                if (sc->sc_rintr)
                        bus_space_write_1(sc->sc_tag, sc->sc_handle, IRQA, 0);
                if (sc->sc_pintr)
                        bus_space_write_1(sc->sc_tag, sc->sc_handle, IRQB, 0);
        }

        if (sc->sc_pintr && (sc->sc_avail <= PLAYBLKSIZE))
                callout_schedule(&sc->sc_pcallout, 0);

        if (sc->sc_rintr && (sc->sc_recavail >= RECBLKSIZE))
                callout_schedule(&sc->sc_rcallout, 0);

        mutex_exit(&sc->sc_intr_lock);
}

static void
ascaudio_intr_enable(void)
{
        int s;

        s = splhigh();
        if (VIA2 == VIA2OFF)
                via2_reg(vIER) = 0x80 | V2IF_ASC;
        else
                via2_reg(rIER) = 0x80 | V2IF_ASC;
        splx(s);
}

static void
ascaudio_done_output(void *arg)
{
        struct ascaudio_softc *sc = arg;

        mutex_enter(&sc->sc_intr_lock);
        if (sc->sc_pintr)
                (*sc->sc_pintr)(sc->sc_pintrarg);
        mutex_exit(&sc->sc_intr_lock);
}

static void
ascaudio_done_input(void *arg)
{
        struct ascaudio_softc *sc = arg;

        mutex_enter(&sc->sc_intr_lock);
        if (sc->sc_rintr)
                (*sc->sc_rintr)(sc->sc_rintrarg);
        mutex_exit(&sc->sc_intr_lock);
}

static void
configure_dfac(uint8_t config)
{
        int i;

        if (pmHardware == PM_HW_PB5XX)
                return;         /* These macs use the pm to configure dfac */

        for (i = 0; i < 8; i++) {
                via_reg(VIA2, vBufB) &= ~DFAC_CLOCK;

                if (config & 0x1)
                        via_reg(VIA2, vBufB) |= DFAC_DATA;
                else
                        via_reg(VIA2, vBufB) &= ~DFAC_DATA;

                via_reg(VIA2, vBufB) |= DFAC_CLOCK;
                config >>= 1;
        }
        via_reg(VIA2, vBufB) &= ~DFAC_CLOCK;
        via_reg(VIA2, vBufB) |= DFAC_LATCH;
        via_reg(VIA2, vBufB) &= ~DFAC_LATCH;
}

#ifdef _MODULE

MODULE(MODULE_CLASS_DRIVER, ascaudio, "audio");

static const struct cfiattrdata audiobuscf_iattrdata = {
        "audiobus", 0, { { NULL, NULL, 0 }, }
};
static const struct cfiattrdata * const ascaudio_attrs[] = {
        &audiobuscf_iattrdata, NULL
};

CFDRIVER_DECL(ascaudio, DV_DULL, ascaud_attrs);
extern struct cfattach ascaudio_ca;
static int ascaudioloc[] = { -1, -1 };

static struct cfdata ascaudio_cfdata[] = {
        {
                .cf_name = "ascaudio",
                .cf_atname = "ascaudio",
                .cf_unit = 0,
                .cf_fstate = FSTATE_STAR,
                .cf_loc = ascaudioloc,
                .cf_flags = 0,
                .cf_pspec = NULL,
        },
        { NULL, NULL, 0, 0, NULL, 0, NULL }
};

static int
ascaudio_modcmd(modcmd_t cmd, void *arg)
{
        devmajor_t cmajor = NODEVMAJOR, bmajor = NODEVMAJOR;
        int error;

        switch (cmd) {
        case MODULE_CMD_INIT:
                error = config_cfdriver_attach(&ascaudio_cd);
                if (error) {
                        return error;
                }

                error = config_cfattach_attach(ascaudio_cd.cd_name, &ascaud_ca);
                if (error) {
                        config_cfdriver_detach(&ascaudio_cd);
                        aprint_error("%s: unable to register cfattach\n",
                                ascaudio_cd.cd_name);

                        return error;
                }

                error = config_cfdata_attach(ascaudio_cfdata, 1);
                if (error) {
                        config_cfattach_detach(ascaudio_cd.cd_name, &ascaud_ca);
                        config_cfdriver_detach(&ascaudio_cd);
                        aprint_error("%s: unable to register cfdata\n",
                                ascaudio_cd.cd_name);

                        return error;
                }

                error = devsw_attach(ascaudio_cd.cd_name, NULL, &bmajor,
                    &ascaudio_cdevsw, &cmajor);
                if (error) {
                        error = config_cfdata_detach(ascaudio_cfdata);
                        if (error) {
                                return error;
                        }
                        config_cfattach_detach(ascaudio_cd.cd_name, &ascaud_ca);
                        config_cfdriver_detach(&ascaudio_cd);
                        aprint_error("%s: unable to register devsw\n",
                                ascaudio_cd.cd_name);

                        return error;
                }

                (void)config_attach_pseudo(ascaudio_cfdata);

                return 0;
        case MODULE_CMD_FINI:
                error = config_cfdata_detach(ascaudio_cfdata);
                if (error) {
                        return error;
                }

                config_cfattach_detach(ascaudio_cd.cd_name, &ascaud_ca);
                config_cfdriver_detach(&ascaudio_cd);
                devsw_detach(NULL, &ascaudio_cdevsw);

                return 0;
        default:
                return ENOTTY;
        }
}

#endif