2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-23 12:43:55 +08:00
linux-next/sound/aoa/soundbus/i2sbus/i2sbus-pcm.c
Paul Mackerras 547ac2ae38 [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-09 09:04:02 +01:00

1059 lines
27 KiB
C

/*
* 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>
/* So apparently there's a reason for requiring driver.h
* to be included first, even if I don't know it... */
#include <sound/driver.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 |
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;
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;
}
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));
}
static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in)
{
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;
}
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;
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;
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;
}
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);
nperiods = pi->substream->runtime->periods;
pi->current_period = 0;
/* generate dbdma command ring first */
command = pi->dbdma_ring.cmds;
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;
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);
}
/* 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);
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;
}
#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;
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 */
}
}
}
/* 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);
/* initialize the frame count and current period */
pi->current_period = 0;
pi->frame_count = in_le32(&i2sdev->intfregs->frame_count);
/* set the DMA controller running */
out_le32(&pi->dbdma->control, (RUN << 16) | RUN);
/* off you go! */
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
if (!pi->dbdma_ring.running) {
result = -EALREADY;
goto out_unlock;
}
pi->dbdma_ring.running = 0;
/* 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;
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;
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);
if (!pi->dbdma_ring.running && !pi->dbdma_ring.stopping)
goto out_unlock;
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;
}
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);
}
/* Turn off DMA controller, clear S0 bit */
out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
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;
out_unlock:
spin_unlock(&i2sdev->low_lock);
}
irqreturn_t i2sbus_tx_intr(int irq, void *devid)
{
handle_interrupt((struct i2sbus_dev *)devid, 0);
return IRQ_HANDLED;
}
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,
.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,
.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");
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);
}
}