/*      $NetBSD: arcofi.c,v 1.4 2022/05/31 08:43:15 andvar Exp $        */
/*      $OpenBSD: arcofi.c,v 1.6 2013/05/15 08:29:24 ratchov Exp $      */

/*
* Copyright (c) 2011 Miodrag Vallat.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/*
* Driver for the HP ``Audio1'' device, which is a FIFO layer around a
* Siemens PSB 2160 ``ARCOFI'' phone quality audio chip.
*
* It is known to exist in two flavours: on-board the HP9000/425e as a DIO
* device, an on-board the HP9000/{705,710,745,747} as a GIO device.
*
* The FIFO logic buffers up to 128 bytes. When using 8 bit samples and
* the logic set to interrupt every half FIFO, the device will interrupt
* 125 times per second.
*/

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/mutex.h>
#include <sys/bus.h>
#include <sys/intr.h>

#include <sys/audioio.h>

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

#include <dev/ic/arcofivar.h>

#include "ioconf.h"

#if 0
#define ARCOFI_DEBUG
#endif

/*
* Siemens PSB2160 registers
*/

/* CMDR */
#define CMDR_AD         0x80    /* SP1/PS2 address convention */
#define CMDR_READ       0x40
#define CMDR_WRITE      0x00
#define CMDR_PU         0x20    /* Power Up */
#define CMDR_RCS        0x10    /* Receive and transmit in CH B2 */
#define CMDR_MASK       0x0f

       /* command           length     data */
#define SOP_0   0x00    /*      5       CR4 CR3 CR2 CR1 */
#define COP_1   0x01    /*      5       t1_hi t1_lo f1_hi f1_lo */
#define COP_2   0x02    /*      3       gr1 gr2 */
#define COP_3   0x03    /*      3       t2_hi t2_lo f2_hi f2_lo */
#define SOP_4   0x04    /*      2       CR1 */
#define SOP_5   0x05    /*      2       CR2 */
#define SOP_6   0x06    /*      2       CR3 */
#define SOP_7   0x07    /*      2       CR4 */
#define COP_8   0x08    /*      3       dtmf_hi dtmf_lo */
#define COP_9   0x09    /*      5       gz a3 a2 a1 */
#define COP_A   0x0a    /*      9       fx1 to fx8 */
#define COP_B   0x0b    /*      3       gx1 gx2 */
#define COP_C   0x0c    /*      9       fr1 to fr 8 */
#define COP_D   0x0d    /*      5       fr9 fr10 fx9 fx10 */
#define COP_E   0x0e    /*      5       t3_hi t3_lo f3_hi f3_lo */

/* CR1 */
#define CR1_GR          0x80    /* GR gain loaded from CRAM vs 0dB */
#define CR1_GZ          0x40    /* Z gain loaded from CRAM vs -18dB */
#define CR1_FX          0x20    /* X filter loaded from CRAM vs 0dB flat */
#define CR1_FR          0x10    /* R filter loaded from CRAM vs 0dB flat */
#define CR1_GX          0x08    /* GX gain loaded from CRAM vs 0dB */
#define CR1_T_MASK      0x07    /* test mode */
#define CR1_DLP         0x07    /* digital loopback via PCM registers */
#define CR1_DLM         0x06    /* D/A output looped back to A/D input */
#define CR1_DLS         0x05    /* digital loopback via converter registers */
#define CR1_IDR         0x04    /* data RAM initialization */
#define CR1_BYP         0x03    /* bypass analog frontend */
#define CR1_ALM         0x02    /* analog loopback via MUX */
#define CR1_ALS         0x01    /* analog loopback via converter registers */

/* CR2 */
#define CR2_SD          0x80    /* SD pin set to input vs output */
#define CR2_SC          0x40    /* SC pin set to input vs output */
#define CR2_SB          0x20    /* SB pin set to input vs output */
#define CR2_SA          0x10    /* SA pin set to input vs output */
#define CR2_ELS         0x08    /* non-input S pins tristate SIP vs sending 0 */
#define CR2_AM          0x04    /* only one device on the SLD bus */
#define CR2_TR          0x02    /* three party conferencing */
#define CR2_EFC         0x01    /* enable feature control */

/* CR3 */
#define CR3_MIC_G_MASK  0xe0            /* MIC input analog gain  */
#define CR3_MIC_X_INPUT         0xe0    /* MIC disabled, X input 15.1 dB */
#define CR3_MIC_G_17            0xc0    /* 17 dB */
#define CR3_MIC_G_22            0xa0    /* 22 dB */
#define CR3_MIC_G_28            0x80    /* 28 dB */
#define CR3_MIC_G_34            0x60    /* 34 dB */
#define CR3_MIC_G_40            0x40    /* 40 dB */
#define CR3_MIC_G_46            0x20    /* 46 dB */
#define CR3_MIC_G_52            0x00    /* 52 dB (reset default) */
#define CR3_AFEC_MASK   0x1c
#define CR3_AFEC_MUTE           0x18    /* mute: Hout */
#define CR3_AFEC_HFS            0x14    /* hands free: FHM, LS out */
#define CR3_AFEC_LH3            0x10    /* loud hearing 3: MIC, H out, LS out */
#define CR3_AFEC_LH2            0x0c    /* loud hearing 2: MIC, LS out */
#define CR3_AFEC_LH1            0x08    /* loud hearing 1: LS out */
#define CR3_AFEC_RDY            0x04    /* ready: MIC, H out */
#define CR3_AFEC_POR            0x00    /* power on reset: all off */
#define CR3_OPMODE_MASK 0x03
#define CR3_OPMODE_LINEAR       0x02    /* linear (16 bit) */
#define CR3_OPMODE_MIXED        0x01    /* mixed */
#define CR3_OPMODE_NORMAL       0x00    /* normal (A/u-Law) */

/* CR4 */
#define CR4_DHF         0x80    /* TX digital high frequency enable */
#define CR4_DTMF        0x40    /* DTMF generator enable */
#define CR4_TG          0x20    /* tone ring enable */
#define CR4_BT          0x10    /* beat tone generator enable */
#define CR4_TM          0x08    /* incoming voice enable */
#define CR4_BM          0x04    /* beat mode (3 tone vs 2 tone) */
#define CR4_PM          0x02    /* tone sent to piezo vs loudspeaker */
#define CR4_ULAW        0x01    /* u-Law vs A-Law */


/*
* Glue logic registers
* Note the register values here are symbolic, as actual addresses
* depend upon the particular bus the device is connected to.
*/

#define ARCOFI_ID               0       /* id (r) and reset (w) register */

#define ARCOFI_CSR              1       /* status and control register */
#define CSR_INTR_ENABLE                 0x80
#define CSR_INTR_REQUEST                0x40    /* unacknowledged interrupt */
/* 0x20 and 0x10 used in DIO flavours, to provide IPL */
#define CSR_WIDTH_16                    0x08    /* 16-bit samples */
#define CSR_CTRL_FIFO_ENABLE            0x04    /* connect FIFO to CMDR */
#define CSR_DATA_FIFO_ENABLE            0x01    /* connect FIFO to DU/DD */

#define ARCOFI_FIFO_IR          2       /* FIFO interrupt register */
#define FIFO_IR_ENABLE(ev)              ((ev) << 4)
#define FIFO_IR_EVENT(ev)               (ev)
#define FIFO_IR_OUT_EMPTY               0x08
#define FIFO_IR_CTRL_EMPTY              0x04
#define FIFO_IR_OUT_HALF_EMPTY          0x02
#define FIFO_IR_IN_HALF_EMPTY           0x01

#define ARCOFI_FIFO_SR          3       /* FIFO status register (ro) */
#define FIFO_SR_CTRL_FULL               0x20
#define FIFO_SR_CTRL_EMPTY              0x10
#define FIFO_SR_OUT_FULL                0x08
#define FIFO_SR_OUT_EMPTY               0x04
#define FIFO_SR_IN_FULL                 0x02
#define FIFO_SR_IN_EMPTY                0x01

#define ARCOFI_FIFO_DATA        4       /* data FIFO port */

#define ARCOFI_FIFO_CTRL        5       /* control FIFO port (wo) */

#define ARCOFI_FIFO_SIZE        128

#ifdef hp300    /* XXX */
#define arcofi_read(sc, r) \
       bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (r))
#define arcofi_write(sc, r, v) \
       bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (r), (v))
#else
#define arcofi_read(sc, r) \
       bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_reg[(r)])
#define arcofi_write(sc, r, v) \
       bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (sc)->sc_reg[(r)], (v))
#endif

static int      arcofi_cmd(struct arcofi_softc *, uint8_t, const uint8_t *);
static int      arcofi_cr3_to_portmask(uint, int);
static int      arcofi_gain_to_mi(uint);
static uint     arcofi_mi_to_gain(int);
static uint     arcofi_portmask_to_cr3(int);
static int      arcofi_recv_data(struct arcofi_softc *);
static int      arcofi_xmit_data(struct arcofi_softc *);

static int      arcofi_open(void *, int);
static int      arcofi_query_format(void *, audio_format_query_t *);
static int      arcofi_set_format(void *, int,
                   const audio_params_t *, const audio_params_t *,
                   audio_filter_reg_t *, audio_filter_reg_t *);
static int      arcofi_round_blocksize(void *, int, int,
                   const audio_params_t *);
static int      arcofi_commit_settings(void *);
static int      arcofi_start_output(void *, void *, int, void (*)(void *),
                   void *);
static int      arcofi_start_input(void *, void *, int, void (*)(void *),
                   void *);
static int      arcofi_halt_output(void *);
static int      arcofi_halt_input(void *);
static int      arcofi_getdev(void *, struct audio_device *);
static int      arcofi_set_port(void *, mixer_ctrl_t *);
static int      arcofi_get_port(void *, mixer_ctrl_t *);
static int      arcofi_query_devinfo(void *, mixer_devinfo_t *);
static int      arcofi_get_props(void *);
static void     arcofi_get_locks(void *, kmutex_t **, kmutex_t **);

static const struct audio_hw_if arcofi_hw_if = {
       .open             = arcofi_open,
       .query_format     = arcofi_query_format,
       .set_format       = arcofi_set_format,
       .round_blocksize  = arcofi_round_blocksize,
       .commit_settings  = arcofi_commit_settings,
       .start_output     = arcofi_start_output,
       .start_input      = arcofi_start_input,
       .halt_output      = arcofi_halt_output,
       .halt_input       = arcofi_halt_input,
       .speaker_ctl      = NULL,
       .getdev           = arcofi_getdev,
       .set_port         = arcofi_set_port,
       .get_port         = arcofi_get_port,
       .query_devinfo    = arcofi_query_devinfo,
       .allocm           = NULL,
       .freem            = NULL,
       .round_buffersize = NULL,
       .get_props        = arcofi_get_props,
       .trigger_output   = NULL,
       .trigger_input    = NULL,
       .dev_ioctl        = NULL,
       .get_locks        = arcofi_get_locks,
};

#define ARCOFI_FORMAT(prio, enc, prec) \
       { \
               .mode           = AUMODE_PLAY | AUMODE_RECORD, \
               .priority       = (prio), \
               .encoding       = (enc), \
               .validbits      = (prec), \
               .precision      = (prec), \
               .channels       = 1, \
               .channel_mask   = AUFMT_MONAURAL, \
               .frequency_type = 1, \
               .frequency      = { 8000 }, \
       }
static const struct audio_format arcofi_formats[] = {
       /*
        * 8-bit u-Law and A-Law are native.
        */
       ARCOFI_FORMAT(1, AUDIO_ENCODING_ULAW,        8),
       ARCOFI_FORMAT(0, AUDIO_ENCODING_ALAW,        8),
       /*
        * 16-bit slinear big-endian is native.
        * But it's hard to use due to hardware restrictions.
        */
       ARCOFI_FORMAT(0, AUDIO_ENCODING_SLINEAR_BE, 16),
};
#define ARCOFI_NFORMATS  __arraycount(arcofi_formats)

/* mixer items */
#define ARCOFI_PORT_AUDIO_IN_VOLUME     0       /* line in volume (GR) */
#define ARCOFI_PORT_AUDIO_OUT_VOLUME    1       /* line out volume (GX) */
#define ARCOFI_PORT_AUDIO_SPKR_VOLUME   2       /* speaker volume (GX) */
#define ARCOFI_PORT_AUDIO_IN_MUTE       3       /* line in mute (MIC) */
#define ARCOFI_PORT_AUDIO_OUT_MUTE      4       /* line out mute (H out) */
#define ARCOFI_PORT_AUDIO_SPKR_MUTE     5       /* line in mute (LS out) */
/* mixer classes */
#define ARCOFI_CLASS_INPUT              6
#define ARCOFI_CLASS_OUTPUT             7

/*
* Gain programming formulae are a complete mystery to me, and of course
* no two chips are compatible - not even the PSB 2163 and PSB 2165
* later ARCOFI chips, from the same manufacturer as the PSB 2160!
*
* Of course, the PSB 2160 datasheet does not give any set of values.
* The following table is taken from the HP-UX audio driver (audio_shared.o
* private_audio_gain_tab).
*/

#define NEGATIVE_GAINS  60
#define POSITIVE_GAINS  14
static const uint16_t arcofi_gains[1 + NEGATIVE_GAINS + 1 + POSITIVE_GAINS] = {
       /* minus infinity */
       0x0988,

       0xf8b8, 0xf8b8, 0xf8b8, 0xf8b8, 0x099f, 0x099f, 0x099f, 0x099f,
       0x09af, 0x09af, 0x09af, 0x09cf, 0x09cf, 0x09cf, 0xf8a9, 0xf83a,
       0xf83a, 0xf82b, 0xf82d, 0xf8a3, 0xf8b2, 0xf8a1, 0xe8aa, 0xe84b,
       0xe89e, 0xe8d3, 0xe891, 0xe8b1, 0xd8aa, 0xd8cb, 0xd8a6, 0xd8b3,
       0xd842, 0xd8b1, 0xc8aa, 0xc8bb, 0xc888, 0xc853, 0xc852, 0xc8b1,
       0xb8aa, 0xb8ab, 0xb896, 0xb892, 0xb842, 0xb8b1, 0xa8aa, 0xa8bb,
       0x199f, 0x195b, 0x29c1, 0x2923, 0x29aa, 0x392b, 0xf998, 0xb988,
       0x1aac, 0x3aa1, 0xbaa1, 0xbb88,

       /* 0 */
       0x8888,

       0xd388, 0x5288, 0xb1a1, 0x31a1, 0x1192, 0x11d0, 0x30c0, 0x2050,
       0x1021, 0x1020, 0x1000, 0x0001, 0x0010, 0x0000
};

static int
arcofi_open(void *v, int flags)
{
       struct arcofi_softc *sc __diagused = (struct arcofi_softc *)v;

       KASSERT(sc->sc_mode == 0);

       return 0;
}

static int
arcofi_query_format(void *v, audio_format_query_t *afp)
{

       return audio_query_format(arcofi_formats, ARCOFI_NFORMATS, afp);
}

static int
arcofi_set_format(void *handle, int setmode,
   const audio_params_t *play, const audio_params_t *rec,
   audio_filter_reg_t *pfil, audio_filter_reg_t *rfil)
{
       struct arcofi_softc *sc;

       sc = handle;

       if ((setmode & AUMODE_PLAY)) {
               switch (play->encoding) {
               case AUDIO_ENCODING_ULAW:
                       pfil->codec = audio_internal_to_mulaw;
                       break;
               case AUDIO_ENCODING_ALAW:
                       pfil->codec = audio_internal_to_alaw;
                       break;
               }
       }
       if ((setmode & AUMODE_RECORD)) {
               switch (rec->encoding) {
               case AUDIO_ENCODING_ULAW:
                       rfil->codec = audio_mulaw_to_internal;
                       break;
               case AUDIO_ENCODING_ALAW:
                       rfil->codec = audio_alaw_to_internal;
                       break;
               }
       }

       /* *play and *rec are identical because !AUDIO_PROP_INDEPENDENT */

       if (play->precision == 8) {
               if (play->encoding == AUDIO_ENCODING_ULAW)
                       sc->sc_shadow.cr4 |= CR4_ULAW;
               else
                       sc->sc_shadow.cr4 &= ~CR4_ULAW;
               sc->sc_shadow.cr3 =
                   (sc->sc_shadow.cr3 & ~CR3_OPMODE_MASK) |
                   CR3_OPMODE_NORMAL;
       } else {
               sc->sc_shadow.cr3 =
                   (sc->sc_shadow.cr3 & ~CR3_OPMODE_MASK) |
                   CR3_OPMODE_LINEAR;
       }

       return 0;
}

static int
arcofi_round_blocksize(void *handle, int block, int mode,
   const audio_params_t *param)
{

       /*
        * Round the size up to a multiple of half the FIFO, to favour
        * smooth interrupt operation.
        */
       return roundup(block, ARCOFI_FIFO_SIZE / 2);
}

static int
arcofi_commit_settings(void *v)
{
       struct arcofi_softc *sc = (struct arcofi_softc *)v;
       int rc;
       uint8_t cmd[2], csr, ocsr;

#ifdef ARCOFI_DEBUG
       printf("%s: %s, gr %04x gx %04x cr3 %02x cr4 %02x mute %d\n",
           device_xname(sc->sc_dev), __func__,
           arcofi_gains[sc->sc_shadow.gr_idx],
           arcofi_gains[sc->sc_shadow.gx_idx],
           sc->sc_shadow.cr3, sc->sc_shadow.cr4, sc->sc_shadow.output_mute);
#endif

       if (memcmp(&sc->sc_active, &sc->sc_shadow, sizeof(sc->sc_active)) == 0)
               return 0;

       mutex_spin_enter(&sc->sc_intr_lock);

       if (sc->sc_active.gr_idx != sc->sc_shadow.gr_idx) {
               cmd[0] = arcofi_gains[sc->sc_shadow.gr_idx] >> 8;
               cmd[1] = arcofi_gains[sc->sc_shadow.gr_idx];
               if ((rc = arcofi_cmd(sc, COP_2, cmd)) != 0)
                       goto error;
               sc->sc_active.gr_idx = sc->sc_shadow.gr_idx;
       }

       if (sc->sc_active.gx_idx != sc->sc_shadow.gx_idx ||
           sc->sc_active.output_mute != sc->sc_shadow.output_mute) {
               if (sc->sc_shadow.output_mute) {
                       cmd[0] = arcofi_gains[0] >> 8;
                       cmd[1] = arcofi_gains[0];
               } else {
                       cmd[0] = arcofi_gains[sc->sc_shadow.gx_idx] >> 8;
                       cmd[1] = arcofi_gains[sc->sc_shadow.gx_idx];
               }
               if ((rc = arcofi_cmd(sc, COP_B, cmd)) != 0)
                       goto error;
               sc->sc_active.gx_idx = sc->sc_shadow.gx_idx;
               sc->sc_active.output_mute = sc->sc_shadow.output_mute;
       }

       if (sc->sc_active.cr3 != sc->sc_shadow.cr3) {
               cmd[0] = sc->sc_shadow.cr3;
               if ((rc = arcofi_cmd(sc, SOP_6, cmd)) != 0)
                       goto error;
               sc->sc_active.cr3 = sc->sc_shadow.cr3;

               ocsr = arcofi_read(sc, ARCOFI_CSR);
               if ((sc->sc_active.cr3 & CR3_OPMODE_MASK) != CR3_OPMODE_NORMAL)
                       csr = ocsr | CSR_WIDTH_16;
               else
                       csr = ocsr & ~CSR_WIDTH_16;
               if (csr != ocsr)
                       arcofi_write(sc, ARCOFI_CSR, csr);
       }

       if (sc->sc_active.cr4 != sc->sc_shadow.cr4) {
               cmd[0] = sc->sc_shadow.cr4;
               if ((rc = arcofi_cmd(sc, SOP_7, cmd)) != 0)
                       goto error;
               sc->sc_active.cr4 = sc->sc_shadow.cr4;
       }

       rc = 0;
error:
       mutex_spin_exit(&sc->sc_intr_lock);
       return rc;
}

/*
* Take it out of the queue as much as possible.
*/
static int
arcofi_recv_data(struct arcofi_softc *sc)
{
       uint8_t *cur;
       uint8_t *past;

       cur = sc->sc_recv.buf;
       past = sc->sc_recv.past;

       while (cur != past &&
           (arcofi_read(sc, ARCOFI_FIFO_SR) & FIFO_SR_IN_EMPTY) == 0) {
               *cur++ = arcofi_read(sc, ARCOFI_FIFO_DATA);
       }
       sc->sc_recv.buf = cur;

       return past - cur;
}

/*
* Fill the queue as much as possible.
*/
static int
arcofi_xmit_data(struct arcofi_softc *sc)
{
       uint8_t *cur;
       uint8_t *past;

       cur = sc->sc_xmit.buf;
       past = sc->sc_xmit.past;

       while (cur != past &&
           (arcofi_read(sc, ARCOFI_FIFO_SR) & FIFO_SR_OUT_FULL) == 0) {
               arcofi_write(sc, ARCOFI_FIFO_DATA, *cur++);
       }
       sc->sc_xmit.buf = cur;

       return past - cur;
}

static int
arcofi_start_input(void *v, void *rbuf, int rsz, void (*cb)(void *),
   void *cbarg)
{
       struct arcofi_softc *sc = (struct arcofi_softc *)v;

#ifdef ARCOFI_DEBUG
       printf("%s: %s, mode %d\n",
           device_xname(sc->sc_dev), __func__, sc->sc_mode);
#endif

       /* enable data FIFO if becoming active */
       if (sc->sc_mode == 0)
               arcofi_write(sc, ARCOFI_CSR,
                   arcofi_read(sc, ARCOFI_CSR) | CSR_DATA_FIFO_ENABLE);
       sc->sc_mode |= AUMODE_RECORD;

       sc->sc_recv.buf = (uint8_t *)rbuf;
       sc->sc_recv.past = (uint8_t *)rbuf + rsz;
       sc->sc_recv.cb = cb;
       sc->sc_recv.cbarg = cbarg;

       /* enable input FIFO interrupts */
       arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) |
           FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY));

       return 0;
}

static int
arcofi_start_output(void *v, void *wbuf, int wsz, void (*cb)(void *),
   void *cbarg)
{
       struct arcofi_softc *sc = (struct arcofi_softc *)v;

#ifdef ARCOFI_DEBUG
       printf("%s: %s, mode %d\n",
           device_xname(sc->sc_dev), __func__, sc->sc_mode);
#endif

       /* enable data FIFO if becoming active */
       if (sc->sc_mode == 0)
               arcofi_write(sc, ARCOFI_CSR,
                   arcofi_read(sc, ARCOFI_CSR) | CSR_DATA_FIFO_ENABLE);
       sc->sc_mode |= AUMODE_PLAY;

       sc->sc_xmit.buf = (uint8_t *)wbuf;
       sc->sc_xmit.past = (uint8_t *)wbuf + wsz;
       sc->sc_xmit.cb = cb;
       sc->sc_xmit.cbarg = cbarg;

       /* Fill FIFO */
       arcofi_xmit_data(sc);

       /* enable output FIFO interrupts */
       arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) |
           FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY));

       return 0;
}

static int
arcofi_halt_input(void *v)
{
       struct arcofi_softc *sc = (struct arcofi_softc *)v;

#ifdef ARCOFI_DEBUG
       printf("%s: %s, mode %d\n",
           device_xname(sc->sc_dev), __func__, sc->sc_mode);
#endif

       /* disable input FIFO interrupts */
       arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) &
           ~FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY));
       /* disable data FIFO if becoming idle */
       sc->sc_mode &= ~AUMODE_RECORD;
       if (sc->sc_mode == 0)
               arcofi_write(sc, ARCOFI_CSR,
                   arcofi_read(sc, ARCOFI_CSR) & ~CSR_DATA_FIFO_ENABLE);

       return 0;
}

static int
arcofi_halt_output(void *v)
{
       struct arcofi_softc *sc = (struct arcofi_softc *)v;

#ifdef ARCOFI_DEBUG
       printf("%s: %s, mode %d\n",
           device_xname(sc->sc_dev), __func__, sc->sc_mode);
#endif

       /* disable output FIFO interrupts */
       arcofi_write(sc, ARCOFI_FIFO_IR, arcofi_read(sc, ARCOFI_FIFO_IR) &
           ~FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY));
       /* disable data FIFO if becoming idle */
       sc->sc_mode &= ~AUMODE_PLAY;
       if (sc->sc_mode == 0)
               arcofi_write(sc, ARCOFI_CSR,
                   arcofi_read(sc, ARCOFI_CSR) & ~CSR_DATA_FIFO_ENABLE);

       return 0;
}

static int
arcofi_getdev(void *v, struct audio_device *ad)
{
       struct arcofi_softc *sc = (struct arcofi_softc *)v;

       *ad = sc->sc_audio_device;
       return 0;
}

/*
* Convert gain table index to AUDIO_MIN_GAIN..AUDIO_MAX_GAIN scale.
*/
static int
arcofi_gain_to_mi(uint idx)
{

       if (idx == 0)
               return AUDIO_MIN_GAIN;
       if (idx == __arraycount(arcofi_gains) - 1)
               return AUDIO_MAX_GAIN;

       return ((idx - 1) * (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN)) /
           (__arraycount(arcofi_gains) - 1) + AUDIO_MIN_GAIN + 1;
}

/*
* Convert AUDIO_MIN_GAIN..AUDIO_MAX_GAIN scale to gain table index.
*/
static uint
arcofi_mi_to_gain(int lvl)
{

       if (lvl <= AUDIO_MIN_GAIN)
               return 0;
       if (lvl >= AUDIO_MAX_GAIN)
               return __arraycount(arcofi_gains) - 1;

       return ((lvl - AUDIO_MIN_GAIN - 1) * (__arraycount(arcofi_gains) - 1)) /
           (AUDIO_MAX_GAIN - AUDIO_MIN_GAIN);
}

/*
* The following routines rely upon this...
*/
#if (AUDIO_SPEAKER == AUDIO_LINE_IN) || (AUDIO_LINE_OUT == AUDIO_LINE_IN) || \
   (AUDIO_SPEAKER == AUDIO_LINE_OUT)
#error Please rework the cr3 handling logic.
#endif

/*
* The mapping between the available inputs and outputs, and CR3, is as
* follows:
* - the `line in' connector is the `MIC' input.
* - the `line out' connector is the `H out' (heaphones) output.
* - the internal `speaker' is the `LS out' (loudspeaker) output.
*
* Each of these can be enabled or disabled independently, except for
* MIC enabled with H out and LS out disabled, which is not allowed
* by the chip (and makes no sense for a chip which was intended to
* be used in phones, not voice recorders); we cheat by keeping one
* output source enabled, but with the output gain forced to minus
* infinity to mute it.
*
* The truth table is thus:
*
*      MIC     LS out  H out   AFEC
*      off     off     off     POR
*      off     off     on      MUTE
*      off     on      off     LH1
*      off     on      on      LH3, X input enabled
*      on      off     off     RDY, GX forced to minus infinity
*      on      off     on      RDY
*      on      on      off     LH2
*      on      on      on      LH3
*/

/*
* Convert logical port enable settings to a valid CR3 value.
*/
static uint
arcofi_portmask_to_cr3(int mask)
{

       switch (mask) {
       default:
       case 0:
               return CR3_MIC_G_17 | CR3_AFEC_POR;
       case AUDIO_LINE_OUT:
               return CR3_MIC_G_17 | CR3_AFEC_MUTE;
       case AUDIO_SPEAKER:
               return CR3_MIC_G_17 | CR3_AFEC_LH1;
       case AUDIO_SPEAKER | AUDIO_LINE_OUT:
               return CR3_MIC_X_INPUT | CR3_AFEC_LH3;
       case AUDIO_LINE_IN:
               /* since we can't do this, just... */
               /* FALLTHROUGH */
       case AUDIO_LINE_IN | AUDIO_LINE_OUT:
               return CR3_MIC_G_17 | CR3_AFEC_RDY;
       case AUDIO_LINE_IN | AUDIO_SPEAKER:
               return CR3_MIC_G_17 | CR3_AFEC_LH2;
       case AUDIO_LINE_IN | AUDIO_SPEAKER | AUDIO_LINE_OUT:
               return CR3_MIC_G_17 | CR3_AFEC_LH3;
       }
}

/*
* Convert CR3 to an enabled ports mask.
*/
static int
arcofi_cr3_to_portmask(uint cr3, int output_mute)
{

       switch (cr3 & CR3_AFEC_MASK) {
       default:
       case CR3_AFEC_POR:
               return 0;
       case CR3_AFEC_RDY:
               return output_mute ?
                   AUDIO_LINE_IN : AUDIO_LINE_IN | AUDIO_LINE_OUT;
       case CR3_AFEC_HFS:
       case CR3_AFEC_LH1:
               return AUDIO_SPEAKER;
       case CR3_AFEC_LH2:
               return AUDIO_LINE_IN | AUDIO_SPEAKER;
       case CR3_AFEC_LH3:
               if ((cr3 & CR3_MIC_G_MASK) == CR3_MIC_X_INPUT)
                       return AUDIO_SPEAKER | AUDIO_LINE_OUT;
               else
                       return AUDIO_LINE_IN | AUDIO_SPEAKER | AUDIO_LINE_OUT;
       case CR3_AFEC_MUTE:
               return AUDIO_LINE_OUT;
       }
}

static int
arcofi_set_port(void *v, mixer_ctrl_t *mc)
{
       struct arcofi_softc *sc = (struct arcofi_softc *)v;
       int portmask = 0;

#ifdef ARCOFI_DEBUG
       printf("%s: %s, mode %d\n",
           device_xname(sc->sc_dev), __func__, sc->sc_mode);
#endif
       /* check for proper type */
       switch (mc->dev) {
       /* volume settings */
       case ARCOFI_PORT_AUDIO_IN_VOLUME:
       case ARCOFI_PORT_AUDIO_OUT_VOLUME:
       case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
               if (mc->un.value.num_channels != 1)
                       return EINVAL;
               break;
       /* mute settings */
       case ARCOFI_PORT_AUDIO_IN_MUTE:
       case ARCOFI_PORT_AUDIO_OUT_MUTE:
       case ARCOFI_PORT_AUDIO_SPKR_MUTE:
               if (mc->type != AUDIO_MIXER_ENUM)
                       return EINVAL;
               portmask = arcofi_cr3_to_portmask(sc->sc_shadow.cr3,
                   sc->sc_shadow.output_mute);
#ifdef ARCOFI_DEBUG
               printf("%s: %s cr3 %02x -> mask %02x\n",
                   device_xname(sc->sc_dev), __func__,
                   sc->sc_shadow.cr3, portmask);
#endif
               break;
       default:
               return EINVAL;
       }

       switch (mc->dev) {
       /* volume settings */
       case ARCOFI_PORT_AUDIO_IN_VOLUME:
               sc->sc_shadow.gr_idx =
                   arcofi_mi_to_gain(mc->un.value.level[AUDIO_MIXER_LEVEL_MONO]);
               return 0;
       case ARCOFI_PORT_AUDIO_OUT_VOLUME:
       case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
               sc->sc_shadow.gx_idx =
                   arcofi_mi_to_gain(mc->un.value.level[AUDIO_MIXER_LEVEL_MONO]);
               return 0;

       /* mute settings */
       case ARCOFI_PORT_AUDIO_IN_MUTE:
               if (mc->un.ord)
                       portmask &= ~AUDIO_LINE_IN;
               else
                       portmask |= AUDIO_LINE_IN;
               break;
       case ARCOFI_PORT_AUDIO_OUT_MUTE:
               if (mc->un.ord)
                       portmask &= ~AUDIO_LINE_OUT;
               else
                       portmask |= AUDIO_LINE_OUT;
               break;
       case ARCOFI_PORT_AUDIO_SPKR_MUTE:
               if (mc->un.ord)
                       portmask &= ~AUDIO_SPEAKER;
               else
                       portmask |= AUDIO_SPEAKER;
               break;
       }

       sc->sc_shadow.cr3 = (sc->sc_shadow.cr3 & CR3_OPMODE_MASK) |
           arcofi_portmask_to_cr3(portmask);
       sc->sc_shadow.output_mute = (portmask == AUDIO_LINE_IN);
#ifdef ARCOFI_DEBUG
       printf("%s: %s mask %02x -> cr3 %02x m %d\n",
           device_xname(sc->sc_dev), __func__,
           portmask, sc->sc_shadow.cr3, sc->sc_shadow.output_mute);
#endif

       return 0;
}

static int
arcofi_get_port(void *v, mixer_ctrl_t *mc)
{
       struct arcofi_softc *sc = (struct arcofi_softc *)v;
       int portmask = 0;

#ifdef ARCOFI_DEBUG
       printf("%s: %s, mode %d\n",
           device_xname(sc->sc_dev), __func__, sc->sc_mode);
#endif
       /* check for proper type */
       switch (mc->dev) {
       /* volume settings */
       case ARCOFI_PORT_AUDIO_IN_VOLUME:
       case ARCOFI_PORT_AUDIO_OUT_VOLUME:
       case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
               if (mc->un.value.num_channels != 1)
                       return EINVAL;
               break;

       /* mute settings */
       case ARCOFI_PORT_AUDIO_IN_MUTE:
       case ARCOFI_PORT_AUDIO_OUT_MUTE:
       case ARCOFI_PORT_AUDIO_SPKR_MUTE:
               if (mc->type != AUDIO_MIXER_ENUM)
                       return EINVAL;
               portmask = arcofi_cr3_to_portmask(sc->sc_shadow.cr3,
                   sc->sc_shadow.output_mute);
#ifdef ARCOFI_DEBUG
               printf("%s: %s cr3 %02x -> mask %02x\n",
                   device_xname(sc->sc_dev), __func__,
                   sc->sc_shadow.cr3, portmask);
#endif
               break;
       default:
               return EINVAL;
       }

       switch (mc->dev) {
       /* volume settings */
       case ARCOFI_PORT_AUDIO_IN_VOLUME:
               mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] =
                   arcofi_gain_to_mi(sc->sc_shadow.gr_idx);
               break;
       case ARCOFI_PORT_AUDIO_OUT_VOLUME:
       case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
               mc->un.value.level[AUDIO_MIXER_LEVEL_MONO] =
                   arcofi_gain_to_mi(sc->sc_shadow.gx_idx);
               break;

       /* mute settings */
       case ARCOFI_PORT_AUDIO_IN_MUTE:
               mc->un.ord = portmask & AUDIO_LINE_IN ? 0 : 1;
               break;
       case ARCOFI_PORT_AUDIO_OUT_MUTE:
               mc->un.ord = portmask & AUDIO_LINE_OUT ? 0 : 1;
               break;
       case ARCOFI_PORT_AUDIO_SPKR_MUTE:
               mc->un.ord = portmask & AUDIO_SPEAKER ? 0 : 1;
               break;
       }

       return 0;
}

static int
arcofi_query_devinfo(void *v, mixer_devinfo_t *md)
{

       switch (md->index) {
       default:
               return ENXIO;

       /* items */
       case ARCOFI_PORT_AUDIO_IN_VOLUME:
               md->type = AUDIO_MIXER_VALUE;
               md->mixer_class = ARCOFI_CLASS_INPUT;
               md->prev = AUDIO_MIXER_LAST;
               md->next = ARCOFI_PORT_AUDIO_IN_MUTE;
               strlcpy(md->label.name, AudioNline,
                   sizeof md->label.name);
               goto mono_volume;
       case ARCOFI_PORT_AUDIO_OUT_VOLUME:
               md->type = AUDIO_MIXER_VALUE;
               md->mixer_class = ARCOFI_CLASS_OUTPUT;
               md->prev = AUDIO_MIXER_LAST;
               md->next = ARCOFI_PORT_AUDIO_OUT_MUTE;
               strlcpy(md->label.name, AudioNline,
                   sizeof md->label.name);
               goto mono_volume;
       case ARCOFI_PORT_AUDIO_SPKR_VOLUME:
               md->type = AUDIO_MIXER_VALUE;
               md->mixer_class = ARCOFI_CLASS_OUTPUT;
               md->prev = AUDIO_MIXER_LAST;
               md->next = ARCOFI_PORT_AUDIO_SPKR_MUTE;
               strlcpy(md->label.name, AudioNspeaker,
                   sizeof md->label.name);
               /* goto mono_volume; */
mono_volume:
               md->un.v.num_channels = 1;
               strlcpy(md->un.v.units.name, AudioNvolume,
                   sizeof md->un.v.units.name);
               break;

       case ARCOFI_PORT_AUDIO_IN_MUTE:
               md->type = AUDIO_MIXER_ENUM;
               md->mixer_class = ARCOFI_CLASS_INPUT;
               md->prev = ARCOFI_PORT_AUDIO_IN_VOLUME;
               md->next = AUDIO_MIXER_LAST;
               goto mute;
       case ARCOFI_PORT_AUDIO_OUT_MUTE:
               md->type = AUDIO_MIXER_ENUM;
               md->mixer_class = ARCOFI_CLASS_OUTPUT;
               md->prev = ARCOFI_PORT_AUDIO_OUT_VOLUME;
               md->next = AUDIO_MIXER_LAST;
               goto mute;
       case ARCOFI_PORT_AUDIO_SPKR_MUTE:
               md->type = AUDIO_MIXER_ENUM;
               md->mixer_class = ARCOFI_CLASS_OUTPUT;
               md->prev = ARCOFI_PORT_AUDIO_SPKR_VOLUME;
               md->next = AUDIO_MIXER_LAST;
               /* goto mute; */
mute:
               strlcpy(md->label.name, AudioNmute, sizeof md->label.name);
               md->un.e.num_mem = 2;
               strlcpy(md->un.e.member[0].label.name, AudioNoff,
                   sizeof md->un.e.member[0].label.name);
               md->un.e.member[0].ord = 0;
               strlcpy(md->un.e.member[1].label.name, AudioNon,
                   sizeof md->un.e.member[1].label.name);
               md->un.e.member[1].ord = 1;
               break;

       /* classes */
       case ARCOFI_CLASS_INPUT:
               md->type = AUDIO_MIXER_CLASS;
               md->mixer_class = ARCOFI_CLASS_INPUT;
               md->prev = AUDIO_MIXER_LAST;
               md->next = AUDIO_MIXER_LAST;
               strlcpy(md->label.name, AudioCinputs,
                   sizeof md->label.name);
               break;
       case ARCOFI_CLASS_OUTPUT:
               md->type = AUDIO_MIXER_CLASS;
               md->mixer_class = ARCOFI_CLASS_OUTPUT;
               md->prev = AUDIO_MIXER_LAST;
               md->next = AUDIO_MIXER_LAST;
               strlcpy(md->label.name, AudioCoutputs,
                   sizeof md->label.name);
               break;
       }

       return 0;
}

static int
arcofi_get_props(void *v)
{

       return AUDIO_PROP_PLAYBACK | AUDIO_PROP_CAPTURE;
}

static void
arcofi_get_locks(void *v, kmutex_t **intr, kmutex_t **thread)
{
       struct arcofi_softc *sc = (struct arcofi_softc *)v;

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

int
arcofi_hwintr(void *v)
{
       struct arcofi_softc *sc = (struct arcofi_softc *)v;
       uint8_t csr, fir;
       int rc = 0;

       csr = arcofi_read(sc, ARCOFI_CSR);
       if ((csr & CSR_INTR_REQUEST) == 0)
               return 0;

       fir = arcofi_read(sc, ARCOFI_FIFO_IR);

       /* receive */
       if ((sc->sc_mode & AUMODE_RECORD) &&
           (fir & FIFO_IR_EVENT(FIFO_IR_IN_HALF_EMPTY))) {
               rc = 1;

               if (arcofi_recv_data(sc) == 0) {
                       /* disable further interrupts */
                       arcofi_write(sc, ARCOFI_FIFO_IR,
                           arcofi_read(sc, ARCOFI_FIFO_IR) &
                           ~FIFO_IR_ENABLE(FIFO_IR_IN_HALF_EMPTY));

                       /* callback */
                       sc->sc_recv.cb(sc->sc_recv.cbarg);
               }
       }

       /* xmit */
       if ((sc->sc_mode & AUMODE_PLAY) &&
           (fir & FIFO_IR_EVENT(FIFO_IR_OUT_HALF_EMPTY))) {
               rc = 1;

               if (arcofi_xmit_data(sc) == 0) {
                       /* disable further interrupts */
                       arcofi_write(sc, ARCOFI_FIFO_IR,
                           arcofi_read(sc, ARCOFI_FIFO_IR) &
                           ~FIFO_IR_ENABLE(FIFO_IR_OUT_HALF_EMPTY));

                       /* callback */
                       sc->sc_xmit.cb(sc->sc_xmit.cbarg);
               }
       }

#ifdef ARCOFI_DEBUG
       if (rc == 0)
               printf("%s: unclaimed interrupt, csr %02x fir %02x fsr %02x\n",
                   device_xname(sc->sc_dev), csr, fir,
                   arcofi_read(sc, ARCOFI_FIFO_SR));
#endif

       return rc;
}

static int
arcofi_cmd(struct arcofi_softc *sc, uint8_t cmd, const uint8_t *data)
{
       size_t len;
       uint8_t csr;
       int cnt;
       static const uint8_t cmdlen[] = {
           [SOP_0] = 4,
           [COP_1] = 4,
           [COP_2] = 2,
           [COP_3] = 2,
           [SOP_4] = 1,
           [SOP_5] = 1,
           [SOP_6] = 1,
           [SOP_7] = 1,
           [COP_8] = 2,
           [COP_9] = 4,
           [COP_A] = 8,
           [COP_B] = 2,
           [COP_C] = 8,
           [COP_D] = 4,
           [COP_E] = 4
       };

       /*
        * Compute command length.
        */
       if (cmd >= __arraycount(cmdlen))
               return EINVAL;
       len = cmdlen[cmd];

       KASSERT(cold || mutex_owned(&sc->sc_intr_lock));

       /*
        * Disable all FIFO processing.
        */
       csr = arcofi_read(sc, ARCOFI_CSR);
       arcofi_write(sc, ARCOFI_CSR,
           csr & ~(CSR_DATA_FIFO_ENABLE | CSR_CTRL_FIFO_ENABLE));

       /*
        * Fill the FIFO with the command bytes.
        */
       arcofi_write(sc, ARCOFI_FIFO_CTRL, CMDR_PU | CMDR_WRITE | cmd);
       for (; len != 0; len--)
               arcofi_write(sc, ARCOFI_FIFO_CTRL, *data++);

       /*
        * Enable command processing.
        */
       arcofi_write(sc, ARCOFI_CSR,
           (csr & ~CSR_DATA_FIFO_ENABLE) | CSR_CTRL_FIFO_ENABLE);

       /*
        * Wait for the command FIFO to be empty.
        */
       cnt = 100;
       while ((arcofi_read(sc, ARCOFI_FIFO_SR) & FIFO_SR_CTRL_EMPTY) == 0) {
               if (cnt-- == 0) {
                       return EBUSY;
               }
               delay(10);
       }

       arcofi_write(sc, ARCOFI_CSR, csr);

       return 0;
}

void
arcofi_attach(struct arcofi_softc *sc, const char *versionstr)
{
       device_t self;
       int rc;
       uint8_t op, cmd[4];

       self = sc->sc_dev;
       mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE);
       mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_AUDIO);

       /*
        * Reset logic.
        */
       arcofi_write(sc, ARCOFI_ID, 0);
       delay(100000);
       arcofi_write(sc, ARCOFI_CSR, 0);

       /*
        * Initialize the chip to default settings (8 bit, u-Law).
        */
       sc->sc_active.cr3 =
           arcofi_portmask_to_cr3(AUDIO_SPEAKER) | CR3_OPMODE_NORMAL;
       sc->sc_active.cr4 = CR4_TM | CR4_ULAW;
       sc->sc_active.gr_idx = sc->sc_active.gx_idx = 1 + NEGATIVE_GAINS;
       sc->sc_active.output_mute = 0;
       memcpy(&sc->sc_shadow, &sc->sc_active, sizeof(sc->sc_active));

       /* clear CRAM */
       op = SOP_4;
       cmd[0] = CR1_IDR;
       if ((rc = arcofi_cmd(sc, op, cmd)) != 0)
               goto error;
       delay(1000);

       /* set gain values before enabling them in CR1 */
       op = COP_2;
       cmd[0] = arcofi_gains[sc->sc_active.gr_idx] >> 8;
       cmd[1] = arcofi_gains[sc->sc_active.gr_idx];
       if ((rc = arcofi_cmd(sc, op, cmd)) != 0)
               goto error;
       /* same value for gx... */
       op = COP_B;
       if ((rc = arcofi_cmd(sc, op, cmd)) != 0)
               goto error;

       /* set all CR registers at once */
       op = SOP_0;
       cmd[0] = sc->sc_active.cr4;
       cmd[1] = sc->sc_active.cr3;
       cmd[2] = CR2_SD | CR2_SC | CR2_SB | CR2_SA | CR2_ELS | CR2_AM | CR2_EFC;
       cmd[3] = CR1_GR | CR1_GX;
       if ((rc = arcofi_cmd(sc, op, cmd)) != 0)
               goto error;

       arcofi_write(sc, ARCOFI_FIFO_IR, 0);
       arcofi_write(sc, ARCOFI_CSR, CSR_INTR_ENABLE);

       strlcpy(sc->sc_audio_device.name, arcofi_cd.cd_name,
           sizeof(sc->sc_audio_device.name));
       strlcpy(sc->sc_audio_device.version, versionstr,
           sizeof(sc->sc_audio_device.version));
       strlcpy(sc->sc_audio_device.config, device_xname(self),
           sizeof(sc->sc_audio_device.config));

       audio_attach_mi(&arcofi_hw_if, sc, self);
       return;

error:
       arcofi_write(sc, ARCOFI_ID, 0);
       aprint_error("%s: %02x command failed, error %d\n",
           __func__, op, rc);
       mutex_destroy(&sc->sc_intr_lock);
       mutex_destroy(&sc->sc_lock);
}