/*
* 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.
*/
/*
* audiots Audio Driver
*
* This Audio Driver controls the T2 audio core in the ALI M1553
* southbridge chip. This chip supports multiple play streams, but just
* a single record stream. It also supports wave table synthesis and
* hardware MIDI and joystick ports. Unfortunately the MIDI ports are
* not available because their pins have been re-assigned to expose
* interrupts. We also aren't going to do anything with the joystick
* ports. The audio core controls an AC-97 V2.1 Codec.
*
* The DMA engine uses a single buffer which is large enough to hold
* two interrupts worth of data. When it gets to the mid point an
* interrupt is generated and data is either sent (for record) or
* requested and put in that half of the buffer (for play). When the
* second half is played we do the same, but the audio core loops the
* pointer back to the beginning.
*
* The audio core has a bug in silicon that doesn't let it read the AC-97
* Codec's register. T2 has provided an algorithm that attempts to read the
* the Codec several times. This is probably heuristic and thus isn't
* absolutely guaranteed to work. However we do have to place a limit on
* the looping, otherwise when we read a valid 0x00 we would never exit
* the loop. Unfortunately there is also a problem with writing the AC-97
* Codec's registers as well. Thus we read it back to verify the write.
*
* The AC'97 common code provides shadow state for AC'97 registers for us,
* so we only need to read those registers during early startup (primarily
* to determine codec id and capabilities.)
*
* We don't save any of the audio controller registers during normal
* operation. When we need to save register state we only have to save
* the aram and eram. The rest of the controller state is never modified
* from the initial programming. Thus restoring the controller state
* can be done from audiots_chip_init() as well.
*
*
* WARNING: The SME birdsnest platform uses a PCI bridge chip between the
* CPU and the southbridge containing the audio core. There is
* a bug in silicon that causes a bogus parity error. With the mixer
* reimplementation project, Bug 4374774, the audio driver is always
* set to the best precision and number of channels. Thus when turning
* the mixer on and off the only thing that changes is the sample rate.
* This change in programming doesn't trigger the silicon error.
* Thus the supported channels must always be 2 and the precision
* must always be 16-bits. This will keep any future change in the
* mixer from exposing this bug.
*
* Due to a hardware bug, system power management is not supported by this
* driver.
*
* CAUTION: If audio controller state is changed outside of aram
* and eram then that information must be saved and restored
* during power management shutdown and bringup.
*
* NOTE: The AC-97 Codec's reset pin is set to PCI reset, so we
* can't power down the Codec all the way.
*
* NOTE: This driver depends on the drv/audio and misc/ac97
* modules being loaded first.
*
* NOTE: Don't OR the ap_stop register to stop a play or record. This
* will just stop all active channels because a read of ap_stop
* returns ap_start. Just set the ap_stop register with the
* channels you want to stop. The same goes for ap_start.
*
* NOTE: There is a hardware problem with P2 rev motherboards. After
* prolonged use, reading the AC97 register will always return
* busy. The AC97 register is now useless. Consequently, we are no
* longer able to program the Codec. This work around disables
* audio when this state is detected. It's not great, but its
* better than having audio blasting out at 100% all the time.
*
* NOTE: Power Management testing has also exposed this AC97 timeout
* problem. Management has decided this is too risky for customers
* and hence they want power management support removed from the
* audio subsystem. All PM support is now removed.
*/
/*
* Synchronization notes:
*
* The audio framework guarantees that our entry points are exclusive
* with suspend and resume. This includes data flow and control entry
* points alike.
*
* The audio framework guarantees that only one control is being
* accessed on any given audio device at a time.
*
* The audio framework guarantees that entry points are themselves
* serialized for a given engine.
*
* We have no interrupt routine or other internal asynchronous routines.
*
* Our device uses completely separate registers for each engine,
* except for the start/stop registers, which are implemented in a
* manner that allows for them to be accessed concurrently safely from
* different threads.
*
* Hence, it turns out that we simply don't need any locking in this
* driver.
*/
#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/pci.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/debug.h>
#include <sys/note.h>
#include <sys/audio/audio_driver.h>
#include <sys/audio/ac97.h>
#include "audiots.h"
/*
* Module linkage routines for the kernel
*/
static int audiots_attach(dev_info_t *, ddi_attach_cmd_t);
static int audiots_detach(dev_info_t *, ddi_detach_cmd_t);
static int audiots_quiesce(dev_info_t *);
/*
* Entry point routine prototypes
*/
static int audiots_open(void *, int, unsigned *, caddr_t *);
static void audiots_close(void *);
static int audiots_start(void *);
static void audiots_stop(void *);
static int audiots_format(void *);
static int audiots_channels(void *);
static int audiots_rate(void *);
static void audiots_chinfo(void *, int, unsigned *, unsigned *);
static uint64_t audiots_count(void *);
static void audiots_sync(void *, unsigned);
static audio_engine_ops_t audiots_engine_ops = {
AUDIO_ENGINE_VERSION,
audiots_open,
audiots_close,
audiots_start,
audiots_stop,
audiots_count,
audiots_format,
audiots_channels,
audiots_rate,
audiots_sync,
NULL,
audiots_chinfo,
NULL,
};
/*
* Local Routine Prototypes
*/
static void audiots_power_up(audiots_state_t *);
static void audiots_chip_init(audiots_state_t *);
static uint16_t audiots_get_ac97(void *, uint8_t);
static void audiots_set_ac97(void *, uint8_t, uint16_t);
static int audiots_init_state(audiots_state_t *, dev_info_t *);
static int audiots_map_regs(dev_info_t *, audiots_state_t *);
static uint16_t audiots_read_ac97(audiots_state_t *, int);
static void audiots_stop_everything(audiots_state_t *);
static void audiots_destroy(audiots_state_t *);
static int audiots_alloc_port(audiots_state_t *, int);
/*
* Global variables, but viewable only by this file.
*/
/* anchor for soft state structures */
static void *audiots_statep;
/*
* DDI Structures
*/
/* Device operations structure */
static struct dev_ops audiots_dev_ops = {
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
NULL, /* devo_getinfo */
nulldev, /* devo_identify - obsolete */
nulldev, /* devo_probe */
audiots_attach, /* devo_attach */
audiots_detach, /* devo_detach */
nodev, /* devo_reset */
NULL, /* devo_cb_ops */
NULL, /* devo_bus_ops */
NULL, /* devo_power */
audiots_quiesce, /* devo_quiesce */
};
/* Linkage structure for loadable drivers */
static struct modldrv audiots_modldrv = {
&mod_driverops, /* drv_modops */
TS_MOD_NAME, /* drv_linkinfo */
&audiots_dev_ops /* drv_dev_ops */
};
/* Module linkage structure */
static struct modlinkage audiots_modlinkage = {
MODREV_1, /* ml_rev */
(void *)&audiots_modldrv, /* ml_linkage */
NULL /* NULL terminates the list */
};
/*
* NOTE: Grover OBP v4.0.166 and rev G of the ALI Southbridge chip force the
* audiots driver to use the upper 2 GB DMA address range. However to maintain
* backwards compatibility with older systems/OBP, we're going to try the full
* 4 GB DMA range.
*
* Eventually, this will be set back to using the proper high 2 GB DMA range.
*/
/* Device attribute structure - full 4 gig address range */
static ddi_dma_attr_t audiots_attr = {
DMA_ATTR_VERSION, /* version */
0x0000000000000000LL, /* dlim_addr_lo */
0x00000000ffffffffLL, /* dlim_addr_hi */
0x0000000000003fffLL, /* DMA counter register - 16 bits */
0x0000000000000008LL, /* DMA address alignment, 64-bit */
0x0000007f, /* 1 through 64 byte burst sizes */
0x00000001, /* min effective DMA size */
0x0000000000003fffLL, /* maximum transfer size, 16k */
0x000000000000ffffLL, /* segment boundary, 64k */
0x00000001, /* s/g list length, no s/g */
0x00000001, /* granularity of device, don't care */
0 /* DMA flags */
};
static ddi_device_acc_attr_t ts_acc_attr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
static ddi_device_acc_attr_t ts_regs_attr = {
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
/*
* _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(&audiots_dev_ops, TS_NAME);
/* initialize the soft state */
if ((error = ddi_soft_state_init(&audiots_statep,
sizeof (audiots_state_t), 1)) != 0) {
audio_fini_ops(&audiots_dev_ops);
return (error);
}
if ((error = mod_install(&audiots_modlinkage)) != 0) {
audio_fini_ops(&audiots_dev_ops);
ddi_soft_state_fini(&audiots_statep);
}
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(&audiots_modlinkage)) != 0) {
return (error);
}
/* free the soft state internal structures */
ddi_soft_state_fini(&audiots_statep);
/* clean up ops */
audio_fini_ops(&audiots_dev_ops);
return (0);
}
/*
* _info()
*
* Description:
* Module information, returns infomation 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)
{
int error;
error = mod_info(&audiots_modlinkage, modinfop);
return (error);
}
/*
* audiots_attach()
*
* Description:
* Attach an instance of the audiots 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
audiots_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
audiots_state_t *state;
int instance;
instance = ddi_get_instance(dip);
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
/* we've already allocated the state structure so get ptr */
state = ddi_get_soft_state(audiots_statep, instance);
ASSERT(dip == state->ts_dip);
/* suspend/resume resets the chip, so we have no more faults */
if (state->ts_flags & TS_AUDIO_READ_FAILED) {
ddi_dev_report_fault(state->ts_dip,
DDI_SERVICE_RESTORED,
DDI_DEVICE_FAULT,
"check port, gain, balance, and mute settings");
/* and clear the fault state flags */
state->ts_flags &=
~(TS_AUDIO_READ_FAILED|TS_READ_FAILURE_PRINTED);
}
audiots_power_up(state);
audiots_chip_init(state);
ac97_reset(state->ts_ac97);
audio_dev_resume(state->ts_adev);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/* before we do anything make sure that we haven't had a h/w failure */
if (ddi_get_devstate(dip) == DDI_DEVSTATE_DOWN) {
cmn_err(CE_WARN, "%s%d: The audio hardware has "
"been disabled.", ddi_driver_name(dip), instance);
cmn_err(CE_CONT, "Please reboot to restore audio.");
return (DDI_FAILURE);
}
/* allocate the state structure */
if (ddi_soft_state_zalloc(audiots_statep, instance) == DDI_FAILURE) {
cmn_err(CE_WARN, "!%s%d: soft state allocate failed",
ddi_driver_name(dip), instance);
return (DDI_FAILURE);
}
/*
* WARNING: From here on all errors require that we free memory,
* including the state structure.
*/
/* get the state structure - cannot fail */
state = ddi_get_soft_state(audiots_statep, instance);
ASSERT(state != NULL);
if ((state->ts_adev = audio_dev_alloc(dip, 0)) == NULL) {
cmn_err(CE_WARN, "unable to allocate audio dev");
goto error;
}
/* map in the registers, allocate DMA buffers, etc. */
if (audiots_map_regs(dip, state) == DDI_FAILURE) {
audio_dev_warn(state->ts_adev, "unable to map registers");
goto error;
}
/* initialize the audio state structures */
if (audiots_init_state(state, dip) == DDI_FAILURE) {
audio_dev_warn(state->ts_adev, "init state structure failed");
goto error;
}
/* power up */
audiots_power_up(state);
/* initialize the audio controller */
audiots_chip_init(state);
/* initialize the AC-97 Codec */
if (ac97_init(state->ts_ac97, state->ts_adev) != 0) {
goto error;
}
/* put the engine interrupts into a known state -- all off */
ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_ainten,
TS_ALL_DMA_OFF);
/* call the framework attach routine */
if (audio_dev_register(state->ts_adev) != DDI_SUCCESS) {
audio_dev_warn(state->ts_adev, "unable to register audio");
goto error;
}
/* everything worked out, so report the device */
ddi_report_dev(dip);
return (DDI_SUCCESS);
error:
audiots_destroy(state);
return (DDI_FAILURE);
}
/*
* audiots_detach()
*
* Description:
* Detach an instance of the audiots 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
audiots_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
audiots_state_t *state;
int instance;
instance = ddi_get_instance(dip);
/* get the state structure */
if ((state = ddi_get_soft_state(audiots_statep, instance)) == NULL) {
cmn_err(CE_WARN, "!%s%d: detach get soft state failed",
ddi_driver_name(dip), instance);
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
audio_dev_suspend(state->ts_adev);
/* stop playing and recording */
(void) audiots_stop_everything(state);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/* attempt to unregister from the framework first */
if (audio_dev_unregister(state->ts_adev) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
audiots_destroy(state);
return (DDI_SUCCESS);
}
/*
* audiots_quiesce()
*
* Description:
* Quiesce an instance of the audiots driver. Stops all DMA and
* interrupts.
*
* Arguments:
* dev_info_t *dip Pointer to the device's dev_info struct
*
* Returns:
* DDI_SUCCESS The driver was quiesced
* DDI_SUCCESS The driver was NOT quiesced
*/
static int
audiots_quiesce(dev_info_t *dip)
{
audiots_state_t *state;
int instance;
instance = ddi_get_instance(dip);
/* get the state structure */
if ((state = ddi_get_soft_state(audiots_statep, instance)) == NULL) {
return (DDI_FAILURE);
}
audiots_stop_everything(state);
return (DDI_SUCCESS);
}
/*
* audiots_power_up()
*
* Description
* Ensure that the device is running in PCI power state D0.
*/
static void
audiots_power_up(audiots_state_t *state)
{
ddi_acc_handle_t pcih = state->ts_pcih;
uint8_t ptr;
uint16_t pmcsr;
if ((pci_config_get16(pcih, PCI_CONF_STAT) & PCI_STAT_CAP) == 0) {
/* does not implement PCI capabilities -- no PM */
return;
}
ptr = pci_config_get8(pcih, PCI_CONF_CAP_PTR);
for (;;) {
if (ptr == PCI_CAP_NEXT_PTR_NULL) {
/* PM capability not found */
return;
}
if (pci_config_get8(pcih, ptr + PCI_CAP_ID) == PCI_CAP_ID_PM) {
/* found it */
break;
}
ptr = pci_config_get8(pcih, ptr + PCI_CAP_NEXT_PTR);
}
/* if we got here, then got valid PMCSR pointer */
ptr += PCI_PMCSR;
/* check to see if we are already in state D0 */
pmcsr = pci_config_get16(pcih, ptr);
if ((pmcsr & PCI_PMCSR_STATE_MASK) != PCI_PMCSR_D0) {
/* D3hot (or any other state) -> D0 */
pmcsr &= ~PCI_PMCSR_STATE_MASK;
pmcsr |= PCI_PMCSR_D0;
pci_config_put16(pcih, ptr, pmcsr);
}
/*
* Wait for it to power up - PCI spec says 10 ms is enough.
* We double it. Note that no locks are held when this routine
* is called, so we can sleep (we are in attach context only).
*
* We do this delay even if already powerd up, just to make
* sure we aren't seeing something that *just* transitioned
* into D0 state.
*/
delay(drv_usectohz(TS_20MS));
/* clear PME# flag */
pmcsr = pci_config_get16(pcih, ptr);
pci_config_put16(pcih, ptr, pmcsr | PCI_PMCSR_PME_STAT);
}
/*
* audiots_chip_init()
*
* Description:
* Initialize the audio core.
*
* Arguments:
* audiots_state_t *state The device's state structure
*/
static void
audiots_chip_init(audiots_state_t *state)
{
ddi_acc_handle_t handle = state->ts_acch;
audiots_regs_t *regs = state->ts_regs;
int str;
/* start with all interrupts & dma channels disabled */
ddi_put32(handle, &regs->aud_regs.ap_stop, TS_ALL_DMA_ENGINES);
ddi_put32(handle, &regs->aud_regs.ap_ainten, TS_ALL_DMA_OFF);
/* set global music and wave volume to 0dB */
ddi_put32(handle, &regs->aud_regs.ap_volume, 0x0);
/* enable end interrupts for all channels. */
ddi_put32(handle, &regs->aud_regs.ap_cir_gc, AP_CIR_GC_ENDLP_IE);
/* for each stream, set gain and vol settings */
for (str = 0; str < TS_MAX_HW_CHANNELS; str++) {
/*
* Set volume to all off, 1st left and then right.
* These are never changed, so we don't have to save them.
*/
ddi_put16(handle,
&regs->aud_ram[str].eram.eram_gvsel_pan_vol,
(ERAM_WAVE_VOL|ERAM_PAN_LEFT|ERAM_PAN_0dB|
ERAM_VOL_MAX_ATTEN));
ddi_put16(handle,
&regs->aud_ram[str].eram.eram_gvsel_pan_vol,
(ERAM_WAVE_VOL|ERAM_PAN_RIGHT|ERAM_PAN_0dB|
ERAM_VOL_MAX_ATTEN));
/*
* The envelope engine *MUST* remain in still mode (off).
* Otherwise bad things like gain randomly disappearing might
* happen. See bug #4332773.
*/
ddi_put32(handle, &regs->aud_ram[str].eram.eram_ebuf1,
ERAM_EBUF_STILL);
ddi_put32(handle, &regs->aud_ram[str].eram.eram_ebuf2,
ERAM_EBUF_STILL);
/* program the initial eram and aram rate */
ddi_put16(handle, &regs->aud_ram[str].aram.aram_delta,
1 << TS_SRC_SHIFT);
ddi_put16(handle, &regs->aud_ram[str].eram.eram_ctrl_ec,
ERAM_16_BITS | ERAM_STEREO | ERAM_LOOP_MODE |
ERAM_SIGNED_PCM);
}
/* program channel 31 for record */
OR_SET_WORD(handle, &state->ts_regs->aud_regs.ap_global_control,
(AP_CLOGAL_CTRL_E_PCMIN_CH31|AP_CLOGAL_CTRL_PCM_OUT_AC97|
AP_CLOGAL_CTRL_MMC_FROM_MIXER|AP_CLOGAL_CTRL_PCM_OUT_TO_AC97));
/* do a warm reset, which powers up the Codec */
OR_SET_WORD(handle, &state->ts_regs->aud_regs.ap_sctrl,
AP_SCTRL_WRST_CODEC);
drv_usecwait(2);
AND_SET_WORD(handle, &state->ts_regs->aud_regs.ap_sctrl,
~AP_SCTRL_WRST_CODEC);
/* do a warm reset via the Codec, yes, I'm being paranoid! */
audiots_set_ac97(state, AC97_RESET_REGISTER, 0);
/* Make sure the Codec is powered up. */
int i = TS_WAIT_CNT;
while ((audiots_get_ac97(state, AC97_POWERDOWN_CTRL_STAT_REGISTER) &
PCSR_POWERD_UP) != PCSR_POWERD_UP && i--) {
drv_usecwait(1);
}
}
/*
* audiots_get_ac97()
*
* Description:
* Get the value in the specified AC-97 Codec register. There is a
* bug in silicon which forces us to do multiple reads of the Codec's
* register. This algorithm was provided by T2 and is heuristic in
* nature. Unfortunately we have no guarantees that the real answer
* isn't 0x0000, which is what we get when a read fails. So we loop
* TS_LOOP_CNT times before we give up. We just have to hope this is
* sufficient to give us the correct value.
*
* Arguments:
* audiots_state_t *state The device's state structure
* int reg AC-97 register number
*
* Returns:
* unsigned short The value in the specified register
*/
static uint16_t
audiots_get_ac97(void *arg, uint8_t reg)
{
audiots_state_t *state = arg;
ddi_acc_handle_t handle = state->ts_acch;
uint16_t *data;
int count;
int delay;
uint16_t first;
uint16_t next;
if (state->ts_revid == AC_REV_ID1) {
data = &state->ts_regs->aud_regs.ap_acrd_35D_data;
} else {
data = &state->ts_regs->aud_regs.ap_acrdwr_data;
}
/* make sure the register is good */
reg &= AP_ACRD_INDEX_MASK;
for (count = TS_LOOP_CNT; count--; ) {
if ((first = audiots_read_ac97(state, reg)) != 0) {
next = first;
break;
}
delay = TS_DELAY_CNT;
while (delay--) {
(void) ddi_get16(handle, data);
}
if ((next = audiots_read_ac97(state, reg)) != 0) {
break;
}
}
/*
* Arggg, if you let the next read happen too soon then it fails.
* 12 usec fails, 13 usec succeeds. So set it to 20 for safety.
*/
drv_usecwait(TS_20US);
return (next);
}
/*
* audiots_init_state()
*
* Description:
* This routine initializes the audio driver's state structure.
* This includes reading the properties.
*
* CAUTION: This routine cannot allocate resources, unless it frees
* them before returning for an error. Also, error_destroy:
* in audiots_attach() would need to be fixed as well.
*
* NOTE: birdsnest supports CD ROM input. We check for the cdrom
* property. If there we turn it on.
*
* Arguments:
* audiots_state_t *state The device's state structure
* dev_info_t *dip Pointer to the device's dev_info struct
*
* Returns:
* DDI_SUCCESS State structure initialized
* DDI_FAILURE State structure not initialized
*/
static int
audiots_init_state(audiots_state_t *state, dev_info_t *dip)
{
state->ts_ac97 = ac97_alloc(dip, audiots_get_ac97,
audiots_set_ac97, state);
if (state->ts_ac97 == NULL) {
return (DDI_FAILURE);
}
/* save the device info pointer */
state->ts_dip = dip;
for (int i = 0; i < TS_NUM_PORTS; i++) {
if (audiots_alloc_port(state, i) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
}
return (DDI_SUCCESS);
}
/*
* audiots_map_regs()
*
* Description:
* This routine maps the registers in.
*
* Once the config space registers are mapped in we determine if the
* audio core may be power managed. It should, but if it doesn't,
* then trying to may cause the core to hang.
*
* CAUTION: Make sure all errors call audio_dev_warn().
*
* Arguments:
* dev_info_t *dip Pointer to the device's devinfo
* audiots_state_t *state The device's state structure
* Returns:
* DDI_SUCCESS Registers successfully mapped
* DDI_FAILURE Registers not successfully mapped
*/
static int
audiots_map_regs(dev_info_t *dip, audiots_state_t *state)
{
char rev[16];
char *name;
/* map in the registers, the config and memory mapped registers */
if (pci_config_setup(dip, &state->ts_pcih) != DDI_SUCCESS) {
audio_dev_warn(state->ts_adev,
"unable to map PCI configuration space");
return (DDI_FAILURE);
}
/* Read the Audio Controller's vendor, device, and revision IDs */
state->ts_devid =
(pci_config_get16(state->ts_pcih, PCI_CONF_VENID) << 16) |
pci_config_get16(state->ts_pcih, PCI_CONF_DEVID);
state->ts_revid = pci_config_get8(state->ts_pcih, PCI_CONF_REVID);
if (ddi_regs_map_setup(dip, TS_MEM_MAPPED_REGS,
(caddr_t *)&state->ts_regs, 0, 0, &ts_regs_attr, &state->ts_acch) !=
DDI_SUCCESS) {
audio_dev_warn(state->ts_adev,
"unable to map PCI device registers");
return (DDI_FAILURE);
}
switch (state->ts_devid) {
case 0x10b95451:
name = "ALI M5451";
break;
default:
name = "audiots";
break;
}
(void) snprintf(rev, sizeof (rev), "Rev %x", state->ts_revid);
audio_dev_set_description(state->ts_adev, name);
audio_dev_set_version(state->ts_adev, rev);
return (DDI_SUCCESS);
}
/*
* audiots_alloc_port()
*
* Description:
* This routine allocates the DMA handles and the memory for the
* DMA engines to use. It then binds each of the buffers to its
* respective handle, getting a DMA cookie.
*
* NOTE: All of the ddi_dma_... routines sleep if they cannot get
* memory. This means these calls should always succeed.
*
* NOTE: ddi_dma_alloc_handle() attempts to use the full 4 GB DMA address
* range. This is to work around Southbridge rev E/G OBP issues.
* (See Grover OBP note above)
*
* CAUTION: Make sure all errors call audio_dev_warn().
*
* Arguments:
* audiots_port_t *state The port structure for a device stream
* int num The port number
*
* Returns:
* DDI_SUCCESS DMA resources mapped
* DDI_FAILURE DMA resources not successfully mapped
*/
int
audiots_alloc_port(audiots_state_t *state, int num)
{
audiots_port_t *port;
dev_info_t *dip = state->ts_dip;
audio_dev_t *adev = state->ts_adev;
int dir;
unsigned caps;
ddi_dma_cookie_t cookie;
unsigned count;
int rc;
ddi_acc_handle_t regsh = state->ts_acch;
uint32_t *gcptr = &state->ts_regs->aud_regs.ap_cir_gc;
port = kmem_zalloc(sizeof (*port), KM_SLEEP);
state->ts_ports[num] = port;
port->tp_num = num;
port->tp_state = state;
port->tp_rate = TS_RATE;
if (num == TS_INPUT_PORT) {
dir = DDI_DMA_READ;
caps = ENGINE_INPUT_CAP;
port->tp_dma_stream = 31;
port->tp_sync_dir = DDI_DMA_SYNC_FORKERNEL;
} else {
dir = DDI_DMA_WRITE;
caps = ENGINE_OUTPUT_CAP;
port->tp_dma_stream = 0;
port->tp_sync_dir = DDI_DMA_SYNC_FORDEV;
}
port->tp_dma_mask = (1U << port->tp_dma_stream);
port->tp_nframes = 4096;
port->tp_size = port->tp_nframes * TS_FRAMESZ;
/* allocate dma handle */
rc = ddi_dma_alloc_handle(dip, &audiots_attr, DDI_DMA_SLEEP,
NULL, &port->tp_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->tp_dmah, port->tp_size, &ts_acc_attr,
DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &port->tp_kaddr,
&port->tp_size, &port->tp_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->tp_dmah, NULL,
port->tp_kaddr, port->tp_size, dir|DDI_DMA_CONSISTENT,
DDI_DMA_SLEEP, NULL, &cookie, &count);
if (rc != DDI_DMA_MAPPED) {
audio_dev_warn(adev,
"ddi_dma_addr_bind_handle failed: %d", rc);
return (DDI_FAILURE);
}
ASSERT(count == 1);
port->tp_paddr = cookie.dmac_address;
if ((unsigned)port->tp_paddr & 0x80000000U) {
ddi_put32(regsh, gcptr,
ddi_get32(regsh, gcptr) | AP_CIR_GC_SYS_MEM_4G_ENABLE);
} else {
ddi_put32(regsh, gcptr,
ddi_get32(regsh, gcptr) & ~(AP_CIR_GC_SYS_MEM_4G_ENABLE));
}
port->tp_engine = audio_engine_alloc(&audiots_engine_ops, caps);
if (port->tp_engine == NULL) {
audio_dev_warn(adev, "audio_engine_alloc failed");
return (DDI_FAILURE);
}
audio_engine_set_private(port->tp_engine, port);
audio_dev_add_engine(adev, port->tp_engine);
return (DDI_SUCCESS);
}
/*
* audiots_read_ac97()
*
* Description:
* This routine actually reads the AC-97 Codec's register. It may
* be called several times to succeed.
*
* NOTE:
* Revision M1535D B1-C of the ALI SouthBridge includes a workaround for
* the broken busy flag. Resetting the busy flag requires a software tweak
* to go with the worked around hardware. When we detect failure, we make
* 10 attempts to reset the chip before we fail. This should reset the new
* SB systems. On all SB systems, this will increse the read delay
* slightly, but shouldn't bother it otherwise.
*
* Arguments:
* audiots_state_t *state The device's state structure
* int reg AC-97 register number
*
* Returns:
* unsigned short The value in the specified register
*/
static uint16_t
audiots_read_ac97(audiots_state_t *state, int reg)
{
ddi_acc_handle_t acch = state->ts_acch;
uint16_t *addr;
uint16_t *data;
uint32_t *stimer = &state->ts_regs->aud_regs.ap_stimer;
uint32_t chk1;
uint32_t chk2;
int resets = 0;
int i;
if (state->ts_revid == AC_REV_ID1) {
addr = &state->ts_regs->aud_regs.ap_acrd_35D_reg;
data = &state->ts_regs->aud_regs.ap_acrd_35D_data;
} else {
addr = &state->ts_regs->aud_regs.ap_acrdwr_reg;
data = &state->ts_regs->aud_regs.ap_acrdwr_data;
}
first_read:
/* wait for ready to send read request */
for (i = 0; i < TS_READ_TRIES; i++) {
if (!(ddi_get16(acch, addr) & AP_ACRD_R_READ_BUSY)) {
break;
}
/* don't beat on the bus */
drv_usecwait(1);
}
if (i >= TS_READ_TRIES) {
if (resets < TS_RESET_TRIES) {
/* Attempt to reset */
drv_usecwait(TS_20US);
ddi_put16(acch, addr, TS_SB_RESET);
resets++;
goto first_read;
} else {
state->ts_flags |= TS_AUDIO_READ_FAILED;
if (!(state->ts_flags & TS_READ_FAILURE_PRINTED)) {
ddi_dev_report_fault(state->ts_dip,
DDI_SERVICE_LOST, DDI_DEVICE_FAULT,
"Unable to communicate with AC97 CODEC");
audio_dev_warn(state->ts_adev,
"The audio AC97 register has timed out.");
audio_dev_warn(state->ts_adev,
"Audio is now disabled.");
audio_dev_warn(state->ts_adev,
"Please reboot to restore audio.");
/* Don't flood the console */
state->ts_flags |= TS_READ_FAILURE_PRINTED;
}
}
return (0);
}
/* program the register to read */
ddi_put16(acch, addr, (reg|AP_ACRD_W_PRIMARY_CODEC|
AP_ACRD_W_READ_MIXER_REG|AP_ACRD_W_AUDIO_READ_REQ&
(~AP_ACWR_W_SELECT_WRITE)));
/* hardware bug work around */
chk1 = ddi_get32(acch, stimer);
chk2 = ddi_get32(acch, stimer);
i = TS_WAIT_CNT;
while (chk1 == chk2 && i) {
chk2 = ddi_get32(acch, stimer);
i--;
}
OR_SET_SHORT(acch, addr, AP_ACRD_W_READ_MIXER_REG);
resets = 0;
second_read:
/* wait again for read to send read request */
for (i = 0; i < TS_READ_TRIES; i++) {
if (!(ddi_get16(acch, addr) & AP_ACRD_R_READ_BUSY)) {
break;
}
/* don't beat on the bus */
drv_usecwait(1);
}
if (i >= TS_READ_TRIES) {
if (resets < TS_RESET_TRIES) {
/* Attempt to reset */
drv_usecwait(TS_20US);
ddi_put16(acch, addr, TS_SB_RESET);
resets++;
goto second_read;
} else {
state->ts_flags |= TS_AUDIO_READ_FAILED;
if (!(state->ts_flags & TS_READ_FAILURE_PRINTED)) {
ddi_dev_report_fault(state->ts_dip,
DDI_SERVICE_LOST, DDI_DEVICE_FAULT,
"Unable to communicate with AC97 CODEC");
audio_dev_warn(state->ts_adev,
"The audio AC97 register has timed out.");
audio_dev_warn(state->ts_adev,
"Audio is now disabled.");
audio_dev_warn(state->ts_adev,
"Please reboot to restore audio.");
/* Don't flood the console */
state->ts_flags |= TS_READ_FAILURE_PRINTED;
}
}
return (0);
}
return (ddi_get16(acch, data));
} /* audiots_read_ac97() */
/*
* audiots_set_ac97()
*
* Description:
* Set the value in the specified AC-97 Codec register. Just like
* reading the AC-97 Codec, it is possible there is a problem writing
* it as well. So we loop.
*
* Arguments:
* audiots_state_t *state The device's state structure
* int reg AC-97 register number
* uint16_t value The value to write
*/
static void
audiots_set_ac97(void *arg, uint8_t reg8, uint16_t data)
{
audiots_state_t *state = arg;
ddi_acc_handle_t handle = state->ts_acch;
uint16_t *data_addr = &state->ts_regs->aud_regs.ap_acrdwr_data;
uint16_t *reg_addr = &state->ts_regs->aud_regs.ap_acrdwr_reg;
int count;
int i;
uint16_t tmp_short;
uint16_t reg = reg8;
reg &= AP_ACWR_INDEX_MASK;
/* Don't touch the reserved bits on the pre 35D+ SouthBridge */
if (state->ts_revid == AC_REV_ID1) {
reg |= AP_ACWR_W_PRIMARY_CODEC|AP_ACWR_W_WRITE_MIXER_REG;
} else {
reg |= AP_ACWR_W_PRIMARY_CODEC|AP_ACWR_W_WRITE_MIXER_REG|
AP_ACWR_W_SELECT_WRITE;
}
for (count = TS_LOOP_CNT; count--; ) {
/* wait for ready to write */
for (i = 0; i < TS_WAIT_CNT; i++) {
if (!(ddi_get16(handle, reg_addr) &
AP_ACWR_R_WRITE_BUSY)) {
/* ready to write */
ddi_put16(handle, reg_addr, reg);
/* Write the data */
ddi_put16(handle, data_addr, data);
break;
}
}
if (i >= TS_WAIT_CNT) {
/* try again */
continue;
}
/* wait for write to complete */
for (i = 0; i < TS_WAIT_CNT; i++) {
if (!(ddi_get16(handle, reg_addr) &
AP_ACWR_R_WRITE_BUSY)) {
/* done writing */
break;
}
}
/* verify the value written */
tmp_short = audiots_get_ac97(state, reg8);
if (data == tmp_short) {
/* successfully loaded, so we can return */
return;
}
}
} /* audiots_set_ac97() */
/*
* audiots_open()
*
* Description:
* Opens a DMA engine for use. Will also ensure the device is powered
* up if not already done so.
*
* 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
audiots_open(void *arg, int flag, unsigned *nframesp, caddr_t *bufp)
{
audiots_port_t *port = arg;
_NOTE(ARGUNUSED(flag));
port->tp_count = 0;
port->tp_cso = 0;
*nframesp = port->tp_nframes;
*bufp = port->tp_kaddr;
return (0);
}
/*
* audiots_close()
*
* Description:
* Closes an audio DMA engine that was previously opened. Since
* nobody is using it, we could take this opportunity to possibly power
* down the entire device, or at least the DMA engine.
*
* Arguments:
* void *arg The DMA engine to shut down
*/
static void
audiots_close(void *arg)
{
_NOTE(ARGUNUSED(arg));
}
/*
* audiots_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
audiots_stop(void *arg)
{
audiots_port_t *port = arg;
audiots_state_t *state = port->tp_state;
ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_stop,
port->tp_dma_mask);
}
/*
* audiots_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
audiots_start(void *arg)
{
audiots_port_t *port = arg;
audiots_state_t *state = port->tp_state;
ddi_acc_handle_t handle = state->ts_acch;
audiots_regs_t *regs = state->ts_regs;
audiots_aram_t *aram;
audiots_eram_t *eram;
unsigned delta;
uint16_t ctrl;
uint16_t gvsel;
uint16_t eso;
aram = &regs->aud_ram[port->tp_dma_stream].aram;
eram = &regs->aud_ram[port->tp_dma_stream].eram;
port->tp_cso = 0;
gvsel = ERAM_WAVE_VOL | ERAM_PAN_0dB | ERAM_VOL_DEFAULT;
ctrl = ERAM_16_BITS | ERAM_STEREO | ERAM_LOOP_MODE | ERAM_SIGNED_PCM;
delta = (port->tp_rate << TS_SRC_SHIFT) / TS_RATE;
if (port->tp_num == TS_INPUT_PORT) {
delta = (TS_RATE << TS_SRC_SHIFT) / port->tp_rate;
}
eso = port->tp_nframes - 1;
/* program the sample rate */
ddi_put16(handle, &aram->aram_delta, (uint16_t)delta);
/* program the precision, number of channels and loop mode */
ddi_put16(handle, &eram->eram_ctrl_ec, ctrl);
/* program the volume settings */
ddi_put16(handle, &eram->eram_gvsel_pan_vol, gvsel);
/* set ALPHA and FMS to 0 */
ddi_put16(handle, &aram->aram_alpha_fms, 0x0);
/* set CSO to 0 */
ddi_put16(handle, &aram->aram_cso, 0x0);
/* set LBA */
ddi_put32(handle, &aram->aram_cptr_lba,
port->tp_paddr & ARAM_LBA_MASK);
/* set ESO */
ddi_put16(handle, &aram->aram_eso, eso);
/* stop the DMA engines */
ddi_put32(handle, &regs->aud_regs.ap_stop, port->tp_dma_mask);
/* now make sure it starts playing */
ddi_put32(handle, &regs->aud_regs.ap_start, port->tp_dma_mask);
return (0);
}
/*
* audiots_chinfo()
*
* Description:
* This is called by the framework to query the channel offsets
* and ordering.
*
* Arguments:
* void *arg The DMA engine to query
* int chan Channel number.
* unsigned *offset Starting offset of channel.
* unsigned *incr Increment (in samples) between frames.
*
* Returns:
* 0 indicating rate array is range instead of enumeration
*/
static void
audiots_chinfo(void *arg, int chan, unsigned *offset, unsigned *incr)
{
_NOTE(ARGUNUSED(arg));
*offset = chan;
*incr = 2;
}
/*
* audiots_format()
*
* Description:
* 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
audiots_format(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (AUDIO_FORMAT_S16_LE);
}
/*
* audiots_channels()
*
* Description:
* Called by the framework to query the channnels for the device.
*
* Arguments:
* void *arg The DMA engine to query
*
* Returns:
* 2 (Stereo).
*/
static int
audiots_channels(void *arg)
{
_NOTE(ARGUNUSED(arg));
return (2);
}
/*
* audiots_rate()
*
* Description:
* Called by the framework to query the sample rates for the device.
*
* Arguments:
* void *arg The DMA engine to query
*
* Returns:
* Sample rate in HZ (always 48000).
*/
static int
audiots_rate(void *arg)
{
audiots_port_t *port = arg;
return (port->tp_rate);
}
/*
* audiots_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
audiots_count(void *arg)
{
audiots_port_t *port = arg;
audiots_state_t *state = port->tp_state;
uint64_t val;
uint16_t cso;
unsigned n;
cso = ddi_get16(state->ts_acch,
&state->ts_regs->aud_ram[port->tp_dma_stream].aram.aram_cso);
n = (cso >= port->tp_cso) ?
cso - port->tp_cso :
cso + port->tp_nframes - port->tp_cso;
port->tp_cso = cso;
port->tp_count += n;
val = port->tp_count;
return (val);
}
/*
* audiots_sync()
*
* Description:
* This is called by the framework to synchronize DMA caches.
*
* Arguments:
* void *arg The DMA engine to sync
*/
static void
audiots_sync(void *arg, unsigned nframes)
{
audiots_port_t *port = arg;
_NOTE(ARGUNUSED(nframes));
(void) ddi_dma_sync(port->tp_dmah, 0, 0, port->tp_sync_dir);
}
/*
* audiots_stop_everything()
*
* Description:
* This routine disables the address engine interrupt for all 32 DMA
* engines. Just to be sure, it then explicitly issues a stop command to
* the address engine and envelope engines for all 32 channels.
*
* NOTE:
*
* There is a hardware bug that generates a spurious interrupt
* when the DMA engines are stopped. It's not consistent - it
* happens every 1 out of 6 stops or so. It will show up as a
* record interrupt. The problem is that once the driver is
* detached or if the system goes into low power mode, nobody
* will service that interrupt. The system will eventually become
* unusable.
*
* Arguments:
* audiots_state_t *state The device's state structure
*/
static void
audiots_stop_everything(audiots_state_t *state)
{
if (state->ts_acch == NULL)
return;
ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_ainten,
TS_ALL_DMA_OFF);
ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_stop,
TS_ALL_DMA_ENGINES);
ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_aint,
TS_ALL_DMA_ENGINES);
}
/*
* audiots_free_port()
*
* Description:
* This routine unbinds the DMA cookies, frees the DMA buffers,
* deallocates the DMA handles.
*
* Arguments:
* audiots_port_t *port The port structure for a device stream.
*/
void
audiots_free_port(audiots_port_t *port)
{
if (port == NULL)
return;
if (port->tp_engine) {
audio_dev_remove_engine(port->tp_state->ts_adev,
port->tp_engine);
audio_engine_free(port->tp_engine);
}
if (port->tp_paddr) {
(void) ddi_dma_unbind_handle(port->tp_dmah);
}
if (port->tp_acch) {
ddi_dma_mem_free(&port->tp_acch);
}
if (port->tp_dmah) {
ddi_dma_free_handle(&port->tp_dmah);
}
kmem_free(port, sizeof (*port));
}
/*
* audiots_destroy()
*
* Description:
* This routine releases all resources held by the device instance,
* as part of either detach or a failure in attach.
*
* Arguments:
* audiots_state_t *state The device soft state.
*/
void
audiots_destroy(audiots_state_t *state)
{
audiots_stop_everything(state);
for (int i = 0; i < TS_NUM_PORTS; i++)
audiots_free_port(state->ts_ports[i]);
if (state->ts_acch)
ddi_regs_map_free(&state->ts_acch);
if (state->ts_pcih)
pci_config_teardown(&state->ts_pcih);
if (state->ts_ac97)
ac97_free(state->ts_ac97);
if (state->ts_adev)
audio_dev_free(state->ts_adev);
ddi_soft_state_free(audiots_statep, ddi_get_instance(state->ts_dip));
}