/*
* 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
* 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
* 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:
* loaded first.
*/
#include "audioixp.h"
/*
* Module linkage routines for the kernel
*/
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);
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[] = {
};
/*
* 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 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 */
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 */
&mod_driverops, /* drv_modops */
IXP_MOD_NAME, /* drv_linkinfo */
&audioixp_dev_ops, /* drv_dev_ops */
};
/* Module linkage structure */
MODREV_1, /* ml_rev */
(void *)&audioixp_modldrv, /* ml_linkage */
NULL /* NULL terminates the list */
};
/*
* device access attributes for register mapping
*/
};
};
/*
* DMA attributes of buffer descriptor list
*/
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 */
};
/*
*/
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;
}
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;
return (error);
}
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
{
}
/* ******************* 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
{
switch (cmd) {
case DDI_ATTACH:
return (audioixp_attach(dip));
/*
*/
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
{
switch (cmd) {
case DDI_DETACH:
return (audioixp_detach(dip));
/*
*/
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
{
/* stop DMA engines */
return (DDI_SUCCESS);
}
static int
{
return (DDI_SUCCESS);
}
static int
{
return (DDI_SUCCESS);
}
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
{
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_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
{
} else {
}
}
/*
* 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
{
} else {
/* clear all slots */
slot &= ~ (IXP_AUDIO_OUT_DMA_SLOT_3 |
/* enable AC'97 output slots (depending on output channels) */
}
}
}
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
{
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_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
{
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
{
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
{
}
/* *********************** 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
{
int dir;
unsigned caps;
int rc;
switch (num) {
case IXP_REC:
dir = DDI_DMA_READ;
break;
case IXP_PLAY:
dir = DDI_DMA_WRITE;
/*
* 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.
*/
"max-channels", 2);
break;
default:
return (DDI_FAILURE);
}
/* allocate dma handle */
if (rc != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/* allocate DMA buffer */
if (rc == DDI_FAILURE) {
return (DDI_FAILURE);
}
/* bind DMA buffer */
"ddi_dma_addr_bind_handle failed: %d", rc);
return (DDI_FAILURE);
}
/*
* now, from here we allocate DMA memory for buffer descriptor list.
* we allocate adjacent DMA memory for all DMA engines.
*/
if (rc != DDI_SUCCESS) {
return (DDI_FAILURE);
}
/*
* we allocate all buffer descriptors lists in continuous dma memory.
*/
if (rc != DDI_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
/*
* Wire up the BD list.
*/
for (int i = 0; i < IXP_BD_NUMS; i++) {
/* set base address of buffer */
bdentry++;
}
return (DDI_FAILURE);
}
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
{
return;
}
}
}
}
if (port->samp_paddr) {
}
}
}
}
/*
* 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
{
unsigned regoff;
unsigned n;
int loop;
} else {
}
/*
* Apparently it may take several tries to get an update on the
* position. Is this a hardware bug?
*/
/* make sure address is reasonable */
continue;
}
} else {
}
return;
}
}
/*
* 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
{
/* map PCI config space */
return (DDI_FAILURE);
}
/* map audio mixer register */
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_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
{
drv_usecwait(1000);
if (sr & IXP_AUDIO_INT_CODEC0_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
{
int i;
for (i = 0; i < 300; i++) {
if (!(cmd & IXP_AUDIO_OUT_PHY_EN)) {
return (DDI_SUCCESS);
}
drv_usecwait(10);
}
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
{
return (0xffff);
((unsigned)reg << IXP_AUDIO_OUT_PHY_ADDR_SHIFT);
return (0xffff);
for (int i = 0; i < 300; i++) {
if (result & IXP_AUDIO_IN_PHY_READY) {
return (result >> IXP_AUDIO_IN_PHY_DATA_SHIFT);
}
drv_usecwait(10);
}
done:
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
{
return;
}
((unsigned)reg << IXP_AUDIO_OUT_PHY_ADDR_SHIFT) |
((unsigned)data << IXP_AUDIO_OUT_PHY_DATA_SHIFT);
}
/*
* 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
{
int i;
drv_usecwait(10);
/* register reset */
/* force a read to flush caches */
(void) GET32(IXP_AUDIO_CMD);
drv_usecwait(10);
/* cold reset */
for (i = 0; i < 300; i++) {
if (cmd & IXP_AUDIO_CMD_AC_ACTIVE) {
return (DDI_SUCCESS);
}
(void) GET32(IXP_AUDIO_CMD);
drv_usecwait(10);
drv_usecwait(10);
}
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
{
/*
* put the audio controller into quiet state, everything off
*/
/* AC97 reset */
return (DDI_FAILURE);
}
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
{
const char *name;
const char *rev;
/* allocate the soft state structure */
/* allocate framework audio device */
goto error;
}
/* map in the registers */
goto error;
}
/* set device information -- this could be smarter */
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:
rev = "SB450";
} else {
rev = "SB400";
}
break;
case IXP_PCI_ID_SB600:
rev = "SB600";
break;
default:
rev = "Unknown";
break;
}
/* set PCI command register */
goto error;
}
/* allocate port structures */
goto error;
}
/*
* If we have locked in a stereo configuration, then don't expose
* multichannel-specific AC'97 codec controls.
*/
int i;
const char *name;
}
}
}
goto error;
}
/* initialize the AC'97 part */
goto error;
}
goto error;
}
return (DDI_SUCCESS);
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
{
return (DDI_FAILURE);
}
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
{
/*
* put the audio controller into quiet state, everything off
*/
}
}
}