/* * 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 2009 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. */ #include #include #include #include #include #include #include #include #include #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 *, 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 size_t audiots_qlen(void *); 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, audiots_qlen, audiots_chinfo }; /* * 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 uint_t audiots_intr(caddr_t); static int audiots_map_regs(dev_info_t *, audiots_state_t *); static void audiots_update_port(audiots_port_t *); static void audiots_start_port(audiots_port_t *); static void audiots_stop_port(audiots_port_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); static void audiots_reset_port(audiots_port_t *); /* * Global variables, but viewable only by this file. */ /* anchor for soft state structures */ static void *audiots_statep; /* driver name, so we don't have to call ddi_driver_name() or hard code strs */ static char *audiots_name = TS_NAME; /* * 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 */ if ((state = ddi_get_soft_state(audiots_statep, instance)) == NULL) { /* this will probably panic */ cmn_err(CE_WARN, "!%s%d: RESUME get soft state failed", audiots_name, instance); return (DDI_FAILURE); } 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_resume(state->ts_ac97); mutex_enter(&state->ts_lock); /* * Initialize/reset ports. Done under the lock, to * avoid race with interrupt service routine. */ state->ts_suspended = B_FALSE; for (int i = 0; i < TS_NUM_PORTS; i++) { audiots_port_t *port = state->ts_ports[i]; if (port != NULL) { /* relocate any streams properly */ if (port->tp_engine) audio_engine_reset(port->tp_engine); /* do a hardware reset on the port */ audiots_reset_port(port); if (port->tp_started) { audiots_start_port(port); } else { audiots_stop_port(port); } } } mutex_exit(&state->ts_lock); return (DDI_SUCCESS); default: cmn_err(CE_WARN, "!%s%d: attach() unknown command: 0x%x", audiots_name, instance, cmd); 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.", audiots_name, instance); cmn_err(CE_CONT, "Please reboot to restore audio."); return (DDI_FAILURE); } /* we don't support high level interrupts in this driver */ if (ddi_intr_hilevel(dip, 0) != 0) { cmn_err(CE_WARN, "!%s%d: unsupported high level interrupt", audiots_name, instance); 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", audiots_name, 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; } /* set up kernel statistics */ state->ts_ksp = kstat_create(TS_NAME, instance, TS_NAME, "controller", KSTAT_TYPE_INTR, 1, KSTAT_FLAG_PERSISTENT); if (state->ts_ksp != NULL) { kstat_install(state->ts_ksp); } /* set up the interrupt handler */ if (ddi_add_intr(dip, 0, NULL, NULL, audiots_intr, (caddr_t)state) != DDI_SUCCESS) { audio_dev_warn(state->ts_adev, "failed to register interrupt handler"); goto error; } state->ts_flags |= TS_INTR_INSTALLED; /* 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", audiots_name, instance); return (DDI_FAILURE); } switch (cmd) { case DDI_DETACH: break; case DDI_SUSPEND: ac97_suspend(state->ts_ac97); mutex_enter(&state->ts_lock); state->ts_suspended = B_TRUE; /* stop new ops */ /* we may already be powered down, so only save state if up */ /* stop playing and recording */ (void) audiots_stop_everything(state); mutex_exit(&state->ts_lock); 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 * * Returns: * void */ 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, ®s->aud_regs.ap_stop, TS_ALL_DMA_ENGINES); ddi_put32(handle, ®s->aud_regs.ap_ainten, TS_ALL_DMA_OFF); /* set global music and wave volume to 0dB */ ddi_put32(handle, ®s->aud_regs.ap_volume, 0x0); /* enable end interrupts for all channels. */ ddi_put32(handle, ®s->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, ®s->aud_ram[str].eram.eram_gvsel_pan_vol, (ERAM_WAVE_VOL|ERAM_PAN_LEFT|ERAM_PAN_0dB| ERAM_VOL_MAX_ATTEN)); ddi_put16(handle, ®s->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, ®s->aud_ram[str].eram.eram_ebuf1, ERAM_EBUF_STILL); ddi_put32(handle, ®s->aud_ram[str].eram.eram_ebuf2, ERAM_EBUF_STILL); /* program the initial eram and aram rate */ ddi_put16(handle, ®s->aud_ram[str].aram.aram_delta, 1 << TS_SRC_SHIFT); ddi_put16(handle, ®s->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; /* get the iblock cookie needed for interrupt context */ if (ddi_get_iblock_cookie(dip, 0, &state->ts_iblock) != DDI_SUCCESS) { audio_dev_warn(state->ts_adev, "cannot get iblock cookie"); return (DDI_FAILURE); } /* initialize the state mutexes and condition variables */ mutex_init(&state->ts_lock, NULL, MUTEX_DRIVER, state->ts_iblock); state->ts_flags |= TS_MUTEX_INIT; for (int i = 0; i < TS_NUM_PORTS; i++) { if (audiots_alloc_port(state, i) != DDI_SUCCESS) { return (DDI_FAILURE); } } /* init power management state */ state->ts_suspended = B_FALSE; return (DDI_SUCCESS); } /* * audiots_intr() * * Description: * Interrupt service routine for both play and record. For play we * get the next buffers worth of audio. For record we send it on to * the mixer. * * NOTE: This device needs to make sure any PIO access required to clear * its interrupt has made it out on the PCI bus before returning from its * interrupt handler so that the interrupt has been deasserted. This is * done by rereading the address engine interrupt register. * * Arguments: * caddr_t T Pointer to the interrupting device's state * structure * * Returns: * DDI_INTR_CLAIMED Interrupt claimed and processed * DDI_INTR_UNCLAIMED Interrupt not claimed, and thus ignored */ static uint_t audiots_intr(caddr_t T) { audiots_state_t *state = (void *)T; audiots_regs_t *regs = state->ts_regs; ddi_acc_handle_t handle = state->ts_acch; uint32_t interrupts; mutex_enter(&state->ts_lock); if (state->ts_suspended) { mutex_exit(&state->ts_lock); return (DDI_INTR_UNCLAIMED); } interrupts = ddi_get32(handle, ®s->aud_regs.ap_aint); if (interrupts == 0) { mutex_exit(&state->ts_lock); /* no interrupts to process, so it's not us */ return (DDI_INTR_UNCLAIMED); } /* * Clear the interrupts to acknowledge. Also, reread the * interrupt reg to ensure that PIO write has completed. */ ddi_put32(handle, ®s->aud_regs.ap_aint, interrupts); (void) ddi_get32(handle, ®s->aud_regs.ap_aint); /* update the kernel interrupt statistics */ if (state->ts_ksp) { TS_KIOP(state)->intrs[KSTAT_INTR_HARD]++; } mutex_exit(&state->ts_lock); for (int i = 0; i < TS_NUM_PORTS; i++) { audiots_port_t *port = state->ts_ports[i]; if (((interrupts & port->tp_int_mask) == 0) || (!port->tp_started)) continue; if (i == TS_INPUT_PORT) { audio_engine_produce(port->tp_engine); } else { audio_engine_consume(port->tp_engine); } } return (DDI_INTR_CLAIMED); } /* * 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; char *prop; 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; char *namestr; port = kmem_zalloc(sizeof (*port), KM_SLEEP); state->ts_ports[num] = port; port->tp_num = num; port->tp_state = state; port->tp_started = B_FALSE; port->tp_rate = 48000; if (num == TS_INPUT_PORT) { prop = "record-interrupts"; dir = DDI_DMA_READ; caps = ENGINE_INPUT_CAP; port->tp_dma_stream = 31; port->tp_int_stream = 2; port->tp_sync_dir = DDI_DMA_SYNC_FORKERNEL; } else { prop = "play-interrupts"; dir = DDI_DMA_WRITE; caps = ENGINE_OUTPUT_CAP; port->tp_dma_stream = 0; port->tp_int_stream = 1; port->tp_sync_dir = DDI_DMA_SYNC_FORDEV; } port->tp_int_mask = (1U << port->tp_int_stream); port->tp_dma_mask = (1U << port->tp_dma_stream); /* get the number of interrupts per second */ port->tp_intrs = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, prop, TS_INTS); /* make sure the values are good */ if (port->tp_intrs < TS_MIN_INTS) { audio_dev_warn(adev, "%s too low, %d, resetting to %d", prop, port->tp_intrs, TS_INTS); port->tp_intrs = TS_INTS; } else if (port->tp_intrs > TS_MAX_INTS) { audio_dev_warn(adev, "%s too high, %d, resetting to %d", prop, port->tp_intrs, TS_INTS); port->tp_intrs = TS_INTS; } /* * Now allocate space. We configure for the worst case. The * worst (biggest) case is 48000 kHz, at 4 bytes per frame * (16-bit stereo), with the lowest interrupt frequency. We * need two fragments though, and each half has to be rounded * up to allow for alignment considerations. */ /* 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, TS_BUFSZ, &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); state->ts_swapped = B_FALSE; /* * SPARCLE platform specific hack. For reasons that I can't * seem to fathom, the SPARCLE platform only gets the * endianness correct when transferring whole 32-bit words and * using little endian mapping. That isn't compatible with * the audio framework's access mode, so we have to set up * explicit swapping of the left and right channels. * * The real mystery here is why this is required for Tadpole * SPARCLE, but not for any other standard Sun platforms that * I've tested. * * "swap_channels" property can be used in driver.conf to * force the left/right as well. */ rc = ddi_prop_lookup_string(DDI_DEV_T_ANY, ddi_root_node(), 0, "name", &namestr); if (rc == DDI_PROP_SUCCESS) { if (strcmp(namestr, "TAD,SPARCLE") == 0) { state->ts_swapped = B_TRUE; } ddi_prop_free(namestr); } state->ts_swapped = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, "swap_channels", state->ts_swapped); 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 * * Returns: * void */ 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_reset_port() * * Description: * Initializes the hardware for a DMA engine. * We only support stereo 16-bit linear PCM (signed native endian). * * The audio core uses a single DMA buffer which is divided into two * halves. An interrupt is generated when the middle of the buffer has * been reached and at the end. The audio core resets the pointer back * to the beginning automatically. After the interrupt the driver clears * the buffer and asks the mixer for more audio samples. If there aren't * enough then silence is played out. * * Arguments: * audiots_port_t *port The DMA engine to reset * * Returns: * void */ static void audiots_reset_port(audiots_port_t *port) { 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; if (state->ts_suspended) return; 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; for (int i = 0; i < 2; i++) { delta = (port->tp_rate << TS_SRC_SHIFT) / TS_RATE; if (i == 0) { /* first do the DMA stream */ aram = ®s->aud_ram[port->tp_dma_stream].aram; eram = ®s->aud_ram[port->tp_dma_stream].eram; if (port->tp_num == TS_INPUT_PORT) { delta = (TS_RATE << TS_SRC_SHIFT) / port->tp_rate; } eso = port->tp_nframes - 1; } else { /* else do the interrupt stream */ aram = ®s->aud_ram[port->tp_int_stream].aram; eram = ®s->aud_ram[port->tp_int_stream].eram; /* interrupt stream is silent */ gvsel |= ERAM_VOL_MAX_ATTEN; eso = port->tp_fragfr - 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 & interrupt engines */ ddi_put32(handle, ®s->aud_regs.ap_stop, port->tp_int_mask | port->tp_dma_mask); /* enable interrupts */ OR_SET_WORD(handle, ®s->aud_regs.ap_ainten, port->tp_int_mask); } /* * 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 *fragfrp Receives number of frames per fragment * unsigned *nfragsp Receives number of fragments * caddr_t *bufp Receives kernel data buffer * * Returns: * 0 on success * errno on failure */ static int audiots_open(void *arg, int flag, unsigned *fragfrp, unsigned *nfragsp, caddr_t *bufp) { audiots_port_t *port = arg; unsigned nfrag; _NOTE(ARGUNUSED(flag)); /* * Round up - we have to have a sample that is a whole number * of 64-bit words. Since our frames are 4 bytes wide, we * just need an even number of frames. */ port->tp_fragfr = port->tp_rate / port->tp_intrs; port->tp_fragfr = (port->tp_fragfr + 1) & ~1; nfrag = port->tp_size / (port->tp_fragfr * TS_FRAMESZ); port->tp_nframes = nfrag * port->tp_fragfr; port->tp_started = B_FALSE; port->tp_count = 0; port->tp_cso = 0; *fragfrp = port->tp_fragfr; *nfragsp = nfrag; *bufp = port->tp_kaddr; /* * This should always be true because we used a worst case * assumption when calculating the port->tp_size. */ ASSERT((port->tp_fragfr * nfrag) <= port->tp_size); mutex_enter(&port->tp_state->ts_lock); audiots_reset_port(port); mutex_exit(&port->tp_state->ts_lock); return (0); } /* * audiots_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 * * Returns: * void */ static void audiots_close(void *arg) { audiots_port_t *port = arg; audiots_state_t *state = port->tp_state; mutex_enter(&state->ts_lock); audiots_stop_port(port); port->tp_started = B_FALSE; mutex_exit(&state->ts_lock); } /* * 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 * * Returns: * void */ static void audiots_stop(void *arg) { audiots_port_t *port = arg; audiots_state_t *state = port->tp_state; mutex_enter(&state->ts_lock); if (port->tp_started) { audiots_stop_port(port); } port->tp_started = B_FALSE; mutex_exit(&state->ts_lock); } /* * 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; mutex_enter(&state->ts_lock); if (!port->tp_started) { audiots_start_port(port); port->tp_started = B_TRUE; } mutex_exit(&state->ts_lock); 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) { audiots_port_t *port = arg; /* if channels are swapped (SPARCLE), then deal with it */ if (port->tp_state->ts_swapped) { *offset = (chan ? 0 : 1); } else { *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; mutex_enter(&state->ts_lock); audiots_update_port(port); val = port->tp_count; mutex_exit(&state->ts_lock); return (val); } /* * audiots_sync() * * Description: * This is called by the framework to synchronize DMA caches. * We also leverage this do some endian swapping, because on SPARC * the chip accesses the DMA region using 32-bit little-endian * accesses. Its not enough to just use the framework's sample * conversion logic, because the channels will also be backwards. * * Arguments: * void *arg The DMA engine to sync * * Returns: * void */ 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_qlen() * * Description: * This is called by the framework to determine on-device queue length. * * Arguments: * void *arg The DMA engine to query * * Returns: * hardware queue length not reported by count (0 for this device) */ static size_t audiots_qlen(void *arg) { _NOTE(ARGUNUSED(arg)); return (0); } /* * audiots_start_port() * * Description: * The audio core uses a single DMA buffer which is divided into two * halves. An interrupt is generated when the middle of the buffer has * been reached and at the end. The audio core resets the pointer back * to the beginning automatically. After the interrupt the driver clears * the buffer and asks the mixer for more audio samples. If there aren't * enough then silence is played out. * * Arguments: * audiots_port_t *port The DMA engine to start up * * Returns: * void */ static void audiots_start_port(audiots_port_t *port) { audiots_state_t *state = port->tp_state; audiots_regs_t *regs = state->ts_regs; ddi_acc_handle_t handle = state->ts_acch; ASSERT(mutex_owned(&state->ts_lock)); /* if suspended then do nothing else */ if (state->ts_suspended) { return; } /* make sure it starts playing */ ddi_put32(handle, ®s->aud_regs.ap_start, port->tp_dma_mask | port->tp_int_mask); ASSERT(mutex_owned(&state->ts_lock)); } /* * audiots_stop_port() * * Description: * This routine stops a DMA engine. * * Arguments: * audiots_port_t *port The port to stop * * Returns: * void */ static void audiots_stop_port(audiots_port_t *port) { audiots_state_t *state = port->tp_state; ASSERT(mutex_owned(&state->ts_lock)); if (state->ts_suspended) return; ddi_put32(state->ts_acch, &state->ts_regs->aud_regs.ap_stop, port->tp_int_mask | port->tp_dma_mask); ASSERT(mutex_owned(&state->ts_lock)); } /* * audiots_update_port() * * Description: * This routine updates the ports frame counter from hardware, and * gracefully handles wraps. * * Arguments: * audiots_port_t *port The port to stop * * Returns: * void */ static void audiots_update_port(audiots_port_t *port) { audiots_state_t *state = port->tp_state; uint16_t cso; unsigned n; ASSERT(mutex_owned(&state->ts_lock)); if (state->ts_suspended) return; 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; } /* * 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 * * Returns: * void */ 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. * * Returns: * None */ 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. * * Returns: * None */ void audiots_destroy(audiots_state_t *state) { audiots_stop_everything(state); if (state->ts_flags & TS_INTR_INSTALLED) ddi_remove_intr(state->ts_dip, 0, NULL); if (state->ts_ksp) kstat_delete(state->ts_ksp); 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); if (state->ts_flags & TS_MUTEX_INIT) { mutex_destroy(&state->ts_lock); } ddi_soft_state_free(audiots_statep, ddi_get_instance(state->ts_dip)); }