--- linux-2.6.11-rc5/sound/arm/tlv320aic23.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.11-rc5-audio9/sound/arm/tlv320aic23.c	2005-03-15 12:11:16.000000000 +0000
@@ -0,0 +1,1001 @@
+/* sound/arm/tlv320aic23.c
+ *
+ * (c) 2004-2005 Simtec Electronics
+ *	http://armlinux.simtec.co.uk/
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ * TLV320AIC23 Audio codec driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/config.h>
+#include <sound/driver.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/control.h>
+#include <sound/tlv320aic23.h>
+
+#include "tlv320aic23.h"
+#include "tlv320aic23-hw.h"
+
+#if 1
+#define DBG(x...) do { printk(KERN_DEBUG x); } while(0)
+#else
+#define DBG(x...)
+#endif
+
+
+#define TLV_MAXRATES		(15)
+
+struct tlv320aic23 {
+	snd_card_t			*card;
+	struct tlv320aic23_cfg		*cfg;
+	struct tlv320aic23_rates	*rates;
+	struct tlv320aic23_hw		*hw;
+	struct semaphore		 lock;
+
+	/* tlv320aic23 state */
+
+	unsigned char			active;
+	unsigned short			regs[0xf];
+
+	unsigned int			rate_list_data[TLV_MAXRATES];
+	snd_pcm_hw_constraint_list_t	rate_list;
+};
+
+/* sample rates */
+
+struct tlv320aic23_rate {
+	unsigned int	adc;
+	unsigned int	dac;
+	unsigned int	sr;
+};
+
+struct tlv320aic23_rates {
+	unsigned int		 base;
+	unsigned int		 nr_rates;
+	struct tlv320aic23_rate *rates;
+};
+
+/* sample rate tables */
+
+static struct tlv320aic23_rate rate_usb[] = {
+	{
+		.adc	= 96000,
+		.dac	= 96000,
+		.sr	= (7 << 2),
+	}, {
+		.adc	= 88200,
+		.dac	= 88200,
+		.sr	= (15 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	}, {
+		.adc	= 48000,
+		.dac	= 48000,
+		.sr	= (0 << 2),
+	}, {
+		.adc	= 44100,
+		.dac	= 44100,
+		.sr	= (8 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	}, {
+		.adc	= 32000,
+		.dac	= 32000,
+		.sr	= (6 << 2), 
+	}, {
+		.adc	= 8021,
+		.dac	= 8021,
+		.sr	= (11 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	}, {
+		.adc	= 8000,
+		.dac	= 8000,
+		.sr	= (3 << 2),
+	}, {
+		.adc	= 48000,
+		.dac	= 8000,
+		.sr	= (1 << 2),
+	}, {
+		.adc	= 44100,
+		.dac	= 8021,
+		.sr	= (9 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	}, {
+		.adc	= 8000,
+		.dac	= 48000,
+		.sr	= (2 << 2),
+	}, {
+		.adc	= 8021,
+		.dac	= 44100,
+		.sr	= (10 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	},
+};
+
+static struct tlv320aic23_rate rate_11289600[] = {
+	{
+		.adc	= 88200,
+		.dac	= 88200,
+		.sr	= (15 << 2),
+	}, {
+		.adc	= 44100,
+		.dac	= 44100,
+		.sr	= (8 << 2),
+	}, {
+		.adc	= 8021,
+		.dac	= 8021,
+		.sr	= (0xb << 2),
+	}, {
+		.adc	= 44100,
+		.dac	= 8021,
+		.sr	= (9 << 2),
+	}, {
+		.adc	= 8021,
+		.dac	= 44100,
+		.sr	= (10 << 2),
+	},
+};
+
+static struct tlv320aic23_rate rate_18432000[] = {
+	{
+		.adc	= 96000,
+		.dac	= 96000,
+		.sr	= (7 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	}, {
+		.adc	= 48000,
+		.dac	= 48000,
+		.sr	= (0 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	}, {
+		.adc	= 32000,
+		.dac	= 32000,
+		.sr	= (6 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	}, {
+		.adc	= 8000,
+		.dac	= 8000,
+		.sr	= (3 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	}, {
+		.adc	= 48000,
+		.dac	= 8000,
+		.sr	= (1 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	}, {
+		.adc	= 8000,
+		.dac	= 48000,
+		.sr	= (6 << 2) | TLV320AIC23_SAMPLERATE_BOSR,
+	},
+};
+
+static struct tlv320aic23_rate rate_12288000[] = {
+	{
+		.adc	= 96000,
+		.dac	= 96000,
+		.sr	= (7 << 2),
+	}, {
+		.adc    = 48000,
+		.dac    = 48000,
+		.sr	= 0,
+	}, {
+		.adc	= 32000,
+		.dac	= 32000,
+		.sr	= (6 << 2),
+	}, {
+		.adc	= 8000,
+		.dac	= 8000,
+		.sr	= (3 << 2),
+	}, {
+		.adc	= 48000,
+		.dac	= 8000,
+		.sr	= (1 << 2),
+	}, {
+		.adc	= 8000,
+		.dac	= 48000,
+		.sr	= (2 << 2),
+	},
+};
+
+
+static struct tlv320aic23_rates rates[] = {
+	{
+		.base		= 12000000,
+		.rates		= rate_usb,
+		.nr_rates	= ARRAY_SIZE(rate_usb),
+	}, {
+		.base		= 12288000,
+		.rates		= rate_12288000,
+		.nr_rates	= ARRAY_SIZE(rate_12288000),
+	}, {
+		.base		= 11289600,
+		.rates		= rate_11289600,
+		.nr_rates	= ARRAY_SIZE(rate_11289600),
+	}, {
+		.base		= 18432000,
+		.rates		= rate_18432000,
+		.nr_rates	= ARRAY_SIZE(rate_18432000),
+	},
+	{ }
+};
+
+static struct tlv320aic23_rates *tlv320aic23_get_rates(struct tlv320aic23 *tlv)
+{
+	struct tlv320aic23_rates *ptr = rates;
+
+	if (tlv->cfg == NULL)
+		return NULL;
+
+	DBG("getting rate for %ld\n", tlv->cfg->clkrate);
+
+	while (ptr->base != 0 && ptr->base != tlv->cfg->clkrate)
+		ptr++;
+
+	return ptr;
+}
+
+/* sound controls */
+
+struct tlv320aic23_ctl {
+	snd_kcontrol_new_t	*ctl;
+	const char		*name;
+
+	int			def;
+	int			reg;
+	int			mask;
+	int			shift;
+	int			min;
+	int			max;
+};
+
+/* tlv register accesses code */
+
+static inline int tlv320aic23_wr(struct tlv320aic23 *tlv, int reg, int val)
+{
+	if (reg != TLV320AIC23_RESET)
+		tlv->regs[reg] = val;
+
+	return (tlv->hw->wr)(tlv->hw, reg, val);
+}
+
+static inline int tlv320aic23_wr_changed(struct tlv320aic23 *tlv, int reg, int val)
+{
+	if (reg == TLV320AIC23_RESET)
+		return tlv320aic23_wr(tlv, reg, val);
+
+	if (tlv->regs[reg] != val) {
+		DBG("%s: reg %02x changed %02x -> %02x\n", __FUNCTION__,
+		    reg, tlv->regs[reg], val);
+
+		return tlv320aic23_wr(tlv, reg, val);
+	}
+
+	return 0;
+}
+
+/* sound control code */
+
+#define kctl_to_tlvctl(kc) ((struct tlv320aic23_ctl *) (kc)->private_value)
+
+static int tlv320aic23_ctl_info(snd_kcontrol_t *kc, snd_ctl_elem_info_t *ei)
+{
+	struct tlv320aic23_ctl *ctl = kctl_to_tlvctl(kc);
+
+	ei->count = 1;
+	ei->type  = (ctl->min == ctl->max) ? SNDRV_CTL_ELEM_TYPE_INTEGER :
+		SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+
+	if (ctl->min == ctl->max) {
+		ei->value.integer.min = 0;
+		ei->value.integer.max = 1;
+	} else {
+		ei->value.integer.min = ctl->min;
+		ei->value.integer.max = ctl->max;
+	}
+
+	return 0;
+}
+
+static int tlv320aic23_ctl_get(snd_kcontrol_t *kc, snd_ctl_elem_value_t *ev)
+{
+	struct tlv320aic23 *tlv = kc->private_data;
+	struct tlv320aic23_ctl *ctl = kctl_to_tlvctl(kc);
+	unsigned int val;
+
+	down(&tlv->lock);
+	val = tlv->regs[ctl->reg];
+	val  &= ctl->mask;
+	val >>= ctl->shift;
+	up(&tlv->lock);
+
+	ev->value.integer.value[0] = val;
+
+	DBG("%s: got %d from 0x%x (0x%0x)\n", __FUNCTION__, val, ctl->reg,
+	    tlv->regs[ctl->reg]);
+
+	return 0;
+}
+
+static int tlv320aic23_ctl_put(snd_kcontrol_t *kc, snd_ctl_elem_value_t *ev)
+{
+	struct tlv320aic23 *tlv = kc->private_data;
+	struct tlv320aic23_ctl *ctl = kctl_to_tlvctl(kc);
+	unsigned int val = ev->value.integer.value[0];
+
+	DBG("%s: put %p, %d\n", __FUNCTION__, kc, val);
+
+	down(&tlv->lock);
+
+	tlv->regs[ctl->reg] &= ~ctl->mask;
+	tlv->regs[ctl->reg] |= val << ctl->shift;
+	
+	if (tlv->active || 1) {
+		tlv320aic23_wr_changed(tlv, ctl->reg, tlv->regs[ctl->reg]);
+
+		/* update both L/R in the case of the volume controls */
+
+		if (ctl->reg == TLV320AIC23_LHPVOL)
+			tlv320aic23_wr_changed(tlv, TLV320AIC23_RHPVOL,
+					       tlv->regs[ctl->reg]);
+		
+		if (ctl->reg == TLV320AIC23_LINPVOL)
+			tlv320aic23_wr_changed(tlv, TLV320AIC23_RINPVOL,
+					       tlv->regs[ctl->reg]);
+	}
+
+	up(&tlv->lock);
+	return 0;
+}
+
+static snd_kcontrol_new_t tlv320aic23_ctl = {
+	.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access	= SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info	= tlv320aic23_ctl_info,
+	.get	= tlv320aic23_ctl_get,
+	.put	= tlv320aic23_ctl_put,
+};
+
+/* microphone input control */
+
+static int tlv320aic23_ctl_micvol_get(snd_kcontrol_t *kc, snd_ctl_elem_value_t *ev)
+{
+	struct tlv320aic23 *tlv = kc->private_data;
+	struct tlv320aic23_ctl *ctl = kctl_to_tlvctl(kc);
+	unsigned int apath;
+	unsigned int val;
+
+	down(&tlv->lock);
+	apath = tlv->regs[TLV320AIC23_ANALOGPATH];
+	up(&tlv->lock);
+
+	if (apath & TLV320AIC23_APATH_MICMUTE)
+		val = 0;
+	else if (apath & TLV320AIC23_APATH_MICBOOST)
+		val = 2;
+	else
+		val = 1;
+
+	ev->value.integer.value[0] = val;
+
+	DBG("%s: got %d from 0x%x (0x%0x)\n", __FUNCTION__, val, ctl->reg,
+	    tlv->regs[ctl->reg]);
+
+	return 0;
+}
+
+static int tlv320aic23_ctl_micvol_put(snd_kcontrol_t *kc, snd_ctl_elem_value_t *ev)
+{
+	struct tlv320aic23 *tlv = kc->private_data;
+	unsigned int val = ev->value.integer.value[0];
+	unsigned int apath;
+	int ret = 0;
+
+	DBG("%s: put %p, %d\n", __FUNCTION__, kc, val);
+
+	down(&tlv->lock);
+
+	apath = tlv->regs[TLV320AIC23_ANALOGPATH];
+
+	apath &= ~(TLV320AIC23_APATH_MICMUTE|TLV320AIC23_APATH_MICBOOST);
+
+	switch (val) {
+	case 0:
+		apath |= TLV320AIC23_APATH_MICMUTE;
+		break;
+
+	case 1:
+		/* do nothing, mute will be off, and no boost */
+		break;
+	     
+	case 2:
+		apath |= TLV320AIC23_APATH_MICBOOST;
+		break;
+	}
+
+	if (tlv->active || 1)
+		ret = tlv320aic23_wr(tlv,TLV320AIC23_ANALOGPATH, apath);
+
+	up(&tlv->lock);
+	return ret;
+}
+
+static snd_kcontrol_new_t tlv320aic23_ctl_micvol = {
+	.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access	= SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.info	= tlv320aic23_ctl_info,
+	.get	= tlv320aic23_ctl_micvol_get,
+	.put	= tlv320aic23_ctl_micvol_put,
+};
+
+/* list of mixer controls */
+
+struct tlv320aic23_ctl ctrls[] = {
+	{
+		.ctl		= &tlv320aic23_ctl,
+		.name		= "Headphone Volume",
+		.reg		= TLV320AIC23_LHPVOL,
+		.def		= TLV320AIC23_HPVOL_0dB,
+		.shift		= 0,
+		.mask		= (1<<7)-1,
+		.min		= 0,
+		.max		= 127,
+	}, {
+		.ctl		= &tlv320aic23_ctl,
+		.name		= "Line Capture Volume",
+		.reg		= TLV320AIC23_LINPVOL,
+		.def		= TLV320AIC23_INP_0dB,
+		.mask		= (1<<7)-1,
+		.min		= 0,
+		.max		= 127,
+	},  {
+		.ctl		= &tlv320aic23_ctl_micvol,
+		.name		= "Mic Volume",
+		.def		= 0,
+		.min		= 0,
+		.max		= 2,
+	}, {
+		.ctl		= &tlv320aic23_ctl,
+		.name		= "Mic Capture Switch",
+		.reg		= TLV320AIC23_ANALOGPATH,
+		.def		= 0,
+		.shift		= 2,
+		.mask		= TLV320AIC23_APATH_MICADC,
+	}, {
+		.ctl		= &tlv320aic23_ctl,
+		.name		= "Bypass",
+		.reg		= TLV320AIC23_ANALOGPATH,
+		.def		= 0,
+		.shift		= 3,
+		.mask		= TLV320AIC23_APATH_BYPASSEN,
+	}, {
+		.ctl		= &tlv320aic23_ctl,
+		.name		= "Sidetone Enable",
+		.reg		= TLV320AIC23_ANALOGPATH,
+		.def		= 0,
+		.shift		= 5,
+		.mask		= TLV320AIC23_APATH_SIDETONE_EN,
+	}, {
+		.ctl		= &tlv320aic23_ctl,
+		.name		= "Sidetone Attenuation",
+		.reg		= TLV320AIC23_ANALOGPATH,
+		.def		= 0,
+		.shift		= 6,
+		.mask		= TLV320AIC23_APATH_SIDETONE15dB,
+		.min		= 0,
+		.max		= 3,
+	}, {
+		.ctl		= &tlv320aic23_ctl,
+		.name		= "DAC Enable",
+		.reg		= TLV320AIC23_ANALOGPATH,
+		.def		= 1,
+		.shift		= 4,
+		.mask		= TLV320AIC23_APATH_DACEN,
+	},
+};
+
+static int
+tlv320aic23_add_ctrl(struct tlv320aic23 *tlv, struct tlv320aic23_ctl *ctl)
+{
+	snd_kcontrol_t *kctl;
+	int ret = -ENOMEM;
+
+	DBG("%s: %p: ctl %p (%s)\n", __FUNCTION__, tlv, ctl, ctl->name);
+
+	kctl = snd_ctl_new1(ctl->ctl, tlv);
+	if (kctl) {
+		strlcpy(kctl->id.name, ctl->name, sizeof(kctl->id.name));
+		kctl->private_value = (unsigned long)ctl;
+		
+		// todo - ensure default value //
+
+		ret = snd_ctl_add(tlv->card, kctl);
+		if (ret < 0)
+			snd_ctl_free_one(kctl);
+	} 
+
+	return ret;
+}
+
+static int tlv320aic23_add_ctrls(struct tlv320aic23 *tlv)
+{
+	int ret;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ctrls); i++) {
+		ret = tlv320aic23_add_ctrl(tlv, &ctrls[i]);
+		if (ret)
+			return ret;
+	}
+	
+	return 0;
+}
+
+/* rate control */
+
+static int tlv320aic23_rates_to_list(struct tlv320aic23_rates *rates,
+				     snd_pcm_hw_constraint_list_t *list,
+				     int dac)
+{
+       struct tlv320aic23_rate *rp;
+       int *ptr = list->list;
+       int clk;
+
+       DBG("%s: rates=%p, list=%p, dac=%d\n", __FUNCTION__, rates, list, dac);
+       DBG("%s: rates->rates =%p, list->list=%p\n", __FUNCTION__, rates->rates, list->list);
+
+       if (rates == NULL)
+	       return -EINVAL;
+
+       if (rates->rates == NULL)
+	       return -EINVAL;
+
+       rp = rates->rates;
+
+       list->count = 0;
+       list->mask = 0;
+
+       for (clk = 0; clk < TLV_MAXRATES; clk++, rp++, ptr++) {
+	       if (clk >= rates->nr_rates)
+		       break;
+	       
+	       *ptr = dac ? rp->dac : rp->adc;
+	       list->count++;
+       }
+
+       /* sort the resutls */
+
+ restart_sort:
+       for (clk = 0; clk < list->count-1; clk++) {
+	       ptr = list->list + clk;
+	       if (ptr[0] > ptr[1]) {
+		       int tmp = ptr[1];
+		       ptr[1] = ptr[0];
+		       ptr[0] = tmp;
+		       goto restart_sort;
+	       }
+       }
+
+       return 0;
+}
+
+static int tlv320aic23_find_sr(struct tlv320aic23 *tlv, int rate)
+{
+	struct tlv320aic23_rate *rp = tlv->rates->rates;
+	int i;
+
+	DBG("%s(%p,%d)\n", __FUNCTION__, tlv, rate);
+
+	for (i = 0; i < tlv->rates->nr_rates; i++, rp++) {
+		if (rp->dac == rate)
+			return rp->sr;
+	}
+	
+	return -1;
+}
+
+
+static int tlv320aic23_reset(struct tlv320aic23 *tlv)
+{
+	int ret;
+
+	ret = tlv320aic23_wr(tlv, TLV320AIC23_RESET, 0x00);
+	if (ret)
+		return ret;
+
+	mdelay(1);
+	return 0;
+}
+
+static int tlv320aic23_configure(struct tlv320aic23 *tlv)
+{
+	struct tlv320aic23_cfg *cfg = tlv->cfg;
+	int ret;
+	int reg;
+
+	/* set wether we are clock master or not */
+
+	tlv->regs[TLV320AIC23_DIGITALFMT] &= ~TLV320AIC23_DIGITALFMT_MASTER;
+
+	if (cfg->master)
+		tlv->regs[TLV320AIC23_DIGITALFMT] |= TLV320AIC23_DIGITALFMT_MASTER;
+
+	/* configure all registers */
+
+	for (reg = 0; reg < TLV320AIC23_RESET; reg++) {
+		ret = tlv320aic23_wr(tlv, reg, tlv->regs[reg]);
+	}
+
+	/* check over the clock */
+	
+	tlv->rates = tlv320aic23_get_rates(tlv);
+	if (tlv->rates == NULL) {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	return 0;
+
+ err:
+	return ret;
+}
+
+static int tlv320aic23_free(snd_device_t *dev)
+{
+	struct tlv320aic23 *tlv = dev->device_data;
+
+	if (tlv->hw && tlv->hw->release)
+		tlv->hw->release(tlv->hw);
+
+	kfree(tlv);
+	return 0;
+}
+
+static snd_device_ops_t tlv_ops = {
+	.dev_free	= tlv320aic23_free,
+};
+
+static unsigned short tlv320aic23_defaultregs[TLV320AIC23_RESET] = {
+	[TLV320AIC23_LINPVOL]		= TLV320AIC23_INP_SIMULTUPD,
+	[TLV320AIC23_RINPVOL]		= TLV320AIC23_INP_SIMULTUPD,
+	[TLV320AIC23_LHPVOL]		= TLV320AIC23_HPVOL_ZEROCROSS,
+	[TLV320AIC23_RHPVOL]		= TLV320AIC23_HPVOL_ZEROCROSS,
+	[TLV320AIC23_DIGITALPATH]	= TLV320AIC23_DPATH_SOFTMUTE,
+	[TLV320AIC23_POWERCTRL]		= TLV320AIC23_PDC_DEFAULT,
+	[TLV320AIC23_DIGITALFMT]	= 0,
+	[TLV320AIC23_SAMPLERATE]	= 0,
+	[TLV320AIC23_DIGITALACT]	= 1,
+};
+
+struct tlv320aic23 *tlv320aic23_attach(snd_card_t *card,
+				       struct device *dev,
+				       struct tlv320aic23_cfg *cfg)
+{
+	struct tlv320aic23 *tlv;
+	int ret = 0;
+	
+	DBG("%s: card=%p, cfg=%p\n", __FUNCTION__, card, cfg);
+
+	tlv = kmalloc(sizeof(*tlv), GFP_KERNEL);
+	if (tlv == NULL)
+		return ERR_PTR(-ENOMEM);
+
+	memset(tlv, 0, sizeof(*tlv));
+	memcpy(&tlv->regs, tlv320aic23_defaultregs, sizeof(tlv->regs));
+	init_MUTEX(&tlv->lock);
+
+	tlv->card	= card;
+	tlv->cfg	= cfg;
+	tlv->hw		= dev->platform_data;
+
+	if (tlv->hw == NULL) {
+		dev_err(dev, "no platform data\n");
+		ret = -ENOENT;
+		goto exit_err;
+	}
+
+	if (tlv->hw->claim) {
+		ret = (tlv->hw->claim)(tlv->hw);
+	
+		if (ret)
+			goto exit_err;
+	}
+
+	/* create a new sound device and attach controls */
+
+	ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, tlv, &tlv_ops);
+	if (ret)
+		goto exit_err;
+
+	/* initialise variables */
+
+	tlv->rate_list.list = tlv->rate_list_data;
+
+	/* configure the device */
+
+	tlv320aic23_reset(tlv);
+	tlv320aic23_configure(tlv);
+
+	/* attach the mixer controls */
+
+	ret = tlv320aic23_add_ctrls(tlv);
+	if (ret)
+		goto exit_err;
+
+	/* ok, return our new client */
+
+	return tlv;
+
+ exit_err:
+	return ERR_PTR(ret);
+}
+
+EXPORT_SYMBOL(tlv320aic23_attach);
+
+void tlv320aic23_detach(struct tlv320aic23 *mixer)
+{
+	/* nothing to do here */
+}
+
+EXPORT_SYMBOL(tlv320aic23_detach);
+
+/* routines to deal with the sound system */
+
+int tlv320aic23_startup(struct tlv320aic23 *tlv)
+{
+	int ret = 0;
+	int tmp;
+
+	/* power up the codec dac section */
+
+	tmp = tlv->regs[TLV320AIC23_POWERCTRL];
+	tmp &= ~TLV320AIC23_PDC_DAC;
+	tmp &= ~TLV320AIC23_PDC_OSC;
+	tmp &= ~TLV320AIC23_PDC_CLOCK;
+
+	ret = tlv320aic23_wr(tlv, TLV320AIC23_POWERCTRL, tmp);
+	if (ret < 0)
+		goto err;
+
+ err:
+	return ret;
+}
+
+EXPORT_SYMBOL(tlv320aic23_startup);
+
+int tlv320aic23_shutdown(struct tlv320aic23 *tlv)
+{
+	int ret;
+	int tmp;
+
+	DBG("%s: tlv=%p\n", __FUNCTION__, tlv);
+
+	/* power down the codec dac section */
+
+	tmp = tlv->regs[TLV320AIC23_POWERCTRL];
+	tmp |= TLV320AIC23_PDC_DAC;
+	tmp |= TLV320AIC23_PDC_OSC;
+	tmp |= TLV320AIC23_PDC_CLOCK;
+
+	ret = tlv320aic23_wr(tlv, TLV320AIC23_POWERCTRL, tmp);
+	if (ret < 0)
+		goto err;
+
+	return 0;
+
+ err:
+	return ret;
+}
+
+EXPORT_SYMBOL(tlv320aic23_shutdown);
+
+int tlv320aic23_open(struct tlv320aic23 *tlv, snd_pcm_substream_t *substream)
+{
+	snd_pcm_runtime_t *runtime = substream->runtime;
+	snd_pcm_hardware_t *hw = &runtime->hw;
+	int tmp;
+
+	DBG("%s: tlv=%p, substream=%p\n", __FUNCTION__, tlv, substream);
+	DBG("%s: runtime=%p, hw=%p\n", __FUNCTION__, runtime, hw);       
+
+	if (tlv == NULL || substream == NULL || hw == NULL)
+		return -EINVAL;
+
+	hw->channels_min = 2;
+	hw->channels_max = 2;
+	hw->rates	 = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT;
+	hw->formats     &= (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE);
+
+	/* constrict the rates */
+
+	tlv320aic23_rates_to_list(tlv->rates, &tlv->rate_list, 0);
+
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				   &tlv->rate_list);
+
+	/* ensure the appropriate hardware is powered up */
+	
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		/* bring power to the ADC and inputs */
+
+		tmp = tlv->regs[TLV320AIC23_POWERCTRL];
+		tmp &= ~TLV320AIC23_PDC_ADC;
+		tmp &= ~TLV320AIC23_PDC_MIC;
+		tmp &= ~TLV320AIC23_PDC_LINE;
+		tlv320aic23_wr_changed(tlv, TLV320AIC23_POWERCTRL, tmp);
+	} else {
+		/* mute the system whilst we power up the dac/output */
+
+		tmp = tlv->regs[TLV320AIC23_DIGITALPATH];
+		tmp |= TLV320AIC23_DPATH_SOFTMUTE;
+		tlv320aic23_wr(tlv, TLV320AIC23_DIGITALPATH, tmp);
+
+		/* bring power to the DAC and Output stages */
+
+		tmp = tlv->regs[TLV320AIC23_POWERCTRL];
+		tmp &= ~TLV320AIC23_PDC_OUTPUT;
+		tmp &= ~TLV320AIC23_PDC_DAC;
+		tlv320aic23_wr_changed(tlv, TLV320AIC23_POWERCTRL, tmp);
+	}
+
+	return 0;
+}
+
+EXPORT_SYMBOL(tlv320aic23_open);
+
+/* tlv320aic23_close
+ *
+ * called to close down an stream, so turn off power and make sure that
+ * anything else is stopped
+*/
+
+int tlv320aic23_close(struct tlv320aic23 *tlv, snd_pcm_substream_t *substream)
+{
+	int tmp;
+
+	DBG("%s: tlv=%p, substream=%p\n", __FUNCTION__, tlv, substream);
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		/* remove power from the ADC and inputs */
+
+		tmp = tlv->regs[TLV320AIC23_POWERCTRL];
+		tmp |= TLV320AIC23_PDC_ADC;
+		tmp |= TLV320AIC23_PDC_MIC;
+		tmp |= TLV320AIC23_PDC_LINE;
+		tlv320aic23_wr_changed(tlv, TLV320AIC23_POWERCTRL, tmp);
+	} else {
+		/* mute output */
+
+		tmp = tlv->regs[TLV320AIC23_DIGITALPATH];
+		tmp |= TLV320AIC23_DPATH_SOFTMUTE;
+		tlv320aic23_wr(tlv, TLV320AIC23_DIGITALPATH, tmp);
+
+		/* remove power from the DAC */
+
+		tmp = tlv->regs[TLV320AIC23_POWERCTRL];
+		tmp |= TLV320AIC23_PDC_DAC;
+		tlv320aic23_wr_changed(tlv, TLV320AIC23_POWERCTRL, tmp);
+	}
+
+
+	return 0;
+}
+
+EXPORT_SYMBOL(tlv320aic23_close);
+
+static int tlv320aic23_prepare_capture(struct tlv320aic23 *tlv) 
+{
+	/* ADC and line should already be powered up */
+
+	return 0;
+}
+
+static int tlv320aic23_prepare_playback(struct tlv320aic23 *tlv) 
+{
+	int tmp;
+
+	tmp = tlv->regs[TLV320AIC23_POWERCTRL];
+	tmp &= ~TLV320AIC23_PDC_OUTPUT;
+	tmp &= ~TLV320AIC23_PDC_DAC;
+	tlv320aic23_wr_changed(tlv, TLV320AIC23_POWERCTRL, tmp);
+
+	/* ensure system not muted */
+	
+	tmp = tlv->regs[TLV320AIC23_DIGITALPATH];
+	tmp &= ~TLV320AIC23_DPATH_SOFTMUTE;
+	tlv320aic23_wr(tlv, TLV320AIC23_DIGITALPATH, tmp);
+		
+	/* update the analogue path */
+
+	tmp = tlv->regs[TLV320AIC23_ANALOGPATH];
+	tmp |= TLV320AIC23_APATH_DACEN;
+	tlv320aic23_wr_changed(tlv, TLV320AIC23_ANALOGPATH, tmp);
+
+	/* ok, let's try a reset */
+
+	if (0) {
+		int reg;
+		int ret;
+
+		tlv320aic23_reset(tlv);
+		
+		/* configure all registers */
+		
+		for (reg = 0; reg < TLV320AIC23_RESET; reg++) {
+			ret = tlv320aic23_wr(tlv, reg, tlv->regs[reg]);
+		}
+	}
+
+	/* try and reset the dac by power-cycling */
+
+	if (1) {
+		tmp = tlv->regs[TLV320AIC23_POWERCTRL];
+		tlv320aic23_wr(tlv, TLV320AIC23_POWERCTRL,
+			       tmp|TLV320AIC23_PDC_DAC);
+		
+		udelay(1);
+
+		tmp &= ~TLV320AIC23_PDC_DAC;
+		tlv320aic23_wr(tlv, TLV320AIC23_POWERCTRL, tmp);
+	}
+
+	return 0;
+}
+
+
+int tlv320aic23_prepare(struct tlv320aic23 *tlv,
+			snd_pcm_substream_t *substream,
+			snd_pcm_runtime_t *runtime)
+{
+	int sr = tlv320aic23_find_sr(tlv, runtime->rate);
+	int tmp;
+	int ret = 0;
+
+	/* update sample rate */
+
+	tmp = tlv->regs[TLV320AIC23_SAMPLERATE];
+	tmp &= ~TLV320AIC23_SAMPLERATE_BOSR;
+	tmp &= ~TLV320AIC23_SAMPLERATE_USB;
+	tmp &= ~TLV320AIC23_SAMPLERATE_MASK;
+
+	if (tlv->cfg->clkrate == 12000000)
+		tmp |= TLV320AIC23_SAMPLERATE_USB;
+
+	if (sr == -1) {
+		printk(KERN_ERR "cannot get rate for %d\n", runtime->rate);
+		return -EINVAL;
+	}
+
+	tlv320aic23_wr_changed(tlv, TLV320AIC23_SAMPLERATE, tmp | sr);
+
+	/* update format */
+
+	tmp = tlv->regs[TLV320AIC23_DIGITALFMT];
+	tmp &= ~TLV320AIC23_DIGITALFMT_BITMASK;		
+	tmp |= TLV320AIC23_DIGITALFMT_I2S; // todo - sortout correct interface
+
+	switch (runtime->format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		tmp |= TLV320AIC23_DIGITALFMT_16BIT;
+		break;
+
+	default:
+		printk(KERN_ERR "unknown data format\n");
+		return -EINVAL;
+	}
+	
+	tlv320aic23_wr_changed(tlv, TLV320AIC23_DIGITALFMT, tmp);
+
+	/* ensure that the system is powered up */
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+		ret = tlv320aic23_prepare_capture(tlv);
+	else
+		ret = tlv320aic23_prepare_playback(tlv);
+
+	return ret;
+}
+
+EXPORT_SYMBOL(tlv320aic23_prepare);
+
--- linux-2.6.11-rc5/sound/arm/tlv320aic23.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.11-rc5-audio9/sound/arm/tlv320aic23.h	2005-03-15 10:10:26.000000000 +0000
@@ -0,0 +1,35 @@
+/* sound/arm/tlv320aic23.h
+ *
+ * (c) 2004-2005 Simtec Electronics
+ *	http://armlinux.simtec.co.uk/
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ * Texas Instruments TLV320AIC23 codec driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+*/
+
+struct tlv320aic23;
+
+struct tlv320aic23_cfg {
+	unsigned long		 clkrate;
+	unsigned char		 master;
+	unsigned char		 split_rates;
+};
+
+extern struct tlv320aic23 *tlv320aic23_attach(snd_card_t *card,
+					      struct device *dev,
+					      struct tlv320aic23_cfg *cfg);
+
+extern void tlv320aic23_detach(struct tlv320aic23 *tlv);
+extern int tlv320aic23_startup(struct tlv320aic23 *tlv);
+extern int tlv320aic23_open(struct tlv320aic23 *, snd_pcm_substream_t *);
+extern int tlv320aic23_close(struct tlv320aic23 *, snd_pcm_substream_t *);
+
+extern int tlv320aic23_prepare(struct tlv320aic23 *tlv,
+			       snd_pcm_substream_t *substream,
+			       snd_pcm_runtime_t *runtime);
+
--- linux-2.6.11-rc5/sound/arm/tlv320aic23-i2c.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.11-rc5-audio9/sound/arm/tlv320aic23-i2c.c	2005-03-15 12:11:27.000000000 +0000
@@ -0,0 +1,180 @@
+/* sound/arm/tlv320aic23-i2c.c
+ *
+ * (c) 2004-2005 Simtec Electronics
+ *	http://armlinux.simtec.co.uk/
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ * TLV320AIC23 Audio codec driver - I2C interface
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+*/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/i2c-sensor.h>
+
+#include "tlv320aic23-hw.h"
+
+static unsigned short normal_i2c[] = { 0x1A,  I2C_CLIENT_END };
+static unsigned int normal_isa[] = { I2C_CLIENT_ISA_END };
+static struct i2c_force_data forces[] = { { NULL }, 0 };
+
+static struct i2c_address_data addr_data = {
+	.normal_i2c	= normal_i2c,
+	.normal_isa	= normal_isa,
+	.forces		= forces,
+};
+
+struct tlv320aic23_data {
+	struct i2c_client	client;
+	struct platform_device	my_dev;
+	struct tlv320aic23_hw	hw;
+};
+
+static struct i2c_driver tlv320aic23_driver;
+
+static int tlv320aic23_id;
+
+static inline struct i2c_client *hw_to_client(struct tlv320aic23_hw *tlv)
+{
+	struct tlv320aic23_data *dp;
+
+	dp = container_of(tlv, struct tlv320aic23_data, hw);
+	return &dp->client;
+}
+
+static int tlv320aic23_i2c_claim(struct tlv320aic23_hw *tlv)
+{
+	struct i2c_client *client = hw_to_client(tlv);
+	return i2c_use_client(client);
+}
+
+static int tlv320aic23_i2c_release(struct tlv320aic23_hw *tlv)
+{
+	struct i2c_client *client = hw_to_client(tlv);
+	return i2c_release_client(client);
+}
+
+static int tlv320aic23_wr_i2c(struct tlv320aic23_hw *tlv, int reg, int val)
+{
+	struct i2c_client *client = hw_to_client(tlv);
+	unsigned char data[2];
+	struct i2c_msg msg[1];
+	int done;
+
+	msg[0].addr  = client->addr;
+	msg[0].flags = 0;
+	msg[0].len   = 2;
+	msg[0].buf   = data;
+
+	data[0] = reg << 1;
+	data[1] = val;
+
+	if (val & (1<<8))
+		data[0] |= 1;
+
+	done = i2c_transfer(client->adapter, msg, 1);
+	
+	printk(KERN_DEBUG "%s: (%02x,%02x) wr %02x,%02x ret %d\n", 
+	       __FUNCTION__, reg, val, data[0], data[1], done);
+
+	return done == 1 ? 0 : -EIO;
+}
+
+
+static int tlv320aic23_detect(struct i2c_adapter *adap, int addr, int kind)
+{
+	struct tlv320aic23_data *tlv;
+	int err;
+
+	tlv = kmalloc(sizeof(*tlv), GFP_KERNEL);
+	if (tlv == NULL) {
+		err = -ENOMEM;
+		goto exit_err;
+	}
+	
+	memset(tlv, 0, sizeof(*tlv));
+
+	i2c_set_clientdata(&tlv->client, tlv);
+	tlv->client.addr	= addr;
+	tlv->client.adapter	= adap;
+	tlv->client.driver	= &tlv320aic23_driver;
+	tlv->client.id		= tlv320aic23_id++;
+
+	tlv->hw.dev		= &adap->dev;
+	tlv->hw.owner		= THIS_MODULE;
+	tlv->hw.wr		= tlv320aic23_wr_i2c;
+	tlv->hw.claim		= tlv320aic23_i2c_claim;
+	tlv->hw.release		= tlv320aic23_i2c_release;
+
+	tlv->my_dev.dev.platform_data	= &tlv->hw;
+	tlv->my_dev.dev.parent		= &adap->dev;
+	tlv->my_dev.name		= "tlv320aic23";
+
+	strlcpy(tlv->client.name, "tlv320aic23", I2C_NAME_SIZE);
+	
+	err = i2c_attach_client(&tlv->client);
+	if (err)
+		goto exit_err;
+
+	/* registered ok */
+
+	platform_device_register(&tlv->my_dev);
+
+	return 0;
+	
+ exit_err:
+	return err;
+}
+
+static int tlv320aic23_attach_adapter(struct i2c_adapter *adapter)
+{
+	return i2c_detect(adapter, &addr_data, tlv320aic23_detect);
+}
+
+static int tlv320aic23_detach_client(struct i2c_client *client)
+{
+	int err;
+
+	if ((err = i2c_detach_client(client))) {
+		dev_err(&client->dev, "Client deregistration failed, "
+			"client not detached.\n");
+		return err;
+	}
+
+	kfree(i2c_get_clientdata(client));
+	return 0;
+}
+
+
+static struct i2c_driver tlv320aic23_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "tlv320aic23",
+	.flags		= I2C_DF_NOTIFY,
+	.attach_adapter	= tlv320aic23_attach_adapter,
+	.detach_client	= tlv320aic23_detach_client,
+};
+
+
+static int __init tlv320aic23_init(void)
+{
+	return i2c_add_driver(&tlv320aic23_driver);
+}
+
+static void __exit tlv320aic23_exit(void)
+{
+	i2c_del_driver(&tlv320aic23_driver);
+}
+
+module_init(tlv320aic23_init);
+module_exit(tlv320aic23_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("TLV320AIC23 I2C Audio driver");
+MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
--- linux-2.6.11-rc5/sound/arm/tlv320aic23-hw.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.11-rc5-audio9/sound/arm/tlv320aic23-hw.h	2005-03-15 10:10:31.000000000 +0000
@@ -0,0 +1,29 @@
+/* sound/arm/tlv320aic23-hw.h
+ *
+ * (c) 2004-2005 Simtec Electronics
+ *	http://armlinux.simtec.co.uk/
+ *	Ben Dooks <ben@simtec.co.uk>
+ *
+ * Texas Instruments TLV320AIC23 codec driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+*/
+
+/* struct tlv320aic23_hw
+ *
+ * exported by the specific hardware driver
+*/
+
+struct tlv320aic23_hw;
+
+struct tlv320aic23_hw {
+	struct module	*owner;
+	struct device	*dev;
+
+	int		(*claim)(struct tlv320aic23_hw *);
+	int		(*release)(struct tlv320aic23_hw *);
+	int		(*wr)(struct tlv320aic23_hw *hw, int reg, int val);
+};

