/*
* 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 <linux/slab.h>
#include <sound/core.h>
#include <asm/macio.h>
#include <linux/pci.h>
#include <linux/module.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;
}