linux/sound/aoa/soundbus/i2sbus/pcm.c

1063 lines
27 KiB
C
Raw Normal View History

/*
* i2sbus driver -- pcm routines
*
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
*
* GPL v2, can be found in COPYING.
*/
#include <asm/io.h>
#include <linux/delay.h>
#include <sound/core.h>
#include <asm/macio.h>
#include <linux/pci.h>
#include "../soundbus.h"
#include "i2sbus.h"
static inline void get_pcm_info(struct i2sbus_dev *i2sdev, int in,
struct pcm_info **pi, struct pcm_info **other)
{
if (in) {
if (pi)
*pi = &i2sdev->in;
if (other)
*other = &i2sdev->out;
} else {
if (pi)
*pi = &i2sdev->out;
if (other)
*other = &i2sdev->in;
}
}
static int clock_and_divisors(int mclk, int sclk, int rate, int *out)
{
/* sclk must be derived from mclk! */
if (mclk % sclk)
return -1;
/* derive sclk register value */
if (i2s_sf_sclkdiv(mclk / sclk, out))
return -1;
if (I2S_CLOCK_SPEED_18MHz % (rate * mclk) == 0) {
if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_18MHz / (rate * mclk), out)) {
*out |= I2S_SF_CLOCK_SOURCE_18MHz;
return 0;
}
}
if (I2S_CLOCK_SPEED_45MHz % (rate * mclk) == 0) {
if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_45MHz / (rate * mclk), out)) {
*out |= I2S_SF_CLOCK_SOURCE_45MHz;
return 0;
}
}
if (I2S_CLOCK_SPEED_49MHz % (rate * mclk) == 0) {
if (!i2s_sf_mclkdiv(I2S_CLOCK_SPEED_49MHz / (rate * mclk), out)) {
*out |= I2S_SF_CLOCK_SOURCE_49MHz;
return 0;
}
}
return -1;
}
#define CHECK_RATE(rate) \
do { if (rates & SNDRV_PCM_RATE_ ##rate) { \
int dummy; \
if (clock_and_divisors(sysclock_factor, \
bus_factor, rate, &dummy)) \
rates &= ~SNDRV_PCM_RATE_ ##rate; \
} } while (0)
static int i2sbus_pcm_open(struct i2sbus_dev *i2sdev, int in)
{
struct pcm_info *pi, *other;
struct soundbus_dev *sdev;
int masks_inited = 0, err;
struct codec_info_item *cii, *rev;
struct snd_pcm_hardware *hw;
u64 formats = 0;
unsigned int rates = 0;
struct transfer_info v;
int result = 0;
int bus_factor = 0, sysclock_factor = 0;
int found_this;
mutex_lock(&i2sdev->lock);
get_pcm_info(i2sdev, in, &pi, &other);
hw = &pi->substream->runtime->hw;
sdev = &i2sdev->sound;
if (pi->active) {
/* alsa messed up */
result = -EBUSY;
goto out_unlock;
}
/* we now need to assign the hw */
list_for_each_entry(cii, &sdev->codec_list, list) {
struct transfer_info *ti = cii->codec->transfers;
bus_factor = cii->codec->bus_factor;
sysclock_factor = cii->codec->sysclock_factor;
while (ti->formats && ti->rates) {
v = *ti;
if (ti->transfer_in == in
&& cii->codec->usable(cii, ti, &v)) {
if (masks_inited) {
formats &= v.formats;
rates &= v.rates;
} else {
formats = v.formats;
rates = v.rates;
masks_inited = 1;
}
}
ti++;
}
}
if (!masks_inited || !bus_factor || !sysclock_factor) {
result = -ENODEV;
goto out_unlock;
}
/* bus dependent stuff */
hw->info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME |
SNDRV_PCM_INFO_JOINT_DUPLEX;
CHECK_RATE(5512);
CHECK_RATE(8000);
CHECK_RATE(11025);
CHECK_RATE(16000);
CHECK_RATE(22050);
CHECK_RATE(32000);
CHECK_RATE(44100);
CHECK_RATE(48000);
CHECK_RATE(64000);
CHECK_RATE(88200);
CHECK_RATE(96000);
CHECK_RATE(176400);
CHECK_RATE(192000);
hw->rates = rates;
/* well. the codec might want 24 bits only, and we'll
* ever only transfer 24 bits, but they are top-aligned!
* So for alsa, we claim that we're doing full 32 bit
* while in reality we'll ignore the lower 8 bits of
* that when doing playback (they're transferred as 0
* as far as I know, no codecs we have are 32-bit capable
* so I can't really test) and when doing recording we'll
* always have those lower 8 bits recorded as 0 */
if (formats & SNDRV_PCM_FMTBIT_S24_BE)
formats |= SNDRV_PCM_FMTBIT_S32_BE;
if (formats & SNDRV_PCM_FMTBIT_U24_BE)
formats |= SNDRV_PCM_FMTBIT_U32_BE;
/* now mask off what we can support. I suppose we could
* also support S24_3LE and some similar formats, but I
* doubt there's a codec that would be able to use that,
* so we don't support it here. */
hw->formats = formats & (SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_U16_BE |
SNDRV_PCM_FMTBIT_S32_BE |
SNDRV_PCM_FMTBIT_U32_BE);
/* we need to set the highest and lowest rate possible.
* These are the highest and lowest rates alsa can
* support properly in its bitfield.
* Below, we'll use that to restrict to the rate
* currently in use (if any). */
hw->rate_min = 5512;
hw->rate_max = 192000;
/* if the other stream is active, then we can only
* support what it is currently using.
* FIXME: I lied. This comment is wrong. We can support
* anything that works with the same serial format, ie.
* when recording 24 bit sound we can well play 16 bit
* sound at the same time iff using the same transfer mode.
*/
if (other->active) {
/* FIXME: is this guaranteed by the alsa api? */
hw->formats &= (1ULL << i2sdev->format);
/* see above, restrict rates to the one we already have */
hw->rate_min = i2sdev->rate;
hw->rate_max = i2sdev->rate;
}
hw->channels_min = 2;
hw->channels_max = 2;
/* these are somewhat arbitrary */
hw->buffer_bytes_max = 131072;
hw->period_bytes_min = 256;
hw->period_bytes_max = 16384;
hw->periods_min = 3;
hw->periods_max = MAX_DBDMA_COMMANDS;
err = snd_pcm_hw_constraint_integer(pi->substream->runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (err < 0) {
result = err;
goto out_unlock;
}
list_for_each_entry(cii, &sdev->codec_list, list) {
if (cii->codec->open) {
err = cii->codec->open(cii, pi->substream);
if (err) {
result = err;
/* unwind */
found_this = 0;
list_for_each_entry_reverse(rev,
&sdev->codec_list, list) {
if (found_this && rev->codec->close) {
rev->codec->close(rev,
pi->substream);
}
if (rev == cii)
found_this = 1;
}
goto out_unlock;
}
}
}
out_unlock:
mutex_unlock(&i2sdev->lock);
return result;
}
#undef CHECK_RATE
static int i2sbus_pcm_close(struct i2sbus_dev *i2sdev, int in)
{
struct codec_info_item *cii;
struct pcm_info *pi;
int err = 0, tmp;
mutex_lock(&i2sdev->lock);
get_pcm_info(i2sdev, in, &pi, NULL);
list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
if (cii->codec->close) {
tmp = cii->codec->close(cii, pi->substream);
if (tmp)
err = tmp;
}
}
pi->substream = NULL;
pi->active = 0;
mutex_unlock(&i2sdev->lock);
return err;
}
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
static void i2sbus_wait_for_stop(struct i2sbus_dev *i2sdev,
struct pcm_info *pi)
{
unsigned long flags;
struct completion done;
long timeout;
spin_lock_irqsave(&i2sdev->low_lock, flags);
if (pi->dbdma_ring.stopping) {
init_completion(&done);
pi->stop_completion = &done;
spin_unlock_irqrestore(&i2sdev->low_lock, flags);
timeout = wait_for_completion_timeout(&done, HZ);
spin_lock_irqsave(&i2sdev->low_lock, flags);
pi->stop_completion = NULL;
if (timeout == 0) {
/* timeout expired, stop dbdma forcefully */
printk(KERN_ERR "i2sbus_wait_for_stop: timed out\n");
/* make sure RUN, PAUSE and S0 bits are cleared */
out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
pi->dbdma_ring.stopping = 0;
timeout = 10;
while (in_le32(&pi->dbdma->status) & ACTIVE) {
if (--timeout <= 0)
break;
udelay(1);
}
}
}
spin_unlock_irqrestore(&i2sdev->low_lock, flags);
}
#ifdef CONFIG_PM
void i2sbus_wait_for_stop_both(struct i2sbus_dev *i2sdev)
{
struct pcm_info *pi;
get_pcm_info(i2sdev, 0, &pi, NULL);
i2sbus_wait_for_stop(i2sdev, pi);
get_pcm_info(i2sdev, 1, &pi, NULL);
i2sbus_wait_for_stop(i2sdev, pi);
}
#endif
static int i2sbus_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
}
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in)
{
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
struct pcm_info *pi;
get_pcm_info(i2sdev, in, &pi, NULL);
if (pi->dbdma_ring.stopping)
i2sbus_wait_for_stop(i2sdev, pi);
snd_pcm_lib_free_pages(substream);
return 0;
}
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
static int i2sbus_playback_hw_free(struct snd_pcm_substream *substream)
{
return i2sbus_hw_free(substream, 0);
}
static int i2sbus_record_hw_free(struct snd_pcm_substream *substream)
{
return i2sbus_hw_free(substream, 1);
}
static int i2sbus_pcm_prepare(struct i2sbus_dev *i2sdev, int in)
{
/* whee. Hard work now. The user has selected a bitrate
* and bit format, so now we have to program our
* I2S controller appropriately. */
struct snd_pcm_runtime *runtime;
struct dbdma_cmd *command;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
int i, periodsize, nperiods;
dma_addr_t offset;
struct bus_info bi;
struct codec_info_item *cii;
int sfr = 0; /* serial format register */
int dws = 0; /* data word sizes reg */
int input_16bit;
struct pcm_info *pi, *other;
int cnt;
int result = 0;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
unsigned int cmd, stopaddr;
mutex_lock(&i2sdev->lock);
get_pcm_info(i2sdev, in, &pi, &other);
if (pi->dbdma_ring.running) {
result = -EBUSY;
goto out_unlock;
}
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
if (pi->dbdma_ring.stopping)
i2sbus_wait_for_stop(i2sdev, pi);
if (!pi->substream || !pi->substream->runtime) {
result = -EINVAL;
goto out_unlock;
}
runtime = pi->substream->runtime;
pi->active = 1;
if (other->active &&
((i2sdev->format != runtime->format)
|| (i2sdev->rate != runtime->rate))) {
result = -EINVAL;
goto out_unlock;
}
i2sdev->format = runtime->format;
i2sdev->rate = runtime->rate;
periodsize = snd_pcm_lib_period_bytes(pi->substream);
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
nperiods = pi->substream->runtime->periods;
pi->current_period = 0;
/* generate dbdma command ring first */
command = pi->dbdma_ring.cmds;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
memset(command, 0, (nperiods + 2) * sizeof(struct dbdma_cmd));
/* commands to DMA to/from the ring */
/*
* For input, we need to do a graceful stop; if we abort
* the DMA, we end up with leftover bytes that corrupt
* the next recording. To do this we set the S0 status
* bit and wait for the DMA controller to stop. Each
* command has a branch condition to
* make it branch to a stop command if S0 is set.
* On input we also need to wait for the S7 bit to be
* set before turning off the DMA controller.
* In fact we do the graceful stop for output as well.
*/
offset = runtime->dma_addr;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
cmd = (in? INPUT_MORE: OUTPUT_MORE) | BR_IFSET | INTR_ALWAYS;
stopaddr = pi->dbdma_ring.bus_cmd_start +
(nperiods + 1) * sizeof(struct dbdma_cmd);
for (i = 0; i < nperiods; i++, command++, offset += periodsize) {
command->command = cpu_to_le16(cmd);
command->cmd_dep = cpu_to_le32(stopaddr);
command->phy_addr = cpu_to_le32(offset);
command->req_count = cpu_to_le16(periodsize);
}
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
/* branch back to beginning of ring */
command->command = cpu_to_le16(DBDMA_NOP | BR_ALWAYS);
command->cmd_dep = cpu_to_le32(pi->dbdma_ring.bus_cmd_start);
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
command++;
/* set stop command */
command->command = cpu_to_le16(DBDMA_STOP);
/* ok, let's set the serial format and stuff */
switch (runtime->format) {
/* 16 bit formats */
case SNDRV_PCM_FORMAT_S16_BE:
case SNDRV_PCM_FORMAT_U16_BE:
/* FIXME: if we add different bus factors we need to
* do more here!! */
bi.bus_factor = 0;
list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
bi.bus_factor = cii->codec->bus_factor;
break;
}
if (!bi.bus_factor) {
result = -ENODEV;
goto out_unlock;
}
input_16bit = 1;
break;
case SNDRV_PCM_FORMAT_S32_BE:
case SNDRV_PCM_FORMAT_U32_BE:
/* force 64x bus speed, otherwise the data cannot be
* transferred quickly enough! */
bi.bus_factor = 64;
input_16bit = 0;
break;
default:
result = -EINVAL;
goto out_unlock;
}
/* we assume all sysclocks are the same! */
list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
bi.sysclock_factor = cii->codec->sysclock_factor;
break;
}
if (clock_and_divisors(bi.sysclock_factor,
bi.bus_factor,
runtime->rate,
&sfr) < 0) {
result = -EINVAL;
goto out_unlock;
}
switch (bi.bus_factor) {
case 32:
sfr |= I2S_SF_SERIAL_FORMAT_I2S_32X;
break;
case 64:
sfr |= I2S_SF_SERIAL_FORMAT_I2S_64X;
break;
}
/* FIXME: THIS ASSUMES MASTER ALL THE TIME */
sfr |= I2S_SF_SCLK_MASTER;
list_for_each_entry(cii, &i2sdev->sound.codec_list, list) {
int err = 0;
if (cii->codec->prepare)
err = cii->codec->prepare(cii, &bi, pi->substream);
if (err) {
result = err;
goto out_unlock;
}
}
/* codecs are fine with it, so set our clocks */
if (input_16bit)
dws = (2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) |
(2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) |
I2S_DWS_DATA_IN_16BIT | I2S_DWS_DATA_OUT_16BIT;
else
dws = (2 << I2S_DWS_NUM_CHANNELS_IN_SHIFT) |
(2 << I2S_DWS_NUM_CHANNELS_OUT_SHIFT) |
I2S_DWS_DATA_IN_24BIT | I2S_DWS_DATA_OUT_24BIT;
/* early exit if already programmed correctly */
/* not locking these is fine since we touch them only in this function */
if (in_le32(&i2sdev->intfregs->serial_format) == sfr
&& in_le32(&i2sdev->intfregs->data_word_sizes) == dws)
goto out_unlock;
/* let's notify the codecs about clocks going away.
* For now we only do mastering on the i2s cell... */
list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
if (cii->codec->switch_clock)
cii->codec->switch_clock(cii, CLOCK_SWITCH_PREPARE_SLAVE);
i2sbus_control_enable(i2sdev->control, i2sdev);
i2sbus_control_cell(i2sdev->control, i2sdev, 1);
out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED);
i2sbus_control_clock(i2sdev->control, i2sdev, 0);
msleep(1);
/* wait for clock stopped. This can apparently take a while... */
cnt = 100;
while (cnt-- &&
!(in_le32(&i2sdev->intfregs->intr_ctl) & I2S_PENDING_CLOCKS_STOPPED)) {
msleep(5);
}
out_le32(&i2sdev->intfregs->intr_ctl, I2S_PENDING_CLOCKS_STOPPED);
/* not locking these is fine since we touch them only in this function */
out_le32(&i2sdev->intfregs->serial_format, sfr);
out_le32(&i2sdev->intfregs->data_word_sizes, dws);
i2sbus_control_enable(i2sdev->control, i2sdev);
i2sbus_control_cell(i2sdev->control, i2sdev, 1);
i2sbus_control_clock(i2sdev->control, i2sdev, 1);
msleep(1);
list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
if (cii->codec->switch_clock)
cii->codec->switch_clock(cii, CLOCK_SWITCH_SLAVE);
out_unlock:
mutex_unlock(&i2sdev->lock);
return result;
}
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
#ifdef CONFIG_PM
void i2sbus_pcm_prepare_both(struct i2sbus_dev *i2sdev)
{
i2sbus_pcm_prepare(i2sdev, 0);
i2sbus_pcm_prepare(i2sdev, 1);
}
#endif
static int i2sbus_pcm_trigger(struct i2sbus_dev *i2sdev, int in, int cmd)
{
struct codec_info_item *cii;
struct pcm_info *pi;
int result = 0;
unsigned long flags;
spin_lock_irqsave(&i2sdev->low_lock, flags);
get_pcm_info(i2sdev, in, &pi, NULL);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
if (pi->dbdma_ring.running) {
result = -EALREADY;
goto out_unlock;
}
list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
if (cii->codec->start)
cii->codec->start(cii, pi->substream);
pi->dbdma_ring.running = 1;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
if (pi->dbdma_ring.stopping) {
/* Clear the S0 bit, then see if we stopped yet */
out_le32(&pi->dbdma->control, 1 << 16);
if (in_le32(&pi->dbdma->status) & ACTIVE) {
/* possible race here? */
udelay(10);
if (in_le32(&pi->dbdma->status) & ACTIVE) {
pi->dbdma_ring.stopping = 0;
goto out_unlock; /* keep running */
}
}
}
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
/* make sure RUN, PAUSE and S0 bits are cleared */
out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
/* set branch condition select register */
out_le32(&pi->dbdma->br_sel, (1 << 16) | 1);
/* write dma command buffer address to the dbdma chip */
out_le32(&pi->dbdma->cmdptr, pi->dbdma_ring.bus_cmd_start);
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
/* initialize the frame count and current period */
pi->current_period = 0;
pi->frame_count = in_le32(&i2sdev->intfregs->frame_count);
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
/* set the DMA controller running */
out_le32(&pi->dbdma->control, (RUN << 16) | RUN);
/* off you go! */
break;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
if (!pi->dbdma_ring.running) {
result = -EALREADY;
goto out_unlock;
}
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
pi->dbdma_ring.running = 0;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
/* Set the S0 bit to make the DMA branch to the stop cmd */
out_le32(&pi->dbdma->control, (1 << 16) | 1);
pi->dbdma_ring.stopping = 1;
list_for_each_entry(cii, &i2sdev->sound.codec_list, list)
if (cii->codec->stop)
cii->codec->stop(cii, pi->substream);
break;
default:
result = -EINVAL;
goto out_unlock;
}
out_unlock:
spin_unlock_irqrestore(&i2sdev->low_lock, flags);
return result;
}
static snd_pcm_uframes_t i2sbus_pcm_pointer(struct i2sbus_dev *i2sdev, int in)
{
struct pcm_info *pi;
u32 fc;
get_pcm_info(i2sdev, in, &pi, NULL);
fc = in_le32(&i2sdev->intfregs->frame_count);
fc = fc - pi->frame_count;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
if (fc >= pi->substream->runtime->buffer_size)
fc %= pi->substream->runtime->buffer_size;
return fc;
}
static inline void handle_interrupt(struct i2sbus_dev *i2sdev, int in)
{
struct pcm_info *pi;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
u32 fc, nframes;
u32 status;
int timeout, i;
int dma_stopped = 0;
struct snd_pcm_runtime *runtime;
spin_lock(&i2sdev->low_lock);
get_pcm_info(i2sdev, in, &pi, NULL);
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
if (!pi->dbdma_ring.running && !pi->dbdma_ring.stopping)
goto out_unlock;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
i = pi->current_period;
runtime = pi->substream->runtime;
while (pi->dbdma_ring.cmds[i].xfer_status) {
if (le16_to_cpu(pi->dbdma_ring.cmds[i].xfer_status) & BT)
/*
* BT is the branch taken bit. If it took a branch
* it is because we set the S0 bit to make it
* branch to the stop command.
*/
dma_stopped = 1;
pi->dbdma_ring.cmds[i].xfer_status = 0;
if (++i >= runtime->periods) {
i = 0;
pi->frame_count += runtime->buffer_size;
}
pi->current_period = i;
/*
* Check the frame count. The DMA tends to get a bit
* ahead of the frame counter, which confuses the core.
*/
fc = in_le32(&i2sdev->intfregs->frame_count);
nframes = i * runtime->period_size;
if (fc < pi->frame_count + nframes)
pi->frame_count = fc - nframes;
}
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
if (dma_stopped) {
timeout = 1000;
for (;;) {
status = in_le32(&pi->dbdma->status);
if (!(status & ACTIVE) && (!in || (status & 0x80)))
break;
if (--timeout <= 0) {
printk(KERN_ERR "i2sbus: timed out "
"waiting for DMA to stop!\n");
break;
}
udelay(1);
}
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
/* Turn off DMA controller, clear S0 bit */
out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
pi->dbdma_ring.stopping = 0;
if (pi->stop_completion)
complete(pi->stop_completion);
}
if (!pi->dbdma_ring.running)
goto out_unlock;
spin_unlock(&i2sdev->low_lock);
/* may call _trigger again, hence needs to be unlocked */
snd_pcm_period_elapsed(pi->substream);
return;
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
out_unlock:
spin_unlock(&i2sdev->low_lock);
}
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 13:55:46 +00:00
irqreturn_t i2sbus_tx_intr(int irq, void *devid)
{
handle_interrupt((struct i2sbus_dev *)devid, 0);
return IRQ_HANDLED;
}
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers Maintain a per-CPU global "struct pt_regs *" variable which can be used instead of passing regs around manually through all ~1800 interrupt handlers in the Linux kernel. The regs pointer is used in few places, but it potentially costs both stack space and code to pass it around. On the FRV arch, removing the regs parameter from all the genirq function results in a 20% speed up of the IRQ exit path (ie: from leaving timer_interrupt() to leaving do_IRQ()). Where appropriate, an arch may override the generic storage facility and do something different with the variable. On FRV, for instance, the address is maintained in GR28 at all times inside the kernel as part of general exception handling. Having looked over the code, it appears that the parameter may be handed down through up to twenty or so layers of functions. Consider a USB character device attached to a USB hub, attached to a USB controller that posts its interrupts through a cascaded auxiliary interrupt controller. A character device driver may want to pass regs to the sysrq handler through the input layer which adds another few layers of parameter passing. I've build this code with allyesconfig for x86_64 and i386. I've runtested the main part of the code on FRV and i386, though I can't test most of the drivers. I've also done partial conversion for powerpc and MIPS - these at least compile with minimal configurations. This will affect all archs. Mostly the changes should be relatively easy. Take do_IRQ(), store the regs pointer at the beginning, saving the old one: struct pt_regs *old_regs = set_irq_regs(regs); And put the old one back at the end: set_irq_regs(old_regs); Don't pass regs through to generic_handle_irq() or __do_IRQ(). In timer_interrupt(), this sort of change will be necessary: - update_process_times(user_mode(regs)); - profile_tick(CPU_PROFILING, regs); + update_process_times(user_mode(get_irq_regs())); + profile_tick(CPU_PROFILING); I'd like to move update_process_times()'s use of get_irq_regs() into itself, except that i386, alone of the archs, uses something other than user_mode(). Some notes on the interrupt handling in the drivers: (*) input_dev() is now gone entirely. The regs pointer is no longer stored in the input_dev struct. (*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does something different depending on whether it's been supplied with a regs pointer or not. (*) Various IRQ handler function pointers have been moved to type irq_handler_t. Signed-Off-By: David Howells <dhowells@redhat.com> (cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 13:55:46 +00:00
irqreturn_t i2sbus_rx_intr(int irq, void *devid)
{
handle_interrupt((struct i2sbus_dev *)devid, 1);
return IRQ_HANDLED;
}
static int i2sbus_playback_open(struct snd_pcm_substream *substream)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
if (!i2sdev)
return -EINVAL;
i2sdev->out.substream = substream;
return i2sbus_pcm_open(i2sdev, 0);
}
static int i2sbus_playback_close(struct snd_pcm_substream *substream)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
int err;
if (!i2sdev)
return -EINVAL;
if (i2sdev->out.substream != substream)
return -EINVAL;
err = i2sbus_pcm_close(i2sdev, 0);
if (!err)
i2sdev->out.substream = NULL;
return err;
}
static int i2sbus_playback_prepare(struct snd_pcm_substream *substream)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
if (!i2sdev)
return -EINVAL;
if (i2sdev->out.substream != substream)
return -EINVAL;
return i2sbus_pcm_prepare(i2sdev, 0);
}
static int i2sbus_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
if (!i2sdev)
return -EINVAL;
if (i2sdev->out.substream != substream)
return -EINVAL;
return i2sbus_pcm_trigger(i2sdev, 0, cmd);
}
static snd_pcm_uframes_t i2sbus_playback_pointer(struct snd_pcm_substream
*substream)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
if (!i2sdev)
return -EINVAL;
if (i2sdev->out.substream != substream)
return 0;
return i2sbus_pcm_pointer(i2sdev, 0);
}
static struct snd_pcm_ops i2sbus_playback_ops = {
.open = i2sbus_playback_open,
.close = i2sbus_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = i2sbus_hw_params,
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
.hw_free = i2sbus_playback_hw_free,
.prepare = i2sbus_playback_prepare,
.trigger = i2sbus_playback_trigger,
.pointer = i2sbus_playback_pointer,
};
static int i2sbus_record_open(struct snd_pcm_substream *substream)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
if (!i2sdev)
return -EINVAL;
i2sdev->in.substream = substream;
return i2sbus_pcm_open(i2sdev, 1);
}
static int i2sbus_record_close(struct snd_pcm_substream *substream)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
int err;
if (!i2sdev)
return -EINVAL;
if (i2sdev->in.substream != substream)
return -EINVAL;
err = i2sbus_pcm_close(i2sdev, 1);
if (!err)
i2sdev->in.substream = NULL;
return err;
}
static int i2sbus_record_prepare(struct snd_pcm_substream *substream)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
if (!i2sdev)
return -EINVAL;
if (i2sdev->in.substream != substream)
return -EINVAL;
return i2sbus_pcm_prepare(i2sdev, 1);
}
static int i2sbus_record_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
if (!i2sdev)
return -EINVAL;
if (i2sdev->in.substream != substream)
return -EINVAL;
return i2sbus_pcm_trigger(i2sdev, 1, cmd);
}
static snd_pcm_uframes_t i2sbus_record_pointer(struct snd_pcm_substream
*substream)
{
struct i2sbus_dev *i2sdev = snd_pcm_substream_chip(substream);
if (!i2sdev)
return -EINVAL;
if (i2sdev->in.substream != substream)
return 0;
return i2sbus_pcm_pointer(i2sdev, 1);
}
static struct snd_pcm_ops i2sbus_record_ops = {
.open = i2sbus_record_open,
.close = i2sbus_record_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = i2sbus_hw_params,
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully This fixes the problem of getting extra bytes inserted at the beginning of a recording when using the Apple i2s interface and DBDMA controller. It turns out that we can't just abort the DMA; we have to let it stop at the end of a command, and then wait for the S7 bit to be set before turning off the DBDMA controller. Doing that for playback doesn't seem to be necessary, but doesn't hurt either. We use the technique used by the Darwin driver: make each transfer command branch to a stop command if the S0 status bit is set. Thus we can ask the DMA controller to stop at the end of the current command by setting S0. The interrupt routine now looks at and clears the status word of the DBDMA command ring. This is necessary so it can know when the DBDMA controller has seen that S0 is set, and so when it should look for the DBDMA controller being stopped and S7 being set. This also ended up simplifying the calculation in i2sbus_pcm_pointer. Tested on a 15 inch albook. [Addition by Johannes] I modified this patch and added the suspend/resume bits to it to get my powermac into a decent state when playing sound across suspend to disk that has a different bitrate from what the firmware programs the hardware to. I also added the SNDRV_PCM_INFO_JOINT_DUPLEX flag because it seemed the right thing to do and I was looking at the info stuff. Signed-off-by: Paul Mackerras <paulus@samba.org> Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Takashi Iwai <tiwai@suse.de> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
2007-02-08 13:25:39 +00:00
.hw_free = i2sbus_record_hw_free,
.prepare = i2sbus_record_prepare,
.trigger = i2sbus_record_trigger,
.pointer = i2sbus_record_pointer,
};
static void i2sbus_private_free(struct snd_pcm *pcm)
{
struct i2sbus_dev *i2sdev = snd_pcm_chip(pcm);
struct codec_info_item *p, *tmp;
i2sdev->sound.pcm = NULL;
i2sdev->out.created = 0;
i2sdev->in.created = 0;
list_for_each_entry_safe(p, tmp, &i2sdev->sound.codec_list, list) {
printk(KERN_ERR "i2sbus: a codec didn't unregister!\n");
list_del(&p->list);
module_put(p->codec->owner);
kfree(p);
}
soundbus_dev_put(&i2sdev->sound);
module_put(THIS_MODULE);
}
int
i2sbus_attach_codec(struct soundbus_dev *dev, struct snd_card *card,
struct codec_info *ci, void *data)
{
int err, in = 0, out = 0;
struct transfer_info *tmp;
struct i2sbus_dev *i2sdev = soundbus_dev_to_i2sbus_dev(dev);
struct codec_info_item *cii;
if (!dev->pcmname || dev->pcmid == -1) {
printk(KERN_ERR "i2sbus: pcm name and id must be set!\n");
return -EINVAL;
}
list_for_each_entry(cii, &dev->codec_list, list) {
if (cii->codec_data == data)
return -EALREADY;
}
if (!ci->transfers || !ci->transfers->formats
|| !ci->transfers->rates || !ci->usable)
return -EINVAL;
/* we currently code the i2s transfer on the clock, and support only
* 32 and 64 */
if (ci->bus_factor != 32 && ci->bus_factor != 64)
return -EINVAL;
/* If you want to fix this, you need to keep track of what transport infos
* are to be used, which codecs they belong to, and then fix all the
* sysclock/busclock stuff above to depend on which is usable */
list_for_each_entry(cii, &dev->codec_list, list) {
if (cii->codec->sysclock_factor != ci->sysclock_factor) {
printk(KERN_DEBUG
"cannot yet handle multiple different sysclocks!\n");
return -EINVAL;
}
if (cii->codec->bus_factor != ci->bus_factor) {
printk(KERN_DEBUG
"cannot yet handle multiple different bus clocks!\n");
return -EINVAL;
}
}
tmp = ci->transfers;
while (tmp->formats && tmp->rates) {
if (tmp->transfer_in)
in = 1;
else
out = 1;
tmp++;
}
cii = kzalloc(sizeof(struct codec_info_item), GFP_KERNEL);
if (!cii) {
printk(KERN_DEBUG "i2sbus: failed to allocate cii\n");
return -ENOMEM;
}
/* use the private data to point to the codec info */
cii->sdev = soundbus_dev_get(dev);
cii->codec = ci;
cii->codec_data = data;
if (!cii->sdev) {
printk(KERN_DEBUG
"i2sbus: failed to get soundbus dev reference\n");
err = -ENODEV;
goto out_free_cii;
}
if (!try_module_get(THIS_MODULE)) {
printk(KERN_DEBUG "i2sbus: failed to get module reference!\n");
err = -EBUSY;
goto out_put_sdev;
}
if (!try_module_get(ci->owner)) {
printk(KERN_DEBUG
"i2sbus: failed to get module reference to codec owner!\n");
err = -EBUSY;
goto out_put_this_module;
}
if (!dev->pcm) {
err = snd_pcm_new(card, dev->pcmname, dev->pcmid, 0, 0,
&dev->pcm);
if (err) {
printk(KERN_DEBUG "i2sbus: failed to create pcm\n");
goto out_put_ci_module;
}
dev->pcm->dev = &dev->ofdev.dev;
}
/* ALSA yet again sucks.
* If it is ever fixed, remove this line. See below. */
out = in = 1;
if (!i2sdev->out.created && out) {
if (dev->pcm->card != card) {
/* eh? */
printk(KERN_ERR
"Can't attach same bus to different cards!\n");
err = -EINVAL;
goto out_put_ci_module;
}
err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, 1);
if (err)
goto out_put_ci_module;
snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK,
&i2sbus_playback_ops);
i2sdev->out.created = 1;
}
if (!i2sdev->in.created && in) {
if (dev->pcm->card != card) {
printk(KERN_ERR
"Can't attach same bus to different cards!\n");
err = -EINVAL;
goto out_put_ci_module;
}
err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, 1);
if (err)
goto out_put_ci_module;
snd_pcm_set_ops(dev->pcm, SNDRV_PCM_STREAM_CAPTURE,
&i2sbus_record_ops);
i2sdev->in.created = 1;
}
/* so we have to register the pcm after adding any substream
* to it because alsa doesn't create the devices for the
* substreams when we add them later.
* Therefore, force in and out on both busses (above) and
* register the pcm now instead of just after creating it.
*/
err = snd_device_register(card, dev->pcm);
if (err) {
printk(KERN_ERR "i2sbus: error registering new pcm\n");
goto out_put_ci_module;
}
/* no errors any more, so let's add this to our list */
list_add(&cii->list, &dev->codec_list);
dev->pcm->private_data = i2sdev;
dev->pcm->private_free = i2sbus_private_free;
/* well, we really should support scatter/gather DMA */
snd_pcm_lib_preallocate_pages_for_all(
dev->pcm, SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(macio_get_pci_dev(i2sdev->macio)),
64 * 1024, 64 * 1024);
return 0;
out_put_ci_module:
module_put(ci->owner);
out_put_this_module:
module_put(THIS_MODULE);
out_put_sdev:
soundbus_dev_put(dev);
out_free_cii:
kfree(cii);
return err;
}
void i2sbus_detach_codec(struct soundbus_dev *dev, void *data)
{
struct codec_info_item *cii = NULL, *i;
list_for_each_entry(i, &dev->codec_list, list) {
if (i->codec_data == data) {
cii = i;
break;
}
}
if (cii) {
list_del(&cii->list);
module_put(cii->codec->owner);
kfree(cii);
}
/* no more codecs, but still a pcm? */
if (list_empty(&dev->codec_list) && dev->pcm) {
/* the actual cleanup is done by the callback above! */
snd_device_free(dev->pcm->card, dev->pcm);
}
}