* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
2008-08-31 14:18 snd-soc-cs4270: Convert to a new-style i2c driver (work in progress) Jean Delvare
@ 2008-08-31 14:47 ` Jon Smirl
[not found] ` <ed82fe3e0808310928v18eed8bdh98faa6796a516142@mail.gmail.com>
2008-09-01 6:01 ` Takashi Iwai
2 siblings, 0 replies; 13+ messages in thread
From: Jon Smirl @ 2008-08-31 14:47 UTC (permalink / raw)
To: Jean Delvare; +Cc: alsa-devel, Timur Tabi
[-- Attachment #1: Type: text/plain, Size: 1048 bytes --]
On 8/31/08, Jean Delvare <khali@linux-fr.org> wrote:
> Hi Timur,
>
> I am in the process of converting your cs4270 codec driver from the
> legacy i2c model to the new (standard) one. This is work in progress.
> The patch below converts the cs4270 driver itself. However we also need
> to convert its users. As far as I can see there's only one user at this
> point: mpc8610_hpcd.
>
> The problem is that this driver doesn't look like the other codec
> drivers I have already converted. So, we need to add code to
> instantiate the cs4270 i2c device, but I don't know where this should
> happen. Given that the mpc8610_hpcd is apparently based on Open
> Firmware, I guess that the i2c device should be instantiated directly
> by the platform code. I see that the device is declared in
> mpc8610_hpcd.dts, so maybe it's already done and my patch should work
> already? What do you think?
I attached my codec and i2s driver. The i2c device is getting created
as part of the device tree loading process.
--
Jon Smirl
jonsmirl@gmail.com
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: tas5504.c --]
[-- Type: text/x-csrc; name=tas5504.c, Size: 26807 bytes --]
/*
* Texas Instruments TAS5504 low power audio CODEC
* ALSA SoC CODEC driver
*
* Copyright (C) Jon Smirl <jonsmirl@gmail.com>
*/
#define DEBUG
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/device.h>
#include <linux/sysfs.h>
#include <linux/i2c.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/soc-of-simple.h>
#include <sound/initval.h>
#include "tas5504.h"
#define TAS_MAX_8B_REG 0x40
#define TAS_REG_MAX 0xe1
#define TAS5504_RATES (SNDRV_PCM_RATE_32000 |\
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000 |\
SNDRV_PCM_RATE_176400 | SNDRV_PCM_RATE_192000)
#define TAS5504_FORMATS SNDRV_PCM_FMTBIT_S32
/* Size and number of variable length entries in the cache */
#define TAS_CACHE_1_MAX 41
#define TAS_CACHE_2_MAX 16
#define TAS_CACHE_3_MAX 2
#define TAS_CACHE_4_MAX 8
#define TAS_CACHE_5_MAX 30
#define TAS_CACHE_6_MAX 4
/* Size and offset of specific entries in the cache */
static struct {
u8 size;
u8 offset;
} cache_map[] = {
[0x00]= {1, 0xff}, /* TAS_REG_CLOCK_CONTROL */
[0x01]= {1, 0xff}, /* TAS_REG_GENERAL_STATUS */
[0x02]= {1, 0xff}, /* TAS_REG_ERROR_STATUS */
[0x03]= {1, 0}, /* TAS_REG_SYS_CONTROL_1 */
[0x04]= {1, 1}, /* TAS_REG_SYS_CONTROL_2 */
[0x05]= {1, 2}, /* TAS_REG_CH_CONFIG_1 */
[0x06]= {1, 3}, /* TAS_REG_CH_CONFIG_2 */
[0x0b]= {1, 4}, /* TAS_REG_CH_CONFIG_3 */
[0x0c]= {1, 5}, /* TAS_REG_CH_CONFIG_4 */
[0x0d]= {1, 6}, /* TAS_REG_HP_CONFIG */
[0x0e]= {1, 0xff}, /* TAS_REG_SERIAL_CONTROL */
[0x0f]= {1, 7}, /* TAS_REG_SOFT_MUTE */
[0x14]= {1, 8}, /* TAS_REG_AUTO_MUTE */
[0x15]= {1, 9}, /* TAS_REG_AUTO_MUTE_PWM */
[0x16]= {1, 10}, /* TAS_REG_MODULATE_LIMIT */
[0x1b]= {1, 11}, /* TAS_REG_IC_DELAY_1 */
[0x1c]= {1, 12}, /* TAS_REG_IC_DELAY_2 */
[0x21]= {1, 13}, /* TAS_REG_IC_DELAY_3 */
[0x22]= {1, 14}, /* TAS_REG_IC_DELAY_4 */
[0x23]= {1, 15}, /* TAS_REG_IC_OFFSET */
[0x40]= {1, 16}, /* TAS_REG_BANK_SWITCH */
[0x41]= {8, 1,}, /* TAS_REG_IN8X4_1 */
[0x42]= {8, 2,}, /* TAS_REG_IN8X4_2 */
[0x47]= {8, 3,}, /* TAS_REG_IN8X4_3 */
[0x48]= {8, TAS_CACHE_2_MAX,}, /* TAS_REG_IN8X4_4 */
[0x49]= {1, 17}, /* TAS_REG_IPMIX_1_TO_CH4 */
[0x4a]= {1, 18}, /* TAS_REG_IPMIX_2_TO_CH4 */
[0x4b]= {1, 19}, /* TAS_REG_IPMIX_3_TO_CH2 */
[0x4c]= {1, 20}, /* TAS_REG_CH3_BP_BQ2 */
[0x4d]= {1, 21}, /* TAS_REG_CH3_BP */
[0x4e]= {1, 22}, /* TAS_REG_IPMIX_4_TO_CH12 */
[0x4f]= {1, 23}, /* TAS_REG_CH4_BP_BQ2 */
[0x50]= {1, 24}, /* TAS_REG_CH4_BP */
[0x51]= {5, 1,}, /* TAS_REG_CH1_BQ_1 */
[0x52]= {5, 2,}, /* TAS_REG_CH1_BQ_2 */
[0x53]= {5, 3,}, /* TAS_REG_CH1_BQ_3 */
[0x54]= {5, 4,}, /* TAS_REG_CH1_BQ_4 */
[0x55]= {5, 5,}, /* TAS_REG_CH1_BQ_5 */
[0x56]= {5, 6,}, /* TAS_REG_CH1_BQ_6 */
[0x57]= {5, 7,}, /* TAS_REG_CH1_BQ_7 */
[0x58]= {5, 8,}, /* TAS_REG_CH2_BQ_1 */
[0x59]= {5, 9,}, /* TAS_REG_CH2_BQ_2 */
[0x5a]= {5, 10,}, /* TAS_REG_CH2_BQ_3 */
[0x5b]= {5, 11,}, /* TAS_REG_CH2_BQ_4 */
[0x5c]= {5, 12,}, /* TAS_REG_CH2_BQ_5 */
[0x5d]= {5, 13,}, /* TAS_REG_CH2_BQ_6 */
[0x5e]= {5, 14,}, /* TAS_REG_CH2_BQ_7 */
[0x7b]= {5, 15,}, /* TAS_REG_CH3_BQ_1 */
[0x7c]= {5, 16,}, /* TAS_REG_CH3_BQ_2 */
[0x7d]= {5, 17,}, /* TAS_REG_CH3_BQ_3 */
[0x7e]= {5, 18,}, /* TAS_REG_CH3_BQ_4 */
[0x7f]= {5, 19,}, /* TAS_REG_CH3_BQ_5 */
[0x80]= {5, 20,}, /* TAS_REG_CH3_BQ_6 */
[0x81]= {5, 21,}, /* TAS_REG_CH3_BQ_7 */
[0x82]= {5, 22,}, /* TAS_REG_CH4_BQ_1 */
[0x83]= {5, 23,}, /* TAS_REG_CH4_BQ_2 */
[0x84]= {5, 24,}, /* TAS_REG_CH4_BQ_3 */
[0x85]= {5, 25,}, /* TAS_REG_CH4_BQ_4 */
[0x86]= {5, 26,}, /* TAS_REG_CH4_BQ_5 */
[0x87]= {5, 27,}, /* TAS_REG_CH4_BQ_6 */
[0x88]= {5, 28,}, /* TAS_REG_CH4_BQ_7 */
[0x89]= {2, 1,}, /* TAS_REG_BT_BYPASS_CH1 */
[0x8a]= {2, 2,}, /* TAS_REG_BT_BYPASS_CH2 */
[0x8f]= {2, 3,}, /* TAS_REG_BT_BYPASS_CH3 */
[0x90]= {2, 4,}, /* TAS_REG_BT_BYPASS_CH4 */
[0x91]= {1, 25,}, /* TAS_REG_LOUDNESS_LG */
[0x92]= {2, 5,}, /* TAS_REG_LOUDNESS_LO */
[0x93]= {1, 26,}, /* TAS_REG_LOUDNESS_G */
[0x94]= {2, 6,}, /* TAS_REG_LOUDNESS_O */
[0x95]= {5, 29,}, /* TAS_REG_LOUDNESS_BQ */
[0x96]= {1, 27,}, /* TAS_REG_DRC1_CNTL_123 */
[0x97]= {1, 28,}, /* TAS_REG_DRC2_CNTL_4 */
[0x98]= {2, 7,}, /* TAS_REG_DRC1_ENERGY */
[0x99]= {4, 3,}, /* TAS_REG_DRC1_THRESHOLD */
[0x9a]= {3, 1,}, /* TAS_REG_DRC1_SLOPE */
[0x9b]= {4, 4,}, /* TAS_REG_DRC1_OFFSET */
[0x9c]= {4, 5,}, /* TAS_REG_DRC1_ATTACK */
[0x9d]= {2, 8,}, /* TAS_REG_DRC2_ENERGY */
[0x9e]= {4, 6,}, /* TAS_REG_DRC2_THRESHOLD */
[0x9f]= {2, TAS_CACHE_3_MAX,}, /* TAS_REG_DRC2_SLOPE */
[0xa0]= {4, 7,}, /* TAS_REG_DRC2_OFFSET */
[0xa1]= {4, TAS_CACHE_2_MAX,}, /* TAS_REG_DRC2_ATTACK */
[0xa2]= {2, 9,}, /* TAS_REG_DRC_BYPASS_1 */
[0xa3]= {2, 10,}, /* TAS_REG_DRC_BYPASS_2 */
[0xa8]= {2, 11,}, /* TAS_REG_DRC_BYPASS_3 */
[0xa9]= {2, 12,}, /* TAS_REG_DRC_BYPASS_4 */
[0xaa]= {2, 13,}, /* TAS_REG_SEL_OP14_S */
[0xab]= {2, 14,}, /* TAS_REG_SEL_OP14_T */
[0xb0]= {2, 15,}, /* TAS_REG_SEL_OP14_Y */
[0xb1]= {2, TAS_CACHE_2_MAX,}, /* TAS_REG_SEL_OP14_Z */
[0xcf]= {5, TAS_CACHE_5_MAX,}, /* TAS_REG_VOLUME_BQ */
[0xd0]= {1, 29}, /* TAS_REG_VOL_TB_SLEW */
[0xd1]= {1, 30}, /* TAS_REG_VOL_CH1 */
[0xd2]= {1, 31}, /* TAS_REG_VOL_CH2 */
[0xd7]= {1, 32}, /* TAS_REG_VOL_CH3 */
[0xd8]= {1, 33}, /* TAS_REG_VOL_CH4 */
[0xd9]= {1, 34}, /* TAS_REG_VOL_MASTER */
[0xda]= {1, 35}, /* TAS_REG_BASS_SET */
[0xdb]= {1, 36}, /* TAS_REG_BASS_INDEX */
[0xdc]= {1, 37}, /* TAS_REG_TREBLE_SET */
[0xdd]= {1, 38}, /* TAS_REG_TREBLE_INDEX */
[0xde]= {1, 39}, /* TAS_REG_AM_MODE */
[0xdf]= {1, 40}, /* TAS_REG_PSVC */
[0xe0]= {1, TAS_CACHE_1_MAX,}, /* TAS_REG_GENERAL_CONTROL */
};
/* location of each variable length segment in the cache array */
#define TAS_CACHE_1_BASE 0
#define TAS_CACHE_2_BASE TAS_CACHE_1_BASE + (TAS_CACHE_1_MAX + 1)
#define TAS_CACHE_3_BASE TAS_CACHE_2_BASE + (TAS_CACHE_2_MAX + 1) * 2
#define TAS_CACHE_4_BASE TAS_CACHE_3_BASE + (TAS_CACHE_3_MAX + 1) * 3
#define TAS_CACHE_5_BASE TAS_CACHE_4_BASE + (TAS_CACHE_4_MAX + 1) * 4
#define TAS_CACHE_8_BASE TAS_CACHE_5_BASE + (TAS_CACHE_5_MAX + 1) * 5
#define TAS_CACHE_MAX TAS_CACHE_8_BASE + (TAS_CACHE_5_MAX + 1) * 8
/* tas5504 driver private data */
struct tas5504_priv {
struct i2c_client *client;
struct snd_soc_codec codec;
/* performance shadow of i2c registers */
u32 reg_cache[TAS_CACHE_MAX];
u8 valid[TAS_CACHE_MAX];
};
/* ---------------------------------------------------------------------
* Register access routines
*/
static u32 *tas5504_get_base(struct tas5504_priv *priv, unsigned int reg)
{
u32 *base;
switch (cache_map[reg].size) {
case 1: base = &priv->reg_cache[TAS_CACHE_1_BASE]; break;
case 2: base = &priv->reg_cache[TAS_CACHE_2_BASE]; break;
case 3: base = &priv->reg_cache[TAS_CACHE_3_BASE]; break;
case 4: base = &priv->reg_cache[TAS_CACHE_4_BASE]; break;
case 5: base = &priv->reg_cache[TAS_CACHE_5_BASE]; break;
case 8: base = &priv->reg_cache[TAS_CACHE_8_BASE]; break;
default: return 0;
}
base += cache_map[reg].offset * cache_map[reg].size;
return base;
}
static void tas5504_update_cache(struct tas5504_priv *priv, u8 reg, u32 *value, unsigned int count)
{
if (cache_map[reg].offset != 0xFF) /* not volatile, cache it */
priv->valid[reg] = 1;
memmove(tas5504_get_base(priv, reg), value, cache_map[reg].size * sizeof *value * count);
}
static void tas5504_reg_read(struct snd_soc_codec *codec, u8 reg)
{
struct tas5504_priv *priv = codec->private_data;
u8 value[32];
int rc;
struct i2c_msg msgs[2] = {
{.addr = priv->client->addr, .flags = 0, .len = 1, .buf = ®},
{.addr = priv->client->addr, .flags = I2C_M_RD, .buf = &value[0]}
};
if (reg < TAS_MAX_8B_REG) /* special case 8 bit regs */
msgs[1].len = 1;
else
msgs[1].len = cache_map[reg].size * 4;
rc = i2c_transfer(priv->client->adapter, msgs, ARRAY_SIZE(msgs));
if (rc != 2) {
dev_err(&priv->client->dev, "%s: rc=%d reg=%02x rc != size\n",
__func__, rc, reg);
return;
}
if (reg < TAS_MAX_8B_REG) /* convert 8b result to 32b */
*(u32 *)&value[0] = value[0];
tas5504_update_cache(priv, reg, (u32*)&value[0], cache_map[reg].size);
}
static unsigned int tas5504_reg_read_cache(struct snd_soc_codec *codec,
unsigned int reg)
{
struct tas5504_priv *priv = codec->private_data;
if (reg > TAS_REG_MAX)
return -1;
/* can only read 32 bit registers, 0xFF is volatile 32b */
if (!((cache_map[reg].size == 1) || (cache_map[reg].size == 0xFF)))
return -1;
if (!priv->valid[reg])
tas5504_reg_read(codec, reg);
return *tas5504_get_base(priv, reg);
}
static int tas5504_reg_write_long(struct snd_soc_codec *codec, unsigned int reg,
u32 *value, unsigned int count)
{
struct tas5504_priv *priv = codec->private_data;
u8 buf[33];
int rc, size;
memmove(tas5504_get_base(priv, reg), value, count * sizeof *value);
buf[0] = reg;
size = 1;
if (reg < TAS_MAX_8B_REG) {
buf[1] = *value;
size += 1;
} else {
size += count * sizeof *value;
memmove(&buf[1], value, size);
}
rc = i2c_master_send(priv->client, buf, size);
if (rc != size) {
dev_err(&priv->client->dev,
"%s: rc=%d reg=%02x size %02x rc != size\n",
__func__, rc, reg, size);
return -EIO;
}
return 0;
}
static int tas5504_reg_write(struct snd_soc_codec *codec, unsigned int reg,
unsigned int value)
{
if (reg > TAS_REG_MAX)
return -EINVAL;
/* can only read 32 bit registers, 0xFF is a volatile 32b */
if (!((cache_map[reg].size == 1) || (cache_map[reg].size == 0xFF)))
return -EINVAL;
return tas5504_reg_write_long(codec, reg, &value, 1);
}
/* ---------------------------------------------------------------------
* Digital Audio Interface Operations
*/
static int tas5504_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
struct snd_soc_codec *codec = socdev->codec;
struct tas5504_priv *priv = codec->private_data;
dev_dbg(&priv->client->dev, "tas5504_hw_params(substream=%p, params=%p)\n",
substream, params);
dev_dbg(&priv->client->dev, "rate=%i format=%i\n", params_rate(params),
params_format(params));
return 0;
}
/**
* tas5504_mute - Mute control to reduce noise when changing audio format
*/
static int tas5504_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
struct tas5504_priv *priv = codec->private_data;
dev_dbg(&priv->client->dev, "tas5504_mute(dai=%p, mute=%i)\n",
dai, mute);
return 0;
}
/* ---------------------------------------------------------------------
* Digital Audio Interface Definition
*/
struct snd_soc_dai tas5504_dai = {
.name = "tas5504",
.playback = {
.stream_name = "Playback",
.channels_min = 2,
.channels_max = 2,
.rates = TAS5504_RATES,
.formats = TAS5504_FORMATS,
},
.ops = {
.hw_params = tas5504_hw_params,
},
.dai_ops = {
.digital_mute = tas5504_mute,
},
};
EXPORT_SYMBOL_GPL(tas5504_dai);
/* ---------------------------------------------------------------------
* ALSA controls
*/
static int tas5504_info_ctl_1(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = -1;
uinfo->value.integer.step = 1;
return 0;
}
static int tas5504_info_ctl_1_64(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER64;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = -1;
uinfo->value.integer.step = 1;
return 0;
}
static int tas5504_info_ctl_2(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = -1;
uinfo->value.integer.step = 1;
return 0;
}
static int tas5504_info_ctl_2_64(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER64;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = -1;
uinfo->value.integer.step = 1;
return 0;
}
static int tas5504_info_ctl_3(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 3;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = -1;
uinfo->value.integer.step = 1;
return 0;
}
static int tas5504_info_ctl_4(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 4;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = -1;
uinfo->value.integer.step = 1;
return 0;
}
static int tas5504_info_ctl_5(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 5;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = -1;
uinfo->value.integer.step = 1;
return 0;
}
static int tas5504_info_ctl_8(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 8;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = -1;
uinfo->value.integer.step = 1;
return 0;
}
static int tas5504_get_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct tas5504_priv *priv = snd_kcontrol_chip(kcontrol);
struct snd_ctl_elem_info uinfo;
int reg, count;
reg = kcontrol->private_value;
kcontrol->info(kcontrol, &uinfo);
count = (uinfo.type == SNDRV_CTL_ELEM_TYPE_INTEGER64 ? 2 : 1);
count *= uinfo.count;
if (!priv->valid[reg])
tas5504_reg_read(&priv->codec, reg);
memmove(tas5504_get_base(priv, reg), &ucontrol->value.integer.value[0], count);
return 0;
}
static int tas5504_put_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
{
struct tas5504_priv *priv = snd_kcontrol_chip(kcontrol);
struct snd_ctl_elem_info uinfo;
int reg, count;
reg = kcontrol->private_value;
kcontrol->info(kcontrol, &uinfo);
count = (uinfo.type == SNDRV_CTL_ELEM_TYPE_INTEGER64 ? 2 : 1);
count *= uinfo.count;
return tas5504_reg_write_long(&priv->codec, reg, (u32 *)&ucontrol->value.integer.value[0], count);
}
#define TAS_CTL_1(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
.name = xname, \
.index = xindex, \
.device = 0, \
.subdevice = xsubdevice, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = tas5504_info_ctl_1, \
.get = tas5504_get_reg, \
.put = tas5504_put_reg, \
.private_value = xregister \
}
#define TAS_CTL_1R(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
.name = xname, \
.index = xindex, \
.device = 0, \
.subdevice = xsubdevice, \
.access = SNDRV_CTL_ELEM_ACCESS_READ, \
.info = tas5504_info_ctl_1, \
.get = tas5504_get_reg, \
.put = tas5504_put_reg, \
.private_value = xregister \
}
#define TAS_CTL_1_64(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
.name = xname, \
.index = xindex, \
.device = 0, \
.subdevice = xsubdevice, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = tas5504_info_ctl_1_64, \
.get = tas5504_get_reg, \
.put = tas5504_put_reg, \
.private_value = xregister \
}
#define TAS_CTL_2(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
.name = xname, \
.index = xindex, \
.device = 0, \
.subdevice = xsubdevice, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = tas5504_info_ctl_2, \
.get = tas5504_get_reg, \
.put = tas5504_put_reg, \
.private_value = xregister \
}
#define TAS_CTL_2_64(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
.name = xname, \
.index = xindex, \
.device = 0, \
.subdevice = xsubdevice, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = tas5504_info_ctl_2_64, \
.get = tas5504_get_reg, \
.put = tas5504_put_reg, \
.private_value = xregister \
}
#define TAS_CTL_3(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
.name = xname, \
.index = xindex, \
.device = 0, \
.subdevice = xsubdevice, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = tas5504_info_ctl_3, \
.get = tas5504_get_reg, \
.put = tas5504_put_reg, \
.private_value = xregister \
}
#define TAS_CTL_4(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
.name = xname, \
.index = xindex, \
.device = 0, \
.subdevice = xsubdevice, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = tas5504_info_ctl_4, \
.get = tas5504_get_reg, \
.put = tas5504_put_reg, \
.private_value = xregister \
}
#define TAS_CTL_5(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
.name = xname, \
.index = xindex, \
.device = 0, \
.subdevice = xsubdevice, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = tas5504_info_ctl_5, \
.get = tas5504_get_reg, \
.put = tas5504_put_reg, \
.private_value = xregister \
}
#define TAS_CTL_8(xregister, xsubdevice, xindex, xname) \
{ .iface = SNDRV_CTL_ELEM_IFACE_HWDEP, \
.name = xname, \
.index = xindex, \
.device = 0, \
.subdevice = xsubdevice, \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
.info = tas5504_info_ctl_8, \
.get = tas5504_get_reg, \
.put = tas5504_put_reg, \
.private_value = xregister \
}
static const struct snd_kcontrol_new tas5504_snd_controls[] = {
TAS_CTL_1R(0x00, 0, 0, "Clock Control"),
TAS_CTL_1R(0x01, 0, 0, "General Status"),
TAS_CTL_1(0x02, 0, 0, "Error Status"),
TAS_CTL_1(0x03, 0, 0, "System Control 1"),
TAS_CTL_1(0x04, 0, 0, "System Control 2"),
TAS_CTL_1(0x05, 1, 0, "General Config"),
TAS_CTL_1(0x06, 2, 0, "General Config"),
TAS_CTL_1(0x0b, 3, 0, "General Config"),
TAS_CTL_1(0x0c, 4, 0, "General Config"),
TAS_CTL_1(0x0d, 0, 0, "Headphone Config"),
TAS_CTL_1R(0x0e, 0, 0, "Serial Data Interface"),
TAS_CTL_1(0x0f, 0, 0, "Soft Mute"),
TAS_CTL_1(0x14, 0, 0, "Auto Mute"),
TAS_CTL_1(0x15, 0, 0, "Auto Mute PWM"),
TAS_CTL_1R(0x16, 0, 0, "Modulation"),
TAS_CTL_1(0x1b, 1, 0, "Interchannel Delay"),
TAS_CTL_1(0x1c, 2, 0, "Interchannel Delay"),
TAS_CTL_1(0x21, 3, 0, "Interchannel Delay"),
TAS_CTL_1(0x22, 4, 0, "Interchannel Delay"),
TAS_CTL_1(0x23, 0, 0, "Interchannel Offset"),
TAS_CTL_1(0x40, 4, 0, "Bank Switching"),
TAS_CTL_8(0x41, 1, 0, "Input Mixer"),
TAS_CTL_8(0x42, 2, 0, "Input Mixer"),
TAS_CTL_8(0x47, 3, 0, "Input Mixer"),
TAS_CTL_8(0x48, 4, 0, "Input Mixer"),
TAS_CTL_1(0x49, 0, 0, "Bass ipmix_1_to_ch4"),
TAS_CTL_1(0x4a, 0, 0, "Bass ipmix_2_to_ch4"),
TAS_CTL_1(0x4b, 0, 0, "Bass ipmix_3_to_ch12"),
TAS_CTL_1(0x4c, 0, 0, "Bass ch3_bp_bq2"),
TAS_CTL_1(0x4d, 0, 0, "Bass ch3_bq2"),
TAS_CTL_1(0x4e, 0, 0, "Bass ipmix_4_to_ch12"),
TAS_CTL_1(0x4f, 0, 0, "Bass ch4_bp_bq2"),
TAS_CTL_1(0x50, 0, 0, "Bass ch4_bq2"),
TAS_CTL_5(0x51, 1, 1, "Biquad"),
TAS_CTL_5(0x52, 1, 2, "Biquad"),
TAS_CTL_5(0x53, 1, 3, "Biquad"),
TAS_CTL_5(0x54, 1, 4, "Biquad"),
TAS_CTL_5(0x55, 1, 5, "Biquad"),
TAS_CTL_5(0x56, 1, 6, "Biquad"),
TAS_CTL_5(0x57, 1, 7, "Biquad"),
TAS_CTL_5(0x58, 2, 1, "Biquad"),
TAS_CTL_5(0x59, 2, 2, "Biquad"),
TAS_CTL_5(0x5a, 2, 3, "Biquad"),
TAS_CTL_5(0x5b, 2, 4, "Biquad"),
TAS_CTL_5(0x5c, 2, 5, "Biquad"),
TAS_CTL_5(0x5d, 2, 6, "Biquad"),
TAS_CTL_5(0x5e, 2, 7, "Biquad"),
TAS_CTL_5(0x7b, 3, 1, "Biquad"),
TAS_CTL_5(0x7c, 3, 2, "Biquad"),
TAS_CTL_5(0x7d, 3, 3, "Biquad"),
TAS_CTL_5(0x7e, 3, 4, "Biquad"),
TAS_CTL_5(0x7f, 3, 5, "Biquad"),
TAS_CTL_5(0x80, 3, 6, "Biquad"),
TAS_CTL_5(0x81, 3, 7, "Biquad"),
TAS_CTL_5(0x82, 4, 1, "Biquad"),
TAS_CTL_5(0x83, 4, 2, "Biquad"),
TAS_CTL_5(0x84, 4, 3, "Biquad"),
TAS_CTL_5(0x85, 4, 4, "Biquad"),
TAS_CTL_5(0x86, 4, 5, "Biquad"),
TAS_CTL_5(0x87, 4, 6, "Biquad"),
TAS_CTL_5(0x88, 4, 7, "Biquad"),
TAS_CTL_2(0x89, 1, 0, "Bass/Treble Bypass"),
TAS_CTL_2(0x8a, 2, 0, "Bass/Treble Bypass"),
TAS_CTL_2(0x8f, 3, 0, "Bass/Treble Bypass"),
TAS_CTL_2(0x90, 4, 0, "Bass/Treble Bypass"),
TAS_CTL_1(0x91, 0, 0, "Loudness Log2 Gain"),
TAS_CTL_1_64(0x92, 0, 0, "Loudness Log2 Offset"),
TAS_CTL_1(0x93, 0, 0, "Loudness Gain"),
TAS_CTL_1_64(0x94, 0, 0, "Loudness Offset"),
TAS_CTL_5(0x95, 0, 0, "Loudness Biquad"),
TAS_CTL_1(0x96, 0, 0, "DRC Control 1-3"),
TAS_CTL_1(0x97, 0, 0, "DRC Control 4"),
TAS_CTL_2(0x97, 0, 0, "DRC Energy"),
TAS_CTL_2_64(0x97, 0, 0, "DRC Threshold"),
TAS_CTL_3(0x97, 0, 0, "DRC Slope"),
TAS_CTL_2_64(0x97, 0, 0, "DRC Offset"),
TAS_CTL_4(0x97, 0, 0, "DRC Attack/Delay"),
TAS_CTL_2(0x9d, 4, 0, "DRC Energy"),
TAS_CTL_2_64(0x9e, 4, 0, "DRC Threshold"),
TAS_CTL_3(0x9f, 4, 0, "DRC Slope"),
TAS_CTL_2_64(0xa0, 4, 0, "DRC Offset"),
TAS_CTL_4(0xa1, 4, 0, "DRC Attack/Delay"),
TAS_CTL_2(0xa2, 1, 0, "DRC Bypass"),
TAS_CTL_2(0xa3, 2, 0, "DRC Bypass"),
TAS_CTL_2(0xa8, 3, 0, "DRC Bypass"),
TAS_CTL_2(0xa9, 4, 0, "DRC Bypass"),
TAS_CTL_2(0xaa, 1, 0, "Output Mixer"),
TAS_CTL_2(0xab, 2, 0, "Output Mixer"),
TAS_CTL_3(0xb0, 3, 0, "Output Mixer"),
TAS_CTL_3(0xb1, 4, 0, "Output Mixer"),
TAS_CTL_5(0xcf, 0, 0, "Volume Biquad"),
TAS_CTL_1(0xd0, 0, 0, "Volume Slew"),
TAS_CTL_1(0xd1, 1, 0, "Volume"),
TAS_CTL_1(0xd2, 2, 0, "Volume"),
TAS_CTL_1(0xd7, 3, 0, "Volume"),
TAS_CTL_1(0xd8, 4, 0, "Volume"),
SOC_SINGLE("PCM Playback Volume", 0xd9, 0, 0x3ff, 1),
TAS_CTL_1(0xda, 0, 0, "Bass Filter Set"),
TAS_CTL_1(0xdb, 0, 0, "Bass Filter Index"),
TAS_CTL_1(0xdc, 0, 0, "Treble Filter Set"),
TAS_CTL_1(0xdd, 0, 0, "Treble Filter Index"),
TAS_CTL_1(0xde, 0, 0, "AM Mode"),
TAS_CTL_1(0xdf, 0, 0, "PSCV Range"),
TAS_CTL_1(0xe0, 0, 0, "General Control"),
};
/* ---------------------------------------------------------------------
* SoC CODEC portion of driver: probe and release routines
*/
static int tas5504_probe(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;
struct snd_kcontrol *kcontrol;
struct tas5504_priv *priv;
int i, ret, err;
dev_info(&pdev->dev, "Probing tas5504 SoC CODEC driver\n");
dev_dbg(&pdev->dev, "socdev=%p\n", socdev);
dev_dbg(&pdev->dev, "codec_data=%p\n", socdev->codec_data);
/* Fetch the relevant tas5504 private data here (it's already been
* stored in the .codec pointer) */
priv = socdev->codec_data;
if (priv == NULL) {
dev_err(&pdev->dev, "tas5504: missing codec pointer\n");
return -ENODEV;
}
codec = &priv->codec;
socdev->codec = codec;
dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n",
&pdev->dev, socdev->dev);
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
dev_err(&pdev->dev, "tas5504: failed to create pcms\n");
return -ENODEV;
}
/* register controls */
dev_dbg(&pdev->dev, "Registering controls\n");
for (i = 0; i < ARRAY_SIZE(tas5504_snd_controls); i++) {
kcontrol = snd_ctl_new1(&tas5504_snd_controls[i], codec);
err = snd_ctl_add(codec->card, kcontrol);
if (err < 0)
dev_err(&pdev->dev, "tas5504: failed to create control %x\n", err);
}
/* CODEC is setup, we can register the card now */
dev_dbg(&pdev->dev, "Registering card\n");
ret = snd_soc_register_card(socdev);
if (ret < 0) {
dev_err(&pdev->dev, "tas5504: failed to register card\n");
goto card_err;
}
return 0;
card_err:
snd_soc_free_pcms(socdev);
return ret;
}
static int tas5504_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
snd_soc_free_pcms(socdev);
return 0;
}
static struct snd_soc_codec_device tas5504_soc_codec_dev = {
.probe = tas5504_probe,
.remove = tas5504_remove,
};
int tas5504_display_register(struct snd_soc_codec *codec, char *buffer, unsigned int limit, unsigned int reg)
{
struct tas5504_priv *priv = codec->private_data;
int i, count;
u32* base;
reg &= 0xFF;
if (reg >= TAS_REG_MAX)
return 0;
if (cache_map[reg].size == 0) /* sparse reg does not exist */
return 0;
if (!priv->valid[reg]) /* ensure it is valid */
tas5504_reg_read(codec, reg);
base = tas5504_get_base(priv, reg);
count = snprintf(buffer, limit, "%2x: ", reg);
if (reg < TAS_MAX_8B_REG)
count += snprintf(buffer + count, limit - count, " %.2x", *base);
else
for (i = 0; i < cache_map[reg].size; i++, base++)
count += snprintf(buffer + count, limit - count, " %.8x", *base);
count += snprintf(buffer + count, limit - count, "\n");
return count;
}
/* ---------------------------------------------------------------------
* i2c device portion of driver: probe and release routines and i2c
* driver registration.
*/
static int tas5504_i2c_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tas5504_priv *priv;
dev_dbg(&client->dev, "probing tas5504 i2c device\n");
/* Allocate driver data */
priv = kzalloc(sizeof *priv, GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* Initialize the driver data */
priv->client = client;
i2c_set_clientdata(client, priv);
/* Setup what we can in the codec structure so that the register
* access functions will work as expected. More will be filled
* out when it is probed by the SoC CODEC part of this driver */
priv->codec.private_data = priv;
priv->codec.name = "tas5504";
priv->codec.owner = THIS_MODULE;
priv->codec.dai = &tas5504_dai;
priv->codec.num_dai = 1;
priv->codec.read = tas5504_reg_read_cache;
priv->codec.write = tas5504_reg_write;
priv->codec.reg_cache_size = TAS_REG_MAX;
priv->codec.display_register = tas5504_display_register;
mutex_init(&priv->codec.mutex);
INIT_LIST_HEAD(&priv->codec.dapm_widgets);
INIT_LIST_HEAD(&priv->codec.dapm_paths);
/* Tell the of_soc helper about this codec */
of_snd_soc_register_codec(&tas5504_soc_codec_dev, priv, &tas5504_dai,
client->dev.archdata.of_node);
dev_dbg(&client->dev, "I2C device initialized\n");
return 0;
}
static int tas5504_i2c_remove(struct i2c_client *client)
{
struct tas5504_priv *priv = dev_get_drvdata(&client->dev);
kfree(priv);
return 0;
}
static const struct i2c_device_id tas5504_device_id[] = {
{ "tas5504", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tas5504_id);
static struct i2c_driver tas5504_driver = {
.driver = {
.name = "tas5504",
.owner = THIS_MODULE,
},
.probe = tas5504_i2c_probe,
.remove = __devexit_p(tas5504_i2c_remove),
.id_table = tas5504_device_id,
};
static __init int tas5504_driver_init(void)
{
return i2c_add_driver(&tas5504_driver);
}
static __exit void tas5504_driver_exit(void)
{
i2c_del_driver(&tas5504_driver);
}
module_init(tas5504_driver_init);
module_exit(tas5504_driver_exit);
MODULE_AUTHOR("Jon Smirl");
MODULE_DESCRIPTION("TAS5504 codec module");
MODULE_LICENSE("GPL");
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: mpc5200_psc_i2s.c --]
[-- Type: text/x-csrc; name=mpc5200_psc_i2s.c, Size: 26928 bytes --]
/*
* Freescale MPC5200 PSC in I2S mode
* ALSA SoC Digital Audio Interface (DAI) driver
*
* Copyright (C) 2008 Secret Lab Technologies Ltd.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/of_device.h>
#include <linux/of_platform.h>
#include <linux/dma-mapping.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/initval.h>
#include <sound/soc.h>
#include <sound/soc-of-simple.h>
#include <sysdev/bestcomm/bestcomm.h>
#include <sysdev/bestcomm/gen_bd.h>
#include <asm/time.h>
#include <asm/mpc52xx.h>
#include <asm/mpc52xx_psc.h>
#include "mpc5200_psc_i2s.h"
MODULE_AUTHOR("Grant Likely <grant.likely@secretlab.ca>");
MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver");
MODULE_LICENSE("GPL");
/**
* PSC_I2S_RATES: sample rates supported by the I2S
*
* This driver currently only supports the PSC running in I2S slave mode,
* which means the codec determines the sample rate. Therefore, we tell
* ALSA that we support all rates and let the codec driver decide what rates
* are really supported.
*/
#define PSC_I2S_RATES (SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000 | \
SNDRV_PCM_RATE_CONTINUOUS)
/**
* PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode
*/
#define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \
SNDRV_PCM_FMTBIT_S32_BE)
/**
* psc_i2s_stream - Data specific to a single stream (playback or capture)
* @active: flag indicating if the stream is active
* @psc_i2s: pointer back to parent psc_i2s data structure
* @bcom_task: bestcomm task structure
* @irq: irq number for bestcomm task
* @period_start: physical address of start of DMA region
* @period_end: physical address of end of DMA region
* @period_next_pt: physical address of next DMA buffer to enqueue
* @period_bytes: size of DMA period in bytes
*/
struct psc_i2s_stream {
int active;
struct psc_i2s *psc_i2s;
struct bcom_task *bcom_task;
int irq;
struct snd_pcm_substream *stream;
dma_addr_t period_start;
dma_addr_t period_end;
dma_addr_t period_next_pt;
dma_addr_t period_current_pt;
int period_bytes;
};
/**
* psc_i2s - Private driver data
* @name: short name for this device ("PSC0", "PSC1", etc)
* @psc_regs: pointer to the PSC's registers
* @fifo_regs: pointer to the PSC's FIFO registers
* @irq: IRQ of this PSC
* @dev: struct device pointer
* @dai: the CPU DAI for this device
* @sicr: Base value used in serial interface control register; mode is ORed
* with this value.
* @playback: Playback stream context data
* @capture: Capture stream context data
*/
struct psc_i2s {
char name[32];
struct mpc52xx_psc __iomem *psc_regs;
struct mpc52xx_psc_fifo __iomem *fifo_regs;
unsigned int irq;
struct device *dev;
struct snd_soc_dai dai;
spinlock_t lock;
u32 sicr;
uint sysclk;
/* per-stream data */
struct psc_i2s_stream playback;
struct psc_i2s_stream capture;
/* Statistics */
struct {
int overrun_count;
int underrun_count;
} stats;
};
/*
* Interrupt handlers
*/
static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s)
{
struct psc_i2s *psc_i2s = _psc_i2s;
struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs;
u16 isr;
isr = in_be16(®s->mpc52xx_psc_isr);
/* Playback underrun error */
if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP))
psc_i2s->stats.underrun_count++;
/* Capture overrun error */
if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR))
psc_i2s->stats.overrun_count++;
out_8(®s->command, 4 << 4); /* reset the error status */
return IRQ_HANDLED;
}
/**
* psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer
* @s: pointer to stream private data structure
*
* Enqueues another audio period buffer into the bestcomm queue.
*
* Note: The routine must only be called when there is space available in
* the queue. Otherwise the enqueue will fail and the audio ring buffer
* will get out of sync
*/
static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s)
{
struct bcom_bd *bd;
/* Prepare and enqueue the next buffer descriptor */
bd = bcom_prepare_next_buffer(s->bcom_task);
bd->status = s->period_bytes;
bd->data[0] = s->period_next_pt;
bcom_submit_next_buffer(s->bcom_task, NULL);
/* Update for next period */
s->period_next_pt += s->period_bytes;
if (s->period_next_pt >= s->period_end)
s->period_next_pt = s->period_start;
}
/* Bestcomm DMA irq handler */
static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream)
{
struct psc_i2s_stream *s = _psc_i2s_stream;
/* For each finished period, dequeue the completed period buffer
* and enqueue a new one in it's place. */
while (bcom_buffer_done(s->bcom_task)) {
bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
s->period_current_pt += s->period_bytes;
if (s->period_current_pt >= s->period_end)
s->period_current_pt = s->period_start;
psc_i2s_bcom_enqueue_next_buffer(s);
bcom_enable(s->bcom_task);
}
/* If the stream is active, then also inform the PCM middle layer
* of the period finished event. */
if (s->active)
snd_pcm_period_elapsed(s->stream);
return IRQ_HANDLED;
}
/**
* psc_i2s_startup: create a new substream
*
* This is the first function called when a stream is opened.
*
* If this is the first stream open, then grab the IRQ and program most of
* the PSC registers.
*/
static int psc_i2s_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
int rc;
dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream);
if (!psc_i2s->playback.active &&
!psc_i2s->capture.active) {
/* Setup the IRQs */
rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED,
"psc-i2s-status", psc_i2s);
rc |= request_irq(psc_i2s->capture.irq,
&psc_i2s_bcom_irq, IRQF_SHARED,
"psc-i2s-capture", &psc_i2s->capture);
rc |= request_irq(psc_i2s->playback.irq,
&psc_i2s_bcom_irq, IRQF_SHARED,
"psc-i2s-playback", &psc_i2s->playback);
if (rc) {
free_irq(psc_i2s->irq, psc_i2s);
free_irq(psc_i2s->capture.irq,
&psc_i2s->capture);
free_irq(psc_i2s->playback.irq,
&psc_i2s->playback);
return -ENODEV;
}
}
return 0;
}
static int psc_i2s_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
uint bits, framesync, bitclk, value;
u32 mode;
dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i"
" periods=%i buffer_size=%i buffer_bytes=%i\n",
__func__, substream, params_period_size(params),
params_period_bytes(params), params_periods(params),
params_buffer_size(params), params_buffer_bytes(params));
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S8:
mode = MPC52xx_PSC_SICR_SIM_CODEC_8;
bits = 8;
break;
case SNDRV_PCM_FORMAT_S16_BE:
mode = MPC52xx_PSC_SICR_SIM_CODEC_16;
bits = 16;
break;
case SNDRV_PCM_FORMAT_S24_BE:
mode = MPC52xx_PSC_SICR_SIM_CODEC_24;
bits = 24;
break;
case SNDRV_PCM_FORMAT_S32_BE:
mode = MPC52xx_PSC_SICR_SIM_CODEC_32;
bits = 32;
break;
default:
dev_dbg(psc_i2s->dev, "invalid format\n");
return -EINVAL;
}
out_be32(&psc_i2s->psc_regs->sicr, psc_i2s->sicr | mode);
if (psc_i2s->sysclk) {
framesync = bits * 2;
bitclk = (psc_i2s->sysclk) / (params_rate(params) * framesync);
/* bitclk field is byte swapped due to mpc5200/b compatibility */
value = ((framesync - 1) << 24) |
(((bitclk - 1) & 0xFF) << 16) | ((bitclk - 1) & 0xFF00);
dev_dbg(psc_i2s->dev, "%s(substream=%p) rate=%i sysclk=%i"
" framesync=%i bitclk=%i reg=%X\n",
__FUNCTION__, substream, params_rate(params), psc_i2s->sysclk,
framesync, bitclk, value);
out_be32(&psc_i2s->psc_regs->ccr, value);
out_8(&psc_i2s->psc_regs->ctur, bits - 1);
}
snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
return 0;
}
static int psc_i2s_hw_free(struct snd_pcm_substream *substream)
{
snd_pcm_set_runtime_buffer(substream, NULL);
return 0;
}
/**
* psc_i2s_trigger: start and stop the DMA transfer.
*
* This function is called by ALSA to start, stop, pause, and resume the DMA
* transfer of data.
*/
static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
struct psc_i2s_stream *s;
struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs;
u16 imr;
u8 psc_cmd;
long flags;
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
s = &psc_i2s->capture;
else
s = &psc_i2s->playback;
dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)"
" stream_id=%i\n",
substream, cmd, substream->pstr->stream);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
s->period_bytes = frames_to_bytes(runtime,
runtime->period_size);
s->period_start = virt_to_phys(runtime->dma_area);
s->period_end = s->period_start +
(s->period_bytes * runtime->periods);
s->period_next_pt = s->period_start;
s->period_current_pt = s->period_start;
s->active = 1;
/* First; reset everything */
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) {
out_8(®s->command, MPC52xx_PSC_RST_RX);
out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT);
} else {
out_8(®s->command, MPC52xx_PSC_RST_TX);
out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT);
}
/* Next, fill up the bestcomm bd queue and enable DMA.
* This will begin filling the PSC's fifo. */
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
bcom_gen_bd_rx_reset(s->bcom_task);
else
bcom_gen_bd_tx_reset(s->bcom_task);
while (!bcom_queue_full(s->bcom_task))
psc_i2s_bcom_enqueue_next_buffer(s);
bcom_enable(s->bcom_task);
/* Due to errata in the i2s mode; need to line up enabling
* the transmitter with a transition on the frame sync
* line */
spin_lock_irqsave(&psc_i2s->lock, flags);
/* first make sure it is low */
while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0)
;
/* then wait for the transition to high */
while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0)
;
/* Finally, enable the PSC.
* Receiver must always be enabled; even when we only want
* transmit. (see 15.3.2.3 of MPC5200B User's Guide) */
psc_cmd = MPC52xx_PSC_RX_ENABLE;
if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK)
psc_cmd |= MPC52xx_PSC_TX_ENABLE;
out_8(®s->command, psc_cmd);
spin_unlock_irqrestore(&psc_i2s->lock, flags);
break;
case SNDRV_PCM_TRIGGER_STOP:
/* Turn off the PSC */
s->active = 0;
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) {
if (!psc_i2s->playback.active) {
out_8(®s->command, 2 << 4); /* reset rx */
out_8(®s->command, 3 << 4); /* reset tx */
out_8(®s->command, 4 << 4); /* reset err */
}
} else {
out_8(®s->command, 3 << 4); /* reset tx */
out_8(®s->command, 4 << 4); /* reset err */
if (!psc_i2s->capture.active)
out_8(®s->command, 2 << 4); /* reset rx */
}
bcom_disable(s->bcom_task);
while (!bcom_queue_empty(s->bcom_task))
bcom_retrieve_buffer(s->bcom_task, NULL, NULL);
break;
default:
dev_dbg(psc_i2s->dev, "invalid command\n");
return -EINVAL;
}
/* Update interrupt enable settings */
imr = 0;
if (psc_i2s->playback.active)
imr |= MPC52xx_PSC_IMR_TXEMP;
if (psc_i2s->capture.active)
imr |= MPC52xx_PSC_IMR_ORERR;
out_be16(®s->isr_imr.imr, imr);
return 0;
}
/**
* psc_i2s_shutdown: shutdown the data transfer on a stream
*
* Shutdown the PSC if there are no other substreams open.
*/
static void psc_i2s_shutdown(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream);
/*
* If this is the last active substream, disable the PSC and release
* the IRQ.
*/
if (!psc_i2s->playback.active &&
!psc_i2s->capture.active) {
/* Disable all interrupts and reset the PSC */
out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0);
out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */
out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */
out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */
out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */
/* Release irqs */
free_irq(psc_i2s->irq, psc_i2s);
free_irq(psc_i2s->capture.irq, &psc_i2s->capture);
free_irq(psc_i2s->playback.irq, &psc_i2s->playback);
}
}
/**
* psc_i2s_set_sysclk: set the clock frequency and direction
*
* This function is called by the machine driver to tell us what the clock
* frequency and direction are.
*
* Currently, we only support operating as a clock slave (SND_SOC_CLOCK_IN),
* and we don't care about the frequency. Return an error if the direction
* is not SND_SOC_CLOCK_IN.
*
* @clk_id: reserved, should be zero
* @freq: the frequency of the given clock ID, currently ignored
* @dir: SND_SOC_CLOCK_IN (clock slave) or SND_SOC_CLOCK_OUT (clock master)
*/
static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai,
int clk_id, unsigned int freq, int dir)
{
struct psc_i2s *psc_i2s = cpu_dai->private_data;
int clkdiv, err;
dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, freq=%u, dir=%i)\n",
cpu_dai, freq, dir);
if (dir == SND_SOC_CLOCK_OUT) {
psc_i2s->sysclk = freq;
if (clk_id == MPC52xx_CLK_CELLSLAVE) {
psc_i2s->sicr |= MPC52xx_PSC_SICR_CELLSLAVE | MPC52xx_PSC_SICR_GENCLK;
} else { /* MPC52xx_CLK_INTERNAL */
psc_i2s->sicr &= ~MPC52xx_PSC_SICR_CELLSLAVE;
psc_i2s->sicr |= MPC52xx_PSC_SICR_GENCLK;
clkdiv = ppc_proc_freq / freq;
err = ppc_proc_freq % freq;
if (err > freq / 2)
clkdiv++;
dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(clkdiv %d freq error=%ldHz)\n",
clkdiv, (ppc_proc_freq / clkdiv - freq));
return mpc52xx_set_psc_clkdiv(psc_i2s->dai.id + 1, clkdiv);
}
}
return 0;
}
/**
* psc_i2s_set_fmt: set the serial format.
*
* This function is called by the machine driver to tell us what serial
* format to use.
*
* This driver only supports I2S mode. Return an error if the format is
* not SND_SOC_DAIFMT_I2S.
*
* @format: one of SND_SOC_DAIFMT_xxx
*/
static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format)
{
struct psc_i2s *psc_i2s = cpu_dai->private_data;
dev_dbg(psc_i2s->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n",
cpu_dai, format);
return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL;
}
/* ---------------------------------------------------------------------
* ALSA SoC Bindings
*
* - Digital Audio Interface (DAI) template
* - create/destroy dai hooks
*/
/**
* psc_i2s_dai_template: template CPU Digital Audio Interface
*/
static struct snd_soc_dai psc_i2s_dai_template = {
.type = SND_SOC_DAI_I2S,
.playback = {
.channels_min = 2,
.channels_max = 2,
.rates = PSC_I2S_RATES,
.formats = PSC_I2S_FORMATS,
},
.capture = {
.channels_min = 2,
.channels_max = 2,
.rates = PSC_I2S_RATES,
.formats = PSC_I2S_FORMATS,
},
.ops = {
.startup = psc_i2s_startup,
.hw_params = psc_i2s_hw_params,
.hw_free = psc_i2s_hw_free,
.shutdown = psc_i2s_shutdown,
.trigger = psc_i2s_trigger,
},
.dai_ops = {
.set_sysclk = psc_i2s_set_sysclk,
.set_fmt = psc_i2s_set_fmt,
},
};
/* ---------------------------------------------------------------------
* The PSC I2S 'ASoC platform' driver
*
* Can be referenced by an 'ASoC machine' driver
* This driver only deals with the audio bus; it doesn't have any
* interaction with the attached codec
*/
static const struct snd_pcm_hardware psc_i2s_pcm_hardware = {
.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER,
.formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE,
.rate_min = 8000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.period_bytes_max = 1024 * 1024,
.period_bytes_min = 32,
.periods_min = 2,
.periods_max = 256,
.buffer_bytes_max = 2 * 1024 * 1024,
.fifo_size = 0,
};
static int psc_i2s_pcm_open(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
struct psc_i2s_stream *s;
dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream);
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
s = &psc_i2s->capture;
else
s = &psc_i2s->playback;
snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware);
s->stream = substream;
return 0;
}
static int psc_i2s_pcm_close(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
struct psc_i2s_stream *s;
dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream);
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
s = &psc_i2s->capture;
else
s = &psc_i2s->playback;
s->stream = NULL;
return 0;
}
static snd_pcm_uframes_t
psc_i2s_pcm_pointer(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data;
struct psc_i2s_stream *s;
dma_addr_t count;
if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE)
s = &psc_i2s->capture;
else
s = &psc_i2s->playback;
count = s->period_current_pt - s->period_start;
return bytes_to_frames(substream->runtime, count);
}
static struct snd_pcm_ops psc_i2s_pcm_ops = {
.open = psc_i2s_pcm_open,
.close = psc_i2s_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.pointer = psc_i2s_pcm_pointer,
};
static u64 psc_i2s_pcm_dmamask = 0xffffffff;
static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
struct snd_pcm *pcm)
{
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
size_t size = psc_i2s_pcm_hardware.buffer_bytes_max;
int rc = 0;
dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n",
card, dai, pcm);
if (!card->dev->dma_mask)
card->dev->dma_mask = &psc_i2s_pcm_dmamask;
if (!card->dev->coherent_dma_mask)
card->dev->coherent_dma_mask = 0xffffffff;
if (pcm->streams[0].substream) {
rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size,
&pcm->streams[0].substream->dma_buffer);
if (rc)
goto playback_alloc_err;
}
if (pcm->streams[1].substream) {
rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size,
&pcm->streams[1].substream->dma_buffer);
if (rc)
goto capture_alloc_err;
}
return 0;
capture_alloc_err:
if (pcm->streams[0].substream)
snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer);
playback_alloc_err:
dev_err(card->dev, "Cannot allocate buffer(s)\n");
return -ENOMEM;
}
static void psc_i2s_pcm_free(struct snd_pcm *pcm)
{
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
struct snd_pcm_substream *substream;
int stream;
dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm);
for (stream = 0; stream < 2; stream++) {
substream = pcm->streams[stream].substream;
if (substream) {
snd_dma_free_pages(&substream->dma_buffer);
substream->dma_buffer.area = NULL;
substream->dma_buffer.addr = 0;
}
}
}
struct snd_soc_platform psc_i2s_pcm_soc_platform = {
.name = "mpc5200-psc-audio",
.pcm_ops = &psc_i2s_pcm_ops,
.pcm_new = &psc_i2s_pcm_new,
.pcm_free = &psc_i2s_pcm_free,
};
/* ---------------------------------------------------------------------
* Sysfs attributes for debugging
*/
static ssize_t psc_i2s_status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x "
"tfnum=%i tfstat=0x%.4x\n",
in_be16(&psc_i2s->psc_regs->sr_csr.status),
in_be32(&psc_i2s->psc_regs->sicr),
in_be16(&psc_i2s->fifo_regs->rfnum) & 0x1ff,
in_be16(&psc_i2s->fifo_regs->rfstat),
in_be16(&psc_i2s->fifo_regs->tfnum) & 0x1ff,
in_be16(&psc_i2s->fifo_regs->tfstat));
}
static int *psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s, const char *name)
{
if (strcmp(name, "playback_underrun") == 0)
return &psc_i2s->stats.underrun_count;
if (strcmp(name, "capture_overrun") == 0)
return &psc_i2s->stats.overrun_count;
return NULL;
}
static ssize_t psc_i2s_stat_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
int *attrib;
attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name);
if (!attrib)
return 0;
return sprintf(buf, "%i\n", *attrib);
}
static ssize_t psc_i2s_stat_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct psc_i2s *psc_i2s = dev_get_drvdata(dev);
int *attrib;
attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name);
if (!attrib)
return 0;
*attrib = simple_strtoul(buf, NULL, 0);
return count;
}
DEVICE_ATTR(status, 0644, psc_i2s_status_show, NULL);
DEVICE_ATTR(playback_underrun, 0644, psc_i2s_stat_show, psc_i2s_stat_store);
DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show, psc_i2s_stat_store);
/* ---------------------------------------------------------------------
* OF platform bus binding code:
* - Probe/remove operations
* - OF device match table
*/
static int __devinit psc_i2s_of_probe(struct of_device *op,
const struct of_device_id *match)
{
phys_addr_t fifo;
struct psc_i2s *psc_i2s;
struct resource res;
int size, psc_id, irq, rc;
const __be32 *prop;
void __iomem *regs;
dev_dbg(&op->dev, "probing psc i2s device\n");
/* Get the PSC ID */
prop = of_get_property(op->node, "cell-index", &size);
if (!prop || size < sizeof *prop)
return -ENODEV;
psc_id = be32_to_cpu(*prop);
/* Fetch the registers and IRQ of the PSC */
irq = irq_of_parse_and_map(op->node, 0);
if (of_address_to_resource(op->node, 0, &res)) {
dev_err(&op->dev, "Missing reg property\n");
return -ENODEV;
}
regs = ioremap(res.start, 1 + res.end - res.start);
if (!regs) {
dev_err(&op->dev, "Could not map registers\n");
return -ENODEV;
}
/* Allocate and initialize the driver private data */
psc_i2s = kzalloc(sizeof *psc_i2s, GFP_KERNEL);
if (!psc_i2s) {
iounmap(regs);
return -ENOMEM;
}
spin_lock_init(&psc_i2s->lock);
psc_i2s->irq = irq;
psc_i2s->psc_regs = regs;
psc_i2s->fifo_regs = regs + sizeof *psc_i2s->psc_regs;
psc_i2s->dev = &op->dev;
psc_i2s->playback.psc_i2s = psc_i2s;
psc_i2s->capture.psc_i2s = psc_i2s;
snprintf(psc_i2s->name, sizeof psc_i2s->name, "PSC%u", psc_id+1);
/* Fill out the CPU DAI structure */
memcpy(&psc_i2s->dai, &psc_i2s_dai_template, sizeof psc_i2s->dai);
psc_i2s->dai.private_data = psc_i2s;
psc_i2s->dai.name = psc_i2s->name;
psc_i2s->dai.id = psc_id;
/* Find the address of the fifo data registers and setup the
* DMA tasks */
fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32);
psc_i2s->capture.bcom_task =
bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512);
psc_i2s->playback.bcom_task =
bcom_psc_gen_bd_tx_init(psc_id, 10, fifo);
if (!psc_i2s->capture.bcom_task ||
!psc_i2s->playback.bcom_task) {
dev_err(&op->dev, "Could not allocate bestcomm tasks\n");
iounmap(regs);
kfree(psc_i2s);
return -ENODEV;
}
/* Disable all interrupts and reset the PSC */
out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0);
out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset transmitter */
out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset receiver */
out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */
out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */
/* Configure the serial interface mode; defaulting to CODEC8 mode */
psc_i2s->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S |
MPC52xx_PSC_SICR_CLKPOL;
out_be32(&psc_i2s->psc_regs->sicr,
psc_i2s->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8);
/* Check for the codec handle. If it is not present then we
* are done */
if (!of_get_property(op->node, "codec-handle", NULL))
return 0;
/* Set up mode register;
* First write: RxRdy (FIFO Alarm) generates rx FIFO irq
* Second write: register Normal mode for non loopback
*/
out_8(&psc_i2s->psc_regs->mode, 0);
out_8(&psc_i2s->psc_regs->mode, 0);
/* Set the TX and RX fifo alarm thresholds */
out_be16(&psc_i2s->fifo_regs->rfalarm, 0x100);
out_8(&psc_i2s->fifo_regs->rfcntl, 0x4);
out_be16(&psc_i2s->fifo_regs->tfalarm, 0x100);
out_8(&psc_i2s->fifo_regs->tfcntl, 0x7);
/* Lookup the IRQ numbers */
psc_i2s->playback.irq =
bcom_get_task_irq(psc_i2s->playback.bcom_task);
psc_i2s->capture.irq =
bcom_get_task_irq(psc_i2s->capture.bcom_task);
/* Save what we've done so it can be found again later */
dev_set_drvdata(&op->dev, psc_i2s);
/* Register the SYSFS files */
rc = device_create_file(psc_i2s->dev, &dev_attr_status);
rc = device_create_file(psc_i2s->dev, &dev_attr_capture_overrun);
rc = device_create_file(psc_i2s->dev, &dev_attr_playback_underrun);
if (rc)
dev_info(psc_i2s->dev, "error creating sysfs files\n");
/* Tell the ASoC OF helpers about it */
of_snd_soc_register_platform(&psc_i2s_pcm_soc_platform, op->node,
&psc_i2s->dai);
return 0;
}
static int __devexit psc_i2s_of_remove(struct of_device *op)
{
struct psc_i2s *psc_i2s = dev_get_drvdata(&op->dev);
dev_dbg(&op->dev, "psc_i2s_remove()\n");
bcom_gen_bd_rx_release(psc_i2s->capture.bcom_task);
bcom_gen_bd_tx_release(psc_i2s->playback.bcom_task);
iounmap(psc_i2s->psc_regs);
iounmap(psc_i2s->fifo_regs);
kfree(psc_i2s);
dev_set_drvdata(&op->dev, NULL);
return 0;
}
/* Match table for of_platform binding */
static struct of_device_id psc_i2s_match[] __devinitdata = {
{ .compatible = "fsl,mpc5200-psc-i2s", },
{ .compatible = "fsl,mpc5200b-psc-i2s", },
{}
};
MODULE_DEVICE_TABLE(of, psc_i2s_match);
static struct of_platform_driver psc_i2s_driver = {
.match_table = psc_i2s_match,
.probe = psc_i2s_of_probe,
.remove = __devexit_p(psc_i2s_of_remove),
.driver = {
.name = "mpc5200-psc-i2s",
.owner = THIS_MODULE,
},
};
/* ---------------------------------------------------------------------
* Module setup and teardown; simply register the of_platform driver
* for the PSC in I2S mode.
*/
static int __init psc_i2s_init(void)
{
return of_register_platform_driver(&psc_i2s_driver);
}
module_init(psc_i2s_init);
static void __exit psc_i2s_exit(void)
{
of_unregister_platform_driver(&psc_i2s_driver);
}
module_exit(psc_i2s_exit);
[-- Attachment #4: dspeak01.dts --]
[-- Type: application/octet-stream, Size: 11326 bytes --]
/*
* phyCORE-MPC5200B-tiny (pcm030) board Device Tree Source
*
* Copyright 2006 Pengutronix
* Sascha Hauer <s.hauer@pengutronix.de>
* Copyright 2007 Pengutronix
* Juergen Beisert <j.beisert@pengutronix.de>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
/dts-v1/;
/ {
model = "digispeaker,dspeak01";
compatible = "digispeaker,dspeak01";
#address-cells = <1>;
#size-cells = <1>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
PowerPC,5200@0 {
device_type = "cpu";
reg = <0>;
d-cache-line-size = <32>;
i-cache-line-size = <32>;
d-cache-size = <0x4000>; /* L1, 16K */
i-cache-size = <0x4000>; /* L1, 16K */
timebase-frequency = <0>; /* From Bootloader */
bus-frequency = <0>; /* From Bootloader */
clock-frequency = <0>; /* From Bootloader */
};
};
memory {
device_type = "memory";
reg = <0x00000000 0x04000000>; /* 64MB */
};
soc5200@f0000000 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "fsl,mpc5200b-immr";
ranges = <0x0 0xf0000000 0x0000c000>;
bus-frequency = <0>; /* From bootloader */
system-frequency = <0>; /* From bootloader */
cdm@200 {
compatible = "fsl,mpc5200b-cdm","fsl,mpc5200-cdm";
reg = <0x200 0x38>;
};
mpc5200_pic: interrupt-controller@500 {
/* 5200 interrupts are encoded into two levels; */
interrupt-controller;
#interrupt-cells = <3>;
device_type = "interrupt-controller";
compatible = "fsl,mpc5200b-pic","fsl,mpc5200-pic";
reg = <0x500 0x80>;
};
timer@600 { /* General Purpose Timer */
compatible = "fsl,mpc5200b-gpt","fsl,mpc5200-gpt";
cell-index = <0>;
reg = <0x600 0x10>;
interrupts = <0x1 0x9 0x0>;
interrupt-parent = <&mpc5200_pic>;
fsl,has-wdt;
};
timer@610 { /* General Purpose Timer */
compatible = "fsl,mpc5200b-gpt","fsl,mpc5200-gpt";
cell-index = <1>;
reg = <0x610 0x10>;
interrupts = <0x1 0xa 0x0>;
interrupt-parent = <&mpc5200_pic>;
};
gpt2: timer@620 { /* General Purpose Timer in GPIO mode */
compatible = "fsl,mpc5200b-gpt-gpio","fsl,mpc5200-gpt-gpio";
cell-index = <2>;
reg = <0x620 0x10>;
interrupts = <0x1 0xb 0x0>;
interrupt-parent = <&mpc5200_pic>;
gpio-controller;
#gpio-cells = <2>;
};
gpt3: timer@630 { /* General Purpose Timer in GPIO mode */
compatible = "fsl,mpc5200b-gpt-gpio","fsl,mpc5200-gpt-gpio";
cell-index = <3>;
reg = <0x630 0x10>;
interrupts = <0x1 0xc 0x0>;
interrupt-parent = <&mpc5200_pic>;
gpio-controller;
#gpio-cells = <2>;
};
gpt4: timer@640 { /* General Purpose Timer in GPIO mode */
compatible = "fsl,mpc5200b-gpt-gpio","fsl,mpc5200-gpt-gpio";
cell-index = <4>;
reg = <0x640 0x10>;
interrupts = <0x1 0xd 0x0>;
interrupt-parent = <&mpc5200_pic>;
gpio-controller;
#gpio-cells = <2>;
};
gpt5: timer@650 { /* General Purpose Timer in GPIO mode */
compatible = "fsl,mpc5200b-gpt-gpio","fsl,mpc5200-gpt-gpio";
cell-index = <5>;
reg = <0x650 0x10>;
interrupts = <0x1 0xe 0x0>;
interrupt-parent = <&mpc5200_pic>;
gpio-controller;
#gpio-cells = <2>;
};
gpt6: timer@660 { /* General Purpose Timer in GPIO mode */
compatible = "fsl,mpc5200b-gpt-gpio","fsl,mpc5200-gpt-gpio";
cell-index = <6>;
reg = <0x660 0x10>;
interrupts = <0x1 0xf 0x0>;
interrupt-parent = <&mpc5200_pic>;
gpio-controller;
#gpio-cells = <2>;
};
gpt7: timer@670 { /* General Purpose Timer in GPIO mode */
compatible = "fsl,mpc5200b-gpt-gpio","fsl,mpc5200-gpt-gpio";
cell-index = <7>;
reg = <0x670 0x10>;
interrupts = <0x1 0x10 0x0>;
interrupt-parent = <&mpc5200_pic>;
gpio-controller;
#gpio-cells = <2>;
};
rtc@800 { // Real time clock
compatible = "fsl,mpc5200b-rtc","fsl,mpc5200-rtc";
device_type = "rtc";
reg = <0x800 0x100>;
interrupts = <0x1 0x5 0x0 0x1 0x6 0x0>;
interrupt-parent = <&mpc5200_pic>;
};
gpio_simple: gpio@b00 {
compatible = "fsl,mpc5200b-gpio","fsl,mpc5200-gpio";
reg = <0xb00 0x40>;
interrupts = <0x1 0x7 0x0>;
interrupt-parent = <&mpc5200_pic>;
gpio-controller;
#gpio-cells = <2>;
};
gpio_wkup: gpio-wkup@c00 {
compatible = "fsl,mpc5200b-gpio-wkup","fsl,mpc5200-gpio-wkup";
reg = <0xc00 0x40>;
interrupts = <0x1 0x8 0x0 0x0 0x3 0x0>;
interrupt-parent = <&mpc5200_pic>;
gpio-controller;
#gpio-cells = <2>;
};
spi@f00 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,mpc5200b-spi","fsl,mpc5200-spi";
reg = <0xf00 0x20>;
interrupts = <0x2 0xd 0x0 0x2 0xe 0x0>;
interrupt-parent = <&mpc5200_pic>;
mmc-slot@0 {
compatible = "linux,mmc-spi";
reg = <0>;
spi-max-frequency = <20000000>;
/* Unregulated slot. */
voltage-range = <3300 3300>;
/*gpios = <&sdcsr_pio 1 0 /* WP */
/* &sdcsr_pio 0 1>; /* nCD */
};
};
usb@1000 {
compatible = "fsl,mpc5200b-ohci","fsl,mpc5200-ohci","ohci-be";
reg = <0x1000 0xff>;
interrupts = <0x2 0x6 0x0>;
interrupt-parent = <&mpc5200_pic>;
};
dma-controller@1200 {
device_type = "dma-controller";
compatible = "fsl,mpc5200b-bestcomm","fsl,mpc5200-bestcomm";
reg = <0x1200 0x80>;
interrupts = <0x3 0x0 0x0 0x3 0x1 0x0 0x3 0x2 0x0 0x3 0x3 0x0
0x3 0x4 0x0 0x3 0x5 0x0 0x3 0x6 0x0 0x3 0x7 0x0
0x3 0x8 0x0 0x3 0x9 0x0 0x3 0xa 0x0 0x3 0xb 0x0
0x3 0xc 0x0 0x3 0xd 0x0 0x3 0xe 0x0 0x3 0xf 0x0>;
interrupt-parent = <&mpc5200_pic>;
};
xlb@1f00 {
compatible = "fsl,mpc5200b-xlb","fsl,mpc5200-xlb";
reg = <0x1f00 0x100>;
};
i2s@2000 { /* PSC1 in i2s mode */
compatible = "fsl,mpc5200b-psc-i2s","fsl,mpc5200-psc-i2s";
cell-index = <0>;
reg = <0x2000 0x100>;
interrupts = <0x2 0x1 0x0>;
interrupt-parent = <&mpc5200_pic>;
};
i2s@2200 { /* PSC2 in i2s mode */
compatible = "fsl,mpc5200b-psc-i2s","fsl,mpc5200-psc-i2s";
cell-index = <1>;
reg = <0x2200 0x100>;
interrupts = <0x2 0x2 0x0>;
interrupt-parent = <&mpc5200_pic>;
codec-handle = <&tas0>;
};
serial@2400 { /* PSC3 in UART mode */
device_type = "serial";
compatible = "fsl,mpc5200b-psc-uart","fsl,mpc5200-psc-uart";
port-number = <0>;
cell-index = <2>;
reg = <0x2400 0x100>;
interrupts = <0x2 0x3 0x0>;
interrupt-parent = <&mpc5200_pic>;
};
/* PSC4 is ??? */
/* PSC5 is ??? */
serial@2c00 { /* PSC6 in UART mode */
device_type = "serial";
compatible = "fsl,mpc5200b-psc-uart","fsl,mpc5200-psc-uart";
port-number = <1>;
cell-index = <5>;
reg = <0x2c00 0x100>;
interrupts = <0x2 0x4 0x0>;
interrupt-parent = <&mpc5200_pic>;
};
ethernet@3000 {
device_type = "network";
compatible = "fsl,mpc5200b-fec","fsl,mpc5200-fec";
reg = <0x3000 0x400>;
local-mac-address = [00 00 00 00 00 00];
interrupts = <0x2 0x5 0x0>;
interrupt-parent = <&mpc5200_pic>;
phy-handle = <&phy0>;
};
mdio@3000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,mpc5200b-mdio", "fsl,mpc5200-mdio";
reg = <0x3000 0x400>; /* fec range, since we need to setup fec interrupts */
interrupts = <0x2 0x5 0x0>; /* these are for "mii command finished", not link changes & co. */
interrupt-parent = <&mpc5200_pic>;
phy0:ethernet-phy@0 {
device_type = "ethernet-phy";
reg = <0x0>;
};
};
ata@3a00 {
device_type = "ata";
compatible = "fsl,mpc5200b-ata","fsl,mpc5200-ata";
reg = <0x3a00 0x100>;
interrupts = <0x2 0x7 0x0>;
interrupt-parent = <&mpc5200_pic>;
};
i2c@3d00 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,mpc5200b-i2c","fsl,mpc5200-i2c","fsl-i2c";
cell-index = <0>;
reg = <0x3d00 0x40>;
interrupts = <0x2 0xf 0x0>;
interrupt-parent = <&mpc5200_pic>;
fsl5200-clocking;
tas0:codec@1b {
compatible = "ti,tas5504";
reg = <0x1b>;
};
clock0:clock@68 {
compatible = "maxim,max9485";
reg = <0x68>;
};
};
i2c@3d40 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,mpc5200b-i2c","fsl,mpc5200-i2c","fsl-i2c";
cell-index = <1>;
reg = <0x3d40 0x40>;
interrupts = <0x2 0x10 0x0>;
interrupt-parent = <&mpc5200_pic>;
fsl5200-clocking;
rtc@51 {
device_type = "rtc";
compatible = "epson,rtc8564";
reg = <0x51>;
};
eeprom@52 {
compatible = "atmel,24c32", "eeprom";
reg = <0x52>;
};
};
sram@8000 {
compatible = "fsl,mpc5200b-sram","fsl,mpc5200-sram","sram";
reg = <0x8000 0x4000>;
};
/* This is only an example device to show the usage of gpios. It maps all available
* gpios to the "gpio-provider" device.
*/
gpio {
compatible = "gpio-provider";
/* mpc52xx exp.con patchfield */
gpios = <&gpio_wkup 0 0 /* GPIO_WKUP_7 11d jp13-3 */
&gpio_wkup 1 0 /* GPIO_WKUP_6 14c */
&gpio_wkup 6 0 /* PSC2_4 43c x5-11 */
&gpio_simple 2 0 /* IRDA_1 24c x7-6 set GPS_PORT_CONFIG[IRDA] = 0 */
&gpio_simple 3 0 /* IRDA_0 x8-5 set GPS_PORT_CONFIG[IRDA] = 0 */
&gpt2 0 0 /* timer2 12d x4-4 */
&gpt3 0 0 /* timer3 13d x6-4 */
&gpt4 0 0 /* timer4 61c x2-16 */
&gpt5 0 0 /* timer5 44c x7-11 */
&gpt6 0 0 /* timer6 60c x8-15 */
&gpt7 0 0 /* timer7 36a x17-9 */
>;
};
};
lpb@ff000000 {
compatible = "fsl,lpb";
#address-cells = <2>;
#size-cells = <1>;
ranges = <0 0 0xff000000 0x01000000>;
flash@0 {
compatible = "cfi-flash";
reg = <0 0 0x01000000>;
bank-width = <2>;
device-width = <2>;
#size-cells = <1>;
#address-cells = <1>;
partition@0 {
label = "ubootl";
reg = <0x00000000 0x00040000>;
};
partition@40000 {
label = "kernel";
reg = <0x00040000 0x001c0000>;
};
partition@200000 {
label = "jffs2";
reg = <0x00200000 0x00D00000>;
};
partition@f00000 {
label = "uboot";
reg = <0x00f00000 0x00040000>;
};
partition@f40000 {
label = "oftree";
reg = <0x00f40000 0x00040000>;
};
partition@f80000 {
label = "space";
reg = <0x00f80000 0x00080000>;
};
};
};
/*
pci@f0000d00 {
#interrupt-cells = <1>;
#size-cells = <2>;
#address-cells = <3>;
device_type = "pci";
compatible = "fsl,mpc5200b-pci","fsl,mpc5200-pci";
reg = <0xf0000d00 0x100>;
interrupt-map-mask = <0xf800 0x0 0x0 0x7>;
interrupt-map = <0xc000 0x0 0x0 0x1 &mpc5200_pic 0x0 0x0 0x3 / 1st slot /
0xc000 0x0 0x0 0x2 &mpc5200_pic 0x1 0x1 0x3
0xc000 0x0 0x0 0x3 &mpc5200_pic 0x1 0x2 0x3
0xc000 0x0 0x0 0x4 &mpc5200_pic 0x1 0x3 0x3
0xc800 0x0 0x0 0x1 &mpc5200_pic 0x1 0x1 0x3 / 2nd slot /
0xc800 0x0 0x0 0x2 &mpc5200_pic 0x1 0x2 0x3
0xc800 0x0 0x0 0x3 &mpc5200_pic 0x1 0x3 0x3
0xc800 0x0 0x0 0x4 &mpc5200_pic 0x0 0x0 0x3>;
clock-frequency = <0>; // From boot loader
interrupts = <0x2 0x8 0x0 0x2 0x9 0x0 0x2 0xa 0x0>;
interrupt-parent = <&mpc5200_pic>;
bus-range = <0 0>;
ranges = <0x42000000 0x0 0x80000000 0x80000000 0x0 0x20000000
0x02000000 0x0 0xa0000000 0xa0000000 0x0 0x10000000
0x01000000 0x0 0x00000000 0xb0000000 0x0 0x01000000>;
};
*/
};
[-- Attachment #5: Type: text/plain, Size: 160 bytes --]
_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
^ permalink raw reply [flat|nested] 13+ messages in thread