2006-06-21 07:42:43 -06:00
|
|
|
/*
|
|
|
|
* 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 |
|
2007-02-08 06:25:39 -07:00
|
|
|
SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME |
|
|
|
|
SNDRV_PCM_INFO_JOINT_DUPLEX;
|
2006-06-21 07:42:43 -06:00
|
|
|
|
|
|
|
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;
|
2007-11-23 07:37:48 -07:00
|
|
|
err = snd_pcm_hw_constraint_integer(pi->substream->runtime,
|
|
|
|
SNDRV_PCM_HW_PARAM_PERIODS);
|
|
|
|
if (err < 0) {
|
|
|
|
result = err;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
2006-06-21 07:42:43 -06:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2007-02-08 06:25:39 -07: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
|
|
|
|
|
2006-06-21 07:42:43 -06:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2007-02-08 06:25:39 -07:00
|
|
|
static inline int i2sbus_hw_free(struct snd_pcm_substream *substream, int in)
|
2006-06-21 07:42:43 -06:00
|
|
|
{
|
2007-02-08 06:25:39 -07: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);
|
2006-06-21 07:42:43 -06:00
|
|
|
snd_pcm_lib_free_pages(substream);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-02-08 06:25:39 -07: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);
|
|
|
|
}
|
|
|
|
|
2006-06-21 07:42:43 -06:00
|
|
|
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;
|
2007-02-08 06:25:39 -07:00
|
|
|
int i, periodsize, nperiods;
|
2006-06-21 07:42:43 -06:00
|
|
|
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;
|
2007-02-08 06:25:39 -07:00
|
|
|
unsigned int cmd, stopaddr;
|
2006-06-21 07:42:43 -06:00
|
|
|
|
|
|
|
mutex_lock(&i2sdev->lock);
|
|
|
|
|
|
|
|
get_pcm_info(i2sdev, in, &pi, &other);
|
|
|
|
|
|
|
|
if (pi->dbdma_ring.running) {
|
|
|
|
result = -EBUSY;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
2007-02-08 06:25:39 -07:00
|
|
|
if (pi->dbdma_ring.stopping)
|
|
|
|
i2sbus_wait_for_stop(i2sdev, pi);
|
|
|
|
|
|
|
|
if (!pi->substream || !pi->substream->runtime) {
|
|
|
|
result = -EINVAL;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
2006-06-21 07:42:43 -06:00
|
|
|
|
|
|
|
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);
|
2007-02-08 06:25:39 -07:00
|
|
|
nperiods = pi->substream->runtime->periods;
|
2006-06-21 07:42:43 -06:00
|
|
|
pi->current_period = 0;
|
|
|
|
|
|
|
|
/* generate dbdma command ring first */
|
|
|
|
command = pi->dbdma_ring.cmds;
|
2007-02-08 06:25:39 -07: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.
|
|
|
|
*/
|
2006-06-21 07:42:43 -06:00
|
|
|
offset = runtime->dma_addr;
|
2007-02-08 06:25:39 -07: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);
|
2006-06-21 07:42:43 -06:00
|
|
|
command->phy_addr = cpu_to_le32(offset);
|
|
|
|
command->req_count = cpu_to_le16(periodsize);
|
|
|
|
}
|
2007-02-08 06:25:39 -07:00
|
|
|
|
|
|
|
/* branch back to beginning of ring */
|
|
|
|
command->command = cpu_to_le16(DBDMA_NOP | BR_ALWAYS);
|
2006-06-21 07:42:43 -06:00
|
|
|
command->cmd_dep = cpu_to_le32(pi->dbdma_ring.bus_cmd_start);
|
2007-02-08 06:25:39 -07:00
|
|
|
command++;
|
|
|
|
|
|
|
|
/* set stop command */
|
|
|
|
command->command = cpu_to_le16(DBDMA_STOP);
|
2006-06-21 07:42:43 -06:00
|
|
|
|
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2007-02-08 06:25:39 -07:00
|
|
|
#ifdef CONFIG_PM
|
|
|
|
void i2sbus_pcm_prepare_both(struct i2sbus_dev *i2sdev)
|
|
|
|
{
|
|
|
|
i2sbus_pcm_prepare(i2sdev, 0);
|
|
|
|
i2sbus_pcm_prepare(i2sdev, 1);
|
|
|
|
}
|
|
|
|
#endif
|
2006-06-21 07:42:43 -06:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2007-02-08 06:25:39 -07: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 */
|
|
|
|
}
|
|
|
|
}
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
|
|
|
|
2007-02-08 06:25:39 -07: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);
|
|
|
|
|
2006-06-21 07:42:43 -06:00
|
|
|
/* write dma command buffer address to the dbdma chip */
|
|
|
|
out_le32(&pi->dbdma->cmdptr, pi->dbdma_ring.bus_cmd_start);
|
|
|
|
|
2007-02-08 06:25:39 -07:00
|
|
|
/* initialize the frame count and current period */
|
|
|
|
pi->current_period = 0;
|
2006-06-21 07:42:43 -06:00
|
|
|
pi->frame_count = in_le32(&i2sdev->intfregs->frame_count);
|
|
|
|
|
2007-02-08 06:25:39 -07:00
|
|
|
/* set the DMA controller running */
|
|
|
|
out_le32(&pi->dbdma->control, (RUN << 16) | RUN);
|
|
|
|
|
2006-06-21 07:42:43 -06:00
|
|
|
/* off you go! */
|
|
|
|
break;
|
2007-02-08 06:25:39 -07:00
|
|
|
|
2006-06-21 07:42:43 -06:00
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
|
|
if (!pi->dbdma_ring.running) {
|
|
|
|
result = -EALREADY;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
2007-02-08 06:25:39 -07:00
|
|
|
pi->dbdma_ring.running = 0;
|
2006-06-21 07:42:43 -06:00
|
|
|
|
2007-02-08 06:25:39 -07: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;
|
2006-06-21 07:42:43 -06:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2007-02-08 06:25:39 -07:00
|
|
|
if (fc >= pi->substream->runtime->buffer_size)
|
|
|
|
fc %= pi->substream->runtime->buffer_size;
|
|
|
|
return fc;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void handle_interrupt(struct i2sbus_dev *i2sdev, int in)
|
|
|
|
{
|
|
|
|
struct pcm_info *pi;
|
2007-02-08 06:25:39 -07:00
|
|
|
u32 fc, nframes;
|
|
|
|
u32 status;
|
|
|
|
int timeout, i;
|
|
|
|
int dma_stopped = 0;
|
|
|
|
struct snd_pcm_runtime *runtime;
|
2006-06-21 07:42:43 -06:00
|
|
|
|
|
|
|
spin_lock(&i2sdev->low_lock);
|
|
|
|
get_pcm_info(i2sdev, in, &pi, NULL);
|
2007-02-08 06:25:39 -07:00
|
|
|
if (!pi->dbdma_ring.running && !pi->dbdma_ring.stopping)
|
2006-06-21 07:42:43 -06:00
|
|
|
goto out_unlock;
|
|
|
|
|
2007-02-08 06:25:39 -07: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;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
|
|
|
|
2007-02-08 06:25:39 -07: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);
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
|
|
|
|
2007-02-08 06:25:39 -07:00
|
|
|
/* Turn off DMA controller, clear S0 bit */
|
|
|
|
out_le32(&pi->dbdma->control, (RUN | PAUSE | 1) << 16);
|
2006-06-21 07:42:43 -06:00
|
|
|
|
2007-02-08 06:25:39 -07:00
|
|
|
pi->dbdma_ring.stopping = 0;
|
|
|
|
if (pi->stop_completion)
|
|
|
|
complete(pi->stop_completion);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pi->dbdma_ring.running)
|
|
|
|
goto out_unlock;
|
2006-06-21 07:42:43 -06:00
|
|
|
spin_unlock(&i2sdev->low_lock);
|
|
|
|
/* may call _trigger again, hence needs to be unlocked */
|
|
|
|
snd_pcm_period_elapsed(pi->substream);
|
|
|
|
return;
|
2007-02-08 06:25:39 -07:00
|
|
|
|
2006-06-21 07:42:43 -06: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 07:55:46 -06:00
|
|
|
irqreturn_t i2sbus_tx_intr(int irq, void *devid)
|
2006-06-21 07:42:43 -06:00
|
|
|
{
|
|
|
|
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 07:55:46 -06:00
|
|
|
irqreturn_t i2sbus_rx_intr(int irq, void *devid)
|
2006-06-21 07:42:43 -06:00
|
|
|
{
|
|
|
|
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,
|
2007-02-08 06:25:39 -07:00
|
|
|
.hw_free = i2sbus_playback_hw_free,
|
2006-06-21 07:42:43 -06:00
|
|
|
.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,
|
2007-02-08 06:25:39 -07:00
|
|
|
.hw_free = i2sbus_record_hw_free,
|
2006-06-21 07:42:43 -06:00
|
|
|
.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");
|
2006-10-05 07:08:23 -06:00
|
|
|
err = -ENODEV;
|
|
|
|
goto out_free_cii;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!try_module_get(THIS_MODULE)) {
|
|
|
|
printk(KERN_DEBUG "i2sbus: failed to get module reference!\n");
|
2006-10-05 07:08:23 -06:00
|
|
|
err = -EBUSY;
|
|
|
|
goto out_put_sdev;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!try_module_get(ci->owner)) {
|
|
|
|
printk(KERN_DEBUG
|
|
|
|
"i2sbus: failed to get module reference to codec owner!\n");
|
2006-10-05 07:08:23 -06:00
|
|
|
err = -EBUSY;
|
|
|
|
goto out_put_this_module;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!dev->pcm) {
|
2006-10-05 07:07:23 -06:00
|
|
|
err = snd_pcm_new(card, dev->pcmname, dev->pcmid, 0, 0,
|
2006-06-21 07:42:43 -06:00
|
|
|
&dev->pcm);
|
|
|
|
if (err) {
|
|
|
|
printk(KERN_DEBUG "i2sbus: failed to create pcm\n");
|
2006-10-05 07:08:23 -06:00
|
|
|
goto out_put_ci_module;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
2006-10-05 07:07:23 -06:00
|
|
|
dev->pcm->dev = &dev->ofdev.dev;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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");
|
2006-10-05 07:08:23 -06:00
|
|
|
err = -EINVAL;
|
|
|
|
goto out_put_ci_module;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
2006-10-05 07:08:23 -06:00
|
|
|
err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_PLAYBACK, 1);
|
|
|
|
if (err)
|
|
|
|
goto out_put_ci_module;
|
2006-06-21 07:42:43 -06:00
|
|
|
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");
|
2007-11-23 07:41:44 -07:00
|
|
|
err = -EINVAL;
|
2006-10-05 07:08:23 -06:00
|
|
|
goto out_put_ci_module;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
2006-10-05 07:08:23 -06:00
|
|
|
err = snd_pcm_new_stream(dev->pcm, SNDRV_PCM_STREAM_CAPTURE, 1);
|
|
|
|
if (err)
|
|
|
|
goto out_put_ci_module;
|
2006-06-21 07:42:43 -06:00
|
|
|
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");
|
2006-10-05 07:08:23 -06:00
|
|
|
goto out_put_ci_module;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
|
|
|
/* 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;
|
2006-10-05 07:08:23 -06:00
|
|
|
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;
|
2006-06-21 07:42:43 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|