/*
* 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));
}