audio_4231_apcdma.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Platform specifc code for the APC DMA controller. The APC is an SBus
* IC that includes play and record DMA engines and an interface for
* the CS4231.
*/
#include <sys/audiovar.h>
/*
* Attribute structure for the APC, used to create DMA handles.
*/
static ddi_dma_attr_t apc_attr = {
DMA_ATTR_V0, /* version */
0x0000000000000000LL, /* dlim_addr_lo */
0x00000000ffffffffLL, /* dlim_addr_hi */
0x0000000000000fffLL, /* DMA counter register */
0x0000000000000001LL, /* DMA address alignment */
0x00000014, /* 4 and 16 byte burst sizes */
0x00000001, /* min effective DMA size */
0x0000000000000fffLL, /* maximum transfer size, 8k */
0x000000000000ffffLL, /* segment boundary, 32k */
0x00000001, /* s/g list length, no s/g */
0x00000001, /* granularity of device, don't care */
0 /* DMA flags */
};
static ddi_device_acc_attr_t acc_attr = {
};
/*
* Local routines
*/
/*
* DMA ops vector functions
*/
static void apc_unmap_regs(CS_state_t *);
static void apc_reset(CS_state_t *);
static int apc_add_intr(CS_state_t *);
static void apc_rem_intr(dev_info_t *);
static int apc_p_start(CS_state_t *);
static void apc_p_pause(CS_state_t *);
static void apc_p_restart(CS_state_t *);
static void apc_p_stop(CS_state_t *);
static int apc_r_start(CS_state_t *);
static void apc_r_stop(CS_state_t *);
static void apc_power(CS_state_t *, int);
"APC DMA controller",
};
/* File name for the cs4231_put8() and cs4231_reg_select() routines */
/*
* apc_map_regs()
*
* 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. Finally, the registers
* are mapped in.
*
* NOTE: All of the ddi_dma_... routines sleep if they cannot get
* memory. This means these calls will almost always succeed.
*
* Arguments:
* dev_info_t *dip Pointer to the device's devinfo
* CS_state_t *state The device's state structure
* size_t pbuf_size The size of the play DMA buffers to
* allocate, we need two
* size_t cbuf_size The size of the capture DMA buffers to
* allocate, we need two
*
* Returns:
* AUDIO_SUCCESS Registers successfully mapped
* AUDIO_FAILURE Registers not successfully mapped
*/
static int
{
int rc;
/* allocate two handles for play and two for record */
"!apc_map_regs() ddi_dma_alloc_handle(0) failed: %d", rc);
goto error;
}
"!apc_map_regs() ddi_dma_alloc_handle(1) failed: %d", rc);
goto error_ph0;
}
"!apc_map_regs() ddi_dma_alloc_handle(2) failed: %d", rc);
goto error_ph1;
}
"!apc_map_regs() ddi_dma_alloc_handle(3) failed: %d", rc);
goto error_rh0;
}
/* allocate the four DMA buffers, two for play and two for record */
"!apc_map_regs() ddi_dma_mem_alloc(0) failed");
goto error_rh1;
}
"!apc_map_regs() ddi_dma_mem_alloc(1) failed");
goto error_pb0;
}
"!apc_map_regs() ddi_dma_mem_alloc(2) failed");
goto error_pb1;
}
"!apc_map_regs() ddi_dma_mem_alloc(3) failed");
goto error_rb0;
}
/* bind each of the buffers to a DMA handle */
&dma_cookie_count)) != DDI_DMA_MAPPED) {
"!apc_map_regs() ddi_dma_addr_bind_handle(0) failed: %d",
rc);
goto error_rb1;
}
&dma_cookie_count)) != DDI_DMA_MAPPED) {
"!apc_map_regs() ddi_dma_addr_bind_handle(1) failed: %d",
rc);
goto error_pc0;
}
&dma_cookie_count)) != DDI_DMA_MAPPED) {
"!apc_map_regs() ddi_dma_addr_bind_handle(2) failed: %d",
rc);
goto error_pc1;
}
&dma_cookie_count)) != DDI_DMA_MAPPED) {
"!apc_map_regs() ddi_dma_addr_bind_handle(3) failed: %d",
rc);
goto error_rc0;
}
/* map in the registers, getting a handle */
"!apc_map_regs() ddi_regs_map_setup() failed: %d", rc);
goto error_rc1;
}
/* clear the CSR so we have all interrupts disabled */
/* let the state structure know about the attributes */
/* we start with play and record buffers 0 */
state->cs_pbuf_toggle = 0;
state->cs_cbuf_toggle = 0;
return (AUDIO_SUCCESS);
return (AUDIO_FAILURE);
} /* apc_map_regs() */
/*
* apc_unmap_regs()
*
* Description:
* This routine unbinds the DMA cookies, frees the DMA buffers,
* deallocated the DMA handles, and finally unmaps the Codec's and
* DMA engine's registers.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* void
*/
static void
{
} /* apc_unmap_regs() */
/*
* apc_reset()
*
* Description:
* Reset both the play and record DMA engines. The engines are left
* with interrupts and the DMA engine disabled.
*
* Arguments:
* dev_info_t *dip Pointer to the device's devinfo structure
* CS_state_t *state The device's state structure
*
* Returns:
* void
*/
static void
{
/*
* The APC has a bug where the reset is not done
* until you do the next pio to the APC. This
* next write to the CSR causes the posted reset to
* happen.
*/
} /* apc_reset() */
/*
* apc_add_intr()
*
* Description:
* Register the APC interrupts with the kernel.
*
* NOTE: This does NOT turn on interrupts.
*
* CAUTION: While the interrupts are added, the Codec interrupts are
* not enabled.
*
* Arguments:
* CS_state_t *state Pointer to the device's state structure
*
* Returns:
* AUDIO_SUCCESS Registers successfully mapped
* AUDIO_FAILURE Registers not successfully mapped
*/
static int
{
/* first we make sure this isn't a high level interrupt */
if (ddi_intr_hilevel(dip, 0) != 0) {
"!apc_add_intr() unsupported high level interrupt");
return (AUDIO_FAILURE);
}
/* okay to register the interrupt */
"!apc_add_intr() bad interrupt specification");
return (AUDIO_FAILURE);
}
return (AUDIO_SUCCESS);
} /* apc_add_intr() */
/*
* apc_rem_intr()
*
* Description:
* Unregister the APC interrupts from the kernel.
*
* CAUTION: While the interrupts are removed, the Codec interrupts are
* not disabled, but then, they never should have been on in
* the first place.
*
* Arguments:
* dev_info_t *dip Pointer to the device's devinfo
*
* Returns:
* void
*/
static void
{
} /* apc_rem_intr() */
/*
* apc_p_start()
*
* Description:
* This routine starts the play DMA engine. This includes "hard" starts
* where the DMA engine's registers need to be loaded as well as starting
* after a pause.
*
* For hard starts the DMA engine is started by programming the Play Next
* Virtual Address and then the Play Next Counter twice, and finally
* enabling the play DMA engine.
*
* Starting after a pause is much eaiser, we just turn on the DMA GO bit.
*
* NOTE: The state structure must be locked before this routine is called.
*
* CAUTION: This routine doesn't start the Codec because the first
* interrupt causes a recursive mutex_enter.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* AUDIO_SUCCESS Successfully started
* AUDIO_FAILURE Start failed
*/
static int
{
int i;
int samples;
/* figure out the number of samples we want */
/* if stereo & sr = 11025 & ints = 50 then 441 samples, bad! - so fix */
/* need to adjust */
samples++;
}
/* try to load 2 buffers worth to get things started */
for (i = 0; i < 2; i++) {
PLAY_COUNT = samples *
ATRACE("apc_p_start() "
"apc_program_play() returns no samples", state);
break;
}
}
/*
* If there wasn't anything to get then we aren't active. This can
* happen if an AUDIO_DRAIN was issued with no audio in the stream.
*/
if (i == 0) {
return (AUDIO_FAILURE);
}
/*
* Even with just one DMA buffer loaded we can still play some audio.
* Read the Codec Status Register so that we clear the Error and
* Initialization register, Index 11, so we'll know when we get
* a play underrun in the ISR. Then enable the play DMA engine,
* including interrupts.
*/
#ifdef DEBUG
ATRACE_8("apc_p_start() Read the Codec status reg, clearing Index 11",
#else
#endif
return (AUDIO_SUCCESS);
} /* apc_p_start() */
/*
* apc_p_pause()
*
* Description:
* This routine pauses the play DMA engine. Buffers, FIFO, etc. are NOT
* lost.
*
* NOTE: The state structure must be locked before this routine is called.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* void
*/
static void
{
int x;
/* clear the PDMA_GO bit, pausing the DMA engine */
/* we wait for the Codec FIFO to underrun */
}
/* stop the Codec */
} /* apc_p_pause() */
/*
* apc_p_restart()
*
* Description:
* This routine restarts the play DMA engine after pauseing. Buffers,
* FIFO, etc. Must be programmed and valid.
*
* NOTE: The state structure must be locked before this routine is called.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* void
*/
static void
{
/* start the Codec */
/* set the PDMA_GO bit, restarting the DMA engine */
} /* apc_p_restart() */
/*
* apc_p_stop()
*
* Description:
* This routine stops the play DMA engine.
*
* The DMA engine is stopped by using the PLAY_ABORT bit.
*
* NOTE: The state structure must be locked before this routine is called.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* void
*/
static void
{
int x;
/* Set stopped flag */
/* set the play abort bit to stop playing audio */
/* wait for the pipeline to empty */
}
#ifdef DEBUG
if (x >= CS4231_TIMEOUT) {
}
#endif
/* now clear the play enable and abort bits */
/* we wait for the Codec FIFO to underrun */
}
/* stop the Codec */
} /* apc_p_stop() */
/*
* apc_r_start()
*
* Description:
* This routine starts the record DMA engine. The DMA engine is never
* paused for record, so a pause is equivalent to a stop. Thus all starts
* are hard starts.
*
* For hard starts the DMA engine is started by programming the Record
* Next Virtual Address and then the Record Next Counter twice, and
* finally enabling the record DMA engine.
*
* NOTE: The state structure must be locked before this routine is called.
*
* CAUTION: This routine doesn't start the Codec because the first
* interrupt causes a recursive mutex_enter.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* AUDIO_SUCCESS The capture DMA was started
* AUDIO_FAILURE The capture DMA was not started
*/
static int
{
int bytes;
int i;
int samples;
int x;
/* figure out the number of samples to capture */
/* if stereo & sr = 11025 & ints = 50 then 441 samples, bad! - so fix */
/* need to adjust */
samples++;
}
/* now convert the number of samples to the "size" in bytes */
}
if (x >= CS4231_TIMEOUT) {
"timeout waiting for Codec, record not started!");
return (AUDIO_FAILURE);
}
/*
* Program the DMA engine with both buffers. We MUST do both buffers
* otherwise CAP_COUNT isn't going to set both byte counts.
*/
for (i = 0; i < 2; i++) {
/* read the CNVA, as per APC document */
/* sync the DMA buffer before it is going to be used */
DDI_DMA_SYNC_FORDEV) == DDI_FAILURE) {
"!apc_r_start() ddi_dma_sync() failed, recording "
"stopped");
/* send a cap. abort, this leaves the DMA engine ok */
/* wait for the pipeline to empty */
x++) {
}
/* now clear it */
return (AUDIO_FAILURE);
}
ATRACE_32("apc_r_start() next address",
/* now program the CNC reg., which enables the state machine */
/* now get ready for the next DMA buffer */
}
/* start the DMA engine */
ATRACE("apc_r_start() returning success", 0);
return (AUDIO_SUCCESS);
} /* apc_r_start() */
/*
* apc_r_stop()
*
* Description:
* This routine stops the record DMA engine. It then sends any collected
* data to the audio mixer.
*
* The DMA engine is stopped by using the CAP_ABORT bit.
*
* NOTE: The state structure must be locked before this routine is called.
*
* Arguments:
* CS_state_t *state The device's state structure
*
* Returns:
* void
*/
static void
{
int samples;
int x;
/* clear the record interrupts so the ISR doesn't get involved */
/* first, abort the record DMA engine */
/* wait for the pipeline to empty */
}
#ifdef DEBUG
if (x >= CS4231_TIMEOUT) {
"apc_r_stop() timeout, record buffer flushed");
}
#endif
/* clear the CXI interrupt bit, but CXI interrupt didn't go out */
/* stop the Codec */
/* figure how many samples were recorded */
/* send the captured audio to the mixer */
DDI_FAILURE) {
"!apc_r_stop() ddi_dma_sync() failed, recorded audio lost");
} else {
samples);
}
/* check that the pipeline is empty */
}
#ifdef DEBUG
if (x >= CS4231_TIMEOUT) {
"apc_r_stop() buffer pipeline not empty");
}
#endif
} /* apc_r_stop() */
/*
* apc_power()
*
* Description:
* This routine turns the Codec off by using the COD_PDWN bit in the
* apc chip. To turn power on we have to reset the APC, which clears
* the COD_PDWN bit. However, this is a settling bug in the APC which
* requires the driver to delay quite a while before we may continue.
* Since this is the first time this feature has actually been used
* it isn't too surprising that it has some problems.
*
* NOTE: The state structure must be locked when this routine is called.
*
* Arguments:
* CS_state_t *state Ptr to the device's state structure
* int level Power level to set
*
* Returns:
* void
*/
static void
{
/*
* wait for state change,
* __lock_lint tells warlock not to flag this delay()
*/
#ifndef __lock_lint
#endif
} else { /* turn power off */
}
} /* apc_power() */
/* ******* Local Routines ************************************************** */
/*
* apc_intr()
*
* Description:
* APC interrupt service routine, which services both play and capture
* interrupts. First we find out why there was an interrupt, then we
* take the appropriate action.
*
* Because this ISR deals with both play and record interrupts we have
* to be careful to not lose an interrupt. So we service the record
* interrupt first and save the incoming data until later. This is all
* done without releasing the lock, thus there can be no race conditions.
* Then we process the play interrupt. While processing the play interrupt
* we have to release the lock. When this happens we send recorded data
* to the mixer and then get the next chunk of data to play. If there
* wasn't a play interrupt then we finish by sending the recorded data,
* if any.
*
* 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
{
int rc = DDI_INTR_UNCLAIMED;
int rec_samples;
int samples;
int x;
/* the state must be protected */
/* get the APC CSR */
/* make sure this device sent the interrupt */
/* Interrupt received when stopping play */
/* Clear stopped flag */
return (DDI_INTR_CLAIMED);
} else if (csr & APC_PMI_EN) {
/*
* Clear device generated interrupt while play is
* active (Only seen while playing and insane mode
* switching)
*/
return (DDI_INTR_CLAIMED);
} else {
/* nope, this isn't our interrupt */
ATRACE_32("apc_intr() device didn't send interrupt",
csr);
return (DDI_INTR_UNCLAIMED);
}
}
/* Clear stopped flag */
/* clear all interrupts we captured this time */
if (csr & APC_CINTR_MASK) {
/* sync DMA memory before sending it to the audio mixer */
DDI_DMA_SYNC_FORCPU) == DDI_FAILURE) {
"!apc_intr(R) ddi_dma_sync(#1) failed, recorded "
"audio lost");
} else {
/* figure how many samples were recorded */
/* save the data for when we free the lock */
}
/* sync the DMA buffer before it is going to be reused */
DDI_DMA_SYNC_FORDEV) == DDI_FAILURE) {
"!apc_intr(R) ddi_dma_sync(#2) failed, recording "
"disabled");
/* send a play abort, this leaves the DMA engine ok */
/* now clear it */
} else {
ATRACE_32("apc_intr(R) next address",
/* read the CNVA, as per APC document */
/* now program the DMA buffer into the Next Address */
/* program the CNC reg., which enables the state mach */
/* now get ready for the next DMA buffer */
ATRACE_32("apc_intr(R) new toggle",
}
/* we always claim the interrupt, even if DMA sync failed */
}
if (csr & APC_PINTR_MASK) {
/* figure out the number of samples we want */
samples = PLAY_COUNT /
/* try to load the next audio buffer, even if pipe is empty */
ATRACE_32("apc_intr(P) samples apc_program_play() returned",
samples);
/*
* There isn't any more data to play, so wait for
* the pipe and the Codec FIFO to empty. Then turn
* off the play DMA engine by aborting. Also, we should
* note that simple play interrupts with no samples
* are ignored, but acknowledged. We wait for the pipe
* to empty before we declare the DMA engine is empty.
*/
ATRACE_32("apc_intr(P) no more data, wait for FIFO",
samples);
/*
* We wait for the Codec FIFO to underrun, this
* implies that the APC pipe is also empty.
*/
x++) {
}
/*
* Clear the flag so if audio is restarted while in
* am_play_shutdown() we can detect it and not mess
* things up.
*/
/* now shutdown the play stream */
/* send the captured audio to the mixer ASAP */
if (rec_buf) {
}
/* make sure playing wasn't restarted when lock lost */
/* yes, it was, so we're done */
ATRACE("apc_intr() restart after shutdown", 0);
goto done;
}
/* reset the DMA engine, putting it in a known state */
/* wait for the pipeline to empty */
x++) {
}
/* clear the abort bit */
/* disable the Codec */
/* and reset the status */
}
}
done:
/* APC error interrupt, not sure what to do here */
"!apc_intr() error interrupt: 0x%x", csr);
}
/* update the kernel interrupt statistics */
if (rc == DDI_INTR_CLAIMED) {
}
}
/* one last chance to send the captured audio to the mixer */
if (rec_buf) {
}
return (rc);
} /* apc_intr() */
/*
* apc_program_play()
*
* Description:
* This routine is used by apc_p_start() and apc_intr() to program
* the play DMA engine with the next buffer full of audio.
*
* Arguments:
* CS_state_t *state The device's state pointer
* int samples The number of samples to be retrieved
* from the mixer
* caddr_t *rec_buf Ptr to data buffer for record
* int rec_samples Num samples in record data buffer
*
* Returns:
* 0 The buffer wasn't programmed, no audio
* > 0 The buffer was programmed
* AUDIO_FAILURE The buffer wasn't programmed
*/
static int
int rec_samples)
{
int rc;
/* we need the precision to calculate the next count correctly */
/* send record audio, then get the first buffer's worth of audio */
/* send the captured audio to the mixer */
}
samples);
if (rc == AUDIO_FAILURE) {
"!apc_program_play() am_get_audio() failed");
return (AUDIO_FAILURE);
} else if (rc == 0) {
ATRACE("apc_program_play() am_get_audio() returned 0 samples",
state);
return (0);
}
/* sync the DMA buffer before it is going to be used */
DDI_DMA_SYNC_FORDEV) == DDI_FAILURE) {
"!apc_program_play() ddi_dma_sync(#2) failed, audio lost");
/* send a play abort, this leaves the DMA engine ok */
/* now clear it */
return (AUDIO_FAILURE);
}
/* program the PNVA */
ATRACE_32("apc_program_play() next address",
/* now program the PNC register, which enables the state machine */
/* now get ready for the next time we need a DMA buffer */
return (rc);
} /* apc_program_play() */