root/usr/src/uts/sun/io/audio/drv/audiocs/audio_4231.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


/*
 * audiocs Audio Driver
 *
 * This Audio Driver controls the Crystal CS4231 Codec used on many SPARC
 * platforms. It does not support the CS4231 on Power PCs or x86 PCs. It
 * does support two different DMA engines, the APC and EB2. The code for
 * those DMA engines is split out and a well defined, but private, interface
 * is used to control those DMA engines.
 *
 * For some reason setting the CS4231's registers doesn't always
 * succeed.  Therefore every time we set a register we always read it
 * back to make sure it was set. If not we wait a little while and
 * then try again. This is all taken care of in the routines
 * audiocs_put_index() and audiocs_sel_index() and the macros ORIDX()
 * and ANDIDX(). We don't worry about the status register because it
 * is cleared by writing anything to it.  So it doesn't matter what
 * the value written is.
 *
 * This driver supports suspending and resuming. A suspend just stops playing
 * and recording. The play DMA buffers end up getting thrown away, but when
 * you shut down the machine there is a break in the audio anyway, so they
 * won't be missed and it isn't worth the effort to save them. When we resume
 * we always start playing and recording. If they aren't needed they get
 * shut off by the mixer.
 *
 * Power management is supported by this driver.
 *
 *      NOTE: This module depends on drv/audio being loaded first.
 */

#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/stropts.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/note.h>
#include <sys/audio/audio_driver.h>
#include "audio_4231.h"

/*
 * Module linkage routines for the kernel
 */
static int audiocs_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
static int audiocs_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
static int audiocs_ddi_power(dev_info_t *, int, int);

/*
 * Entry point routine prototypes
 */
static int audiocs_open(void *, int, unsigned *, caddr_t *);
static void audiocs_close(void *);
static int audiocs_start(void *);
static void audiocs_stop(void *);
static int audiocs_format(void *);
static int audiocs_channels(void *);
static int audiocs_rate(void *);
static uint64_t audiocs_count(void *);
static void audiocs_sync(void *, unsigned);

/*
 * Control callbacks.
 */
static int audiocs_get_value(void *, uint64_t *);
static int audiocs_set_ogain(void *, uint64_t);
static int audiocs_set_igain(void *, uint64_t);
static int audiocs_set_mgain(void *, uint64_t);
static int audiocs_set_inputs(void *, uint64_t);
static int audiocs_set_outputs(void *, uint64_t);
static int audiocs_set_micboost(void *, uint64_t);

/* Local Routines */
static int audiocs_resume(dev_info_t *);
static int audiocs_attach(dev_info_t *);
static int audiocs_detach(dev_info_t *);
static int audiocs_suspend(dev_info_t *);

static void audiocs_destroy(CS_state_t *);
static int audiocs_init_state(CS_state_t *);
static int audiocs_chip_init(CS_state_t *);
static int audiocs_alloc_engine(CS_state_t *, int);
static void audiocs_free_engine(CS_engine_t *);
static void audiocs_get_ports(CS_state_t *);
static void audiocs_configure_input(CS_state_t *);
static void audiocs_configure_output(CS_state_t *);
static CS_ctrl_t *audiocs_alloc_ctrl(CS_state_t *, uint32_t, uint64_t);
static void audiocs_free_ctrl(CS_ctrl_t *);
static int audiocs_add_controls(CS_state_t *);
static void audiocs_del_controls(CS_state_t *);
static void audiocs_power_up(CS_state_t *);
static void audiocs_power_down(CS_state_t *);
static int audiocs_poll_ready(CS_state_t *);
#ifdef  DEBUG
static void audiocs_put_index(CS_state_t *,  uint8_t, uint8_t, int);
static void audiocs_sel_index(CS_state_t *, uint8_t, int);
#define SELIDX(s, idx)          audiocs_sel_index(s, idx, __LINE__)
#define PUTIDX(s, val, mask)    audiocs_put_index(s, val, mask, __LINE__)
#else
static void audiocs_put_index(CS_state_t *,  uint8_t, uint8_t);
static void audiocs_sel_index(CS_state_t *, uint8_t);
#define SELIDX(s, idx)          audiocs_sel_index(s, idx)
#define PUTIDX(s, val, mask)    audiocs_put_index(s, val, mask)
#endif
#define GETIDX(s)               ddi_get8((handle), &CS4231_IDR)

#define ORIDX(s, val, mask)                                             \
        PUTIDX(s,                                                       \
            (ddi_get8((handle), &CS4231_IDR) | (uint8_t)(val)),         \
            (uint8_t)(mask))

#define ANDIDX(s, val, mask)                                            \
        PUTIDX(s, (ddi_get8((handle), &CS4231_IDR) & (uint8_t)(val)),   \
            (uint8_t)(mask))

static audio_engine_ops_t audiocs_engine_ops = {
        AUDIO_ENGINE_VERSION,
        audiocs_open,
        audiocs_close,
        audiocs_start,
        audiocs_stop,
        audiocs_count,
        audiocs_format,
        audiocs_channels,
        audiocs_rate,
        audiocs_sync,
        NULL,
        NULL,
        NULL,
};

#define OUTPUT_SPEAKER          0
#define OUTPUT_HEADPHONES       1
#define OUTPUT_LINEOUT          2

static const char *audiocs_outputs[] = {
        AUDIO_PORT_SPEAKER,
        AUDIO_PORT_HEADPHONES,
        AUDIO_PORT_LINEOUT,
        NULL
};

#define INPUT_MIC               0
#define INPUT_LINEIN            1
#define INPUT_STEREOMIX         2
#define INPUT_CD                3

static const char *audiocs_inputs[] = {
        AUDIO_PORT_MIC,
        AUDIO_PORT_LINEIN,
        AUDIO_PORT_STEREOMIX,
        AUDIO_PORT_CD,
        NULL
};

/*
 * Global variables, but viewable only by this file.
 */

/* play gain array, converts linear gain to 64 steps of log10 gain */
static uint8_t cs4231_atten[] = {
        0x3f,   0x3e,   0x3d,   0x3c,   0x3b,   /* [000] -> [004] */
        0x3a,   0x39,   0x38,   0x37,   0x36,   /* [005] -> [009] */
        0x35,   0x34,   0x33,   0x32,   0x31,   /* [010] -> [014] */
        0x30,   0x2f,   0x2e,   0x2d,   0x2c,   /* [015] -> [019] */
        0x2b,   0x2a,   0x29,   0x29,   0x28,   /* [020] -> [024] */
        0x28,   0x27,   0x27,   0x26,   0x26,   /* [025] -> [029] */
        0x25,   0x25,   0x24,   0x24,   0x23,   /* [030] -> [034] */
        0x23,   0x22,   0x22,   0x21,   0x21,   /* [035] -> [039] */
        0x20,   0x20,   0x1f,   0x1f,   0x1f,   /* [040] -> [044] */
        0x1e,   0x1e,   0x1e,   0x1d,   0x1d,   /* [045] -> [049] */
        0x1d,   0x1c,   0x1c,   0x1c,   0x1b,   /* [050] -> [054] */
        0x1b,   0x1b,   0x1a,   0x1a,   0x1a,   /* [055] -> [059] */
        0x1a,   0x19,   0x19,   0x19,   0x19,   /* [060] -> [064] */
        0x18,   0x18,   0x18,   0x18,   0x17,   /* [065] -> [069] */
        0x17,   0x17,   0x17,   0x16,   0x16,   /* [070] -> [074] */
        0x16,   0x16,   0x16,   0x15,   0x15,   /* [075] -> [079] */
        0x15,   0x15,   0x15,   0x14,   0x14,   /* [080] -> [084] */
        0x14,   0x14,   0x14,   0x13,   0x13,   /* [085] -> [089] */
        0x13,   0x13,   0x13,   0x12,   0x12,   /* [090] -> [094] */
        0x12,   0x12,   0x12,   0x12,   0x11,   /* [095] -> [099] */
        0x11,   0x11,   0x11,   0x11,   0x11,   /* [100] -> [104] */
        0x10,   0x10,   0x10,   0x10,   0x10,   /* [105] -> [109] */
        0x10,   0x0f,   0x0f,   0x0f,   0x0f,   /* [110] -> [114] */
        0x0f,   0x0f,   0x0e,   0x0e,   0x0e,   /* [114] -> [119] */
        0x0e,   0x0e,   0x0e,   0x0e,   0x0d,   /* [120] -> [124] */
        0x0d,   0x0d,   0x0d,   0x0d,   0x0d,   /* [125] -> [129] */
        0x0d,   0x0c,   0x0c,   0x0c,   0x0c,   /* [130] -> [134] */
        0x0c,   0x0c,   0x0c,   0x0b,   0x0b,   /* [135] -> [139] */
        0x0b,   0x0b,   0x0b,   0x0b,   0x0b,   /* [140] -> [144] */
        0x0b,   0x0a,   0x0a,   0x0a,   0x0a,   /* [145] -> [149] */
        0x0a,   0x0a,   0x0a,   0x0a,   0x09,   /* [150] -> [154] */
        0x09,   0x09,   0x09,   0x09,   0x09,   /* [155] -> [159] */
        0x09,   0x09,   0x08,   0x08,   0x08,   /* [160] -> [164] */
        0x08,   0x08,   0x08,   0x08,   0x08,   /* [165] -> [169] */
        0x08,   0x07,   0x07,   0x07,   0x07,   /* [170] -> [174] */
        0x07,   0x07,   0x07,   0x07,   0x07,   /* [175] -> [179] */
        0x06,   0x06,   0x06,   0x06,   0x06,   /* [180] -> [184] */
        0x06,   0x06,   0x06,   0x06,   0x05,   /* [185] -> [189] */
        0x05,   0x05,   0x05,   0x05,   0x05,   /* [190] -> [194] */
        0x05,   0x05,   0x05,   0x05,   0x04,   /* [195] -> [199] */
        0x04,   0x04,   0x04,   0x04,   0x04,   /* [200] -> [204] */
        0x04,   0x04,   0x04,   0x04,   0x03,   /* [205] -> [209] */
        0x03,   0x03,   0x03,   0x03,   0x03,   /* [210] -> [214] */
        0x03,   0x03,   0x03,   0x03,   0x03,   /* [215] -> [219] */
        0x02,   0x02,   0x02,   0x02,   0x02,   /* [220] -> [224] */
        0x02,   0x02,   0x02,   0x02,   0x02,   /* [225] -> [229] */
        0x02,   0x01,   0x01,   0x01,   0x01,   /* [230] -> [234] */
        0x01,   0x01,   0x01,   0x01,   0x01,   /* [235] -> [239] */
        0x01,   0x01,   0x01,   0x00,   0x00,   /* [240] -> [244] */
        0x00,   0x00,   0x00,   0x00,   0x00,   /* [245] -> [249] */
        0x00,   0x00,   0x00,   0x00,   0x00,   /* [250] -> [254] */
        0x00                                    /* [255] */
};

/*
 * STREAMS Structures
 */

/*
 * DDI Structures
 */

/* Device operations structure */
static struct dev_ops audiocs_dev_ops = {
        DEVO_REV,                       /* devo_rev */
        0,                              /* devo_refcnt */
        NULL,                           /* devo_getinfo */
        nulldev,                        /* devo_identify - obsolete */
        nulldev,                        /* devo_probe - not needed */
        audiocs_ddi_attach,             /* devo_attach */
        audiocs_ddi_detach,             /* devo_detach */
        nodev,                          /* devo_reset */
        NULL,                           /* devi_cb_ops */
        NULL,                           /* devo_bus_ops */
        audiocs_ddi_power,              /* devo_power */
        ddi_quiesce_not_supported,      /* devo_quiesce */
};

/* Linkage structure for loadable drivers */
static struct modldrv audiocs_modldrv = {
        &mod_driverops,         /* drv_modops */
        CS4231_MOD_NAME,        /* drv_linkinfo */
        &audiocs_dev_ops        /* drv_dev_ops */
};

/* Module linkage structure */
static struct modlinkage audiocs_modlinkage = {
        MODREV_1,                       /* ml_rev */
        (void *)&audiocs_modldrv,       /* ml_linkage */
        NULL                            /* NULL terminates the list */
};


/* *******  Loadable Module Configuration Entry Points  ********************* */

/*
 * _init()
 *
 * Description:
 *      Implements _init(9E).
 *
 * Returns:
 *      mod_install() status, see mod_install(9f)
 */
int
_init(void)
{
        int     rv;

        audio_init_ops(&audiocs_dev_ops, CS4231_NAME);

        if ((rv = mod_install(&audiocs_modlinkage)) != 0) {
                audio_fini_ops(&audiocs_dev_ops);
        }

        return (rv);
}

/*
 * _fini()
 *
 * Description:
 *      Implements _fini(9E).
 *
 * Returns:
 *      mod_remove() status, see mod_remove(9f)
 */
int
_fini(void)
{
        int     rv;

        if ((rv = mod_remove(&audiocs_modlinkage)) == 0) {
                audio_fini_ops(&audiocs_dev_ops);
        }

        return (rv);
}

/*
 * _info()
 *
 * Description:
 *      Implements _info(9E).
 *
 * Arguments:
 *      modinfo *modinfop       Pointer to the opaque modinfo structure
 *
 * Returns:
 *      mod_info() status, see mod_info(9f)
 */
int
_info(struct modinfo *modinfop)
{
        return (mod_info(&audiocs_modlinkage, modinfop));
}


/* *******  Driver Entry Points  ******************************************** */

/*
 * audiocs_ddi_attach()
 *
 * Description:
 *      Implement attach(9e).
 *
 * Arguments:
 *      dev_info_t      *dip    Pointer to the device's dev_info struct
 *      ddi_attach_cmd_t cmd    Attach command
 *
 * Returns:
 *      DDI_SUCCESS             The driver was initialized properly
 *      DDI_FAILURE             The driver couldn't be initialized properly
 */
static int
audiocs_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
        switch (cmd) {
        case DDI_ATTACH:
                return (audiocs_attach(dip));

        case DDI_RESUME:
                return (audiocs_resume(dip));

        default:
                return (DDI_FAILURE);
        }
}

/*
 * audiocs_ddi_detach()
 *
 * Description:
 *      Implement detach(9e).
 *
 * Arguments:
 *      dev_info_t      *dip    Pointer to the device's dev_info struct
 *      ddi_detach_cmd_t cmd    Detach command
 *
 * Returns:
 *      DDI_SUCCESS             Success.
 *      DDI_FAILURE             Failure.
 */
static int
audiocs_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
        switch (cmd) {
        case DDI_DETACH:
                return (audiocs_detach(dip));

        case DDI_SUSPEND:
                return (audiocs_suspend(dip));

        default:
                return (DDI_FAILURE);
        }
}

/*
 * audiocs_ddi_power()
 *
 * Description:
 *      Implements power(9E).
 *
 * Arguments:
 *      def_info_t      *dip            Ptr to the device's dev_info structure
 *      int             component       Which component to power up/down
 *      int             level           The power level for the component
 *
 * Returns:
 *      DDI_SUCCESS             Power level changed, we always succeed
 */
static int
audiocs_ddi_power(dev_info_t *dip, int component, int level)
{
        CS_state_t              *state;

        if (component != CS4231_COMPONENT)
                return (DDI_FAILURE);

        /* get the state structure */
        state = ddi_get_driver_private(dip);

        ASSERT(!mutex_owned(&state->cs_lock));

        /* make sure we have some work to do */
        mutex_enter(&state->cs_lock);

        /*
         * We don't do anything if we're suspended.  Suspend/resume diddles
         * with power anyway.
         */
        if (!state->cs_suspended) {

                /* check the level change to see what we need to do */
                if (level == CS4231_PWR_OFF && state->cs_powered) {

                        /* power down and save the state */
                        audiocs_power_down(state);
                        state->cs_powered = B_FALSE;

                } else if (level == CS4231_PWR_ON && !state->cs_powered) {

                        /* power up */
                        audiocs_power_up(state);
                        state->cs_powered = B_TRUE;
                }
        }

        mutex_exit(&state->cs_lock);

        ASSERT(!mutex_owned(&state->cs_lock));

        return (DDI_SUCCESS);
}

/* ******* Local Routines *************************************************** */

static void
audiocs_destroy(CS_state_t *state)
{
        if (state == NULL)
                return;

        for (int i = CS4231_PLAY; i <= CS4231_REC; i++) {
                audiocs_free_engine(state->cs_engines[i]);
        }
        audiocs_del_controls(state);

        if (state->cs_adev) {
                audio_dev_free(state->cs_adev);
        }

        /* unmap the registers */
        CS4231_DMA_UNMAP_REGS(state);

        /* destroy the state mutex */
        mutex_destroy(&state->cs_lock);
        kmem_free(state, sizeof (*state));
}

/*
 * audiocs_attach()
 *
 * Description:
 *      Attach an instance of the CS4231 driver. This routine does the device
 *      dependent attach tasks.  When it is complete it calls
 *      audio_dev_register() to register with the framework.
 *
 * Arguments:
 *      dev_info_t      *dip    Pointer to the device's dev_info struct
 *
 * Returns:
 *      DDI_SUCCESS             The driver was initialized properly
 *      DDI_FAILURE             The driver couldn't be initialized properly
 */
static int
audiocs_attach(dev_info_t *dip)
{
        CS_state_t              *state;
        audio_dev_t             *adev;

        /* allocate the state structure */
        state = kmem_zalloc(sizeof (*state), KM_SLEEP);
        state->cs_dip = dip;
        ddi_set_driver_private(dip, state);

        /* now fill it in, initialize the state mutexs first */
        mutex_init(&state->cs_lock, NULL, MUTEX_DRIVER, NULL);

        /*
         * audio state initialization... should always succeed,
         * framework will message failure.
         */
        if ((state->cs_adev = audio_dev_alloc(dip, 0)) == NULL) {
                goto error;
        }
        adev = state->cs_adev;
        audio_dev_set_description(adev, CS_DEV_CONFIG_ONBRD1);
        audio_dev_add_info(adev, "Legacy codec: Crystal Semiconductor CS4231");

        /* initialize the audio state structures */
        if ((audiocs_init_state(state)) == DDI_FAILURE) {
                audio_dev_warn(adev, "init_state() failed");
                goto error;
        }

        mutex_enter(&state->cs_lock);

        /* initialize the audio chip */
        if ((audiocs_chip_init(state)) == DDI_FAILURE) {
                mutex_exit(&state->cs_lock);
                audio_dev_warn(adev, "chip_init() failed");
                goto error;
        }
        /* chip init will have powered us up */
        state->cs_powered = B_TRUE;

        mutex_exit(&state->cs_lock);

        /* finally register with framework to kick everything off */
        if (audio_dev_register(state->cs_adev) != DDI_SUCCESS) {
                audio_dev_warn(state->cs_adev, "unable to register audio dev");
        }

        /* everything worked out, so report the device */
        ddi_report_dev(dip);

        return (DDI_SUCCESS);

error:
        audiocs_destroy(state);
        return (DDI_FAILURE);
}

/*
 * audiocs_resume()
 *
 * Description:
 *      Resume a suspended device instance.
 *
 * Arguments:
 *      dev_info_t      *dip    Pointer to the device's dev_info struct
 *
 * Returns:
 *      DDI_SUCCESS             The driver was initialized properly
 *      DDI_FAILURE             The driver couldn't be initialized properly
 */
static int
audiocs_resume(dev_info_t *dip)
{
        CS_state_t              *state;
        audio_dev_t             *adev;

        /* we've already allocated the state structure so get ptr */
        state = ddi_get_driver_private(dip);
        adev = state->cs_adev;

        ASSERT(dip == state->cs_dip);
        ASSERT(!mutex_owned(&state->cs_lock));

        /* mark the Codec busy -- this should keep power(9e) away */
        (void) pm_busy_component(state->cs_dip, CS4231_COMPONENT);

        /* power it up */
        audiocs_power_up(state);
        state->cs_powered = B_TRUE;

        mutex_enter(&state->cs_lock);

        /* initialize the audio chip */
        if ((audiocs_chip_init(state)) == DDI_FAILURE) {
                mutex_exit(&state->cs_lock);
                audio_dev_warn(adev, "chip_init() failed");
                (void) pm_idle_component(state->cs_dip, CS4231_COMPONENT);
                return (DDI_FAILURE);
        }

        state->cs_suspended = B_FALSE;

        mutex_exit(&state->cs_lock);

        /*
         * We have already powered up the chip, but this alerts the
         * framework to the fact.
         */
        (void) pm_raise_power(dip, CS4231_COMPONENT, CS4231_PWR_ON);
        (void) pm_idle_component(state->cs_dip, CS4231_COMPONENT);

        audio_dev_resume(state->cs_adev);

        return (DDI_SUCCESS);
}

/*
 * audiocs_detach()
 *
 * Description:
 *      Detach an instance of the CS4231 driver.
 *
 * Arguments:
 *      dev_info_t      *dip    Pointer to the device's dev_info struct
 *
 * Returns:
 *      DDI_SUCCESS             The driver was detached
 *      DDI_FAILURE             The driver couldn't be detached (busy)
 */
static int
audiocs_detach(dev_info_t *dip)
{
        CS_state_t              *state;
        audio_dev_t             *adev;
        ddi_acc_handle_t        handle;

        /* get the state structure */
        state = ddi_get_driver_private(dip);
        handle = CODEC_HANDLE;
        adev = state->cs_adev;

        /* don't detach if still in use */
        if (audio_dev_unregister(adev) != DDI_SUCCESS) {
                return (DDI_FAILURE);
        }

        if (state->cs_powered) {
                /*
                 * Make sure the Codec and DMA engine are off.
                 */
                SELIDX(state, INTC_REG);
                ANDIDX(state, ~(INTC_PEN|INTC_CEN), INTC_VALID_MASK);

                /* make sure the DMA engine isn't going to do anything */
                CS4231_DMA_RESET(state);

                /*
                 * power down the device, no reason to waste power without
                 * a driver
                 */
                (void) pm_lower_power(dip, CS4231_COMPONENT, CS4231_PWR_OFF);
        }

        audiocs_destroy(state);

        return (DDI_SUCCESS);
}

/*
 * audiocs_suspend()
 *
 * Description:
 *      Suspend an instance of the CS4231 driver.
 *
 * Arguments:
 *      dev_info_t      *dip    Pointer to the device's dev_info struct
 *
 * Returns:
 *      DDI_SUCCESS             The driver was detached
 *      DDI_FAILURE             The driver couldn't be detached
 */
static int
audiocs_suspend(dev_info_t *dip)
{
        CS_state_t              *state;

        /* get the state structure */
        state = ddi_get_driver_private(dip);

        mutex_enter(&state->cs_lock);

        ASSERT(!state->cs_suspended);

        audio_dev_suspend(state->cs_adev);

        if (state->cs_powered) {
                /* now we can power down the Codec */
                audiocs_power_down(state);
                state->cs_powered = B_FALSE;
        }
        state->cs_suspended = B_TRUE;   /* stop new ops */
        mutex_exit(&state->cs_lock);

        return (DDI_SUCCESS);
}

#define PLAYCTL (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_PLAY)
#define RECCTL  (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_REC)
#define MONCTL  (AUDIO_CTRL_FLAG_RW | AUDIO_CTRL_FLAG_MONITOR)
#define PCMVOL  (PLAYCTL | AUDIO_CTRL_FLAG_PCMVOL)
#define MAINVOL (PLAYCTL | AUDIO_CTRL_FLAG_MAINVOL)
#define RECVOL  (RECCTL | AUDIO_CTRL_FLAG_RECVOL)
#define MONVOL  (MONCTL | AUDIO_CTRL_FLAG_MONVOL)

/*
 * audiocs_alloc_ctrl
 *
 * Description:
 *      Allocates a control structure for the audio mixer.
 *
 * Arguments:
 *      CS_state_t      *state          Device soft state.
 *      uint32_t        num             Control number to allocate.
 *      uint64_t        val             Initial value.
 *
 * Returns:
 *      Pointer to newly allocated CS_ctrl_t structure.
 */
static CS_ctrl_t *
audiocs_alloc_ctrl(CS_state_t *state, uint32_t num, uint64_t val)
{
        audio_ctrl_desc_t       desc;
        audio_ctrl_wr_t         fn;
        CS_ctrl_t               *cc;

        cc = kmem_zalloc(sizeof (*cc), KM_SLEEP);
        cc->cc_state = state;
        cc->cc_num = num;

        bzero(&desc, sizeof (desc));

        switch (num) {
        case CTL_VOLUME:
                desc.acd_name = AUDIO_CTRL_ID_VOLUME;
                desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
                desc.acd_minvalue = 0;
                desc.acd_maxvalue = 100;
                desc.acd_flags = PCMVOL;
                fn = audiocs_set_ogain;
                break;

        case CTL_IGAIN:
                desc.acd_name = AUDIO_CTRL_ID_RECGAIN;
                desc.acd_type = AUDIO_CTRL_TYPE_STEREO;
                desc.acd_minvalue = 0;
                desc.acd_maxvalue = 100;
                desc.acd_flags = RECVOL;
                fn = audiocs_set_igain;
                break;

        case CTL_MGAIN:
                desc.acd_name = AUDIO_CTRL_ID_MONGAIN;
                desc.acd_type = AUDIO_CTRL_TYPE_MONO;
                desc.acd_minvalue = 0;
                desc.acd_maxvalue = 100;
                desc.acd_flags = MONVOL;
                fn = audiocs_set_mgain;
                break;

        case CTL_INPUTS:
                desc.acd_name = AUDIO_CTRL_ID_RECSRC;
                desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
                desc.acd_minvalue = state->cs_imask;
                desc.acd_maxvalue = state->cs_imask;
                desc.acd_flags = RECCTL;
                for (int i = 0; audiocs_inputs[i]; i++) {
                        desc.acd_enum[i] = audiocs_inputs[i];
                }
                fn = audiocs_set_inputs;

                break;

        case CTL_OUTPUTS:
                desc.acd_name = AUDIO_CTRL_ID_OUTPUTS;
                desc.acd_type = AUDIO_CTRL_TYPE_ENUM;
                desc.acd_minvalue = state->cs_omod;
                desc.acd_maxvalue = state->cs_omask;
                desc.acd_flags = PLAYCTL | AUDIO_CTRL_FLAG_MULTI;
                for (int i = 0; audiocs_outputs[i]; i++) {
                        desc.acd_enum[i] = audiocs_outputs[i];
                }
                fn = audiocs_set_outputs;
                break;

        case CTL_MICBOOST:
                desc.acd_name = AUDIO_CTRL_ID_MICBOOST;
                desc.acd_type = AUDIO_CTRL_TYPE_BOOLEAN;
                desc.acd_minvalue = 0;
                desc.acd_maxvalue = 1;
                desc.acd_flags = RECCTL;
                fn = audiocs_set_micboost;
                break;
        }

        cc->cc_val = val;
        cc->cc_ctrl = audio_dev_add_control(state->cs_adev, &desc,
            audiocs_get_value, fn, cc);

        return (cc);
}

/*
 * audiocs_free_ctrl
 *
 * Description:
 *      Frees a control and all resources associated with it.
 *
 * Arguments:
 *      CS_ctrl_t       *cc     Pointer to control structure.
 */
static void
audiocs_free_ctrl(CS_ctrl_t *cc)
{
        if (cc == NULL)
                return;
        if (cc->cc_ctrl)
                audio_dev_del_control(cc->cc_ctrl);
        kmem_free(cc, sizeof (*cc));
}

/*
 * audiocs_add_controls
 *
 * Description:
 *      Allocates and registers all controls for this device.
 *
 * Arguments:
 *      CS_state_t      *state          Device soft state.
 *
 * Returns:
 *      DDI_SUCCESS     All controls added and registered
 *      DDI_FAILURE     At least one control was not added or registered.
 */
static int
audiocs_add_controls(CS_state_t *state)
{
#define ADD_CTRL(CTL, ID, VAL)                                          \
        state->cs_##CTL = audiocs_alloc_ctrl(state, ID, VAL);           \
        if (state->cs_##CTL == NULL) {                                  \
                audio_dev_warn(state->cs_adev,                          \
                    "unable to allocate %s control", #ID);              \
                return (DDI_FAILURE);                                   \
        }

        ADD_CTRL(ogain, CTL_VOLUME, 0x4b4b);
        ADD_CTRL(igain, CTL_IGAIN, 0x3232);
        ADD_CTRL(mgain, CTL_MGAIN, 0);
        ADD_CTRL(micboost, CTL_MICBOOST, 0);
        ADD_CTRL(outputs, CTL_OUTPUTS, (state->cs_omask & ~state->cs_omod) |
            (1U << OUTPUT_SPEAKER));
        ADD_CTRL(inputs, CTL_INPUTS, (1U << INPUT_MIC));

        return (DDI_SUCCESS);
}

/*
 * audiocs_del_controls
 *
 * Description:
 *      Unregisters and frees all controls for this device.
 *
 * Arguments:
 *      CS_state_t      *state          Device soft state.
 */
void
audiocs_del_controls(CS_state_t *state)
{
        audiocs_free_ctrl(state->cs_ogain);
        audiocs_free_ctrl(state->cs_igain);
        audiocs_free_ctrl(state->cs_mgain);
        audiocs_free_ctrl(state->cs_micboost);
        audiocs_free_ctrl(state->cs_inputs);
        audiocs_free_ctrl(state->cs_outputs);
}


/*
 * audiocs_chip_init()
 *
 * Description:
 *      Power up the audio core, initialize the audio Codec, prepare the chip
 *      for use.
 *
 * Arguments:
 *      CS_state_t      *state          The device's state structure
 *
 * Returns:
 *      DDI_SUCCESS                     Chip initialized and ready to use
 *      DDI_FAILURE                     Chip not initialized and not ready
 */
static int
audiocs_chip_init(CS_state_t *state)
{
        ddi_acc_handle_t        handle = CODEC_HANDLE;

        /* make sure we are powered up */
        CS4231_DMA_POWER(state, CS4231_PWR_ON);

        CS4231_DMA_RESET(state);

        /* wait for the Codec before we continue */
        if (audiocs_poll_ready(state) == DDI_FAILURE) {
                return (DDI_FAILURE);
        }

        /* activate registers 16 -> 31 */
        SELIDX(state, MID_REG);
        ddi_put8(handle, &CS4231_IDR, MID_MODE2);

        /* now figure out what version we have */
        SELIDX(state, VID_REG);
        if (ddi_get8(handle, &CS4231_IDR) & VID_A) {
                state->cs_revA = B_TRUE;
        } else {
                state->cs_revA = B_FALSE;
        }

        /* get rid of annoying popping by muting the output channels */
        SELIDX(state, LDACO_REG);
        PUTIDX(state, LDACO_LDM | LDACO_MID_GAIN, LDAC0_VALID_MASK);
        SELIDX(state, RDACO_REG);
        PUTIDX(state, RDACO_RDM | RDACO_MID_GAIN, RDAC0_VALID_MASK);

        /* initialize aux input channels to known gain values & muted */
        SELIDX(state, LAUX1_REG);
        PUTIDX(state, LAUX1_LX1M | LAUX1_UNITY_GAIN, LAUX1_VALID_MASK);
        SELIDX(state, RAUX1_REG);
        PUTIDX(state, RAUX1_RX1M | RAUX1_UNITY_GAIN, RAUX1_VALID_MASK);
        SELIDX(state, LAUX2_REG);
        PUTIDX(state, LAUX2_LX2M | LAUX2_UNITY_GAIN, LAUX2_VALID_MASK);
        SELIDX(state, RAUX2_REG);
        PUTIDX(state, RAUX2_RX2M | RAUX2_UNITY_GAIN, RAUX2_VALID_MASK);

        /* initialize aux input channels to known gain values & muted */
        SELIDX(state, LLIC_REG);
        PUTIDX(state, LLIC_LLM | LLIC_UNITY_GAIN, LLIC_VALID_MASK);
        SELIDX(state, RLIC_REG);
        PUTIDX(state, RLIC_RLM | RLIC_UNITY_GAIN, RLIC_VALID_MASK);

        /* program the sample rate, play and capture must be the same */
        SELIDX(state, FSDF_REG | IAR_MCE);
        PUTIDX(state, FS_48000 | PDF_LINEAR16NE | PDF_STEREO, FSDF_VALID_MASK);
        if (audiocs_poll_ready(state) == DDI_FAILURE) {
                return (DDI_FAILURE);
        }

        SELIDX(state, CDF_REG | IAR_MCE);
        PUTIDX(state, CDF_LINEAR16NE | CDF_STEREO, CDF_VALID_MASK);
        if (audiocs_poll_ready(state) == DDI_FAILURE) {
                return (DDI_FAILURE);
        }

        /*
         * Set up the Codec for playback and capture disabled, dual DMA, and
         * playback and capture DMA.
         */
        SELIDX(state, (INTC_REG | IAR_MCE));
        PUTIDX(state, INTC_DDC | INTC_PDMA | INTC_CDMA, INTC_VALID_MASK);
        if (audiocs_poll_ready(state) == DDI_FAILURE) {
                return (DDI_FAILURE);
        }

        /*
         * Turn on the output level bit to be 2.8 Vpp. Also, don't go to 0 on
         * underflow.
         */
        SELIDX(state, AFE1_REG);
        PUTIDX(state, AFE1_OLB, AFE1_VALID_MASK);

        /* turn on the high pass filter if Rev A */
        SELIDX(state, AFE2_REG);
        if (state->cs_revA) {
                PUTIDX(state, AFE2_HPF, AFE2_VALID_MASK);
        } else {
                PUTIDX(state, 0, AFE2_VALID_MASK);
        }


        /* clear the play and capture interrupt flags */
        SELIDX(state, AFS_REG);
        ddi_put8(handle, &CS4231_STATUS, (AFS_RESET_STATUS));

        /* the play and record gains will be set by the audio mixer */

        /* unmute the output */
        SELIDX(state, LDACO_REG);
        ANDIDX(state, ~LDACO_LDM, LDAC0_VALID_MASK);
        SELIDX(state, RDACO_REG);
        ANDIDX(state, ~RDACO_RDM, RDAC0_VALID_MASK);

        /* unmute the mono speaker and mute mono in */
        SELIDX(state, MIOC_REG);
        PUTIDX(state, MIOC_MIM, MIOC_VALID_MASK);

        audiocs_configure_output(state);
        audiocs_configure_input(state);

        return (DDI_SUCCESS);
}

/*
 * audiocs_init_state()
 *
 * Description:
 *      This routine initializes the audio driver's state structure and
 *      maps in the registers. This also includes reading the properties.
 *
 *      CAUTION: This routine maps the registers and initializes a mutex.
 *               Failure cleanup is handled by cs4231_attach(). It is not
 *               handled locally by this routine.
 *
 * Arguments:
 *      CS_state_t      *state          The device's state structure
 *
 * Returns:
 *      DDI_SUCCESS                     State structure initialized
 *      DDI_FAILURE                     State structure not initialized
 */
static int
audiocs_init_state(CS_state_t *state)
{
        audio_dev_t     *adev = state->cs_adev;
        dev_info_t      *dip = state->cs_dip;
        char            *prop_str;
        char            *pm_comp[] = {
                                "NAME=audiocs audio device",
                                "0=off",
                                "1=on" };

        /* set up the pm-components */
        if (ddi_prop_update_string_array(DDI_DEV_T_NONE, dip,
            "pm-components", pm_comp, 3) != DDI_PROP_SUCCESS) {
                audio_dev_warn(adev, "couldn't create pm-components property");
                return (DDI_FAILURE);
        }

        /* figure out which DMA engine hardware we have */
        if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
            "dma-model", &prop_str) == DDI_PROP_SUCCESS) {
                if (strcmp(prop_str, "eb2dma") == 0) {
                        state->cs_dma_engine = EB2_DMA;
                        state->cs_dma_ops = &cs4231_eb2dma_ops;
                } else {
                        state->cs_dma_engine = APC_DMA;
                        state->cs_dma_ops = &cs4231_apcdma_ops;
                }
                ddi_prop_free(prop_str);
        } else {
                state->cs_dma_engine = APC_DMA;
                state->cs_dma_ops = &cs4231_apcdma_ops;
        }

        /* cs_regs, cs_eb2_regs and cs_handles filled in later */

        /* most of what's left is filled in when the registers are mapped */

        audiocs_get_ports(state);

        /* Allocate engines, must be done before register mapping called  */
        if ((audiocs_alloc_engine(state, CS4231_PLAY) != DDI_SUCCESS) ||
            (audiocs_alloc_engine(state, CS4231_REC) != DDI_SUCCESS)) {
                return (DDI_FAILURE);
        }

        /* Map in the registers */
        if (CS4231_DMA_MAP_REGS(state) == DDI_FAILURE) {
                return (DDI_FAILURE);
        }


        /* Allocate and add controls, must be done *after* registers mapped */
        if (audiocs_add_controls(state) != DDI_SUCCESS) {
                return (DDI_FAILURE);
        }

        state->cs_suspended = B_FALSE;
        state->cs_powered = B_FALSE;

        return (DDI_SUCCESS);
}

/*
 * audiocs_get_ports()
 *
 * Description:
 *      Get which audiocs h/w version we have and use this to
 *      determine the input and output ports as well whether or not
 *      the hardware has internal loopbacks or not. We also have three
 *      different ways for the properties to be specified, which we
 *      also need to worry about.
 *
 * Vers Platform(s)     DMA eng.        audio-module**  loopback
 * a    SS-4+/SS-5+     apcdma          no              no
 * b    Ultra-1&2       apcdma          no              yes
 * c    positron        apcdma          no              yes
 * d    PPC - retired
 * e    x86 - retired
 * f    tazmo           eb2dma          Perigee         no
 * g    tazmo           eb2dma          Quark           yes
 * h    darwin+         eb2dma          no              N/A
 *
 * Vers model~          aux1*           aux2*
 * a    N/A             N/A             N/A
 * b    N/A             N/A             N/A
 * c    N/A             N/A             N/A
 * d    retired
 * e    retired
 * f    SUNW,CS4231f    N/A             N/A
 * g    SUNW,CS4231g    N/A             N/A
 * h    SUNW,CS4231h    cdrom           none
 *
 * *   = Replaces internal-loopback for latest property type, can be
 *       set to "cdrom", "loopback", or "none".
 *
 * **  = For plugin audio modules only. Starting with darwin, this
 *       property is replaces by the model property.
 *
 * ~   = Replaces audio-module.
 *
 * +   = Has the capability of having a cable run from the internal
 *       CD-ROM to the audio device.
 *
 * N/A = Not applicable, the property wasn't created for early
 *       platforms, or the property has been retired.
 *
 * NOTE: Older tazmo and quark machines don't have the model property.
 *
 * Arguments:
 *      CS_state_t      *state          The device's state structure
 */
static void
audiocs_get_ports(CS_state_t *state)
{
        dev_info_t      *dip = state->cs_dip;
        audio_dev_t     *adev = state->cs_adev;
        char            *prop_str;

        /* First we set the common ports, etc. */
        state->cs_omask = state->cs_omod =
            (1U << OUTPUT_SPEAKER) |
            (1U << OUTPUT_HEADPHONES) |
            (1U << OUTPUT_LINEOUT);
        state->cs_imask =
            (1U << INPUT_MIC) |
            (1U << INPUT_LINEIN) |
            (1U << INPUT_STEREOMIX);

        /* now we try the new "model" property */
        if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
            "model", &prop_str) == DDI_PROP_SUCCESS) {
                if (strcmp(prop_str, "SUNW,CS4231h") == 0) {
                        /* darwin */
                        audio_dev_set_version(adev, CS_DEV_VERSION_H);
                        state->cs_imask |= (1U << INPUT_CD);
                        state->cs_omod = (1U << OUTPUT_SPEAKER);
                } else if (strcmp(prop_str, "SUNW,CS4231g") == 0) {
                        /* quark audio module */
                        audio_dev_set_version(adev, CS_DEV_VERSION_G);
                        /*
                         * NB: This could do SUNVTS LOOPBACK, but we
                         * don't support it for now... owing to no
                         * support in framework.
                         */
                } else if (strcmp(prop_str, "SUNW,CS4231f") == 0) {
                        /* tazmo */
                        audio_dev_set_version(adev, CS_DEV_VERSION_F);
                } else {
                        audio_dev_set_version(adev, prop_str);
                        audio_dev_warn(adev,
                            "unknown audio model: %s, some parts of "
                            "audio may not work correctly", prop_str);
                }
                ddi_prop_free(prop_str);        /* done with the property */
        } else {        /* now try the older "audio-module" property */
                if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip,
                    DDI_PROP_DONTPASS, "audio-module", &prop_str) ==
                    DDI_PROP_SUCCESS) {
                        switch (*prop_str) {
                        case 'Q':       /* quark audio module */
                                audio_dev_set_version(adev, CS_DEV_VERSION_G);
                                /* See quark comment above about SunVTS */
                                break;
                        case 'P':       /* tazmo */
                                audio_dev_set_version(adev, CS_DEV_VERSION_F);
                                break;
                        default:
                                audio_dev_set_version(adev, prop_str);
                                audio_dev_warn(adev,
                                    "unknown audio module: %s, some "
                                    "parts of audio may not work correctly",
                                    prop_str);
                                break;
                        }
                        ddi_prop_free(prop_str);        /* done with the prop */
                } else {        /* now try heuristics, ;-( */
                        if (ddi_prop_get_int(DDI_DEV_T_ANY, dip,
                            DDI_PROP_DONTPASS, "internal-loopback", B_FALSE)) {
                                if (state->cs_dma_engine == EB2_DMA) {
                                        audio_dev_set_version(adev,
                                            CS_DEV_VERSION_C);
                                } else {
                                        audio_dev_set_version(adev,
                                            CS_DEV_VERSION_B);
                                }
                                /*
                                 * Again, we don't support SunVTS for these
                                 * boards, although we potentially could.
                                 */
                        } else {
                                audio_dev_set_version(adev, CS_DEV_VERSION_A);
                                state->cs_imask |= (1U << INPUT_CD);
                        }
                }
        }
}

/*
 * audiocs_power_up()
 *
 * Description:
 *      Power up the Codec and restore the codec's registers.
 *
 *      NOTE: We don't worry about locking since the only routines
 *              that may call us are attach() and power() Both of
 *              which should be the only threads in the driver.
 *
 * Arguments:
 *      CS_state_t      *state          The device's state structure
 */
static void
audiocs_power_up(CS_state_t *state)
{
        ddi_acc_handle_t        handle = CODEC_HANDLE;
        int                     i;

        /* turn on the Codec */
        CS4231_DMA_POWER(state, CS4231_PWR_ON);

        /* reset the DMA engine(s) */
        CS4231_DMA_RESET(state);

        (void) audiocs_poll_ready(state);

        /*
         * Reload the Codec's registers, the DMA engines will be
         * taken care of when play and record start up again. But
         * first enable registers 16 -> 31.
         */
        SELIDX(state, MID_REG);
        PUTIDX(state, state->cs_save[MID_REG], MID_VALID_MASK);

        for (i = 0; i < CS4231_REGS; i++) {
                /* restore Codec registers */
                SELIDX(state, (i | IAR_MCE));
                ddi_put8(handle, &CS4231_IDR, state->cs_save[i]);
                (void) audiocs_poll_ready(state);
        }
        /* clear MCE bit */
        SELIDX(state, 0);
}

/*
 * audiocs_power_down()
 *
 * Description:
 *      Power down the Codec and save the codec's registers.
 *
 *      NOTE: See the note in cs4231_power_up() about locking.
 *
 * Arguments:
 *      CS_state_t      *state          The device's state structure
 */
static void
audiocs_power_down(CS_state_t *state)
{
        ddi_acc_handle_t        handle;
        int                     i;

        handle = state->cs_handles.cs_codec_hndl;

        /*
         * We are powering down, so we don't need to do a thing with
         * the DMA engines. However, we do need to save the Codec
         * registers.
         */

        for (i = 0; i < CS4231_REGS; i++) {
                /* save Codec regs */
                SELIDX(state, i);
                state->cs_save[i] = ddi_get8(handle, &CS4231_IDR);
        }

        /* turn off the Codec */
        CS4231_DMA_POWER(state, CS4231_PWR_OFF);

}       /* cs4231_power_down() */

/*
 * audiocs_configure_input()
 *
 * Description:
 *      Configure input properties of the mixer (e.g. igain, ports).
 *
 * Arguments:
 *      CS_state_t      *state          The device's state structure
 */
static void
audiocs_configure_input(CS_state_t *state)
{
        uint8_t         l, r;
        uint64_t        inputs;
        uint64_t        micboost;

        ASSERT(mutex_owned(&state->cs_lock));

        inputs = state->cs_inputs->cc_val;
        micboost = state->cs_micboost->cc_val;
        r = (state->cs_igain->cc_val & 0xff);
        l = ((state->cs_igain->cc_val & 0xff00) >> 8);

        /* rescale these for our atten array */
        l = (((uint32_t)l * 255) / 100) & 0xff;
        r = (((uint32_t)r * 255) / 100) & 0xff;

        /* we downshift by 4 bits -- igain only has 16 possible values */
        /* NB: that we do not scale here!  The SADA driver didn't do so. */
        l = l >> 4;
        r = r >> 4;

        if (inputs & (1U << INPUT_MIC)) {
                l |= LADCI_LMIC;
                r |= RADCI_RMIC;
        }
        if (inputs & (1U << INPUT_LINEIN)) {
                l |= LADCI_LLINE;
                r |= RADCI_RLINE;
        }
        if (inputs & (1U << INPUT_CD)) {
                /* note that SunVTS also uses this */
                l |= LADCI_LAUX1;
                r |= RADCI_RAUX1;
        }
        if (inputs & (1U << INPUT_STEREOMIX)) {
                l |= LADCI_LLOOP;
                r |= RADCI_RLOOP;
        }
        if (micboost) {
                l |= LADCI_LMGE;
                r |= RADCI_RMGE;
        }

        SELIDX(state, LADCI_REG);
        PUTIDX(state, l, LADCI_VALID_MASK);

        SELIDX(state, RADCI_REG);
        PUTIDX(state, r, RADCI_VALID_MASK);
}

/*
 * audiocs_configure_output()
 *
 * Description:
 *      Configure output properties of the mixer (e.g. ogain, mgain).
 *
 * Arguments:
 *      CS_state_t      *state          The device's state structure
 */
static void
audiocs_configure_output(CS_state_t *state)
{
        uint64_t                outputs;
        uint8_t                 l, r;
        uint8_t                 rmute, lmute;
        uint8_t                 mgain;
        ddi_acc_handle_t        handle = CODEC_HANDLE;

        rmute = lmute = 0;

        ASSERT(mutex_owned(&state->cs_lock));

        outputs = state->cs_outputs->cc_val;

        /* port selection */
        SELIDX(state, MIOC_REG);
        if (outputs & (1U << OUTPUT_SPEAKER)) {
                ANDIDX(state, ~MIOC_MONO_SPKR_MUTE, MIOC_VALID_MASK);
        } else {
                ORIDX(state, MIOC_MONO_SPKR_MUTE, MIOC_VALID_MASK);
        }
        SELIDX(state, PC_REG);
        if (outputs & (1U << OUTPUT_HEADPHONES)) {
                ANDIDX(state, ~PC_HEADPHONE_MUTE, PC_VALID_MASK);
        } else {
                ORIDX(state, PC_HEADPHONE_MUTE, PC_VALID_MASK);
        }
        SELIDX(state, PC_REG);
        if (outputs & (1U << OUTPUT_LINEOUT)) {
                ANDIDX(state, ~PC_LINE_OUT_MUTE, PC_VALID_MASK);
        } else {
                ORIDX(state, PC_LINE_OUT_MUTE, PC_VALID_MASK);
        }

        /* monitor gain */
        mgain = cs4231_atten[((state->cs_mgain->cc_val * 255) / 100) & 0xff];
        SELIDX(state, LC_REG);
        if (mgain == 0) {
                /* disable loopbacks when gain == 0 */
                PUTIDX(state, LC_OFF, LC_VALID_MASK);
        } else {
                /* we use cs4231_atten[] to linearize attenuation */
                PUTIDX(state, (mgain << 2) | LC_LBE, LC_VALID_MASK);
        }

        /* output gain */
        l = ((state->cs_ogain->cc_val >> 8) & 0xff);
        r = (state->cs_ogain->cc_val & 0xff);
        if (l == 0) {
                lmute = LDACO_LDM;
        }
        if (r == 0) {
                rmute = RDACO_RDM;
        }

        /* rescale these for our atten array */
        l = cs4231_atten[(((uint32_t)l * 255) / 100) & 0xff] | lmute;
        r = cs4231_atten[(((uint32_t)r * 255) / 100) & 0xff] | rmute;

        SELIDX(state, LDACO_REG);
        PUTIDX(state, l, LDAC0_VALID_MASK);
        SELIDX(state, RDACO_REG);
        PUTIDX(state, r, RDAC0_VALID_MASK);
}

/*
 * audiocs_get_value()
 *
 * Description:
 *      Get a control value
 *
 * Arguments:
 *      void            *arg            The device's state structure
 *      uint64_t        *valp           Pointer to store value.
 *
 * Returns:
 *      0               The Codec parameter has been retrieved.
 */
static int
audiocs_get_value(void *arg, uint64_t *valp)
{
        CS_ctrl_t               *cc = arg;
        CS_state_t              *state = cc->cc_state;

        mutex_enter(&state->cs_lock);
        *valp = cc->cc_val;
        mutex_exit(&state->cs_lock);
        return (0);
}


/*
 * audiocs_set_ogain()
 *
 * Description:
 *      Set the play gain.
 *
 * Arguments:
 *      void            *arg            The device's state structure
 *      uint64_t        val             The gain to set (both left and right)
 *
 * Returns:
 *      0               The Codec parameter has been set
 */
static int
audiocs_set_ogain(void *arg, uint64_t val)
{
        CS_ctrl_t               *cc = arg;
        CS_state_t              *state = cc->cc_state;

        if ((val & ~0xffff) ||
            ((val & 0xff) > 100) ||
            (((val & 0xff00) >> 8) > 100))
                return (EINVAL);

        mutex_enter(&state->cs_lock);
        cc->cc_val = val;
        audiocs_configure_output(state);
        mutex_exit(&state->cs_lock);
        return (0);
}

/*
 * audiocs_set_micboost()
 *
 * Description:
 *      Set the 20 dB microphone boost.
 *
 * Arguments:
 *      void            *arg            The device's state structure
 *      uint64_t        val             The 1 to enable, 0 to disable.
 *
 * Returns:
 *      0               The Codec parameter has been set
 */
static int
audiocs_set_micboost(void *arg, uint64_t val)
{
        CS_ctrl_t       *cc = arg;
        CS_state_t      *state = cc->cc_state;

        mutex_enter(&state->cs_lock);
        cc->cc_val = val ? B_TRUE : B_FALSE;
        audiocs_configure_input(state);
        mutex_exit(&state->cs_lock);
        return (0);
}

/*
 * audiocs_set_igain()
 *
 * Description:
 *      Set the record gain.
 *
 * Arguments:
 *      void            *arg            The device's state structure
 *      uint64_t        val             The gain to set (both left and right)
 *
 * Returns:
 *      0               The Codec parameter has been set
 */
static int
audiocs_set_igain(void *arg, uint64_t val)
{
        CS_ctrl_t       *cc = arg;
        CS_state_t      *state = cc->cc_state;

        if ((val & ~0xffff) ||
            ((val & 0xff) > 100) ||
            (((val & 0xff00) >> 8) > 100))
                return (EINVAL);

        mutex_enter(&state->cs_lock);
        cc->cc_val = val;
        audiocs_configure_input(state);
        mutex_exit(&state->cs_lock);

        return (0);
}

/*
 * audiocs_set_inputs()
 *
 * Description:
 *      Set the input ports.
 *
 * Arguments:
 *      void            *arg            The device's state structure
 *      uint64_t        val             The mask of output ports.
 *
 * Returns:
 *      0               The Codec parameter has been set
 */
static int
audiocs_set_inputs(void *arg, uint64_t val)
{
        CS_ctrl_t       *cc = arg;
        CS_state_t      *state = cc->cc_state;

        if (val & ~(state->cs_imask))
                return (EINVAL);

        mutex_enter(&state->cs_lock);
        cc->cc_val = val;
        audiocs_configure_input(state);
        mutex_exit(&state->cs_lock);

        return (0);
}

/*
 * audiocs_set_outputs()
 *
 * Description:
 *      Set the output ports.
 *
 * Arguments:
 *      void            *arg            The device's state structure
 *      uint64_t        val             The mask of input ports.
 *
 * Returns:
 *      0               The Codec parameter has been set
 */
static int
audiocs_set_outputs(void *arg, uint64_t val)
{
        CS_ctrl_t       *cc = arg;
        CS_state_t      *state = cc->cc_state;

        if ((val & ~(state->cs_omod)) !=
            (state->cs_omask & ~state->cs_omod))
                return (EINVAL);

        mutex_enter(&state->cs_lock);
        cc->cc_val = val;
        audiocs_configure_output(state);
        mutex_exit(&state->cs_lock);

        return (0);
}

/*
 * audiocs_set_mgain()
 *
 * Description:
 *      Set the monitor gain.
 *
 * Arguments:
 *      void            *arg            The device's state structure
 *      uint64_t        val             The gain to set (monoaural).)
 *
 * Returns:
 *      0               The Codec parameter has been set
 */
static int
audiocs_set_mgain(void *arg, uint64_t gain)
{
        CS_ctrl_t       *cc = arg;
        CS_state_t      *state = cc->cc_state;

        if (gain > 100)
                return (EINVAL);

        mutex_enter(&state->cs_lock);
        cc->cc_val = gain;
        audiocs_configure_output(state);
        mutex_exit(&state->cs_lock);

        return (0);
}

/*
 * audiocs_open()
 *
 * Description:
 *      Opens a DMA engine for use.
 *
 * Arguments:
 *      void            *arg            The DMA engine to set up
 *      int             flag            Open flags
 *      unsigned        *nframesp       Receives number of frames
 *      caddr_t         *bufp           Receives kernel data buffer
 *
 * Returns:
 *      0       on success
 *      errno   on failure
 */
static int
audiocs_open(void *arg, int flag, unsigned *nframesp, caddr_t *bufp)
{
        CS_engine_t     *eng = arg;
        CS_state_t      *state = eng->ce_state;
        dev_info_t      *dip = state->cs_dip;

        _NOTE(ARGUNUSED(flag));

        (void) pm_busy_component(dip, CS4231_COMPONENT);
        if (pm_raise_power(dip, CS4231_COMPONENT, CS4231_PWR_ON) ==
            DDI_FAILURE) {

                /* match the busy call above */
                (void) pm_idle_component(dip, CS4231_COMPONENT);

                audio_dev_warn(state->cs_adev, "power up failed");
        }

        eng->ce_count = 0;
        *nframesp = CS4231_NFRAMES;
        *bufp = eng->ce_kaddr;

        return (0);
}

/*
 * audiocs_close()
 *
 * Description:
 *      Closes an audio DMA engine that was previously opened.  Since
 *      nobody is using it, we take this opportunity to possibly power
 *      down the entire device.
 *
 * Arguments:
 *      void    *arg            The DMA engine to shut down
 */
static void
audiocs_close(void *arg)
{
        CS_engine_t     *eng = arg;
        CS_state_t      *state = eng->ce_state;

        (void) pm_idle_component(state->cs_dip, CS4231_COMPONENT);
}

/*
 * audiocs_stop()
 *
 * Description:
 *      This is called by the framework to stop an engine that is
 *      transferring data.
 *
 * Arguments:
 *      void    *arg            The DMA engine to stop
 */
static void
audiocs_stop(void *arg)
{
        CS_engine_t             *eng = arg;
        CS_state_t              *state = eng->ce_state;
        ddi_acc_handle_t        handle = CODEC_HANDLE;

        mutex_enter(&state->cs_lock);
        /*
         * Stop the DMA engine.
         */
        CS4231_DMA_STOP(state, eng);

        /*
         * Stop the codec.
         */
        SELIDX(state, INTC_REG);
        ANDIDX(state, ~(eng->ce_codec_en), INTC_VALID_MASK);
        mutex_exit(&state->cs_lock);
}

/*
 * audiocs_start()
 *
 * Description:
 *      This is called by the framework to start an engine transferring data.
 *
 * Arguments:
 *      void    *arg            The DMA engine to start
 *
 * Returns:
 *      0       on success, an errno otherwise
 */
static int
audiocs_start(void *arg)
{
        CS_engine_t             *eng = arg;
        CS_state_t              *state = eng->ce_state;
        ddi_acc_handle_t        handle = CODEC_HANDLE;
        uint8_t                 mask;
        uint8_t                 value;
        uint8_t                 reg;
        int                     rv;

        mutex_enter(&state->cs_lock);

        if (eng->ce_num == CS4231_PLAY) {
                /* sample rate only set on play side */
                value = FS_48000 | PDF_STEREO | PDF_LINEAR16NE;
                reg = FSDF_REG;
                mask = FSDF_VALID_MASK;
        } else {
                value = CDF_STEREO | CDF_LINEAR16NE;
                reg = CDF_REG;
                mask = CDF_VALID_MASK;
        }
        eng->ce_curoff = 0;
        eng->ce_curidx = 0;

        SELIDX(state, reg | IAR_MCE);
        PUTIDX(state, value, mask);

        if (audiocs_poll_ready(state) != DDI_SUCCESS) {
                rv = EIO;
        } else if (CS4231_DMA_START(state, eng) != DDI_SUCCESS) {
                rv = EIO;
        } else {
                /*
                 * Start the codec.
                 */
                SELIDX(state, INTC_REG);
                ORIDX(state, eng->ce_codec_en, INTC_VALID_MASK);
                rv = 0;
        }

        mutex_exit(&state->cs_lock);
        return (rv);
}

/*
 * audiocs_format()
 *
 * Description:
 *      Called by the framework to query the format of the device.
 *
 * Arguments:
 *      void    *arg            The DMA engine to query
 *
 * Returns:
 *      AUDIO_FORMAT_S16_NE
 */
static int
audiocs_format(void *arg)
{
        _NOTE(ARGUNUSED(arg));

        return (AUDIO_FORMAT_S16_NE);
}

/*
 * audiocs_channels()
 *
 * Description:
 *      Called by the framework to query the channels of the device.
 *
 * Arguments:
 *      void    *arg            The DMA engine to query
 *
 * Returns:
 *      2 (stereo)
 */
static int
audiocs_channels(void *arg)
{
        _NOTE(ARGUNUSED(arg));

        return (2);
}

/*
 * audiocs_rates()
 *
 * Description:
 *      Called by the framework to query the sample rate of the device.
 *
 * Arguments:
 *      void    *arg            The DMA engine to query
 *
 * Returns:
 *      48000
 */
static int
audiocs_rate(void *arg)
{
        _NOTE(ARGUNUSED(arg));

        return (48000);
}

/*
 * audiocs_count()
 *
 * Description:
 *      This is called by the framework to get the engine's frame counter
 *
 * Arguments:
 *      void    *arg            The DMA engine to query
 *
 * Returns:
 *      frame count for current engine
 */
static uint64_t
audiocs_count(void *arg)
{
        CS_engine_t             *eng = arg;
        CS_state_t              *state = eng->ce_state;
        uint64_t                val;
        uint32_t                off;

        mutex_enter(&state->cs_lock);

        off = CS4231_DMA_ADDR(state, eng);
        ASSERT(off >= eng->ce_paddr);
        off -= eng->ce_paddr;

        /*
         * Every now and then, we get a value that is just a wee bit
         * too large.  This seems to be a small value related to
         * prefetch.  Rather than believe it, we just assume the last
         * offset in the buffer.  This should allow us to handle
         * wraps, but without inserting bogus sample counts.
         */
        if (off >= CS4231_BUFSZ) {
                off = CS4231_BUFSZ - 4;
        }

        off /= 4;

        val = (off >= eng->ce_curoff) ?
            off - eng->ce_curoff :
            off + CS4231_NFRAMES - eng->ce_curoff;

        eng->ce_count += val;
        eng->ce_curoff = off;
        val = eng->ce_count;

        /* while here, possibly reload the next address */
        CS4231_DMA_RELOAD(state, eng);
        mutex_exit(&state->cs_lock);

        return (val);
}

/*
 * audiocs_sync()
 *
 * Description:
 *      This is called by the framework to synchronize DMA caches.
 *
 * Arguments:
 *      void    *arg            The DMA engine to sync
 */
static void
audiocs_sync(void *arg, unsigned nframes)
{
        CS_engine_t *eng = arg;
        _NOTE(ARGUNUSED(nframes));

        (void) ddi_dma_sync(eng->ce_dmah, 0, 0, eng->ce_syncdir);
}

/*
 * audiocs_alloc_engine()
 *
 * Description:
 *      Allocates the DMA handles and the memory for the DMA engine.
 *
 * Arguments:
 *      CS_state_t      *dip    Pointer to the device's soft state
 *      int             num     Engine number, CS4231_PLAY or CS4231_REC.
 *
 * Returns:
 *      DDI_SUCCESS             Engine initialized.
 *      DDI_FAILURE             Engine not initialized.
 */
int
audiocs_alloc_engine(CS_state_t *state, int num)
{
        unsigned                caps;
        int                     dir;
        int                     rc;
        audio_dev_t             *adev;
        dev_info_t              *dip;
        CS_engine_t             *eng;
        uint_t                  ccnt;
        ddi_dma_cookie_t        dmac;
        size_t                  bufsz;

        static ddi_device_acc_attr_t buf_attr = {
                DDI_DEVICE_ATTR_V0,
                DDI_NEVERSWAP_ACC,
                DDI_STRICTORDER_ACC
        };

        adev = state->cs_adev;
        dip = state->cs_dip;

        eng = kmem_zalloc(sizeof (*eng), KM_SLEEP);
        eng->ce_state = state;
        eng->ce_started = B_FALSE;
        eng->ce_num = num;

        switch (num) {
        case CS4231_REC:
                dir = DDI_DMA_READ;
                caps = ENGINE_INPUT_CAP;
                eng->ce_syncdir = DDI_DMA_SYNC_FORKERNEL;
                eng->ce_codec_en = INTC_CEN;
                break;
        case CS4231_PLAY:
                dir = DDI_DMA_WRITE;
                caps = ENGINE_OUTPUT_CAP;
                eng->ce_syncdir = DDI_DMA_SYNC_FORDEV;
                eng->ce_codec_en = INTC_PEN;
                break;
        default:
                kmem_free(eng, sizeof (*eng));
                audio_dev_warn(adev, "bad engine number (%d)!", num);
                return (DDI_FAILURE);
        }
        state->cs_engines[num] = eng;

        /* allocate dma handle */
        rc = ddi_dma_alloc_handle(dip, CS4231_DMA_ATTR(state), DDI_DMA_SLEEP,
            NULL, &eng->ce_dmah);
        if (rc != DDI_SUCCESS) {
                audio_dev_warn(adev, "ddi_dma_alloc_handle failed: %d", rc);
                return (DDI_FAILURE);
        }
        /* allocate DMA buffer */
        rc = ddi_dma_mem_alloc(eng->ce_dmah, CS4231_BUFSZ, &buf_attr,
            DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &eng->ce_kaddr,
            &bufsz, &eng->ce_acch);
        if (rc == DDI_FAILURE) {
                audio_dev_warn(adev, "dma_mem_alloc failed");
                return (DDI_FAILURE);
        }

        /* bind DMA buffer */
        rc = ddi_dma_addr_bind_handle(eng->ce_dmah, NULL,
            eng->ce_kaddr, CS4231_BUFSZ, dir | DDI_DMA_CONSISTENT,
            DDI_DMA_SLEEP, NULL, &dmac, &ccnt);
        if ((rc != DDI_DMA_MAPPED) || (ccnt != 1)) {
                audio_dev_warn(adev,
                    "ddi_dma_addr_bind_handle failed: %d", rc);
                return (DDI_FAILURE);
        }

        eng->ce_paddr = dmac.dmac_address;

        eng->ce_engine = audio_engine_alloc(&audiocs_engine_ops, caps);
        if (eng->ce_engine == NULL) {
                audio_dev_warn(adev, "audio_engine_alloc failed");
                return (DDI_FAILURE);
        }

        audio_engine_set_private(eng->ce_engine, eng);
        audio_dev_add_engine(adev, eng->ce_engine);
        return (DDI_SUCCESS);
}

/*
 * audiocs_free_engine()
 *
 * Description:
 *      This routine fress the engine and all associated resources.
 *
 * Arguments:
 *      CS_engine_t     *eng    Engine to free.
 */
void
audiocs_free_engine(CS_engine_t *eng)
{
        CS_state_t      *state = eng->ce_state;
        audio_dev_t     *adev = state->cs_adev;

        if (eng == NULL)
                return;
        if (eng->ce_engine) {
                audio_dev_remove_engine(adev, eng->ce_engine);
                audio_engine_free(eng->ce_engine);
        }
        if (eng->ce_paddr) {
                (void) ddi_dma_unbind_handle(eng->ce_dmah);
        }
        if (eng->ce_acch) {
                ddi_dma_mem_free(&eng->ce_acch);
        }
        if (eng->ce_dmah) {
                ddi_dma_free_handle(&eng->ce_dmah);
        }
        kmem_free(eng, sizeof (*eng));
}

/*
 * audiocs_poll_ready()
 *
 * Description:
 *      This routine waits for the Codec to complete its initialization
 *      sequence and is done with its autocalibration.
 *
 *      Early versions of the Codec have a bug that can take as long as
 *      15 seconds to complete its initialization. For these cases we
 *      use a timeout mechanism so we don't keep the machine locked up.
 *
 * Arguments:
 *      CS_state_t      *state  The device's state structure
 *
 * Returns:
 *      DDI_SUCCESS             The Codec is ready to continue
 *      DDI_FAILURE             The Codec isn't ready to continue
 */
int
audiocs_poll_ready(CS_state_t *state)
{
        ddi_acc_handle_t        handle = CODEC_HANDLE;
        int                     x = 0;
        uint8_t                 iar;
        uint8_t                 idr;

        ASSERT(state->cs_regs != NULL);
        ASSERT(handle != NULL);

        /* wait for the chip to initialize itself */
        iar = ddi_get8(handle, &CS4231_IAR);

        while ((iar & IAR_INIT) && x++ < CS4231_TIMEOUT) {
                drv_usecwait(50);
                iar = ddi_get8(handle, &CS4231_IAR);
        }

        if (x >= CS4231_TIMEOUT) {
                return (DDI_FAILURE);
        }

        x = 0;

        /*
         * Now wait for the chip to complete its autocalibration.
         * Set the test register.
         */
        SELIDX(state, ESI_REG);

        idr = ddi_get8(handle, &CS4231_IDR);

        while ((idr & ESI_ACI) && x++ < CS4231_TIMEOUT) {
                drv_usecwait(50);
                idr = ddi_get8(handle, &CS4231_IDR);
        }

        if (x >= CS4231_TIMEOUT) {
                return (DDI_FAILURE);
        }


        return (DDI_SUCCESS);

}

/*
 * audiocs_sel_index()
 *
 * Description:
 *      Select a cs4231 register. The cs4231 has a hardware bug where a
 *      register is not always selected the first time. We try and try
 *      again until the proper register is selected or we time out and
 *      print an error message.
 *
 * Arguments:
 *      audiohdl_t      ahandle         Handle to this device
 *      ddi_acc_handle_t handle         A handle to the device's registers
 *      uint8_t         addr            The register address to program
 *      int             reg             The register to select
 */
void
#ifdef  DEBUG
audiocs_sel_index(CS_state_t *state, uint8_t reg, int n)
#else
audiocs_sel_index(CS_state_t *state, uint8_t reg)
#endif
{
        int                     x;
        uint8_t                 T;
        ddi_acc_handle_t        handle = CODEC_HANDLE;
        uint8_t                 *addr = &CS4231_IAR;

        for (x = 0; x < CS4231_RETRIES; x++) {
                ddi_put8(handle, addr, reg);
                T = ddi_get8(handle, addr);
                if (T == reg) {
                        break;
                }
                drv_usecwait(1000);
        }

        if (x == CS4231_RETRIES) {
                audio_dev_warn(state->cs_adev,
#ifdef  DEBUG
                    "line %d: Couldn't select index (0x%02x 0x%02x)", n,
#else
                    "Couldn't select index (0x%02x 0x%02x)",
#endif
                    T, reg);
                audio_dev_warn(state->cs_adev,
                    "audio may not work correctly until it is stopped and "
                    "restarted");
        }
}

/*
 * audiocs_put_index()
 *
 * Description:
 *      Program a cs4231 register. The cs4231 has a hardware bug where a
 *      register is not programmed properly the first time. We program a value,
 *      then immediately read back the value and reprogram if nescessary.
 *      We do this until the register is properly programmed or we time out and
 *      print an error message.
 *
 * Arguments:
 *      CS_state_t      state           Handle to this device
 *      uint8_t         mask            Mask to not set reserved register bits
 *      int             val             The value to program
 */
void
#ifdef DEBUG
audiocs_put_index(CS_state_t *state, uint8_t val, uint8_t mask, int n)
#else
audiocs_put_index(CS_state_t *state, uint8_t val, uint8_t mask)
#endif
{
        int                     x;
        uint8_t                 T;
        ddi_acc_handle_t        handle = CODEC_HANDLE;
        uint8_t                 *addr = &CS4231_IDR;

        val &= mask;

        for (x = 0; x < CS4231_RETRIES; x++) {
                ddi_put8(handle, addr, val);
                T = ddi_get8(handle, addr);
                if (T == val) {
                        break;
                }
                drv_usecwait(1000);
        }

        if (x == CS4231_RETRIES) {
#ifdef DEBUG
                audio_dev_warn(state->cs_adev,
                    "line %d: Couldn't set value (0x%02x 0x%02x)", n, T, val);
#else
                audio_dev_warn(state->cs_adev,
                    "Couldn't set value (0x%02x 0x%02x)", T, val);
#endif
                audio_dev_warn(state->cs_adev,
                    "audio may not work correctly until it is stopped and "
                    "restarted");
        }
}