All of lore.kernel.org
 help / color / mirror / Atom feed
* snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
@ 2008-08-31 14:18 Jean Delvare
  2008-08-31 14:47 ` Jon Smirl
                   ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Jean Delvare @ 2008-08-31 14:18 UTC (permalink / raw)
  To: Timur Tabi; +Cc: alsa-devel

Hi Tibur,

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?

Thanks.

* * * * *

Convert the cs4270 codec driver to the new (standard) device driver
binding model. After this change, CS4720 devices are no longer
discovered automatically and must instead be instantiated explicitly.

Signed-off-by: Jean Delvare <khali@linux-fr.org>
Cc: Timur Tabi <timur@freescale.com>
---
 sound/soc/codecs/cs4270.c |   79 +++++++++------------------------------------
 1 file changed, 16 insertions(+), 63 deletions(-)

--- linux-2.6.27-rc5.orig/sound/soc/codecs/cs4270.c	2008-08-31 14:23:34.000000000 +0200
+++ linux-2.6.27-rc5/sound/soc/codecs/cs4270.c	2008-08-31 15:16:43.000000000 +0200
@@ -272,17 +272,6 @@ static int cs4270_set_dai_fmt(struct snd
 }
 
 /*
- * A list of addresses on which this CS4270 could use.  I2C addresses are
- * 7 bits.  For the CS4270, the upper four bits are always 1001, and the
- * lower three bits are determined via the AD2, AD1, and AD0 pins
- * (respectively).
- */
-static const unsigned short normal_i2c[] = {
-	0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, I2C_CLIENT_END
-};
-I2C_CLIENT_INSMOD;
-
-/*
  * Pre-fill the CS4270 register cache.
  *
  * We use the auto-increment feature of the CS4270 to read all registers in
@@ -490,32 +479,18 @@ static int cs4270_mute(struct snd_soc_da
 
 #endif
 
-static int cs4270_i2c_probe(struct i2c_adapter *adap, int addr, int kind);
+static int cs4270_i2c_probe(struct i2c_client *i2c_client,
+			    const struct i2c_device_id *id);
 
-/*
- * Notify the driver that a new I2C bus has been found.
- *
- * This function is called for each I2C bus in the system.  The function
- * then asks the I2C subsystem to probe that bus at the addresses on which
- * our device (the CS4270) could exist.  If a device is found at one of
- * those addresses, then our probe function (cs4270_i2c_probe) is called.
- */
-static int cs4270_i2c_attach(struct i2c_adapter *adapter)
-{
-	return i2c_probe(adapter, &addr_data, cs4270_i2c_probe);
-}
-
-static int cs4270_i2c_detach(struct i2c_client *client)
+static int cs4270_i2c_remove(struct i2c_client *client)
 {
 	struct snd_soc_codec *codec = i2c_get_clientdata(client);
 
-	i2c_detach_client(client);
 	codec->control_data = NULL;
 
 	kfree(codec->reg_cache);
 	codec->reg_cache = NULL;
 
-	kfree(client);
 	return 0;
 }
 
@@ -525,14 +500,19 @@ static const struct snd_kcontrol_new cs4
 		CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1)
 };
 
+static const struct i2c_device_id cs4270_i2c_id[] = {
+	{ "cs4270", 0 },
+	{ }
+};
+
 static struct i2c_driver cs4270_i2c_driver = {
 	.driver = {
 		.name = "CS4270 I2C",
 		.owner = THIS_MODULE,
 	},
-	.id =             I2C_DRIVERID_CS4270,
-	.attach_adapter = cs4270_i2c_attach,
-	.detach_client =  cs4270_i2c_detach,
+	.probe =    cs4270_i2c_probe,
+	.remove =   cs4270_i2c_remove,
+	.id_table = cs4270_i2c_id,
 };
 
 /*
@@ -555,17 +535,14 @@ static struct snd_soc_device *cs4270_soc
 /*
  * Initialize the I2C interface of the CS4270
  *
- * This function is called for whenever the I2C subsystem finds a device
- * at a particular address.
- *
  * Note: snd_soc_new_pcms() must be called before this function can be called,
  * because of snd_ctl_add().
  */
-static int cs4270_i2c_probe(struct i2c_adapter *adapter, int addr, int kind)
+static int cs4270_i2c_probe(struct i2c_client *i2c_client,
+			    const struct i2c_device_id *id)
 {
 	struct snd_soc_device *socdev = cs4270_socdev;
 	struct snd_soc_codec *codec = socdev->codec;
-	struct i2c_client *i2c_client = NULL;
 	int i;
 	int ret = 0;
 
@@ -578,12 +555,6 @@ static int cs4270_i2c_probe(struct i2c_a
 
 	/* Note: codec_dai->codec is NULL here */
 
-	i2c_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
-	if (!i2c_client) {
-		printk(KERN_ERR "cs4270: could not allocate I2C client\n");
-		return -ENOMEM;
-	}
-
 	codec->reg_cache = kzalloc(CS4270_NUMREGS, GFP_KERNEL);
 	if (!codec->reg_cache) {
 		printk(KERN_ERR "cs4270: could not allocate register cache\n");
@@ -592,11 +563,6 @@ static int cs4270_i2c_probe(struct i2c_a
 	}
 
 	i2c_set_clientdata(i2c_client, codec);
-	strcpy(i2c_client->name, "CS4270");
-
-	i2c_client->driver = &cs4270_i2c_driver;
-	i2c_client->adapter = adapter;
-	i2c_client->addr = addr;
 
 	/* Verify that we have a CS4270 */
 
@@ -612,18 +578,10 @@ static int cs4270_i2c_probe(struct i2c_a
 		goto error;
 	}
 
-	printk(KERN_INFO "cs4270: found device at I2C address %X\n", addr);
+	printk(KERN_INFO "cs4270: found device at I2C address %X\n",
+	       i2c_client->addr);
 	printk(KERN_INFO "cs4270: hardware revision %X\n", ret & 0xF);
 
-	/* Tell the I2C layer a new client has arrived */
-
-	ret = i2c_attach_client(i2c_client);
-	if (ret) {
-		printk(KERN_ERR "cs4270: could not attach codec, "
-			"I2C address %x, error code %i\n", addr, ret);
-		goto error;
-	}
-
 	codec->control_data = i2c_client;
 	codec->read = cs4270_read_reg_cache;
 	codec->write = cs4270_i2c_write;
@@ -651,17 +609,12 @@ static int cs4270_i2c_probe(struct i2c_a
 	return 0;
 
 error:
-	if (codec->control_data) {
-		i2c_detach_client(i2c_client);
-		codec->control_data = NULL;
-	}
+	codec->control_data = NULL;
 
 	kfree(codec->reg_cache);
 	codec->reg_cache = NULL;
 	codec->reg_cache_size = 0;
 
-	kfree(i2c_client);
-
 	return ret;
 }
 


-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 13+ messages in thread

* 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 = &reg},
		{.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(&regs->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(&regs->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(&regs->command, MPC52xx_PSC_RST_RX);
			out_8(&regs->command, MPC52xx_PSC_RST_ERR_STAT);
		} else {
			out_8(&regs->command, MPC52xx_PSC_RST_TX);
			out_8(&regs->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(&regs->ipcr_acr.ipcr) & 0x80) != 0)
			;
		/* then wait for the transition to high */
		while ((in_8(&regs->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(&regs->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(&regs->command, 2 << 4);	/* reset rx */
				out_8(&regs->command, 3 << 4);	/* reset tx */
				out_8(&regs->command, 4 << 4);	/* reset err */
			}
		} else {
			out_8(&regs->command, 3 << 4);	/* reset tx */
			out_8(&regs->command, 4 << 4);	/* reset err */
			if (!psc_i2s->capture.active)
				out_8(&regs->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(&regs->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

* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
       [not found] ` <ed82fe3e0808310928v18eed8bdh98faa6796a516142@mail.gmail.com>
@ 2008-08-31 17:09   ` Jean Delvare
  2008-08-31 17:17     ` Mark Brown
                       ` (2 more replies)
  0 siblings, 3 replies; 13+ messages in thread
From: Jean Delvare @ 2008-08-31 17:09 UTC (permalink / raw)
  To: Timur Tabi; +Cc: alsa-devel

Hi Timur,

On Sun, 31 Aug 2008 11:28:08 -0500, Timur Tabi wrote:
> On Sun, Aug 31, 2008 at 9:18 AM, Jean Delvare <khali@linux-fr.org> wrote:
> > Hi Tibur,
> 
> It's "Timur"

Oops, apparently I mixed your first name and last name. Sorry about
that.

> > I am in the process of converting your cs4270 codec driver from the
> > legacy i2c model to the new (standard) one.
> 
> It has already been converted.
> 
> http://git.kernel.org/?p=linux/kernel/git/tiwai/sound-2.6.git;a=commit;h=0daa075ea4905823ee7c2375d26bb134e5e74f74

Ah, excellent. One less thing for me to do :) Thanks!

Two comments about your patch:

* Your driver now lacks a remove method. Unless I miss something, if
the snd-soc-cs4270 driver is unloaded, you will leave dangling
resources behind (codec->reg_cache in particular.)

* I2C_DRIVERID_CS4270 must be removed from i2c-id.h.

> I don't know why this commit hasn't been pushed upstream, though.

As I understand it, without that patch the mpc8610_hpcd doesn't work,
as the I2C address of the sound codec will be made busy by the platform
code and thus the snd-soc-cs4270 driver won't be able to attach to it.
If I am correct then I suggest that you ask Takashi to push the patch
to Linus now to fix that.

> > 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.
> 
> That's correct.  I've never seen any interest in the CS4270 outside of
> Freescale.
> 
> > 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 think you need to use the right git repository for your development. :-)

Very good point indeed. I'll make sure to check Takashi's sound-2.6
tree before attempting to convert any other SoC codec driver. Do you
know off the top of your head if other drivers have already been
converted?

Thanks,
-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
  2008-08-31 17:09   ` Jean Delvare
@ 2008-08-31 17:17     ` Mark Brown
  2008-09-02 15:03     ` Timur Tabi
  2008-09-03 19:47     ` Timur Tabi
  2 siblings, 0 replies; 13+ messages in thread
From: Mark Brown @ 2008-08-31 17:17 UTC (permalink / raw)
  To: Jean Delvare; +Cc: alsa-devel, Timur Tabi

On Sun, Aug 31, 2008 at 07:09:28PM +0200, Jean Delvare wrote:
> On Sun, 31 Aug 2008 11:28:08 -0500, Timur Tabi wrote:

> > I don't know why this commit hasn't been pushed upstream, though.

> As I understand it, without that patch the mpc8610_hpcd doesn't work,
> as the I2C address of the sound codec will be made busy by the platform
> code and thus the snd-soc-cs4270 driver won't be able to attach to it.
> If I am correct then I suggest that you ask Takashi to push the patch
> to Linus now to fix that.

Yeah, AFAICR it wasn't made clear when this was posted that it was a bug
fix.

> > That's correct.  I've never seen any interest in the CS4270 outside of
> > Freescale.

That's not entirely suprising - the vast majority of embedded devices
never go anywhere near mainline so it tends to only be reference boards
and devices with some sort of public community that have activity that's
visible upstream.

> Very good point indeed. I'll make sure to check Takashi's sound-2.6
> tree before attempting to convert any other SoC codec driver. Do you
> know off the top of your head if other drivers have already been
> converted?

None - the only other one is Takashi's current tree is WM8903.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* 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: Takashi Iwai @ 2008-09-01  6:01 UTC (permalink / raw)
  To: Jean Delvare; +Cc: alsa-devel, Timur Tabi

At Sun, 31 Aug 2008 16:18:43 +0200,
Jean Delvare wrote:
> 
> Hi Tibur,
> 
> 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.

This was already converted by Timur.  Please check the latest sound
git tree.
  git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-2.6.git

The asoc specific patches are found in topic/asoc branch, which are
eventually merged to master and for-next branches.


thanks,

Takashi

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
  2008-08-31 17:09   ` Jean Delvare
  2008-08-31 17:17     ` Mark Brown
@ 2008-09-02 15:03     ` Timur Tabi
  2008-09-03 19:47     ` Timur Tabi
  2 siblings, 0 replies; 13+ messages in thread
From: Timur Tabi @ 2008-09-02 15:03 UTC (permalink / raw)
  To: Jean Delvare; +Cc: alsa-devel

Jean Delvare wrote:

> * Your driver now lacks a remove method. Unless I miss something, if
> the snd-soc-cs4270 driver is unloaded, you will leave dangling
> resources behind (codec->reg_cache in particular.)

Indeed, but then, I don't think I ever supported loading and unload this driver
as a module.  The Kconfig does say it's a tristate, though.  I'll take a look at
it.

Most of my real development is going to the ASoC V2 version of this driver, and
that version should be a lot better.  I'll take another look at both driver to
make sure I didn't screw this up.

> * I2C_DRIVERID_CS4270 must be removed from i2c-id.h.

It can't be removed until this patch goes upstream, but thanks for the reminder.

> As I understand it, without that patch the mpc8610_hpcd doesn't work,
> as the I2C address of the sound codec will be made busy by the platform
> code and thus the snd-soc-cs4270 driver won't be able to attach to it.

That's correct.  I though I made that clear in the changelog.

> Do you
> know off the top of your head if other drivers have already been
> converted?

I doubt it.  ASoC V1 doesn't generally support PowerPC, although there is code
to make it work.  That's why there's not a lot of support across the board for
PowerPC-isms.

-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
  2008-08-31 17:09   ` Jean Delvare
  2008-08-31 17:17     ` Mark Brown
  2008-09-02 15:03     ` Timur Tabi
@ 2008-09-03 19:47     ` Timur Tabi
  2008-09-03 20:30       ` Timur Tabi
  2 siblings, 1 reply; 13+ messages in thread
From: Timur Tabi @ 2008-09-03 19:47 UTC (permalink / raw)
  To: Jean Delvare; +Cc: alsa-devel

On Sun, Aug 31, 2008 at 12:09 PM, Jean Delvare <khali@linux-fr.org> wrote:

> * Your driver now lacks a remove method. Unless I miss something, if
> the snd-soc-cs4270 driver is unloaded, you will leave dangling
> resources behind (codec->reg_cache in particular.)

I'm working on a fix for this, but there's something you need to know.
 I don't think ASoC V1 modules can be unloaded.  The drivers have
hard-coded cross-dependencies.  The codec driver doesn't have this
problem, but in general it's not really possible to select ASoC
drivers as modules in Kconfig anyway.  The "tristate" I mentioned is
misleading.

-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
  2008-09-03 19:47     ` Timur Tabi
@ 2008-09-03 20:30       ` Timur Tabi
  2008-09-03 20:44         ` Jean Delvare
  2008-09-04 10:42         ` Mark Brown
  0 siblings, 2 replies; 13+ messages in thread
From: Timur Tabi @ 2008-09-03 20:30 UTC (permalink / raw)
  To: Jean Delvare; +Cc: alsa-devel

On Wed, Sep 3, 2008 at 2:47 PM, Timur Tabi <timur@freescale.com> wrote:
> I'm working on a fix for this, but there's something you need to know.
>  I don't think ASoC V1 modules can be unloaded.

Correction: ASoC drivers can't be compiled as modules, not even the
codec drivers.  I had to hack up the Kconfigs to get this to work, but
there is no way to compile the drivers as modules.  If you try, you
get this:

  CC [M]  sound/soc/codecs/cs4270.o
  LD [M]  sound/soc/codecs/snd-soc-cs4270.o
...
sound/built-in.o: In function `mpc8610_hpcd_probe':
/temp/alsa.1994/sound/soc/fsl/mpc8610_hpcd.c:455: undefined reference
to `cs4270_dai'
/temp/alsa.1994/sound/soc/fsl/mpc8610_hpcd.c:455: undefined reference
to `cs4270_dai'
/temp/alsa.1994/sound/soc/fsl/mpc8610_hpcd.c:469: undefined reference
to `soc_codec_device_cs4270'
/temp/alsa.1994/sound/soc/fsl/mpc8610_hpcd.c:469: undefined reference
to `soc_codec_device_cs4270'
make: *** [.tmp_vmlinux1] Error 1

So I don't see how an I2C 'remove' function would ever be called
anyway.  If you can show me how, I'll add it.

Now, the ASoC V2 driver can be loaded and unloaded, and I'm pretty
sure I have bugs there.

-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
  2008-09-03 20:30       ` Timur Tabi
@ 2008-09-03 20:44         ` Jean Delvare
  2008-09-03 21:21           ` Timur Tabi
  2008-09-04 10:42         ` Mark Brown
  1 sibling, 1 reply; 13+ messages in thread
From: Jean Delvare @ 2008-09-03 20:44 UTC (permalink / raw)
  To: Timur Tabi; +Cc: alsa-devel

On Wed, 3 Sep 2008 15:30:33 -0500, Timur Tabi wrote:
> On Wed, Sep 3, 2008 at 2:47 PM, Timur Tabi <timur@freescale.com> wrote:
> > I'm working on a fix for this, but there's something you need to know.
> >  I don't think ASoC V1 modules can be unloaded.
> 
> Correction: ASoC drivers can't be compiled as modules, not even the
> codec drivers.  I had to hack up the Kconfigs to get this to work, but
> there is no way to compile the drivers as modules.  If you try, you
> get this:
> 
>   CC [M]  sound/soc/codecs/cs4270.o
>   LD [M]  sound/soc/codecs/snd-soc-cs4270.o
> ...
> sound/built-in.o: In function `mpc8610_hpcd_probe':
> /temp/alsa.1994/sound/soc/fsl/mpc8610_hpcd.c:455: undefined reference
> to `cs4270_dai'
> /temp/alsa.1994/sound/soc/fsl/mpc8610_hpcd.c:455: undefined reference
> to `cs4270_dai'
> /temp/alsa.1994/sound/soc/fsl/mpc8610_hpcd.c:469: undefined reference
> to `soc_codec_device_cs4270'
> /temp/alsa.1994/sound/soc/fsl/mpc8610_hpcd.c:469: undefined reference
> to `soc_codec_device_cs4270'
> make: *** [.tmp_vmlinux1] Error 1
> 
> So I don't see how an I2C 'remove' function would ever be called
> anyway.  If you can show me how, I'll add it.

By writing to the unbind file that the i2c driver in question exposes
in /sys? I didn't try it but it should cause the driver to unbind from
the device in question and thus ->remove would be called.

Note that I do not care much myself. I mentioned the lack of a remove
function merely because the driver could be build as a module and
because the original code did have an equivalent function. If you are
confident that the code isn't needed or you decide that you don't care,
up to you. I am really not familiar with the ASoC drivers so I don't
think my view really matters.

> Now, the ASoC V2 driver can be loaded and unloaded, and I'm pretty
> sure I have bugs there.


-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
  2008-09-03 20:44         ` Jean Delvare
@ 2008-09-03 21:21           ` Timur Tabi
  2008-09-03 21:27             ` Timur Tabi
  0 siblings, 1 reply; 13+ messages in thread
From: Timur Tabi @ 2008-09-03 21:21 UTC (permalink / raw)
  To: Jean Delvare; +Cc: alsa-devel

Jean Delvare wrote:

> By writing to the unbind file that the i2c driver in question exposes
> in /sys? I didn't try it but it should cause the driver to unbind from
> the device in question and thus ->remove would be called.

Can you give me specifics on how to use the unbind file?  I tried a few things,
but none of them worked, and google isn't helpful either.

-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
  2008-09-03 21:21           ` Timur Tabi
@ 2008-09-03 21:27             ` Timur Tabi
  0 siblings, 0 replies; 13+ messages in thread
From: Timur Tabi @ 2008-09-03 21:27 UTC (permalink / raw)
  To: Jean Delvare; +Cc: alsa-devel

On Wed, Sep 3, 2008 at 4:21 PM, Timur Tabi <timur@freescale.com> wrote:
> Can you give me specifics on how to use the unbind file?  I tried a few things,
> but none of them worked, and google isn't helpful either.

Never mind, I figured it out.  Sure enough, it works.  It probably
breaks ASoC, though.  I'm going to add support for it anyway, though.

-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
  2008-09-03 20:30       ` Timur Tabi
  2008-09-03 20:44         ` Jean Delvare
@ 2008-09-04 10:42         ` Mark Brown
  2008-09-04 14:44           ` Timur Tabi
  1 sibling, 1 reply; 13+ messages in thread
From: Mark Brown @ 2008-09-04 10:42 UTC (permalink / raw)
  To: Timur Tabi; +Cc: Jean Delvare, alsa-devel

On Wed, Sep 03, 2008 at 03:30:33PM -0500, Timur Tabi wrote:
> On Wed, Sep 3, 2008 at 2:47 PM, Timur Tabi <timur@freescale.com> wrote:

> > I'm working on a fix for this, but there's something you need to know.
> >  I don't think ASoC V1 modules can be unloaded.

> Correction: ASoC drivers can't be compiled as modules, not even the
> codec drivers.  I had to hack up the Kconfigs to get this to work, but

They can be - I do this on a regular basis during development.  If it
weren't possible I'd probably get annoyed enough to make it so :)

> there is no way to compile the drivers as modules.  If you try, you
> get this:

>   CC [M]  sound/soc/codecs/cs4270.o
>   LD [M]  sound/soc/codecs/snd-soc-cs4270.o
> ...
> sound/built-in.o: In function `mpc8610_hpcd_probe':
> /temp/alsa.1994/sound/soc/fsl/mpc8610_hpcd.c:455: undefined reference
> to `cs4270_dai'

If you build the codec driver as a module then you also need to build
the machine driver as a module.  Your Kconfig ought to be enforcing
that which I guess is the hack you had to put in?

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: snd-soc-cs4270: Convert to a new-style i2c driver (work in progress)
  2008-09-04 10:42         ` Mark Brown
@ 2008-09-04 14:44           ` Timur Tabi
  0 siblings, 0 replies; 13+ messages in thread
From: Timur Tabi @ 2008-09-04 14:44 UTC (permalink / raw)
  To: Timur Tabi, Jean Delvare, alsa-devel

Mark Brown wrote:

> If you build the codec driver as a module then you also need to build
> the machine driver as a module.  Your Kconfig ought to be enforcing
> that which I guess is the hack you had to put in?

I guess that's my problem, I was just loading the codec driver as a module.

-- 
Timur Tabi
Linux kernel developer at Freescale

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2008-09-04 14:44 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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-08-31 17:09   ` Jean Delvare
2008-08-31 17:17     ` Mark Brown
2008-09-02 15:03     ` Timur Tabi
2008-09-03 19:47     ` Timur Tabi
2008-09-03 20:30       ` Timur Tabi
2008-09-03 20:44         ` Jean Delvare
2008-09-03 21:21           ` Timur Tabi
2008-09-03 21:27             ` Timur Tabi
2008-09-04 10:42         ` Mark Brown
2008-09-04 14:44           ` Timur Tabi
2008-09-01  6:01 ` Takashi Iwai

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.