usr/src/uts/common/io/audio/drv/audioixp/audioixp.c
author Garrett D'Amore <gdamore@opensolaris.org>
Tue, 16 Mar 2010 09:30:41 -0700
changeset 11936 54dc8a89ba0d
parent 11420 10bb27d50181
permissions -rw-r--r--
PSARC 2009/689 Audio DDI Simplifications PSARC 2009/674 Decoupling audio device interrupts 6921849 interrupt free audio 6921851 audio DDI simplifications 6930541 Synchronization issue between usb_ac and usb_as

/*
 * 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.
 */

/*
 * audioixp Audio Driver
 *
 * This driver supports audio hardware integrated in ATI IXP400 chipset.
 *
 * The IXP400 audio core is an AC'97 controller, which has independent
 * channels for PCM in, PCM out. The AC'97 controller is a PCI bus master
 * with scatter/gather support. Each channel has a DMA engine. Currently,
 * we use only the PCM in and PCM out channels. Each DMA engine uses one
 * buffer descriptor list.  Each entry contains a pointer to a data buffer,
 * status, length of the buffer being pointed to and the pointer to the next
 * entry. Length of the buffer is in number of bytes. Interrupt will be
 * triggered each time a entry is processed by hardware.
 *
 * System power management is not yet supported by the driver.
 *
 * 	NOTE:
 * 	This driver depends on the misc/ac97 and drv/audio modules being
 *	loaded first.
 */
#include <sys/types.h>
#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/pci.h>
#include <sys/note.h>
#include <sys/audio/audio_driver.h>
#include <sys/audio/ac97.h>
#include "audioixp.h"

/*
 * Module linkage routines for the kernel
 */
static int audioixp_ddi_attach(dev_info_t *, ddi_attach_cmd_t);
static int audioixp_ddi_detach(dev_info_t *, ddi_detach_cmd_t);
static int audioixp_quiesce(dev_info_t *);
static int audioixp_resume(dev_info_t *);
static int audioixp_suspend(dev_info_t *);

/*
 * Entry point routine prototypes
 */
static int audioixp_open(void *, int, unsigned *, caddr_t *);
static void audioixp_close(void *);
static int audioixp_start(void *);
static void audioixp_stop(void *);
static int audioixp_format(void *);
static int audioixp_channels(void *);
static int audioixp_rate(void *);
static uint64_t audioixp_count(void *);
static void audioixp_sync(void *, unsigned);

static audio_engine_ops_t audioixp_engine_ops = {
	AUDIO_ENGINE_VERSION,
	audioixp_open,
	audioixp_close,
	audioixp_start,
	audioixp_stop,
	audioixp_count,
	audioixp_format,
	audioixp_channels,
	audioixp_rate,
	audioixp_sync,
	NULL,
	NULL,
	NULL
};

/*
 * We drive audioixp in stereo only, so we don't want to display controls
 * that are used for multichannel codecs.  Note that this multichannel
 * configuration limitation is a problem for audioixp devices.
 */
const char *audioixp_remove_ac97[] = {
	AUDIO_CTRL_ID_CENTER,
	AUDIO_CTRL_ID_LFE,
	AUDIO_CTRL_ID_SURROUND,
	AUDIO_CTRL_ID_JACK1,
	AUDIO_CTRL_ID_JACK2,
};

/*
 * Local Routine Prototypes
 */
static int audioixp_attach(dev_info_t *);
static int audioixp_detach(dev_info_t *);
static int audioixp_alloc_port(audioixp_state_t *, int);
static void audioixp_update_port(audioixp_port_t *);

static int audioixp_codec_sync(audioixp_state_t *);
static void audioixp_wr97(void *, uint8_t, uint16_t);
static uint16_t audioixp_rd97(void *, uint8_t);
static int audioixp_reset_ac97(audioixp_state_t *);
static int audioixp_map_regs(audioixp_state_t *);
static void audioixp_unmap_regs(audioixp_state_t *);
static int audioixp_chip_init(audioixp_state_t *);
static void audioixp_destroy(audioixp_state_t *);

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

/*
 * DDI Structures
 */

/* Device operations structure */
static struct dev_ops audioixp_dev_ops = {
	DEVO_REV,		/* devo_rev */
	0,			/* devo_refcnt */
	NULL,			/* devo_getinfo */
	nulldev,		/* devo_identify - obsolete */
	nulldev,		/* devo_probe */
	audioixp_ddi_attach,	/* devo_attach */
	audioixp_ddi_detach,	/* devo_detach */
	nodev,			/* devo_reset */
	NULL,			/* devi_cb_ops */
	NULL,			/* devo_bus_ops */
	NULL,			/* devo_power */
	audioixp_quiesce,	/* devo_quiesce */
};

/* Linkage structure for loadable drivers */
static struct modldrv audioixp_modldrv = {
	&mod_driverops,		/* drv_modops */
	IXP_MOD_NAME,		/* drv_linkinfo */
	&audioixp_dev_ops,	/* drv_dev_ops */
};

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

/*
 * device access attributes for register mapping
 */
static struct ddi_device_acc_attr dev_attr = {
	DDI_DEVICE_ATTR_V0,
	DDI_STRUCTURE_LE_ACC,
	DDI_STRICTORDER_ACC
};
static struct ddi_device_acc_attr buf_attr = {
	DDI_DEVICE_ATTR_V0,
	DDI_NEVERSWAP_ACC,
	DDI_STRICTORDER_ACC
};

/*
 * DMA attributes of buffer descriptor list
 */
static ddi_dma_attr_t	bdlist_dma_attr = {
	DMA_ATTR_V0,	/* version */
	0,		/* addr_lo */
	0xffffffff,	/* addr_hi */
	0x0000ffff,	/* count_max */
	8,		/* align, BDL must be aligned on a 8-byte boundary */
	0x3c,		/* burstsize */
	8,		/* minxfer, set to the size of a BDlist entry */
	0x0000ffff,	/* maxxfer */
	0x00000fff,	/* seg, set to the RAM pagesize of intel platform */
	1,		/* sgllen, there's no scatter-gather list */
	8,		/* granular, set to the value of minxfer */
	0		/* flags, use virtual address */
};

/*
 * DMA attributes of buffers to be used to receive/send audio data
 */
static ddi_dma_attr_t	sample_buf_dma_attr = {
	DMA_ATTR_V0,
	0,		/* addr_lo */
	0xffffffff,	/* addr_hi */
	0x0001fffe,	/* count_max */
	4,		/* align, data buffer is aligned on a 2-byte boundary */
	0x3c,		/* burstsize */
	4,		/* minxfer, set to the size of a sample data */
	0x0001ffff,	/* maxxfer */
	0x0001ffff,	/* seg */
	1,		/* sgllen, no scatter-gather */
	4,		/* granular, set to the value of minxfer */
	0,		/* flags, use virtual address */
};

/*
 * _init()
 *
 * Description:
 *	Driver initialization, called when driver is first loaded.
 *	This is how access is initially given to all the static structures.
 *
 * Arguments:
 *	None
 *
 * Returns:
 *	ddi_soft_state_init() status, see ddi_soft_state_init(9f), or
 *	mod_install() status, see mod_install(9f)
 */
int
_init(void)
{
	int	error;

	audio_init_ops(&audioixp_dev_ops, IXP_NAME);

	if ((error = mod_install(&audioixp_modlinkage)) != 0) {
		audio_fini_ops(&audioixp_dev_ops);
	}

	return (error);
}

/*
 * _fini()
 *
 * Description:
 *	Module de-initialization, called when the driver is to be unloaded.
 *
 * Arguments:
 *	None
 *
 * Returns:
 *	mod_remove() status, see mod_remove(9f)
 */
int
_fini(void)
{
	int		error;

	if ((error = mod_remove(&audioixp_modlinkage)) != 0) {
		return (error);
	}

	audio_fini_ops(&audioixp_dev_ops);

	return (0);
}

/*
 * _info()
 *
 * Description:
 *	Module information, returns information about the driver.
 *
 * 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(&audioixp_modlinkage, modinfop));
}


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

/*
 * audioixp_ddi_attach()
 *
 * Description:
 *	Attach an instance of the audioixp driver.
 *
 * 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
audioixp_ddi_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
	switch (cmd) {
	case DDI_ATTACH:
		return (audioixp_attach(dip));

	/*
	 * now, no suspend/resume supported. we'll do it in the future.
	 */
	case DDI_RESUME:
		return (audioixp_resume(dip));
	default:
		return (DDI_FAILURE);
	}
}

/*
 * audioixp_ddi_detach()
 *
 * Description:
 *	Detach an instance of the audioixp driver.
 *
 * Arguments:
 *	dev_info_t		*dip	Pointer to the device's dev_info struct
 *	ddi_detach_cmd_t	cmd	Detach command
 *
 * Returns:
 *	DDI_SUCCESS	The driver was detached
 *	DDI_FAILURE	The driver couldn't be detached
 */
static int
audioixp_ddi_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
	switch (cmd) {
	case DDI_DETACH:
		return (audioixp_detach(dip));

	/*
	 * now, no suspend/resume supported. we'll do it in the future.
	 */
	case DDI_SUSPEND:
		return (audioixp_suspend(dip));

	default:
		return (DDI_FAILURE);
	}
}

/*
 * quiesce(9E) entry point.
 *
 * This function is called when the system is single-threaded at high
 * PIL with preemption disabled. Therefore, this function must not be blocked.
 *
 * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
 * DDI_FAILURE indicates an error condition and should almost never happen.
 */
static int
audioixp_quiesce(dev_info_t *dip)
{
	audioixp_state_t		*statep;

	statep = ddi_get_driver_private(dip);
	ASSERT(statep != NULL);

	/* stop DMA engines */
	CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_OUT);
	CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_OUT_DMA);
	CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_IN);
	CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_IN_DMA);

	return (DDI_SUCCESS);
}

static int
audioixp_suspend(dev_info_t *dip)
{
	audioixp_state_t		*statep;

	statep = ddi_get_driver_private(dip);
	ASSERT(statep != NULL);

	audio_dev_suspend(statep->adev);

	return (DDI_SUCCESS);
}

static int
audioixp_resume(dev_info_t *dip)
{
	audioixp_state_t		*statep;

	statep = ddi_get_driver_private(dip);
	ASSERT(statep != NULL);

	if (audioixp_chip_init(statep) != DDI_SUCCESS) {
		audio_dev_warn(statep->adev, "DDI_RESUME failed to init chip");
		return (DDI_SUCCESS);
	}

	ac97_reset(statep->ac97);
	audio_dev_resume(statep->adev);

	return (DDI_SUCCESS);
}

/*
 * audioixp_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
audioixp_open(void *arg, int flag, unsigned *nframesp, caddr_t *bufp)
{
	audioixp_port_t	*port = arg;

	_NOTE(ARGUNUSED(flag));

	port->started = B_FALSE;
	port->count = 0;
	port->offset = 0;
	*nframesp = port->nframes;
	*bufp = port->samp_kaddr;

	return (0);
}

/*
 * audioixp_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
audioixp_close(void *arg)
{
	_NOTE(ARGUNUSED(arg));
}

/*
 * audioixp_stop()
 *
 * Description:
 *	This is called by the framework to stop a port that is
 *	transferring data.
 *
 * Arguments:
 *	void	*arg		The DMA engine to stop
 */
static void
audioixp_stop(void *arg)
{
	audioixp_port_t		*port = arg;
	audioixp_state_t	*statep = port->statep;

	mutex_enter(&statep->inst_lock);
	if (port->num == IXP_REC) {
		CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_IN);
		CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_IN_DMA);
	} else {
		CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_OUT);
		CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_OUT_DMA);
	}
	mutex_exit(&statep->inst_lock);
}

/*
 * audioixp_start()
 *
 * Description:
 *	This is called by the framework to start a port transferring data.
 *
 * Arguments:
 *	void	*arg		The DMA engine to start
 *
 * Returns:
 *	0 	on success (never fails, errno if it did)
 */
static int
audioixp_start(void *arg)
{
	audioixp_port_t		*port = arg;
	audioixp_state_t	*statep = port->statep;

	mutex_enter(&statep->inst_lock);

	port->offset = 0;

	if (port->num == IXP_REC) {
		PUT32(IXP_AUDIO_FIFO_FLUSH, IXP_AUDIO_FIFO_FLUSH_IN);
		SET32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_INTER_IN);

		SET32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_IN_DMA);
		PUT32(IXP_AUDIO_IN_DMA_LINK_P,
		    port->bdl_paddr | IXP_AUDIO_IN_DMA_LINK_P_EN);

		SET32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_IN);
	} else {
		uint32_t slot = GET32(IXP_AUDIO_OUT_DMA_SLOT_EN_THRESHOLD);
		PUT32(IXP_AUDIO_FIFO_FLUSH, IXP_AUDIO_FIFO_FLUSH_OUT);
		/* clear all slots */
		slot &= ~ (IXP_AUDIO_OUT_DMA_SLOT_3 |
		    IXP_AUDIO_OUT_DMA_SLOT_4 |
		    IXP_AUDIO_OUT_DMA_SLOT_5 |
		    IXP_AUDIO_OUT_DMA_SLOT_6 |
		    IXP_AUDIO_OUT_DMA_SLOT_7 |
		    IXP_AUDIO_OUT_DMA_SLOT_8 |
		    IXP_AUDIO_OUT_DMA_SLOT_9 |
		    IXP_AUDIO_OUT_DMA_SLOT_10 |
		    IXP_AUDIO_OUT_DMA_SLOT_11 |
		    IXP_AUDIO_OUT_DMA_SLOT_12);
		/* enable AC'97 output slots (depending on output channels) */
		slot |= IXP_AUDIO_OUT_DMA_SLOT_3 |
		    IXP_AUDIO_OUT_DMA_SLOT_4;
		if (port->nchan >= 4) {
			slot |= IXP_AUDIO_OUT_DMA_SLOT_6 |
			    IXP_AUDIO_OUT_DMA_SLOT_9;
		}
		if (port->nchan >= 6) {
			slot |= IXP_AUDIO_OUT_DMA_SLOT_7 |
			    IXP_AUDIO_OUT_DMA_SLOT_8;
		}

		PUT32(IXP_AUDIO_OUT_DMA_SLOT_EN_THRESHOLD, slot);

		SET32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_INTER_OUT);

		SET32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_OUT_DMA);
		PUT32(IXP_AUDIO_OUT_DMA_LINK_P,
		    port->bdl_paddr | IXP_AUDIO_OUT_DMA_LINK_P_EN);

		SET32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_OUT);
	}
	mutex_exit(&statep->inst_lock);
	return (0);
}

/*
 * audioixp_format()
 *
 * Description:
 *	This is called by the framework to query the format for the device.
 *
 * Arguments:
 *	void	*arg		The DMA engine to query
 *
 * Returns:
 *	AUDIO_FORMAT_S16_LE
 */
static int
audioixp_format(void *arg)
{
	_NOTE(ARGUNUSED(arg));

	return (AUDIO_FORMAT_S16_LE);
}

/*
 * audioixp_channels()
 *
 * Description:
 *	This is called by the framework to query the channels for the device.
 *
 * Arguments:
 *	void	*arg		The DMA engine to query
 *
 * Returns:
 *	Number of channels for the device.
 */
static int
audioixp_channels(void *arg)
{
	audioixp_port_t *port = arg;

	return (port->nchan);
}

/*
 * audioixp_rate()
 *
 * Description:
 *	This is called by the framework to query the rate of the device.
 *
 * Arguments:
 *	void	*arg		The DMA engine to query
 *
 * Returns:
 *	48000
 */
static int
audioixp_rate(void *arg)
{
	_NOTE(ARGUNUSED(arg));

	return (48000);
}

/*
 * audioixp_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
audioixp_count(void *arg)
{
	audioixp_port_t		*port = arg;
	audioixp_state_t	*statep = port->statep;
	uint64_t		val;

	mutex_enter(&statep->inst_lock);
	audioixp_update_port(port);
	val = port->count;
	mutex_exit(&statep->inst_lock);

	return (val);
}

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

	(void) ddi_dma_sync(port->samp_dmah, 0, 0, port->sync_dir);
}

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

/*
 * audioixp_alloc_port()
 *
 * Description:
 *	This routine allocates the DMA handles and the memory for the
 *	DMA engines to use.  It also configures the BDL lists properly
 *	for use.
 *
 * Arguments:
 *	dev_info_t	*dip	Pointer to the device's devinfo
 *
 * Returns:
 *	DDI_SUCCESS		Registers successfully mapped
 *	DDI_FAILURE		Registers not successfully mapped
 */
static int
audioixp_alloc_port(audioixp_state_t *statep, int num)
{
	ddi_dma_cookie_t	cookie;
	uint_t			count;
	int			dir;
	unsigned		caps;
	audio_dev_t		*adev;
	audioixp_port_t		*port;
	uint32_t		paddr;
	int			rc;
	dev_info_t		*dip;
	audioixp_bd_entry_t	*bdentry;

	adev = statep->adev;
	dip = statep->dip;

	port = kmem_zalloc(sizeof (*port), KM_SLEEP);
	port->statep = statep;
	port->started = B_FALSE;
	port->num = num;

	switch (num) {
	case IXP_REC:
		statep->rec_port = port;
		dir = DDI_DMA_READ;
		caps = ENGINE_INPUT_CAP;
		port->sync_dir = DDI_DMA_SYNC_FORKERNEL;
		port->nchan = 2;
		break;
	case IXP_PLAY:
		statep->play_port = port;
		dir = DDI_DMA_WRITE;
		caps = ENGINE_OUTPUT_CAP;
		port->sync_dir = DDI_DMA_SYNC_FORDEV;
		/*
		 * We allow for end users to configure more channels
		 * than just two, but we default to just two.  The
		 * default stereo configuration works well.  On the
		 * configurations we have tested, we've found that
		 * more than two channels (or rather 6 channels) can
		 * cause inexplicable noise.  The noise is more
		 * noticeable when the system is running under load.
		 * (Holding the space bar in "top" while playing an
		 * MP3 is an easy way to recreate it.)  End users who
		 * want to experiment, or have configurations that
		 * don't suffer from this, may increase the channels
		 * by setting this max-channels property.  We leave it
		 * undocumented for now.
		 */
		port->nchan = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0,
		    "max-channels", 2);
		port->nchan = min(ac97_num_channels(statep->ac97),
		    port->nchan);
		port->nchan &= ~1;	/* make sure its an even number */
		port->nchan = max(port->nchan, 2);
		break;
	default:
		audio_dev_warn(adev, "bad port number (%d)!", num);
		return (DDI_FAILURE);
	}

	port->nframes = 4096;
	port->fragfr = port->nframes / IXP_BD_NUMS;
	port->fragsz = port->fragfr * port->nchan * 2;
	port->samp_size = port->nframes * port->nchan * 2;

	/* allocate dma handle */
	rc = ddi_dma_alloc_handle(dip, &sample_buf_dma_attr, DDI_DMA_SLEEP,
	    NULL, &port->samp_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(port->samp_dmah, port->samp_size, &buf_attr,
	    DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &port->samp_kaddr,
	    &port->samp_size, &port->samp_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(port->samp_dmah, NULL,
	    port->samp_kaddr, port->samp_size, dir|DDI_DMA_CONSISTENT,
	    DDI_DMA_SLEEP, NULL, &cookie, &count);
	if ((rc != DDI_DMA_MAPPED) || (count != 1)) {
		audio_dev_warn(adev,
		    "ddi_dma_addr_bind_handle failed: %d", rc);
		return (DDI_FAILURE);
	}
	port->samp_paddr = cookie.dmac_address;

	/*
	 * now, from here we allocate DMA memory for buffer descriptor list.
	 * we allocate adjacent DMA memory for all DMA engines.
	 */
	rc = ddi_dma_alloc_handle(dip, &bdlist_dma_attr, DDI_DMA_SLEEP,
	    NULL, &port->bdl_dmah);
	if (rc != DDI_SUCCESS) {
		audio_dev_warn(adev, "ddi_dma_alloc_handle(bdlist) failed");
		return (DDI_FAILURE);
	}

	/*
	 * we allocate all buffer descriptors lists in continuous dma memory.
	 */
	port->bdl_size = sizeof (audioixp_bd_entry_t) * IXP_BD_NUMS;
	rc = ddi_dma_mem_alloc(port->bdl_dmah, port->bdl_size,
	    &dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL,
	    &port->bdl_kaddr, &port->bdl_size, &port->bdl_acch);
	if (rc != DDI_SUCCESS) {
		audio_dev_warn(adev, "ddi_dma_mem_alloc(bdlist) failed");
		return (DDI_FAILURE);
	}

	rc = ddi_dma_addr_bind_handle(port->bdl_dmah, NULL, port->bdl_kaddr,
	    port->bdl_size, DDI_DMA_WRITE|DDI_DMA_CONSISTENT, DDI_DMA_SLEEP,
	    NULL, &cookie, &count);
	if ((rc != DDI_DMA_MAPPED) || (count != 1)) {
		audio_dev_warn(adev, "addr_bind_handle failed");
		return (DDI_FAILURE);
	}
	port->bdl_paddr = cookie.dmac_address;

	/*
	 * Wire up the BD list.
	 */
	paddr = port->samp_paddr;
	bdentry = (void *)port->bdl_kaddr;

	for (int i = 0; i < IXP_BD_NUMS; i++) {

		/* set base address of buffer */
		ddi_put32(port->bdl_acch, &bdentry->buf_base, paddr);
		ddi_put16(port->bdl_acch, &bdentry->status, 0);
		ddi_put16(port->bdl_acch, &bdentry->buf_len, port->fragsz / 4);
		ddi_put32(port->bdl_acch, &bdentry->next, port->bdl_paddr +
		    (((i + 1) % IXP_BD_NUMS) * sizeof (audioixp_bd_entry_t)));
		paddr += port->fragsz;
		bdentry++;
	}
	(void) ddi_dma_sync(port->bdl_dmah, 0, 0, DDI_DMA_SYNC_FORDEV);

	port->engine = audio_engine_alloc(&audioixp_engine_ops, caps);
	if (port->engine == NULL) {
		audio_dev_warn(adev, "audio_engine_alloc failed");
		return (DDI_FAILURE);
	}

	audio_engine_set_private(port->engine, port);
	audio_dev_add_engine(adev, port->engine);

	return (DDI_SUCCESS);
}

/*
 * audioixp_free_port()
 *
 * Description:
 *	This routine unbinds the DMA cookies, frees the DMA buffers,
 *	deallocates the DMA handles.
 *
 * Arguments:
 *	audioixp_port_t	*port	The port structure for a DMA engine.
 */
static void
audioixp_free_port(audioixp_port_t *port)
{
	if (port == NULL)
		return;

	if (port->engine) {
		audio_dev_remove_engine(port->statep->adev, port->engine);
		audio_engine_free(port->engine);
	}
	if (port->bdl_paddr) {
		(void) ddi_dma_unbind_handle(port->bdl_dmah);
	}
	if (port->bdl_acch) {
		ddi_dma_mem_free(&port->bdl_acch);
	}
	if (port->bdl_dmah) {
		ddi_dma_free_handle(&port->bdl_dmah);
	}
	if (port->samp_paddr) {
		(void) ddi_dma_unbind_handle(port->samp_dmah);
	}
	if (port->samp_acch) {
		ddi_dma_mem_free(&port->samp_acch);
	}
	if (port->samp_dmah) {
		ddi_dma_free_handle(&port->samp_dmah);
	}
	kmem_free(port, sizeof (*port));
}

/*
 * audioixp_update_port()
 *
 * Description:
 *	This routine updates the ports frame counter from hardware, and
 *	gracefully handles wraps.
 *
 * Arguments:
 *	audioixp_port_t	*port		The port to update.
 */
static void
audioixp_update_port(audioixp_port_t *port)
{
	audioixp_state_t	*statep = port->statep;
	unsigned		regoff;
	unsigned		n;
	int			loop;
	uint32_t		offset;
	uint32_t		paddr;

	if (port->num == IXP_REC) {
		regoff = IXP_AUDIO_IN_DMA_DT_CUR;
	} else {
		regoff = IXP_AUDIO_OUT_DMA_DT_CUR;
	}

	/*
	 * Apparently it may take several tries to get an update on the
	 * position.  Is this a hardware bug?
	 */
	for (loop = 100; loop; loop--) {
		paddr = GET32(regoff);

		/* make sure address is reasonable */
		if ((paddr < port->samp_paddr) ||
		    (paddr >= (port->samp_paddr + port->samp_size))) {
			continue;
		}

		offset = paddr - port->samp_paddr;

		if (offset >= port->offset) {
			n = offset - port->offset;
		} else {
			n = offset + (port->samp_size - port->offset);
		}
		port->offset = offset;
		port->count += (n / (port->nchan * sizeof (uint16_t)));
		return;
	}

	audio_dev_warn(statep->adev, "Unable to update count (h/w bug?)");
}


/*
 * audioixp_map_regs()
 *
 * Description:
 *	The registers are mapped in.
 *
 * Arguments:
 *	audioixp_state_t	*state		  The device's state structure
 *
 * Returns:
 *	DDI_SUCCESS		Registers successfully mapped
 *	DDI_FAILURE		Registers not successfully mapped
 */
static int
audioixp_map_regs(audioixp_state_t *statep)
{
	dev_info_t		*dip = statep->dip;

	/* map PCI config space */
	if (pci_config_setup(statep->dip, &statep->pcih) == DDI_FAILURE) {
		audio_dev_warn(statep->adev, "unable to map PCI config space");
		return (DDI_FAILURE);
	}

	/* map audio mixer register */
	if ((ddi_regs_map_setup(dip, IXP_IO_AM_REGS, &statep->regsp, 0, 0,
	    &dev_attr, &statep->regsh)) != DDI_SUCCESS) {
		audio_dev_warn(statep->adev, "unable to map audio registers");
		return (DDI_FAILURE);
	}
	return (DDI_SUCCESS);
}

/*
 * audioixp_unmap_regs()
 *
 * Description:
 *	This routine unmaps control registers.
 *
 * Arguments:
 *	audioixp_state_t	*state		The device's state structure
 */
static void
audioixp_unmap_regs(audioixp_state_t *statep)
{
	if (statep->regsh) {
		ddi_regs_map_free(&statep->regsh);
	}

	if (statep->pcih) {
		pci_config_teardown(&statep->pcih);
	}
}

/*
 * audioixp_codec_ready()
 *
 * Description:
 *	This routine checks the state of codecs.  It checks the flag to confirm
 *	that primary codec is ready.
 *
 * Arguments:
 *	audioixp_state_t	*state		The device's state structure
 *
 * Returns:
 *	DDI_SUCCESS	 codec is ready
 *	DDI_FAILURE	 codec is not ready
 */
static int
audioixp_codec_ready(audioixp_state_t *statep)
{
	uint32_t	sr;

	PUT32(IXP_AUDIO_INT, 0xffffffff);
	drv_usecwait(1000);

	sr = GET32(IXP_AUDIO_INT);
	if (sr & IXP_AUDIO_INT_CODEC0_NOT_READY) {
		PUT32(IXP_AUDIO_INT, IXP_AUDIO_INT_CODEC0_NOT_READY);
		audio_dev_warn(statep->adev, "primary codec not ready");

		return (DDI_FAILURE);
	}
	return (DDI_SUCCESS);
}

/*
 * audioixp_codec_sync()
 *
 * Description:
 *	Serialize access to the AC97 audio mixer registers.
 *
 * Arguments:
 *	audioixp_state_t	*state		The device's state structure
 *
 * Returns:
 *	DDI_SUCCESS		Ready for an I/O access to the codec
 *	DDI_FAILURE		An I/O access is currently in progress, can't
 *				perform another I/O access.
 */
static int
audioixp_codec_sync(audioixp_state_t *statep)
{
	int 		i;
	uint32_t	cmd;

	for (i = 0; i < 300; i++) {
		cmd = GET32(IXP_AUDIO_OUT_PHY_ADDR_DATA);
		if (!(cmd & IXP_AUDIO_OUT_PHY_EN)) {
			return (DDI_SUCCESS);
		}
		drv_usecwait(10);
	}

	audio_dev_warn(statep->adev, "unable to synchronize codec");
	return (DDI_FAILURE);
}

/*
 * audioixp_rd97()
 *
 * Description:
 *	Get the specific AC97 Codec register.
 *
 * Arguments:
 *	void		*arg		The device's state structure
 *	uint8_t		reg		AC97 register number
 *
 * Returns:
 *	Register value.
 */
static uint16_t
audioixp_rd97(void *arg, uint8_t reg)
{
	audioixp_state_t	*statep = arg;
	uint32_t		value;
	uint32_t		result;

	if (audioixp_codec_sync(statep) != DDI_SUCCESS)
		return (0xffff);

	value = IXP_AUDIO_OUT_PHY_PRIMARY_CODEC |
	    IXP_AUDIO_OUT_PHY_READ |
	    IXP_AUDIO_OUT_PHY_EN |
	    ((unsigned)reg << IXP_AUDIO_OUT_PHY_ADDR_SHIFT);
	PUT32(IXP_AUDIO_OUT_PHY_ADDR_DATA, value);

	if (audioixp_codec_sync(statep) != DDI_SUCCESS)
		return (0xffff);

	for (int i = 0; i < 300; i++) {
		result = GET32(IXP_AUDIO_IN_PHY_ADDR_DATA);
		if (result & IXP_AUDIO_IN_PHY_READY)	{
			return (result >> IXP_AUDIO_IN_PHY_DATA_SHIFT);
		}
		drv_usecwait(10);
	}

done:
	audio_dev_warn(statep->adev, "time out reading codec reg %d", reg);
	return (0xffff);
}

/*
 * audioixp_wr97()
 *
 * Description:
 *	Set the specific AC97 Codec register.
 *
 * Arguments:
 *	void		*arg		The device's state structure
 *	uint8_t		reg		AC97 register number
 *	uint16_t	data		The data want to be set
 */
static void
audioixp_wr97(void *arg, uint8_t reg, uint16_t data)
{
	audioixp_state_t	*statep = arg;
	uint32_t		value;

	if (audioixp_codec_sync(statep) != DDI_SUCCESS) {
		return;
	}

	value = IXP_AUDIO_OUT_PHY_PRIMARY_CODEC |
	    IXP_AUDIO_OUT_PHY_WRITE |
	    IXP_AUDIO_OUT_PHY_EN |
	    ((unsigned)reg << IXP_AUDIO_OUT_PHY_ADDR_SHIFT) |
	    ((unsigned)data << IXP_AUDIO_OUT_PHY_DATA_SHIFT);
	PUT32(IXP_AUDIO_OUT_PHY_ADDR_DATA, value);

	(void) audioixp_rd97(statep, reg);
}

/*
 * audioixp_reset_ac97()
 *
 * Description:
 *	Reset AC97 Codec register.
 *
 * Arguments:
 *	audioixp_state_t	*state		The device's state structure
 *
 * Returns:
 *	DDI_SUCCESS		Reset the codec successfully
 *	DDI_FAILURE		Failed to reset the codec
 */
static int
audioixp_reset_ac97(audioixp_state_t *statep)
{
	uint32_t	cmd;
	int i;

	CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_POWER_DOWN);
	drv_usecwait(10);

	/* register reset */
	SET32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_AC_SOFT_RESET);
	/* force a read to flush caches */
	(void) GET32(IXP_AUDIO_CMD);

	drv_usecwait(10);
	CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_AC_SOFT_RESET);

	/* cold reset */
	for (i = 0; i < 300; i++) {
		cmd = GET32(IXP_AUDIO_CMD);
		if (cmd & IXP_AUDIO_CMD_AC_ACTIVE) {
			cmd |= IXP_AUDIO_CMD_AC_RESET | IXP_AUDIO_CMD_AC_SYNC;
			PUT32(IXP_AUDIO_CMD, cmd);
			return (DDI_SUCCESS);
		}
		cmd &= ~IXP_AUDIO_CMD_AC_RESET;
		cmd |= IXP_AUDIO_CMD_AC_SYNC;
		PUT32(IXP_AUDIO_CMD, cmd);
		(void) GET32(IXP_AUDIO_CMD);
		drv_usecwait(10);
		cmd |= IXP_AUDIO_CMD_AC_RESET;
		PUT32(IXP_AUDIO_CMD, cmd);
		drv_usecwait(10);
	}

	audio_dev_warn(statep->adev, "AC'97 reset timed out");
	return (DDI_FAILURE);
}

/*
 * audioixp_chip_init()
 *
 * Description:
 *	This routine initializes ATI IXP audio controller and the AC97
 *	codec.
 *
 * Arguments:
 *	audioixp_state_t	*state		The device's state structure
 *
 * Returns:
 *	DDI_SUCCESS	The hardware was initialized properly
 *	DDI_FAILURE	The hardware couldn't be initialized properly
 */
static int
audioixp_chip_init(audioixp_state_t *statep)
{
	/*
	 * put the audio controller into quiet state, everything off
	 */
	CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_OUT_DMA);
	CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_IN_DMA);

	/* AC97 reset */
	if (audioixp_reset_ac97(statep) != DDI_SUCCESS) {
		audio_dev_warn(statep->adev, "AC97 codec reset failed");
		return (DDI_FAILURE);
	}

	if (audioixp_codec_ready(statep) != DDI_SUCCESS) {
		audio_dev_warn(statep->adev, "AC97 codec not ready");
		return (DDI_FAILURE);
	}

	return (DDI_SUCCESS);

}	/* audioixp_chip_init() */

/*
 * audioixp_attach()
 *
 * Description:
 *	Attach an instance of the audioixp driver. This routine does
 * 	the device dependent attach tasks.
 *
 * 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
audioixp_attach(dev_info_t *dip)
{
	uint16_t		cmdeg;
	audioixp_state_t	*statep;
	audio_dev_t		*adev;
	uint32_t		devid;
	const char		*name;
	const char		*rev;

	/* allocate the soft state structure */
	statep = kmem_zalloc(sizeof (*statep), KM_SLEEP);
	statep->dip = dip;
	ddi_set_driver_private(dip, statep);
	mutex_init(&statep->inst_lock, NULL, MUTEX_DRIVER, NULL);

	/* allocate framework audio device */
	if ((adev = audio_dev_alloc(dip, 0)) == NULL) {
		cmn_err(CE_WARN, "!%s%d: unable to allocate audio dev",
		    ddi_driver_name(dip), ddi_get_instance(dip));
		goto error;
	}
	statep->adev = adev;

	/* map in the registers */
	if (audioixp_map_regs(statep) != DDI_SUCCESS) {
		audio_dev_warn(adev, "couldn't map registers");
		goto error;
	}

	/* set device information -- this could be smarter */
	devid = ((pci_config_get16(statep->pcih, PCI_CONF_VENID)) << 16) |
	    pci_config_get16(statep->pcih, PCI_CONF_DEVID);

	name = "ATI AC'97";
	switch (devid) {
	case IXP_PCI_ID_200:
		rev = "IXP150";
		break;
	case IXP_PCI_ID_300:
		rev = "SB300";
		break;
	case IXP_PCI_ID_400:
		if (pci_config_get8(statep->pcih, PCI_CONF_REVID) & 0x80) {
			rev = "SB450";
		} else {
			rev = "SB400";
		}
		break;
	case IXP_PCI_ID_SB600:
		rev = "SB600";
		break;
	default:
		rev = "Unknown";
		break;
	}
	audio_dev_set_description(adev, name);
	audio_dev_set_version(adev, rev);

	/* set PCI command register */
	cmdeg = pci_config_get16(statep->pcih, PCI_CONF_COMM);
	pci_config_put16(statep->pcih, PCI_CONF_COMM,
	    cmdeg | PCI_COMM_IO | PCI_COMM_MAE);

	statep->ac97 = ac97_alloc(dip, audioixp_rd97, audioixp_wr97, statep);
	if (statep->ac97 == NULL) {
		audio_dev_warn(adev, "failed to allocate ac97 handle");
		goto error;
	}

	/* allocate port structures */
	if ((audioixp_alloc_port(statep, IXP_PLAY) != DDI_SUCCESS) ||
	    (audioixp_alloc_port(statep, IXP_REC) != DDI_SUCCESS)) {
		goto error;
	}

	/*
	 * If we have locked in a stereo configuration, then don't expose
	 * multichannel-specific AC'97 codec controls.
	 */
	if (statep->play_port->nchan == 2) {
		int i;
		ac97_ctrl_t *ctrl;
		const char *name;

		for (i = 0; (name = audioixp_remove_ac97[i]) != NULL; i++) {
			ctrl = ac97_control_find(statep->ac97, name);
			if (ctrl != NULL) {
				ac97_control_unregister(ctrl);
			}
		}
	}

	if (audioixp_chip_init(statep) != DDI_SUCCESS) {
		audio_dev_warn(statep->adev, "failed to init chip");
		goto error;
	}

	/* initialize the AC'97 part */
	if (ac97_init(statep->ac97, adev) != DDI_SUCCESS) {
		audio_dev_warn(adev, "ac'97 initialization failed");
		goto error;
	}

	if (audio_dev_register(adev) != DDI_SUCCESS) {
		audio_dev_warn(adev, "unable to register with framework");
		goto error;
	}

	ddi_report_dev(dip);

	return (DDI_SUCCESS);

error:
	audioixp_destroy(statep);
	return (DDI_FAILURE);
}

/*
 * audioixp_detach()
 *
 * Description:
 *	Detach an instance of the audioixp 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
audioixp_detach(dev_info_t *dip)
{
	audioixp_state_t	*statep;

	statep = ddi_get_driver_private(dip);

	if (audio_dev_unregister(statep->adev) != DDI_SUCCESS) {
		return (DDI_FAILURE);
	}

	audioixp_destroy(statep);
	return (DDI_SUCCESS);
}

/*
 * audioixp_destroy()
 *
 * Description:
 *	This routine releases all resources held by the device instance,
 *	as part of either detach or a failure in attach.
 *
 * Arguments:
 *	audioixp_state_t	*state	The device soft state.
 */
static void
audioixp_destroy(audioixp_state_t *statep)
{
	/*
	 * put the audio controller into quiet state, everything off
	 */
	CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_OUT_DMA);
	CLR32(IXP_AUDIO_CMD, IXP_AUDIO_CMD_EN_IN_DMA);

	audioixp_free_port(statep->play_port);
	audioixp_free_port(statep->rec_port);

	audioixp_unmap_regs(statep);

	if (statep->ac97) {
		ac97_free(statep->ac97);
	}

	if (statep->adev) {
		audio_dev_free(statep->adev);
	}

	mutex_destroy(&statep->inst_lock);
	kmem_free(statep, sizeof (*statep));
}