Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
diff --git a/sound/oss/dmasound/tas3001c.c b/sound/oss/dmasound/tas3001c.c
new file mode 100644
index 0000000..f227c9f
--- /dev/null
+++ b/sound/oss/dmasound/tas3001c.c
@@ -0,0 +1,850 @@
+/*
+ * Driver for the i2c/i2s based TA3004 sound chip used
+ * on some Apple hardware. Also known as "snapper".
+ *
+ * Tobias Sargeant <tobias.sargeant@bigpond.com>
+ * Based upon, tas3001c.c by Christopher C. Chimelis <chris@debian.org>:
+ *
+ *   TODO:
+ *   -----
+ *   * Enable control over input line 2 (is this connected?)
+ *   * Implement sleep support (at least mute everything and
+ *   * set gains to minimum during sleep)
+ *   * Look into some of Darwin's tweaks regarding the mute
+ *   * lines (delays & different behaviour on some HW)
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/ioport.h>
+#include <linux/sysctl.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/soundcard.h>
+#include <linux/workqueue.h>
+#include <asm/uaccess.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+
+#include "dmasound.h"
+#include "tas_common.h"
+#include "tas3001c.h"
+
+#include "tas_ioctl.h"
+
+#define TAS3001C_BIQUAD_FILTER_COUNT  6
+#define TAS3001C_BIQUAD_CHANNEL_COUNT 2
+
+#define VOL_DEFAULT	(100 * 4 / 5)
+#define INPUT_DEFAULT	(100 * 4 / 5)
+#define BASS_DEFAULT	(100 / 2)
+#define TREBLE_DEFAULT	(100 / 2)
+
+struct tas3001c_data_t {
+	struct tas_data_t super;
+	int device_id;
+	int output_id;
+	int speaker_id;
+	struct tas_drce_t drce_state;
+};
+
+
+static const union tas_biquad_t
+tas3001c_eq_unity={
+	.buf = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 }
+};
+
+
+static inline unsigned char db_to_regval(short db) {
+	int r=0;
+
+	r=(db+0x59a0) / 0x60;
+
+	if (r < 0x91) return 0x91;
+	if (r > 0xef) return 0xef;
+	return r;
+}
+
+static inline short quantize_db(short db) {
+	return db_to_regval(db) * 0x60 - 0x59a0;
+}
+
+
+static inline int
+register_width(enum tas3001c_reg_t r)
+{
+	switch(r) {
+	case TAS3001C_REG_MCR:
+ 	case TAS3001C_REG_TREBLE:
+	case TAS3001C_REG_BASS:
+		return 1;
+
+	case TAS3001C_REG_DRC:
+		return 2;
+
+	case TAS3001C_REG_MIXER1:
+	case TAS3001C_REG_MIXER2:
+		return 3;
+
+	case TAS3001C_REG_VOLUME:
+		return 6;
+
+	case TAS3001C_REG_LEFT_BIQUAD0:
+	case TAS3001C_REG_LEFT_BIQUAD1:
+	case TAS3001C_REG_LEFT_BIQUAD2:
+	case TAS3001C_REG_LEFT_BIQUAD3:
+	case TAS3001C_REG_LEFT_BIQUAD4:
+	case TAS3001C_REG_LEFT_BIQUAD5:
+	case TAS3001C_REG_LEFT_BIQUAD6:
+
+	case TAS3001C_REG_RIGHT_BIQUAD0:
+	case TAS3001C_REG_RIGHT_BIQUAD1:
+	case TAS3001C_REG_RIGHT_BIQUAD2:
+	case TAS3001C_REG_RIGHT_BIQUAD3:
+	case TAS3001C_REG_RIGHT_BIQUAD4:
+	case TAS3001C_REG_RIGHT_BIQUAD5:
+	case TAS3001C_REG_RIGHT_BIQUAD6:
+		return 15;
+
+	default:
+		return 0;
+	}
+}
+
+static int
+tas3001c_write_register(	struct tas3001c_data_t *self,
+				enum tas3001c_reg_t reg_num,
+				char *data,
+				uint write_mode)
+{
+	if (reg_num==TAS3001C_REG_MCR ||
+	    reg_num==TAS3001C_REG_BASS ||
+	    reg_num==TAS3001C_REG_TREBLE) {
+		return tas_write_byte_register(&self->super,
+					       (uint)reg_num,
+					       *data,
+					       write_mode);
+	} else {
+		return tas_write_register(&self->super,
+					  (uint)reg_num,
+					  register_width(reg_num),
+					  data,
+					  write_mode);
+	}
+}
+
+static int
+tas3001c_sync_register(	struct tas3001c_data_t *self,
+			enum tas3001c_reg_t reg_num)
+{
+	if (reg_num==TAS3001C_REG_MCR ||
+	    reg_num==TAS3001C_REG_BASS ||
+	    reg_num==TAS3001C_REG_TREBLE) {
+		return tas_sync_byte_register(&self->super,
+					      (uint)reg_num,
+					      register_width(reg_num));
+	} else {
+		return tas_sync_register(&self->super,
+					 (uint)reg_num,
+					 register_width(reg_num));
+	}
+}
+
+static int
+tas3001c_read_register(	struct tas3001c_data_t *self,
+			enum tas3001c_reg_t reg_num,
+			char *data,
+			uint write_mode)
+{
+	return tas_read_register(&self->super,
+				 (uint)reg_num,
+				 register_width(reg_num),
+				 data);
+}
+
+static inline int
+tas3001c_fast_load(struct tas3001c_data_t *self, int fast)
+{
+	if (fast)
+		self->super.shadow[TAS3001C_REG_MCR][0] |= 0x80;
+	else
+		self->super.shadow[TAS3001C_REG_MCR][0] &= 0x7f;
+	return tas3001c_sync_register(self,TAS3001C_REG_MCR);
+}
+
+static uint
+tas3001c_supported_mixers(struct tas3001c_data_t *self)
+{
+	return SOUND_MASK_VOLUME |
+		SOUND_MASK_PCM |
+		SOUND_MASK_ALTPCM |
+		SOUND_MASK_TREBLE |
+		SOUND_MASK_BASS;
+}
+
+static int
+tas3001c_mixer_is_stereo(struct tas3001c_data_t *self,int mixer)
+{
+	switch(mixer) {
+	case SOUND_MIXER_VOLUME:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static uint
+tas3001c_stereo_mixers(struct tas3001c_data_t *self)
+{
+	uint r=tas3001c_supported_mixers(self);
+	uint i;
+	
+	for (i=1; i<SOUND_MIXER_NRDEVICES; i++)
+		if (r&(1<<i) && !tas3001c_mixer_is_stereo(self,i))
+			r &= ~(1<<i);
+	return r;
+}
+
+static int
+tas3001c_get_mixer_level(struct tas3001c_data_t *self,int mixer,uint *level)
+{
+	if (!self)
+		return -1;
+		
+	*level=self->super.mixer[mixer];
+	
+	return 0;
+}
+
+static int
+tas3001c_set_mixer_level(struct tas3001c_data_t *self,int mixer,uint level)
+{
+	int rc;
+	tas_shadow_t *shadow;
+
+	uint temp;
+	uint offset=0;
+
+	if (!self)
+		return -1;
+		
+	shadow=self->super.shadow;
+
+	if (!tas3001c_mixer_is_stereo(self,mixer))
+		level = tas_mono_to_stereo(level);
+
+	switch(mixer) {
+	case SOUND_MIXER_VOLUME:
+		temp = tas3001c_gain.master[level&0xff];
+		shadow[TAS3001C_REG_VOLUME][0] = (temp >> 16) & 0xff;
+		shadow[TAS3001C_REG_VOLUME][1] = (temp >> 8)  & 0xff;
+		shadow[TAS3001C_REG_VOLUME][2] = (temp >> 0)  & 0xff;
+		temp = tas3001c_gain.master[(level>>8)&0xff];
+		shadow[TAS3001C_REG_VOLUME][3] = (temp >> 16) & 0xff;
+		shadow[TAS3001C_REG_VOLUME][4] = (temp >> 8)  & 0xff;
+		shadow[TAS3001C_REG_VOLUME][5] = (temp >> 0)  & 0xff;
+		rc = tas3001c_sync_register(self,TAS3001C_REG_VOLUME);
+		break;
+	case SOUND_MIXER_ALTPCM:
+		/* tas3001c_fast_load(self, 1); */
+		level = tas_mono_to_stereo(level);
+		temp = tas3001c_gain.mixer[level&0xff];
+		shadow[TAS3001C_REG_MIXER2][offset+0] = (temp >> 16) & 0xff;
+		shadow[TAS3001C_REG_MIXER2][offset+1] = (temp >> 8)  & 0xff;
+		shadow[TAS3001C_REG_MIXER2][offset+2] = (temp >> 0)  & 0xff;
+		rc = tas3001c_sync_register(self,TAS3001C_REG_MIXER2);
+		/* tas3001c_fast_load(self, 0); */
+		break;
+	case SOUND_MIXER_PCM:
+		/* tas3001c_fast_load(self, 1); */
+		level = tas_mono_to_stereo(level);
+		temp = tas3001c_gain.mixer[level&0xff];
+		shadow[TAS3001C_REG_MIXER1][offset+0] = (temp >> 16) & 0xff;
+		shadow[TAS3001C_REG_MIXER1][offset+1] = (temp >> 8)  & 0xff;
+		shadow[TAS3001C_REG_MIXER1][offset+2] = (temp >> 0)  & 0xff;
+		rc = tas3001c_sync_register(self,TAS3001C_REG_MIXER1);
+		/* tas3001c_fast_load(self, 0); */
+		break;
+	case SOUND_MIXER_TREBLE:
+		temp = tas3001c_gain.treble[level&0xff];
+		shadow[TAS3001C_REG_TREBLE][0]=temp&0xff;
+		rc = tas3001c_sync_register(self,TAS3001C_REG_TREBLE);
+		break;
+	case SOUND_MIXER_BASS:
+		temp = tas3001c_gain.bass[level&0xff];
+		shadow[TAS3001C_REG_BASS][0]=temp&0xff;
+		rc = tas3001c_sync_register(self,TAS3001C_REG_BASS);
+		break;
+	default:
+		rc = -1;
+		break;
+	}
+	if (rc < 0)
+		return rc;
+	self->super.mixer[mixer]=level;
+	return 0;
+}
+
+static int
+tas3001c_leave_sleep(struct tas3001c_data_t *self)
+{
+	unsigned char mcr = (1<<6)+(2<<4)+(2<<2);
+
+	if (!self)
+		return -1;
+
+	/* Make sure something answers on the i2c bus */
+	if (tas3001c_write_register(self, TAS3001C_REG_MCR, &mcr,
+	    WRITE_NORMAL|FORCE_WRITE) < 0)
+	    	return -1;
+
+	tas3001c_fast_load(self, 1);
+
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD0);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD1);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD2);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD3);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD4);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD5);
+
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD0);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD1);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD2);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD3);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD4);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD5);
+
+	tas3001c_fast_load(self, 0);
+
+	(void)tas3001c_sync_register(self,TAS3001C_REG_BASS);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_TREBLE);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER1);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER2);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_VOLUME);
+
+	return 0;
+}
+
+static int
+tas3001c_enter_sleep(struct tas3001c_data_t *self)
+{
+	/* Stub for now, but I have the details on low-power mode */
+	if (!self)
+		return -1; 
+	return 0;
+}
+
+static int
+tas3001c_sync_biquad(	struct tas3001c_data_t *self,
+			u_int channel,
+			u_int filter)
+{
+	enum tas3001c_reg_t reg;
+
+	if (channel >= TAS3001C_BIQUAD_CHANNEL_COUNT ||
+	    filter  >= TAS3001C_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+	reg=( channel ? TAS3001C_REG_RIGHT_BIQUAD0 : TAS3001C_REG_LEFT_BIQUAD0 ) + filter;
+
+	return tas3001c_sync_register(self,reg);
+}
+
+static int
+tas3001c_write_biquad_shadow(	struct tas3001c_data_t *self,
+				u_int channel,
+				u_int filter,
+				const union tas_biquad_t *biquad)
+{
+	tas_shadow_t *shadow=self->super.shadow;
+	enum tas3001c_reg_t reg;
+
+	if (channel >= TAS3001C_BIQUAD_CHANNEL_COUNT ||
+	    filter  >= TAS3001C_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+	reg=( channel ? TAS3001C_REG_RIGHT_BIQUAD0 : TAS3001C_REG_LEFT_BIQUAD0 ) + filter;
+
+	SET_4_20(shadow[reg], 0,biquad->coeff.b0);
+	SET_4_20(shadow[reg], 3,biquad->coeff.b1);
+	SET_4_20(shadow[reg], 6,biquad->coeff.b2);
+	SET_4_20(shadow[reg], 9,biquad->coeff.a1);
+	SET_4_20(shadow[reg],12,biquad->coeff.a2);
+
+	return 0;
+}
+
+static int
+tas3001c_write_biquad(	struct tas3001c_data_t *self,
+			u_int channel,
+			u_int filter,
+			const union tas_biquad_t *biquad)
+{
+	int rc;
+
+	rc=tas3001c_write_biquad_shadow(self, channel, filter, biquad);
+	if (rc < 0) return rc;
+
+	return tas3001c_sync_biquad(self, channel, filter);
+}
+
+static int
+tas3001c_write_biquad_list(	struct tas3001c_data_t *self,
+				u_int filter_count,
+				u_int flags,
+				struct tas_biquad_ctrl_t *biquads)
+{
+	int i;
+	int rc;
+
+	if (flags & TAS_BIQUAD_FAST_LOAD) tas3001c_fast_load(self,1);
+
+	for (i=0; i<filter_count; i++) {
+		rc=tas3001c_write_biquad(self,
+					 biquads[i].channel,
+					 biquads[i].filter,
+					 &biquads[i].data);
+		if (rc < 0) break;
+	}
+
+	if (flags & TAS_BIQUAD_FAST_LOAD) {
+		tas3001c_fast_load(self,0);
+
+		(void)tas3001c_sync_register(self,TAS3001C_REG_BASS);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_TREBLE);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER1);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER2);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_VOLUME);
+	}
+
+	return rc;
+}
+
+static int
+tas3001c_read_biquad(	struct tas3001c_data_t *self,
+			u_int channel,
+			u_int filter,
+			union tas_biquad_t *biquad)
+{
+	tas_shadow_t *shadow=self->super.shadow;
+	enum tas3001c_reg_t reg;
+
+	if (channel >= TAS3001C_BIQUAD_CHANNEL_COUNT ||
+	    filter  >= TAS3001C_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+	reg=( channel ? TAS3001C_REG_RIGHT_BIQUAD0 : TAS3001C_REG_LEFT_BIQUAD0 ) + filter;
+
+	biquad->coeff.b0=GET_4_20(shadow[reg], 0);
+	biquad->coeff.b1=GET_4_20(shadow[reg], 3);
+	biquad->coeff.b2=GET_4_20(shadow[reg], 6);
+	biquad->coeff.a1=GET_4_20(shadow[reg], 9);
+	biquad->coeff.a2=GET_4_20(shadow[reg],12);
+	
+	return 0;	
+}
+
+static int
+tas3001c_eq_rw(	struct tas3001c_data_t *self,
+		u_int cmd,
+		u_long arg)
+{
+	int rc;
+	struct tas_biquad_ctrl_t biquad;
+	void __user *argp = (void __user *)arg;
+
+	if (copy_from_user(&biquad, argp, sizeof(struct tas_biquad_ctrl_t))) {
+		return -EFAULT;
+	}
+
+	if (cmd & SIOC_IN) {
+		rc=tas3001c_write_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+		if (rc != 0) return rc;
+	}
+
+	if (cmd & SIOC_OUT) {
+		rc=tas3001c_read_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+		if (rc != 0) return rc;
+
+		if (copy_to_user(argp, &biquad, sizeof(struct tas_biquad_ctrl_t))) {
+			return -EFAULT;
+		}
+
+	}
+	return 0;
+}
+
+static int
+tas3001c_eq_list_rw(	struct tas3001c_data_t *self,
+			u_int cmd,
+			u_long arg)
+{
+	int rc;
+	int filter_count;
+	int flags;
+	int i,j;
+	char sync_required[2][6];
+	struct tas_biquad_ctrl_t biquad;
+	struct tas_biquad_ctrl_list_t __user *argp = (void __user *)arg;
+
+	memset(sync_required,0,sizeof(sync_required));
+
+	if (copy_from_user(&filter_count, &argp->filter_count, sizeof(int)))
+		return -EFAULT;
+
+	if (copy_from_user(&flags, &argp->flags, sizeof(int)))
+		return -EFAULT;
+
+	if (cmd & SIOC_IN) {
+	}
+
+	for (i=0; i < filter_count; i++) {
+		if (copy_from_user(&biquad, &argp->biquads[i],
+				   sizeof(struct tas_biquad_ctrl_t))) {
+			return -EFAULT;
+		}
+
+		if (cmd & SIOC_IN) {
+			sync_required[biquad.channel][biquad.filter]=1;
+			rc=tas3001c_write_biquad_shadow(self, biquad.channel, biquad.filter, &biquad.data);
+			if (rc != 0) return rc;
+		}
+
+		if (cmd & SIOC_OUT) {
+			rc=tas3001c_read_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+			if (rc != 0) return rc;
+
+			if (copy_to_user(&argp->biquads[i], &biquad,
+					 sizeof(struct tas_biquad_ctrl_t))) {
+				return -EFAULT;
+			}
+		}
+	}
+
+	if (cmd & SIOC_IN) {
+		if (flags & TAS_BIQUAD_FAST_LOAD) tas3001c_fast_load(self,1);
+		for (i=0; i<2; i++) {
+			for (j=0; j<6; j++) {
+				if (sync_required[i][j]) {
+					rc=tas3001c_sync_biquad(self, i, j);
+					if (rc < 0) return rc;
+				}
+			}
+		}
+		if (flags & TAS_BIQUAD_FAST_LOAD) {
+			tas3001c_fast_load(self,0);
+			/* now we need to set up the mixers again,
+			   because leaving fast mode resets them. */
+			(void)tas3001c_sync_register(self,TAS3001C_REG_BASS);
+			(void)tas3001c_sync_register(self,TAS3001C_REG_TREBLE);
+			(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER1);
+			(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER2);
+			(void)tas3001c_sync_register(self,TAS3001C_REG_VOLUME);
+		}
+	}
+
+	return 0;
+}
+
+static int
+tas3001c_update_drce(	struct tas3001c_data_t *self,
+			int flags,
+			struct tas_drce_t *drce)
+{
+	tas_shadow_t *shadow;
+	shadow=self->super.shadow;
+
+	shadow[TAS3001C_REG_DRC][1] = 0xc1;
+
+	if (flags & TAS_DRCE_THRESHOLD) {
+		self->drce_state.threshold=quantize_db(drce->threshold);
+		shadow[TAS3001C_REG_DRC][2] = db_to_regval(self->drce_state.threshold);
+	}
+
+	if (flags & TAS_DRCE_ENABLE) {
+		self->drce_state.enable = drce->enable;
+	}
+
+	if (!self->drce_state.enable) {
+		shadow[TAS3001C_REG_DRC][0] = 0xf0;
+	}
+
+#ifdef DEBUG_DRCE
+	printk("DRCE IOCTL: set [ ENABLE:%x THRESH:%x\n",
+	       self->drce_state.enable,
+	       self->drce_state.threshold);
+
+	printk("DRCE IOCTL: reg [ %02x %02x ]\n",
+	       (unsigned char)shadow[TAS3001C_REG_DRC][0],
+	       (unsigned char)shadow[TAS3001C_REG_DRC][1]);
+#endif
+
+	return tas3001c_sync_register(self, TAS3001C_REG_DRC);
+}
+
+static int
+tas3001c_drce_rw(	struct tas3001c_data_t *self,
+			u_int cmd,
+			u_long arg)
+{
+	int rc;
+	struct tas_drce_ctrl_t drce_ctrl;
+	void __user *argp = (void __user *)arg;
+
+	if (copy_from_user(&drce_ctrl, argp, sizeof(struct tas_drce_ctrl_t)))
+		return -EFAULT;
+
+#ifdef DEBUG_DRCE
+	printk("DRCE IOCTL: input [ FLAGS:%x ENABLE:%x THRESH:%x\n",
+	       drce_ctrl.flags,
+	       drce_ctrl.data.enable,
+	       drce_ctrl.data.threshold);
+#endif
+
+	if (cmd & SIOC_IN) {
+		rc = tas3001c_update_drce(self, drce_ctrl.flags, &drce_ctrl.data);
+		if (rc < 0)
+			return rc;
+	}
+
+	if (cmd & SIOC_OUT) {
+		if (drce_ctrl.flags & TAS_DRCE_ENABLE)
+			drce_ctrl.data.enable = self->drce_state.enable;
+
+		if (drce_ctrl.flags & TAS_DRCE_THRESHOLD)
+			drce_ctrl.data.threshold = self->drce_state.threshold;
+
+		if (copy_to_user(argp, &drce_ctrl,
+				 sizeof(struct tas_drce_ctrl_t))) {
+			return -EFAULT;
+		}
+	}
+
+	return 0;
+}
+
+static void
+tas3001c_update_device_parameters(struct tas3001c_data_t *self)
+{
+	int i,j;
+
+	if (!self) return;
+
+	if (self->output_id == TAS_OUTPUT_HEADPHONES) {
+		tas3001c_fast_load(self, 1);
+
+		for (i=0; i<TAS3001C_BIQUAD_CHANNEL_COUNT; i++) {
+			for (j=0; j<TAS3001C_BIQUAD_FILTER_COUNT; j++) {
+				tas3001c_write_biquad(self, i, j, &tas3001c_eq_unity);
+			}
+		}
+
+		tas3001c_fast_load(self, 0);
+
+		(void)tas3001c_sync_register(self,TAS3001C_REG_BASS);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_TREBLE);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER1);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_MIXER2);
+		(void)tas3001c_sync_register(self,TAS3001C_REG_VOLUME);
+
+		return;
+	}
+
+	for (i=0; tas3001c_eq_prefs[i]; i++) {
+		struct tas_eq_pref_t *eq = tas3001c_eq_prefs[i];
+
+		if (eq->device_id == self->device_id &&
+		    (eq->output_id == 0 || eq->output_id == self->output_id) &&
+		    (eq->speaker_id == 0 || eq->speaker_id == self->speaker_id)) {
+
+			tas3001c_update_drce(self, TAS_DRCE_ALL, eq->drce);
+			tas3001c_write_biquad_list(self, eq->filter_count, TAS_BIQUAD_FAST_LOAD, eq->biquads);
+
+			break;
+		}
+	}
+}
+
+static void
+tas3001c_device_change_handler(void *self)
+{
+	if (self)
+		tas3001c_update_device_parameters(self);
+}
+
+static struct work_struct device_change;
+
+static int
+tas3001c_output_device_change(	struct tas3001c_data_t *self,
+				int device_id,
+				int output_id,
+				int speaker_id)
+{
+	self->device_id=device_id;
+	self->output_id=output_id;
+	self->speaker_id=speaker_id;
+
+	schedule_work(&device_change);
+	return 0;
+}
+
+static int
+tas3001c_device_ioctl(	struct tas3001c_data_t *self,
+			u_int cmd,
+			u_long arg)
+{
+	uint __user *argp = (void __user *)arg;
+	switch (cmd) {
+	case TAS_READ_EQ:
+	case TAS_WRITE_EQ:
+		return tas3001c_eq_rw(self, cmd, arg);
+
+	case TAS_READ_EQ_LIST:
+	case TAS_WRITE_EQ_LIST:
+		return tas3001c_eq_list_rw(self, cmd, arg);
+
+	case TAS_READ_EQ_FILTER_COUNT:
+		put_user(TAS3001C_BIQUAD_FILTER_COUNT, argp);
+		return 0;
+
+	case TAS_READ_EQ_CHANNEL_COUNT:
+		put_user(TAS3001C_BIQUAD_CHANNEL_COUNT, argp);
+		return 0;
+
+	case TAS_READ_DRCE:
+	case TAS_WRITE_DRCE:
+		return tas3001c_drce_rw(self, cmd, arg);
+
+	case TAS_READ_DRCE_CAPS:
+		put_user(TAS_DRCE_ENABLE | TAS_DRCE_THRESHOLD, argp);
+		return 0;
+
+	case TAS_READ_DRCE_MIN:
+	case TAS_READ_DRCE_MAX: {
+		struct tas_drce_ctrl_t drce_ctrl;
+
+		if (copy_from_user(&drce_ctrl, argp,
+				   sizeof(struct tas_drce_ctrl_t))) {
+			return -EFAULT;
+		}
+
+		if (drce_ctrl.flags & TAS_DRCE_THRESHOLD) {
+			if (cmd == TAS_READ_DRCE_MIN) {
+				drce_ctrl.data.threshold=-36<<8;
+			} else {
+				drce_ctrl.data.threshold=-6<<8;
+			}
+		}
+
+		if (copy_to_user(argp, &drce_ctrl,
+				 sizeof(struct tas_drce_ctrl_t))) {
+			return -EFAULT;
+		}
+	}
+	}
+
+	return -EINVAL;
+}
+
+static int
+tas3001c_init_mixer(struct tas3001c_data_t *self)
+{
+	unsigned char mcr = (1<<6)+(2<<4)+(2<<2);
+
+	/* Make sure something answers on the i2c bus */
+	if (tas3001c_write_register(self, TAS3001C_REG_MCR, &mcr,
+	    WRITE_NORMAL|FORCE_WRITE) < 0)
+		return -1;
+
+	tas3001c_fast_load(self, 1);
+
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD0);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD1);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD2);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD3);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD4);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD5);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_RIGHT_BIQUAD6);
+
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD0);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD1);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD2);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD3);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD4);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD5);
+	(void)tas3001c_sync_register(self,TAS3001C_REG_LEFT_BIQUAD6);
+
+	tas3001c_fast_load(self, 0);
+
+	tas3001c_set_mixer_level(self, SOUND_MIXER_VOLUME, VOL_DEFAULT<<8 | VOL_DEFAULT);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_PCM, INPUT_DEFAULT<<8 | INPUT_DEFAULT);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0);
+
+	tas3001c_set_mixer_level(self, SOUND_MIXER_BASS, BASS_DEFAULT);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_TREBLE, TREBLE_DEFAULT);
+
+	return 0;
+}
+
+static int
+tas3001c_uninit_mixer(struct tas3001c_data_t *self)
+{
+	tas3001c_set_mixer_level(self, SOUND_MIXER_VOLUME, 0);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_PCM,    0);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0);
+
+	tas3001c_set_mixer_level(self, SOUND_MIXER_BASS,   0);
+	tas3001c_set_mixer_level(self, SOUND_MIXER_TREBLE, 0);
+
+	return 0;
+}
+
+static int
+tas3001c_init(struct i2c_client *client)
+{
+	struct tas3001c_data_t *self;
+	size_t sz = sizeof(*self) + (TAS3001C_REG_MAX*sizeof(tas_shadow_t));
+	int i, j;
+
+	self = kmalloc(sz, GFP_KERNEL);
+	if (!self)
+		return -ENOMEM;
+	memset(self, 0, sz);
+
+	self->super.client = client;
+	self->super.shadow = (tas_shadow_t *)(self+1);
+	self->output_id = TAS_OUTPUT_HEADPHONES;
+
+	dev_set_drvdata(&client->dev, self);
+
+	for (i = 0; i < TAS3001C_BIQUAD_CHANNEL_COUNT; i++)
+		for (j = 0; j < TAS3001C_BIQUAD_FILTER_COUNT; j++)
+			tas3001c_write_biquad_shadow(self, i, j,
+				&tas3001c_eq_unity);
+
+	INIT_WORK(&device_change, tas3001c_device_change_handler, self);
+	return 0;
+}
+
+static void
+tas3001c_uninit(struct tas3001c_data_t *self)
+{
+	tas3001c_uninit_mixer(self);
+	kfree(self);
+}
+
+struct tas_driver_hooks_t tas3001c_hooks = {
+	.init			= (tas_hook_init_t)tas3001c_init,
+	.post_init		= (tas_hook_post_init_t)tas3001c_init_mixer,
+	.uninit			= (tas_hook_uninit_t)tas3001c_uninit,
+	.get_mixer_level	= (tas_hook_get_mixer_level_t)tas3001c_get_mixer_level,
+	.set_mixer_level	= (tas_hook_set_mixer_level_t)tas3001c_set_mixer_level,
+	.enter_sleep		= (tas_hook_enter_sleep_t)tas3001c_enter_sleep,
+	.leave_sleep		= (tas_hook_leave_sleep_t)tas3001c_leave_sleep,
+	.supported_mixers	= (tas_hook_supported_mixers_t)tas3001c_supported_mixers,
+	.mixer_is_stereo	= (tas_hook_mixer_is_stereo_t)tas3001c_mixer_is_stereo,
+	.stereo_mixers		= (tas_hook_stereo_mixers_t)tas3001c_stereo_mixers,
+	.output_device_change	= (tas_hook_output_device_change_t)tas3001c_output_device_change,
+	.device_ioctl		= (tas_hook_device_ioctl_t)tas3001c_device_ioctl
+};