]> git.hungrycats.org Git - linux/commitdiff
dmasound update from Christoph Hellwig
authorBenjamin Herrenschmidt <benh@kernel.crashing.org>
Tue, 9 Sep 2003 23:11:38 +0000 (01:11 +0200)
committerBenjamin Herrenschmidt <benh@kernel.crashing.org>
Tue, 9 Sep 2003 23:11:38 +0000 (01:11 +0200)
16 files changed:
sound/oss/dmasound/Kconfig
sound/oss/dmasound/Makefile
sound/oss/dmasound/awacs_defs.h
sound/oss/dmasound/dac3550a.c [new file with mode: 0644]
sound/oss/dmasound/dmasound_awacs.c
sound/oss/dmasound/tas3001c.c [new file with mode: 0644]
sound/oss/dmasound/tas3001c.h [new file with mode: 0644]
sound/oss/dmasound/tas3001c_tables.c [new file with mode: 0644]
sound/oss/dmasound/tas3004.c [new file with mode: 0644]
sound/oss/dmasound/tas3004.h [new file with mode: 0644]
sound/oss/dmasound/tas3004_tables.c [new file with mode: 0644]
sound/oss/dmasound/tas_common.c [new file with mode: 0644]
sound/oss/dmasound/tas_common.h [new file with mode: 0644]
sound/oss/dmasound/tas_eq_prefs.h [new file with mode: 0644]
sound/oss/dmasound/tas_ioctl.h [new file with mode: 0644]
sound/oss/dmasound/trans_16.c [new file with mode: 0644]

index e642457a4a52cc21110230a76417ac8c5783f746..6107ba86d2815deb059312f7abcf224476a52d31 100644 (file)
@@ -12,9 +12,9 @@ config DMASOUND_ATARI
          want). If you want to compile it as a module, say M here and read
          <file:Documentation/modules.txt>.
 
-config DMASOUND_AWACS
+config DMASOUND_PMAC
        tristate "PowerMac DMA sound support"
-       depends on PPC_PMAC && SOUND
+       depends on PPC_PMAC && SOUND && I2C
        help
          If you want to use the internal audio of your PowerMac in Linux,
          answer Y to this question. This will provide a Sun-like /dev/audio,
index a1c3dee5e967937bec3721723ad47e6dd786c67f..4611636b1a81c455cc83b0baac28d21a020c1d84 100644 (file)
@@ -2,7 +2,12 @@
 # Makefile for the DMA sound driver
 #
 
-obj-$(CONFIG_DMASOUND_ATARI)  += dmasound_core.o dmasound_atari.o
-obj-$(CONFIG_DMASOUND_AWACS)  += dmasound_core.o dmasound_awacs.o
-obj-$(CONFIG_DMASOUND_PAULA)  += dmasound_core.o dmasound_paula.o
-obj-$(CONFIG_DMASOUND_Q40)    += dmasound_core.o dmasound_q40.o
+dmasound_pmac-y                        += dmasound_awacs.o \
+                                  trans_16.o dac3550a.o tas_common.o \
+                                  tas3001c.o tas3001c_tables.o \
+                                  tas3004.o tas3004_tables.o
+
+obj-$(CONFIG_DMASOUND_ATARI)   += dmasound_core.o dmasound_atari.o
+obj-$(CONFIG_DMASOUND_PMAC)    += dmasound_core.o dmasound_pmac.o
+obj-$(CONFIG_DMASOUND_PAULA)   += dmasound_core.o dmasound_paula.o
+obj-$(CONFIG_DMASOUND_Q40)     += dmasound_core.o dmasound_q40.o
index ba6669aa679c34088350f23220660af328ae4a8c..2194f46b046c4c34d6b3a7364b20d72af5065a43 100644 (file)
@@ -71,17 +71,20 @@ struct awacs_regs {
 /* ------- - --- ----- - ------ */
 #define MASK_GAINRIGHT (0xf)           /* Gain Right Mask */
 #define MASK_GAINLEFT  (0xf << 4)      /* Gain Left Mask */
-#define MASK_GAINLINE  (0x1 << 8)      /* Change Gain for Line??? */
-#define MASK_GAINMIC   (0x0 << 8)      /* Change Gain for Mic??? */
+#define MASK_GAINLINE  (0x1 << 8)      /* Disable Mic preamp */
+#define MASK_GAINMIC   (0x0 << 8)      /* Enable Mic preamp */
 
 #define MASK_MUX_CD    (0x1 << 9)      /* Select CD in MUX */
-#define MASK_MUX_AUDIN (0x1 << 10)     /* Select Audio In in MUX */
-#define MASK_MUX_MIC   (0x1 << 11)     /* Select Mic in MUX */
+#define MASK_MUX_MIC   (0x1 << 10)     /* Select Mic in MUX */
+#define MASK_MUX_AUDIN (0x1 << 11)     /* Select Audio In in MUX */
 #define MASK_MUX_LINE  MASK_MUX_AUDIN
 
 #define GAINRIGHT(x)   ((x) & MASK_GAINRIGHT)
 #define GAINLEFT(x)    (((x) << 4) & MASK_GAINLEFT)
 
+#define DEF_CD_GAIN 0x00bb
+#define DEF_MIC_GAIN 0x00cc
+
 /* Address 1 Bit Masks */
 /* ------- - --- ----- */
 #define MASK_ADDR1RES1 (0x3)           /* Reserved */
@@ -93,7 +96,10 @@ struct awacs_regs {
 #define MASK_ADDR1RES2 (0x1 << 8)      /* Reserved */
 #define MASK_AMUTE     (0x1 << 9)      /* Output A (Headphone) Mute when 1 */
 #define MASK_HDMUTE    MASK_AMUTE
-#define MASK_PAROUT    (0x3 << 10)     /* Parallel Out (???) */
+#define MASK_PAROUT0   (0x1 << 10)     /* Parallel Output 0 */
+#define MASK_PAROUT1   (0x2 << 10)     /* Parallel Output 1 */
+
+#define MASK_MIC_BOOST  (0x4)           /* screamer mic boost */
 
 #define SAMPLERATE_48000       (0x0 << 3)      /* 48 or 44.1 kHz */
 #define SAMPLERATE_32000       (0x1 << 3)      /* 32 or 29.4 kHz */
@@ -162,8 +168,9 @@ struct awacs_regs {
 
 #define RATE_LOW       1       /* HIGH = 48kHz, etc;  LOW = 44.1kHz, etc. */
 
-
+/*******************/
 /* Burgundy values */
+/*******************/
 
 #define MASK_ADDR_BURGUNDY_INPSEL21 (0x11 << 12)
 #define MASK_ADDR_BURGUNDY_INPSEL3 (0x12 << 12)
@@ -226,4 +233,19 @@ struct awacs_regs {
 #define DEF_BURGUNDY_ATTENLINEOUT (0xCC)
 #define DEF_BURGUNDY_ATTENHP (0xCC)
 
+/*********************/
+/* i2s layout values */
+/*********************/
+
+#define I2S_REG_INT_CTL                        0x00
+#define I2S_REG_SERIAL_FORMAT          0x10
+#define I2S_REG_CODEC_MSG_OUT          0x20
+#define I2S_REG_CODEC_MSG_IN           0x30
+#define I2S_REG_FRAME_COUNT            0x40
+#define I2S_REG_FRAME_MATCH            0x50
+#define I2S_REG_DATAWORD_SIZES         0x60
+#define I2S_REG_PEAKLEVEL_SEL          0x70
+#define I2S_REG_PEAKLEVEL_IN0          0x80
+#define I2S_REG_PEAKLEVEL_IN1          0x90
+
 #endif /* _AWACS_DEFS_H_ */
diff --git a/sound/oss/dmasound/dac3550a.c b/sound/oss/dmasound/dac3550a.c
new file mode 100644 (file)
index 0000000..ef09906
--- /dev/null
@@ -0,0 +1,222 @@
+/*
+ * Driver for the i2c/i2s based DAC3550a sound chip used
+ * on some Apple iBooks. Also known as "DACA".
+ *
+ *  This file is subject to the terms and conditions of the GNU General Public
+ *  License.  See the file COPYING in the main directory of this archive
+ *  for more details.
+ */
+
+#include <linux/version.h>
+#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 <asm/uaccess.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+
+#include "dmasound.h"
+
+/* FYI: This code was derived from the tas3001c.c Texas/Tumbler mixer
+ * control code, as well as info derived from the AppleDACAAudio driver
+ * from Darwin CVS (main thing I derived being register numbers and 
+ * values, as well as when to make the calls). */
+
+#define I2C_DRIVERID_DACA (0xFDCB)
+
+#define DACA_VERSION   "0.1"
+#define DACA_DATE "20010930"
+
+static int cur_left_vol;
+static int cur_right_vol;
+static struct i2c_client *daca_client;
+
+static int daca_attach_adapter(struct i2c_adapter *adapter);
+static int daca_detect_client(struct i2c_adapter *adapter, int address);
+static int daca_detach_client(struct i2c_client *client);
+
+/* Unique ID allocation */
+static int daca_id;
+
+struct daca_data
+{
+       int arf; /* place holder for furture use */
+};
+
+struct i2c_driver daca_driver = {  
+       .owner                  = THIS_MODULE,
+       .name                   = "DAC3550A driver  V " DACA_VERSION,
+       .id                     = I2C_DRIVERID_DACA,
+       .flags                  = I2C_DF_NOTIFY,
+       .attach_adapter         = daca_attach_adapter,
+       .detach_client          = daca_detach_client,
+};
+
+#define VOL_MAX ((1<<20) - 1)
+
+void daca_get_volume(uint * left_vol, uint  *right_vol)
+{
+       *left_vol = cur_left_vol >> 5;
+       *right_vol = cur_right_vol >> 5;
+}
+
+int daca_set_volume(uint left_vol, uint right_vol)
+{
+       unsigned short voldata;
+  
+       if (!daca_client)
+               return -1;
+
+       /* Derived from experience, not from any specific values */
+       left_vol <<= 5;
+       right_vol <<= 5;
+
+       if (left_vol > VOL_MAX)
+               left_vol = VOL_MAX;
+       if (right_vol > VOL_MAX)
+               right_vol = VOL_MAX;
+
+       voldata = ((left_vol >> 14)  & 0x3f) << 8;
+       voldata |= (right_vol >> 14)  & 0x3f;
+  
+       if (i2c_smbus_write_word_data(daca_client, 2, voldata) < 0) {
+               printk("daca: failed to set volume \n");
+               return -1; 
+       }
+
+       cur_left_vol = left_vol;
+       cur_right_vol = right_vol;
+  
+       return 0;
+}
+
+int daca_leave_sleep(void)
+{
+       if (!daca_client)
+               return -1;
+  
+       /* Do a short sleep, just to make sure I2C bus is awake and paying
+        * attention to us
+        */
+       wait_ms(20);
+       /* Write the sample rate reg the value it needs */
+       i2c_smbus_write_byte_data(daca_client, 1, 8);
+       daca_set_volume(cur_left_vol >> 5, cur_right_vol >> 5);
+       /* Another short delay, just to make sure the other I2C bus writes
+        * have taken...
+        */
+       wait_ms(20);
+       /* Write the global config reg - invert right power amp,
+        * DAC on, use 5-volt mode */
+       i2c_smbus_write_byte_data(daca_client, 3, 0x45);
+
+       return 0;
+}
+
+int daca_enter_sleep(void)
+{
+       if (!daca_client)
+               return -1;
+
+       i2c_smbus_write_byte_data(daca_client, 1, 8);
+       daca_set_volume(cur_left_vol >> 5, cur_right_vol >> 5);
+
+       /* Write the global config reg - invert right power amp,
+        * DAC on, enter low-power mode, use 5-volt mode
+        */
+       i2c_smbus_write_byte_data(daca_client, 3, 0x65);
+
+       return 0;
+}
+
+static int daca_attach_adapter(struct i2c_adapter *adapter)
+{
+       if (!strncmp(adapter->name, "mac-io", 6))
+               daca_detect_client(adapter, 0x4d);
+       return 0;
+}
+
+static int daca_init_client(struct i2c_client * new_client)
+{
+       /* 
+        * Probe is not working with the current i2c-keywest
+        * driver. We try to use addr 0x4d on each adapters
+        * instead, by setting the format register.
+        * 
+        * FIXME: I'm sure that can be obtained from the
+        * device-tree. --BenH.
+        */
+  
+       /* Write the global config reg - invert right power amp,
+        * DAC on, use 5-volt mode
+        */
+       if (i2c_smbus_write_byte_data(new_client, 3, 0x45))
+               return -1;
+
+       i2c_smbus_write_byte_data(new_client, 1, 8);
+       daca_client = new_client;
+       daca_set_volume(15000, 15000);
+
+       return 0;
+}
+
+static int daca_detect_client(struct i2c_adapter *adapter, int address)
+{
+       const char *client_name = "DAC 3550A Digital Equalizer";
+       struct i2c_client *new_client;
+       struct daca_data *data;
+       int rc = -ENODEV;
+
+       new_client = kmalloc(sizeof(*new_client) + sizeof(*data), GFP_KERNEL);
+       if (!new_client)
+               return -ENOMEM;
+
+       new_client->addr = address;
+       new_client->adapter = adapter;
+       new_client->driver = &daca_driver;
+       new_client->flags = 0;
+       strcpy(new_client->name, client_name);
+       new_client->id = daca_id++; /* racy... */
+
+       data = (struct daca_data *)(new_client+1);
+       dev_set_drvdata(&new_client->dev, data);
+
+       if (daca_init_client(new_client))
+               goto bail;
+
+       /* Tell the i2c layer a new client has arrived */
+       if (i2c_attach_client(new_client))
+               goto bail;
+
+       return 0;
+ bail:
+       kfree(new_client);
+       return rc;
+}
+
+
+static int daca_detach_client(struct i2c_client *client)
+{
+       if (client == daca_client)
+               daca_client = NULL;
+
+       i2c_detach_client(client);
+       kfree(client);
+       return 0;
+}
+
+void daca_cleanup(void)
+{
+       i2c_del_driver(&daca_driver);
+}
+
+int daca_init(void)
+{
+       printk("dac3550a driver version %s (%s)\n",DACA_VERSION,DACA_DATE);
+       return i2c_add_driver(&daca_driver);
+}
index ae3a2d460135ab6a62bc687a2df5de23ead6ddae..4bb7535a6919e423566b6891dcafc6a4a0b0cfcc 100644 (file)
  *      01/02/2002 [0.7] - BenH
  *             - all sort of minor bits went in since the latest update, I
  *               bumped the version number for that reason
-*/
+ *
+ *      07/26/2002 [0.8] - BenH
+ *             - More minor bits since last changelog (I should be more careful
+ *               with those)
+ *             - Support for snapper & better tumbler integration by Toby Sargeant
+ *             - Headphone detect for scremer by Julien Blache
+ *             - More tumbler fixed by Andreas Schwab
+ */
 
 /* GENERAL FIXME/TODO: check that the assumptions about what is written to
    mac-io is valid for DACA & Tumbler.
@@ -68,6 +75,7 @@
 #include <linux/irq.h>
 #include <linux/spinlock.h>
 #include <linux/kmod.h>
+#include <linux/interrupt.h>
 #include <asm/semaphore.h>
 #ifdef CONFIG_ADB_CUDA
 #include <linux/cuda.h>
 
 #include "awacs_defs.h"
 #include "dmasound.h"
+#include "tas3001c.h"
+#include "tas3004.h"
+#include "tas_common.h"
 
 #define DMASOUND_AWACS_REVISION        0
 #define DMASOUND_AWACS_EDITION 7
 
+#define AWACS_SNAPPER   110    /* fake revision # for snapper */
 #define AWACS_BURGUNDY 100     /* fake revision # for burgundy */
 #define AWACS_TUMBLER    90    /* fake revision # for tumbler */
 #define AWACS_DACA      80     /* fake revision # for daca (ibook) */
  */
 static int awacs_irq, awacs_tx_irq, awacs_rx_irq;
 static volatile struct awacs_regs *awacs;
+static volatile u32 *i2s;
 static volatile struct dbdma_regs *awacs_txdma, *awacs_rxdma;
 static int awacs_rate_index;
 static int awacs_subframe;
 static int awacs_spkr_vol;
 static struct device_node* awacs_node;
+static struct device_node* i2s_node;
 
 static char awacs_name[64];
 static int awacs_revision;
@@ -164,6 +178,8 @@ static int mic_lev;
 static int cd_lev = 0x6363 ; /* 99 % */
 static int line_lev;
 
+static int hdp_connected;
+
 /*
  * Stuff for outputting a beep.  The values range from -327 to +327
  * so we can multiply by an amplitude in the range 0..100 to get a
@@ -215,7 +231,6 @@ static int awacs_beep_state;
 static short *beep_buf;
 static void *beep_dbdma_cmd_space;
 static volatile struct dbdma_cmd *beep_dbdma_cmd;
-static void (*orig_mksound)(unsigned int, unsigned int);
 
 /* Burgundy functions */
 static void awacs_burgundy_wcw(unsigned addr,unsigned newval);
@@ -286,25 +301,12 @@ extern TRANS transAwacsExpand ;
 extern TRANS transAwacsNormalRead ;
 
 extern int daca_init(void);
-extern int daca_cleanup(void);
+extern void daca_cleanup(void);
 extern int daca_set_volume(uint left_vol, uint right_vol);
 extern void daca_get_volume(uint * left_vol, uint  *right_vol);
 extern int daca_enter_sleep(void);
 extern int daca_leave_sleep(void);
 
-extern int tas_init(void);
-extern int tas_cleanup(void);
-extern int tumbler_set_volume(uint left_vol, uint right_vol);
-extern void tumbler_get_volume(uint * left_vol, uint  *right_vol);
-extern void tumbler_set_treble(int treble);
-extern void tumbler_get_treble(int *treble);
-extern void tumbler_set_bass(int bass);
-extern void tumbler_get_bass(int *bass);
-extern void tumbler_set_pcm_lvl(int pcm_lvl);
-extern void tumbler_get_pcm_lvl(int *pcm_lvl);
-extern int tumbler_enter_sleep(void);
-extern int tumbler_leave_sleep(void);
-
 #define TRY_LOCK()     \
        if ((rc = down_interruptible(&dmasound_sem)) != 0)      \
                return rc;
@@ -331,7 +333,7 @@ static inline int ioctl_return2(int *addr, int value)
 }
 
 
-/*** AE - TUMBLER START *********************************************************/
+/*** AE - TUMBLER / SNAPPER START ************************************************/
 
 
 int gpio_audio_reset, gpio_audio_reset_pol;
@@ -393,31 +395,35 @@ read_audio_gpio(int gpio_addr)
        return ((pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio_addr, 0) & 0x02) !=0);
 }
 
+/*
+ * Headphone interrupt via GPIO (Tumbler, Snapper, DACA)
+ */
 static irqreturn_t
 headphone_intr(int irq, void *devid, struct pt_regs *regs)
 {
-       int handled = 0;
-       spin_lock(&dmasound.lock);
+       unsigned long flags;
+
+       spin_lock_irqsave(&dmasound.lock, flags);
        if (read_audio_gpio(gpio_headphone_detect) == gpio_headphone_detect_pol) {
-               handled = 1;
                printk(KERN_INFO "Audio jack plugged, muting speakers.\n");
-               write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol);
                write_audio_gpio(gpio_headphone_mute, !gpio_headphone_mute_pol);
+               write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol);
+               tas_output_device_change(sound_device_id,TAS_OUTPUT_HEADPHONES,0);
        } else {
-               handled = 1;
                printk(KERN_INFO "Audio jack unplugged, enabling speakers.\n");
                write_audio_gpio(gpio_amp_mute, !gpio_amp_mute_pol);
                write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol);
+               tas_output_device_change(sound_device_id,TAS_OUTPUT_INTERNAL_SPKR,0);
        }
-       spin_unlock(&dmasound.lock);
-       return IRQ_RETVAL(handled);
+       spin_unlock_irqrestore(&dmasound.lock, flags);
+       return IRQ_HANDLED;
 }
 
 
 /* Initialize tumbler */
 
 static int
-awacs_tumbler_init(void)
+tas_dmasound_init(void)
 {
        setup_audio_gpio(
                "audio-hw-reset",
@@ -474,15 +480,124 @@ awacs_tumbler_init(void)
 
 
 static int
-awacs_tumbler_cleanup(void)
+tas_dmasound_cleanup(void)
 {
        if (gpio_headphone_irq)
                free_irq(gpio_headphone_irq, 0);
        return 0;
 }
 
+/* We don't support 48k yet */
+static int tas_freqs[1] = { 44100 } ;
+static int tas_freqs_ok[1] = { 1 } ;
+
+/* don't know what to do really - just have to leave it where
+ * OF left things
+*/
+
+static int
+tas_set_frame_rate(void)
+{
+       if (i2s) {
+               out_le32(i2s + (I2S_REG_SERIAL_FORMAT >> 2), 0x41190000);
+               out_le32(i2s + (I2S_REG_DATAWORD_SIZES >> 2), 0x02000200);
+       }
+       dmasound.hard.speed = 44100 ;
+       awacs_rate_index = 0 ;
+       return 44100 ;
+}
+
+static int
+tas_mixer_ioctl(u_int cmd, u_long arg)
+{
+       int data;
+       int rc;
 
-/*** AE - TUMBLER END *********************************************************/
+        rc=tas_device_ioctl(cmd, arg);
+        if (rc != -EINVAL) {
+               return rc;
+        }
+
+        if ((cmd & ~0xff) == MIXER_WRITE(0) &&
+            tas_supported_mixers() & (1<<(cmd & 0xff))) {
+               rc = get_user(data, (int *)(arg));
+                if (rc<0) return rc;
+               tas_set_mixer_level(cmd & 0xff, data);
+               tas_get_mixer_level(cmd & 0xff, &data);
+               return ioctl_return2((int *)(arg), data);
+        }
+        if ((cmd & ~0xff) == MIXER_READ(0) &&
+            tas_supported_mixers() & (1<<(cmd & 0xff))) {
+               tas_get_mixer_level(cmd & 0xff, &data);
+               return ioctl_return2((int *)(arg), data);
+        }
+
+       switch(cmd) {
+       case SOUND_MIXER_READ_DEVMASK:
+               data = tas_supported_mixers() | SOUND_MASK_SPEAKER;
+               rc = IOCTL_OUT(arg, data);
+               break;
+       case SOUND_MIXER_READ_STEREODEVS:
+               data = tas_stereo_mixers();
+               rc = IOCTL_OUT(arg, data);
+               break;
+       case SOUND_MIXER_READ_CAPS:
+               rc = IOCTL_OUT(arg, 0);
+               break;
+       case SOUND_MIXER_READ_RECMASK:
+               data = 0;
+               rc = IOCTL_OUT(arg, data);
+               break;
+       case SOUND_MIXER_READ_RECSRC:
+               data = 0;
+               rc = IOCTL_OUT(arg, data);
+               break;
+       case SOUND_MIXER_WRITE_RECSRC:
+               IOCTL_IN(arg, data);
+               data =0;
+               rc = IOCTL_OUT(arg, data);
+               break;
+       case SOUND_MIXER_WRITE_SPEAKER: /* really bell volume */
+               IOCTL_IN(arg, data);
+               beep_vol = data & 0xff;
+               /* fall through */
+       case SOUND_MIXER_READ_SPEAKER:
+               rc = IOCTL_OUT(arg, (beep_vol<<8) | beep_vol);
+               break;
+       case SOUND_MIXER_OUTMASK:
+       case SOUND_MIXER_OUTSRC:
+       default:
+               rc = -EINVAL;
+       }
+
+       return rc;
+}
+
+static void __init
+tas_init_frame_rates(unsigned int *prop, unsigned int l)
+{
+       int i ;
+       if (prop) {
+               for (i=0; i<1; i++)
+                       tas_freqs_ok[i] = 0;
+               for (l /= sizeof(int); l > 0; --l) {
+                       unsigned int r = *prop++;
+                       /* Apple 'Fixed' format */
+                       if (r >= 0x10000)
+                               r >>= 16;
+                       for (i = 0; i < 1; ++i) {
+                               if (r == tas_freqs[i]) {
+                                       tas_freqs_ok[i] = 1;
+                                       break;
+                               }
+                       }
+               }
+       }
+       /* else we assume that all the rates are available */
+}
+
+
+/*** AE - TUMBLER / SNAPPER END ************************************************/
 
 
 
@@ -503,8 +618,10 @@ static void PMacFree(void *ptr, unsigned int size)
 
 static int __init PMacIrqInit(void)
 {
-       if (request_irq(awacs_irq, pmac_awacs_intr, 0, "Built-in Sound misc", 0)
-           || request_irq(awacs_tx_irq, pmac_awacs_tx_intr, 0, "Built-in Sound out", 0)
+       if (awacs)
+               if (request_irq(awacs_irq, pmac_awacs_intr, 0, "Built-in Sound misc", 0))
+                       return 0;
+       if (request_irq(awacs_tx_irq, pmac_awacs_tx_intr, 0, "Built-in Sound out", 0)
            || request_irq(awacs_rx_irq, pmac_awacs_rx_intr, 0, "Built-in Sound in", 0))
                return 0;
        return 1;
@@ -517,23 +634,28 @@ static void PMacIrqCleanup(void)
        DBDMA_DO_STOP(awacs_txdma);
        DBDMA_DO_STOP(awacs_rxdma);
 
-       /* disable interrupts from awacs interface */
-       out_le32(&awacs->control, in_le32(&awacs->control) & 0xfff);
-
+       if (awacs)
+               /* disable interrupts from awacs interface */
+               out_le32(&awacs->control, in_le32(&awacs->control) & 0xfff);
+       
        /* Switch off the sound clock */
        pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 0);
        /* Make sure proper bits are set on pismo & tipb */
-       if (machine_is_compatible("PowerBook3,1") ||
-           machine_is_compatible("PowerBook3,2")) {
+       if ((machine_is_compatible("PowerBook3,1") ||
+           machine_is_compatible("PowerBook3,2")) && awacs) {
                awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1;
                awacs_write(MASK_ADDR1 | awacs_reg[1]);
                wait_ms(200);
        }
-       free_irq(awacs_irq, 0);
+       if (awacs)
+               free_irq(awacs_irq, 0);
        free_irq(awacs_tx_irq, 0);
        free_irq(awacs_rx_irq, 0);
-       /* all OF versions I've seen use this value */
-       iounmap((void *)awacs);
+       
+       if (awacs)
+               iounmap((void *)awacs);
+       if (i2s)
+               iounmap((void *)i2s);
        iounmap((void *)awacs_txdma);
        iounmap((void *)awacs_rxdma);
 
@@ -547,10 +669,8 @@ static void PMacIrqCleanup(void)
                kfree(awacs_rx_cmd_space);
        if (beep_dbdma_cmd_space)
                kfree(beep_dbdma_cmd_space);
-       if (beep_buf) {
+       if (beep_buf)
                kfree(beep_buf);
-               kd_mksound = orig_mksound;
-       }
 #ifdef CONFIG_PMAC_PBOOK
        pmu_unregister_sleep_notifier(&awacs_sleep_notifier);
 #endif
@@ -563,26 +683,16 @@ static void PMacSilence(void)
        DBDMA_DO_STOP(awacs_txdma);
 }
 
-static int tumbler_freqs[2] = { 48000, 44100 } ;
-static int tumbler_freqs_ok[2] = { 1, 1 } ;
-
-/* don't know what to do really - just have to leave it where
- * OF left things
-*/
-
-static int tumbler_set_frame_rate(void)
-{
-       dmasound.hard.speed = 44100 ;
-       awacs_rate_index = 0 ;
-       return 44100 ;
-}
-
 /* don't know what to do really - just have to leave it where
  * OF left things
 */
 
 static int daca_set_frame_rate(void)
 {
+       if (i2s) {
+               out_le32(i2s + (I2S_REG_SERIAL_FORMAT >> 2), 0x41190000);
+               out_le32(i2s + (I2S_REG_DATAWORD_SIZES >> 2), 0x02000200);
+       }
        dmasound.hard.speed = 44100 ;
        awacs_rate_index = 0 ;
        return 44100 ;
@@ -593,7 +703,8 @@ static int awacs_freqs[8] = {
 };
 static int awacs_freqs_ok[8] = { 1, 1, 1, 1, 1, 1, 1, 1 };
 
-static int awacs_set_frame_rate(int desired, int catch_r)
+static int
+awacs_set_frame_rate(int desired, int catch_r)
 {
        int tolerance, i = 8 ;
        /*
@@ -617,13 +728,9 @@ static int awacs_set_frame_rate(int desired, int catch_r)
        return dmasound.hard.speed;
 }
 
-static int burgundy_frame_rates = 1 ;
-static int burgundy_set_frame_rate(void)
+static int
+burgundy_set_frame_rate(void)
 {
-#ifdef DEBUG_DMASOUND
-if (burgundy_frame_rates > 1)
-       printk("dmasound_pmac: warning Burgundy had more than one frame rate\n");
-#endif
        awacs_rate_index = 0 ;
        awacs_reg[1] = (awacs_reg[1] & ~MASK_SAMPLERATE) ;
        /* XXX disable error interrupt on burgundy for now */
@@ -631,24 +738,24 @@ if (burgundy_frame_rates > 1)
        return 44100 ;
 }
 
-static int set_frame_rate(int desired, int catch_r)
+static int
+set_frame_rate(int desired, int catch_r)
 {
        switch (awacs_revision) {
                case AWACS_BURGUNDY:
-                       dmasound.hard.speed =
-                         burgundy_set_frame_rate();
+                       dmasound.hard.speed = burgundy_set_frame_rate();
                        break ;
                case AWACS_TUMBLER:
-                       dmasound.hard.speed =
-                         tumbler_set_frame_rate();
+               case AWACS_SNAPPER:
+                       dmasound.hard.speed = tas_set_frame_rate();
                        break ;
                case AWACS_DACA:
                        dmasound.hard.speed =
                          daca_set_frame_rate();
                        break ;
                default:
-                       dmasound.hard.speed =
-                         awacs_set_frame_rate(desired, catch_r);
+                       dmasound.hard.speed = awacs_set_frame_rate(desired,
+                                               catch_r);
                        break ;
        }
        return dmasound.hard.speed ;
@@ -698,11 +805,13 @@ static void PMacInit(void)
                dmasound.trans_write = &transAwacsExpand;
        dmasound.trans_read = &transAwacsNormalRead;
 
-       if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
-               out_le32(&awacs->byteswap, BS_VAL);
-       else
-               out_le32(&awacs->byteswap, 0);
-
+       if (awacs) {
+               if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
+                       out_le32(&awacs->byteswap, BS_VAL);
+               else
+                       out_le32(&awacs->byteswap, 0);
+       }
+       
        expand_bal = -dmasound.soft.speed;
 }
 
@@ -787,18 +896,28 @@ static int awacs_volume_setter(int volume, int n, int mute, int lshift)
 
 static int PMacSetVolume(int volume)
 {
-       return awacs_volume_setter(volume, 2, MASK_AMUTE, 6);
+       printk(KERN_WARNING "Bogus call to PMacSetVolume !\n");
+       return 0;
+}
+
+static void awacs_setup_for_beep(int speed)
+{
+       out_le32(&awacs->control,
+                (in_le32(&awacs->control) & ~0x1f00)
+                | ((speed > 0 ? speed : awacs_rate_index) << 8));
+
+       if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE) && speed == -1)
+               out_le32(&awacs->byteswap, BS_VAL);
+       else
+               out_le32(&awacs->byteswap, 0);
 }
 
+/* CHECK: how much of this *really* needs IRQs masked? */
 static void __PMacPlay(void)
 {
        volatile struct dbdma_cmd *cp;
        int next_frg, count;
-       unsigned long flags;
 
-       /* CHECK: how much of this *really* needs IRQs masked? */
-
-       spin_lock_irqsave(&dmasound.lock, flags);
        count = 300 ; /* > two cycles at the lowest sample rate */
 
        /* what we want to send next */
@@ -810,15 +929,8 @@ static void __PMacPlay(void)
                out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
                while ( (in_le32(&awacs_txdma->status) & RUN) && count--)
                        udelay(1);
-               /* FIXME: check that this is OK for other chip sets */
-               out_le32(&awacs->control,
-                        (in_le32(&awacs->control) & ~0x1f00)
-                        | (awacs_rate_index << 8));
-
-               if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
-                       out_le32(&awacs->byteswap, BS_VAL);
-               else
-                       out_le32(&awacs->byteswap, 0);
+               if (awacs)
+                       awacs_setup_for_beep(-1);
                out_le32(&awacs_txdma->cmdptr,
                         virt_to_bus(&(awacs_tx_cmds[next_frg])));
 
@@ -865,14 +977,18 @@ static void __PMacPlay(void)
                out_le32(&awacs_txdma->control, ((RUN|WAKE) << 16) + (RUN|WAKE));
                ++write_sq.active;
        }
-       spin_unlock_irqrestore(&dmasound.lock, flags);
 }
 
 static void PMacPlay(void)
 {
        LOCK();
-       if (!awacs_sleeping)
+       if (!awacs_sleeping) {
+               unsigned long flags;
+
+               spin_lock_irqsave(&dmasound.lock, flags);
                __PMacPlay();
+               spin_unlock_irqrestore(&dmasound.lock, flags);
+       }
        UNLOCK();
 }
 
@@ -919,6 +1035,7 @@ pmac_awacs_tx_intr(int irq, void *devid, struct pt_regs *regs)
 {
        int i = write_sq.front;
        int stat;
+       int i_nowrap = write_sq.front;
        volatile struct dbdma_cmd *cp;
        /* != 0 when we are dealing with a DEAD xfer */
        static int emergency_in_use;
@@ -976,6 +1093,7 @@ printk("dmasound_pmac: tx-irq: xfer died - patching it up...\n") ;
                        emergency_in_use = 0 ; /* done that */
                --write_sq.count;
                --write_sq.active;
+               i_nowrap++;
                if (++i >= write_sq.max_count)
                        i = 0;
        }
@@ -988,7 +1106,7 @@ printk("dmasound_pmac: tx-irq: xfer died - patching it up...\n") ;
        }
 
        /* if we used some data up then wake the writer to supply some more*/
-       if (i != write_sq.front)
+       if (i_nowrap != write_sq.front)
                WAKE_UP(write_sq.action_queue);
        write_sq.front = i;
 
@@ -1091,11 +1209,30 @@ static irqreturn_t
 pmac_awacs_intr(int irq, void *devid, struct pt_regs *regs)
 {
        int ctrl;
+       int status;
+       int r1;
+
        spin_lock(&dmasound.lock);
        ctrl = in_le32(&awacs->control);
+       status = in_le32(&awacs->codec_stat);
 
        if (ctrl & MASK_PORTCHG) {
-               /* do something when headphone is plugged/unplugged? */
+               /* tested on Screamer, should work on others too */
+               if (awacs_revision == AWACS_SCREAMER) {
+                       if (((status & MASK_HDPCONN) >> 3) && (hdp_connected == 0)) {
+                               hdp_connected = 1;
+                               
+                               r1 = awacs_reg[1] | MASK_SPKMUTE;
+                               awacs_reg[1] = r1;
+                               awacs_write(r1 | MASK_ADDR_MUTE);
+                       } else if (((status & MASK_HDPCONN) >> 3 == 0) && (hdp_connected == 1)) {
+                               hdp_connected = 0;
+                               
+                               r1 = awacs_reg[1] & ~MASK_SPKMUTE;
+                               awacs_reg[1] = r1;
+                               awacs_write(r1 | MASK_ADDR_MUTE);
+                       }
+               }
        }
        if (ctrl & MASK_CNTLERR) {
                int err = (in_le32(&awacs->codec_stat) & MASK_ERRCODE) >> 16;
@@ -1113,7 +1250,7 @@ static void
 awacs_write(int val)
 {
        int count = 300 ;
-       if (awacs_revision >= AWACS_DACA)
+       if (awacs_revision >= AWACS_DACA || !awacs)
                return ;
 
        while ((in_le32(&awacs->codec_ctrl) & MASK_NEWECMD) && count--)
@@ -1136,22 +1273,16 @@ static void awacs_nosound(unsigned long xx)
                out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
                while ((in_le32(&awacs_txdma->status) & RUN) && count--)
                        udelay(1);
-               /* FIXME: check this is OK for DACA, Tumbler */
-               out_le32(&awacs->control,
-                        (in_le32(&awacs->control) & ~0x1f00)
-                        | (awacs_rate_index << 8));
-               if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
-                       out_le32(&awacs->byteswap, BS_VAL);
-               else
-                       out_le32(&awacs->byteswap, 0);
+               if (awacs)
+                       awacs_setup_for_beep(-1);
                beep_playing = 0;
        }
        spin_unlock_irqrestore(&dmasound.lock, flags);
 }
 
 static struct timer_list beep_timer = TIMER_INITIALIZER(awacs_nosound, 0, 0);
-};
 
+#if 0 /* would need to go through the input layer in 2.6, later..  --hch */
 /* we generate the beep with a single dbdma command that loops a buffer
    forever - without generating interrupts.
    So, to stop it you have to stop dma output as per awacs_nosound.
@@ -1237,17 +1368,15 @@ static void awacs_mksound(unsigned int hz, unsigned int ticks)
                out_le32(&awacs_txdma->control, (RUN|WAKE|FLUSH|PAUSE) << 16);
                while ((in_le32(&awacs_txdma->status) & RUN) && count--)
                        udelay(1); /* timeout > 2 samples at lowest rate*/
-               /* FIXME: check this is OK on DACA, Tumbler */
-               out_le32(&awacs->control,
-                        (in_le32(&awacs->control) & ~0x1f00)
-                        | (beep_speed << 8));
-               out_le32(&awacs->byteswap, 0); /* force BE */
+               if (awacs)
+                       awacs_setup_for_beep(beep_speed);
                out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd));
                (void)in_le32(&awacs_txdma->status);
                out_le32(&awacs_txdma->control, RUN | (RUN << 16));
        }
        spin_unlock_irqrestore(&dmasound.lock, flags);
 }
+#endif
 
 /* used in init and for wake-up */
 
@@ -1267,10 +1396,12 @@ load_awacs(void)
                awacs_write(awacs_reg[1] + MASK_ADDR1);
                awacs_write(awacs_reg[7] + MASK_ADDR7);
        }
-       if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
-               out_le32(&awacs->byteswap, BS_VAL);
-       else
-               out_le32(&awacs->byteswap, 0);
+       if (awacs) {
+               if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
+                       out_le32(&awacs->byteswap, BS_VAL);
+               else
+                       out_le32(&awacs->byteswap, 0);
+       }
 }
 
 #ifdef CONFIG_PMAC_PBOOK
@@ -1280,6 +1411,8 @@ load_awacs(void)
 /* FIXME: sort out disabling/re-enabling of read stuff as well */
 static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when)
 {
+       unsigned long flags;
+
        switch (when) {
        case PBOOK_SLEEP_NOW:           
                LOCK();
@@ -1297,9 +1430,18 @@ static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when)
                /* stop rx - if going - a bit of a daft user... but */
                out_le32(&awacs_rxdma->control, (RUN|WAKE|FLUSH << 16));
                /* deny interrupts */
+               if (awacs)
+                       disable_irq(awacs_irq);
+               disable_irq(awacs_tx_irq);
+               disable_irq(awacs_rx_irq);
+               /* Chip specific sleep code */
                switch (awacs_revision) {
                        case AWACS_TUMBLER:
-                               tumbler_enter_sleep(); /* Stub for now */
+                       case AWACS_SNAPPER:
+                               write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol);
+                               write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol);
+                               tas_enter_sleep();
+                               write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol);
                                break ;
                        case AWACS_DACA:
                                daca_enter_sleep();
@@ -1312,17 +1454,14 @@ static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when)
                                out_le32(&awacs->control, 0x11) ;
                                break ;
                }
-               disable_irq(awacs_irq);
-               disable_irq(awacs_tx_irq);
-               disable_irq(awacs_rx_irq);
                /* Disable sound clock */
                pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 0);
                /* According to Darwin, we do that after turning off the sound
                 * chip clock. All this will have to be cleaned up once we properly
                 * parse the OF sound-objects
                 */
-               if (machine_is_compatible("PowerBook3,1") ||
-                   machine_is_compatible("PowerBook3,2")) {
+               if ((machine_is_compatible("PowerBook3,1") ||
+                   machine_is_compatible("PowerBook3,2")) && awacs) {
                        awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1;
                        awacs_write(MASK_ADDR1 | awacs_reg[1]);
                        wait_ms(200);
@@ -1331,8 +1470,8 @@ static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when)
        case PBOOK_WAKE:
                /* Enable sound clock */
                pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 1);
-               if (machine_is_compatible("PowerBook3,1") ||
-                   machine_is_compatible("PowerBook3,2")) {
+               if ((machine_is_compatible("PowerBook3,1") ||
+                   machine_is_compatible("PowerBook3,2")) && awacs) {
                        wait_ms(100);
                        awacs_reg[1] &= ~(MASK_PAROUT0 | MASK_PAROUT1);
                        awacs_write(MASK_ADDR1 | awacs_reg[1]);
@@ -1342,13 +1481,20 @@ static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when)
                /* restore settings */
                switch (awacs_revision) {
                        case AWACS_TUMBLER:
+                       case AWACS_SNAPPER:
+                               write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol);
+                               write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol);
+                               write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol);
+                               wait_ms(100);
+                               write_audio_gpio(gpio_audio_reset, !gpio_audio_reset_pol);
+                               wait_ms(150);
+                               tas_leave_sleep(); /* Stub for now */
                                headphone_intr(0,0,0);
-                               tumbler_leave_sleep(); /* Stub for now */
                                break;
                        case AWACS_DACA:
                                wait_ms(10); /* Check this !!! */
                                daca_leave_sleep();
-                               break ;         /* don't know how yet */
+                               break ;         /* dont know how yet */
                        case AWACS_BURGUNDY:
                                break ;
                        case AWACS_SCREAMER:
@@ -1358,17 +1504,20 @@ static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when)
                                break ;
                }
                /* Recalibrate chip */
-               if (awacs_revision == AWACS_SCREAMER)
+               if (awacs_revision == AWACS_SCREAMER && awacs)
                        awacs_recalibrate();
                /* Make sure dma is stopped */
                PMacSilence();
-               enable_irq(awacs_irq);
+               if (awacs)
+                       enable_irq(awacs_irq);
                enable_irq(awacs_tx_irq);
                enable_irq(awacs_rx_irq);
-               /* OK, allow ints back again */
-               out_le32(&awacs->control, MASK_IEPC
-                       | (awacs_rate_index << 8) | 0x11
-                        | (awacs_revision < AWACS_DACA ? MASK_IEE: 0));
+               if (awacs) {
+                       /* OK, allow ints back again */
+                       out_le32(&awacs->control, MASK_IEPC
+                               | (awacs_rate_index << 8) | 0x11
+                                | (awacs_revision < AWACS_DACA ? MASK_IEE: 0));
+               }
                if (macio_base && is_pbook_g3) {
                        /* FIXME: should restore the setup we had...*/
                        out_8(macio_base + 0x37, 3);
@@ -1384,7 +1533,9 @@ static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when)
                awacs_sleeping = 0;
                /* Resume pending sounds. */
                /* we don't try to restart input... */
+               spin_lock_irqsave(&dmasound.lock, flags);
                __PMacPlay();
+               spin_unlock_irqrestore(&dmasound.lock, flags);
                UNLOCK();
        }
        return PBOOK_SLEEP_OK;
@@ -1956,7 +2107,7 @@ static int burgundy_mixer_ioctl(u_int cmd, u_long arg)
        case SOUND_MIXER_READ_SPEAKER:
                data = awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER);
                data = (((data & 0xf)*100)/16) + ((((data>>4)*100)/16)<<8);
-               rc = IOCTL_OUT(arg, ~data);
+               rc = IOCTL_OUT(arg, (~data) & 0x0000ffff);
                break;
        case SOUND_MIXER_WRITE_ALTPCM:  /* really bell volume */
                IOCTL_IN(arg, data);
@@ -2011,89 +2162,6 @@ static int burgundy_mixer_ioctl(u_int cmd, u_long arg)
        return rc;
 }
 
-static int tumbler_mixer_ioctl(u_int cmd, u_long arg)
-{
-       int data;
-       int rc;
-
-       /* We are, we are, we are... Tumbler (and very dumb) */
-       /* Ok, we're not THAT dumb anymore, but still pretty dumb :-) */
-
-       switch(cmd) {
-       case SOUND_MIXER_READ_DEVMASK:
-               data =  SOUND_MASK_VOLUME | SOUND_MASK_ALTPCM |
-                       SOUND_MASK_BASS | SOUND_MASK_TREBLE |
-                       SOUND_MASK_PCM;
-               rc = IOCTL_OUT(arg, data);
-               break;
-       case SOUND_MIXER_READ_RECMASK:
-               data = 0;
-               rc = IOCTL_OUT(arg, data);
-               break;
-       case SOUND_MIXER_READ_RECSRC:
-               data = 0;
-               rc = IOCTL_OUT(arg, data);
-               break;
-       case SOUND_MIXER_WRITE_RECSRC:
-               IOCTL_IN(arg, data);
-               data =0;
-               rc = IOCTL_OUT(arg, data);
-               break;
-       case SOUND_MIXER_READ_STEREODEVS:
-               data = SOUND_MASK_VOLUME | SOUND_MASK_PCM;
-               rc = IOCTL_OUT(arg, data);
-               break;
-       case SOUND_MIXER_READ_CAPS:
-               rc = IOCTL_OUT(arg, 0);
-               break;
-       case SOUND_MIXER_WRITE_BASS:
-               IOCTL_IN(arg, data);
-               tumbler_set_bass(data);
-               /* Fall through */
-       case SOUND_MIXER_READ_BASS:
-               tumbler_get_bass(&data);
-               rc = IOCTL_OUT(arg, data);
-               break;
-       case SOUND_MIXER_WRITE_TREBLE:
-               IOCTL_IN(arg, data);
-               tumbler_set_treble(data);
-               /* Fall through */
-       case SOUND_MIXER_READ_TREBLE:
-               tumbler_get_treble(&data);
-               rc = IOCTL_OUT(arg, data);
-               break;
-       case SOUND_MIXER_WRITE_PCM:
-               IOCTL_IN(arg, data);
-               tumbler_set_pcm_lvl(data);
-               /* Fall through */
-       case SOUND_MIXER_READ_PCM:
-               tumbler_get_pcm_lvl(&data);
-               IOCTL_OUT(arg, data);
-               break;
-       case SOUND_MIXER_WRITE_VOLUME:
-               IOCTL_IN(arg, data);
-               tumbler_set_volume(data, data);
-               /* Fall through */
-       case SOUND_MIXER_READ_VOLUME:
-               tumbler_get_volume(& data, &data);
-               rc = IOCTL_OUT(arg, data);
-               break;
-       case SOUND_MIXER_WRITE_ALTPCM:  /* really bell volume */
-               IOCTL_IN(arg, data);
-               beep_vol = data & 0xff;
-               /* fall through */
-       case SOUND_MIXER_READ_ALTPCM:
-               rc = IOCTL_OUT(arg, beep_vol);
-               break;
-       case SOUND_MIXER_OUTMASK:
-       case SOUND_MIXER_OUTSRC:
-       default:
-               rc = -EINVAL;
-       }
-
-       return rc;
-}
-
 static int daca_mixer_ioctl(u_int cmd, u_long arg)
 {
        int data;
@@ -2158,7 +2226,8 @@ static int PMacMixerIoctl(u_int cmd, u_long arg)
                        rc = daca_mixer_ioctl(cmd, arg);
                        break;
                case AWACS_TUMBLER:
-                       rc = tumbler_mixer_ioctl(cmd, arg);
+               case AWACS_SNAPPER:
+                       rc = tas_mixer_ioctl(cmd, arg);
                        break ;
                default: /* ;-)) */
                        rc = awacs_mixer_ioctl(cmd, arg);
@@ -2175,7 +2244,9 @@ static void PMacMixerInit(void)
                case AWACS_TUMBLER:
                  printk("AE-Init tumbler mixer\n");
                  break ;
-                 
+               case AWACS_SNAPPER:
+                 printk("AE-Init snapper mixer\n");
+                 break ;
                case AWACS_DACA:
                case AWACS_BURGUNDY:
                        break ; /* don't know yet */
@@ -2366,12 +2437,12 @@ static int PMacStateInfo(char *b, size_t sp)
                        len += sprintf(b,"44100 ") ;
                        break ;
                case AWACS_TUMBLER:
-                       for (i=0; i<2; i++){
-                               if (tumbler_freqs_ok[i])
-                                       len += sprintf(b+len,"%d ", tumbler_freqs[i]) ;
+               case AWACS_SNAPPER:
+                       for (i=0; i<1; i++){
+                               if (tas_freqs_ok[i])
+                                       len += sprintf(b+len,"%d ", tas_freqs[i]) ;
                        }
                        break ;
-
                case AWACS_AWACS:
                case AWACS_SCREAMER:
                default:
@@ -2472,8 +2543,8 @@ set_model(void)
   code that looks for chip properties knows how to go about it.
 */
 
-static struct device_node
-__init *get_snd_io_node(void)
+static struct device_node* __init
+get_snd_io_node(void)
 {
        struct device_node *np = NULL;
 
@@ -2494,7 +2565,7 @@ __init *get_snd_io_node(void)
         * this seems to be what iBooks (& Tumbler) have.
         */
        if (np == NULL)
-               np = find_devices("i2s-a");
+               np = i2s_node = find_devices("i2s-a");
 
        /* if we didn't find this - perhaps we are on an early model
         * which _only_ has an 'awacs' node
@@ -2514,23 +2585,22 @@ __init *get_snd_io_node(void)
    we have to deduce the info other ways for these.
 */
 
-static struct device_node
-__init *get_snd_info_node(struct device_node *io)
+static struct device_node* __init
+get_snd_info_node(struct device_node *io)
 {
        struct device_node *info;
 
        info = find_devices("sound");
-       while (info != 0 && info->parent != io)
+       while (info && info->parent != io)
                info = info->next;
-
-       return info ;
+       return info;
 }
 
 /* Find out what type of codec we have.
 */
 
-static int
-__init get_codec_type(struct device_node *info)
+static int __init
+get_codec_type(struct device_node *info)
 {
        /* already set if pre-davbus model and info will be NULL */
        int codec = awacs_revision ;
@@ -2547,14 +2617,16 @@ __init get_codec_type(struct device_node *info)
                        codec = AWACS_DACA;
                if (device_is_compatible(info, "tumbler"))
                        codec = AWACS_TUMBLER;
+               if (device_is_compatible(info, "snapper"))
+                       codec = AWACS_SNAPPER;
        }
        return codec ;
 }
 
 /* find out what type, if any, of expansion card we have
 */
-static void
-__init get_expansion_type(void)
+static void __init
+get_expansion_type(void)
 {
        if (find_devices("perch") != NULL)
                has_perch = 1;
@@ -2572,8 +2644,8 @@ __init get_expansion_type(void)
  *       Set dmasound.mach.max_dsp_rate on the basis of these routines.
 */
 
-static void
-__init init_awacs_frame_rates(unsigned int *prop, unsigned int l)
+static void __init
+awacs_init_frame_rates(unsigned int *prop, unsigned int l)
 {
        int i ;
        if (prop) {
@@ -2595,31 +2667,8 @@ __init init_awacs_frame_rates(unsigned int *prop, unsigned int l)
        /* else we assume that all the rates are available */
 }
 
-static void
-__init init_tumbler_frame_rates(unsigned int *prop, unsigned int l)
-{
-       int i ;
-       if (prop) {
-               for (i=0; i<2; i++)
-                       tumbler_freqs_ok[i] = 0;
-               for (l /= sizeof(int); l > 0; --l) {
-                       unsigned int r = *prop++;
-                       /* Apple 'Fixed' format */
-                       if (r >= 0x10000)
-                               r >>= 16;
-                       for (i = 0; i < 2; ++i) {
-                               if (r == tumbler_freqs[i]) {
-                                       tumbler_freqs_ok[i] = 1;
-                                       break;
-                               }
-                       }
-               }
-       }
-       /* else we assume that all the rates are available */
-}
-
-static void
-__init init_burgundy_frame_rates(unsigned int *prop, unsigned int l)
+static void __init
+burgundy_init_frame_rates(unsigned int *prop, unsigned int l)
 {
        int temp[9] ;
        int i = 0 ;
@@ -2644,8 +2693,8 @@ if (i > 1){
 #endif
 }
 
-static void
-__init init_daca_frame_rates(unsigned int *prop, unsigned int l)
+static void __init
+daca_init_frame_rates(unsigned int *prop, unsigned int l)
 {
        int temp[9] ;
        int i = 0 ;
@@ -2671,21 +2720,22 @@ if (i > 1){
 #endif
 }
 
-static void
-__init init_frame_rates(unsigned int *prop, unsigned int l)
+static void __init
+init_frame_rates(unsigned int *prop, unsigned int l)
 {
-       switch (awacs_revision){
+       switch (awacs_revision) {
                case AWACS_TUMBLER:
-                       init_tumbler_frame_rates(prop, l);
+               case AWACS_SNAPPER:
+                       tas_init_frame_rates(prop, l);
                        break ;
                case AWACS_DACA:
-                       init_daca_frame_rates(prop, l);
+                       daca_init_frame_rates(prop, l);
                        break ;
                case AWACS_BURGUNDY:
-                       init_burgundy_frame_rates(prop, l);
+                       burgundy_init_frame_rates(prop, l);
                        break ;
-               default: /* ;-))) */
-                       init_awacs_frame_rates(prop, l);
+               default:
+                       awacs_init_frame_rates(prop, l);
                        break ;
        }
 }
@@ -2693,11 +2743,11 @@ __init init_frame_rates(unsigned int *prop, unsigned int l)
 /* find things/machines that can't do mac-io byteswap
 */
 
-static void
-__init set_hw_byteswap(struct device_node *io)
+static void __init
+set_hw_byteswap(struct device_node *io)
 {
        struct device_node *mio ;
-       unsigned int *p, kl = 0 ;
+       unsigned int kl = 0 ;
 
        /* if seems that Keylargo can't byte-swap  */
 
@@ -2744,9 +2794,6 @@ __init setup_beep(void)
                if( beep_dbdma_cmd_space ) kfree(beep_dbdma_cmd_space) ;
                return -ENOMEM ;
        }
-       /* OK, we should be safe to claim the mksound vector now */
-       orig_mksound = kd_mksound;
-       kd_mksound = awacs_mksound;
        return 0 ;
 }
 
@@ -2842,23 +2889,26 @@ printk("dmasound_pmac: couldn't find a Codec we can handle\n");
        }
 
        /* all OF versions I've seen use this value */
-       awacs = (volatile struct awacs_regs *)
-               ioremap(io->addrs[0].address, 0x1000);
+       if (i2s_node)
+               i2s = (u32 *)ioremap(io->addrs[0].address, 0x1000);
+       else
+               awacs = (volatile struct awacs_regs *)
+                       ioremap(io->addrs[0].address, 0x1000);
        awacs_txdma = (volatile struct dbdma_regs *)
                ioremap(io->addrs[1].address, 0x100);
        awacs_rxdma = (volatile struct dbdma_regs *)
                ioremap(io->addrs[2].address, 0x100);
 
-#ifdef CONFIG_PMAC_PBOOK
        /* first of all make sure that the chip is powered up....*/
        pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, io, 0, 1);
-       if (awacs_revision == AWACS_SCREAMER)
+       if (awacs_revision == AWACS_SCREAMER && awacs)
                awacs_recalibrate();
-#endif
+
        awacs_irq = io->intrs[0].line;
        awacs_tx_irq = io->intrs[1].line;
        awacs_rx_irq = io->intrs[2].line;
 
+       /* Hack for legacy crap that will be killed someday */
        awacs_node = io;
 
        /* if we have an awacs or screamer - probe the chip to make
@@ -2909,8 +2959,9 @@ printk("dmasound_pmac: Awacs/Screamer Codec Mfct: %d Rev %d\n", mfg, rev);
                /* if it's there use it to set up frame rates */
                init_frame_rates(prop, l) ;
        }
-       
-       out_le32(&awacs->control, 0x11); /* set everything quiesent */
+
+       if (awacs)
+               out_le32(&awacs->control, 0x11); /* set everything quiesent */
 
        set_hw_byteswap(io) ; /* figure out if the h/w can do it */
 
@@ -2942,25 +2993,27 @@ printk("dmasound_pmac: Awacs/Screamer Codec Mfct: %d Rev %d\n", mfg, rev);
 
        switch (awacs_revision) {
                case AWACS_TUMBLER:
-#ifdef CONFIG_KMOD
-                       request_module("i2c-keywest");
-#endif /* CONFIG_KMOD */       
-                       awacs_tumbler_init();
-                       tas_init();
+                        tas_register_driver(&tas3001c_hooks);
+                       tas_init(I2C_DRIVERID_TAS3001C, I2C_DRIVERNAME_TAS3001C);
+                       tas_dmasound_init();
+                       tas_post_init();
                        break ;
+               case AWACS_SNAPPER:
+                        tas_register_driver(&tas3004_hooks);
+                       tas_init(I2C_DRIVERID_TAS3004,I2C_DRIVERNAME_TAS3004);
+                       tas_dmasound_init();
+                       tas_post_init();
+                       break;
                case AWACS_DACA:
-#ifdef CONFIG_KMOD
-                       request_module("i2c-keywest");
-#endif /* CONFIG_KMOD */
                        daca_init();
-                       break ;         /* don't know how yet */
+                       break;  
                case AWACS_BURGUNDY:
                        awacs_burgundy_init();
                        break ;
                case AWACS_SCREAMER:
                case AWACS_AWACS:
                default:
-                       load_awacs() ;
+                       load_awacs();
                        break ;
        }
 
@@ -3031,11 +3084,15 @@ printk("dmasound_pmac: Awacs/Screamer Codec Mfct: %d Rev %d\n", mfg, rev);
                dmasound.mach.hardware_afmts = AFMT_S16_BE ;
 
        /* shut out chips that do output only.
-          may need to extend this to machines which have no inputs - even tho'
-          they use screamer - IIRC one of the powerbooks is like this.
-       */
+        * may need to extend this to machines which have no inputs - even tho'
+        * they use screamer - IIRC one of the powerbooks is like this.
+        *
+        * FIXME: Actually, some TUMBLER and SNAPPER do have inputs...  
+        */
 
-       if (awacs_revision != AWACS_TUMBLER && awacs_revision != AWACS_DACA) {
+       if (awacs_revision != AWACS_TUMBLER &&
+            awacs_revision != AWACS_SNAPPER &&
+            awacs_revision != AWACS_DACA) {
                dmasound.mach.capabilities = DSP_CAP_DUPLEX ;
                dmasound.mach.record = PMacRecord ;
        }
@@ -3053,6 +3110,9 @@ printk("dmasound_pmac: Awacs/Screamer Codec Mfct: %d Rev %d\n", mfg, rev);
                case AWACS_TUMBLER:
                        sprintf(awacs_name, "PowerMac Tumbler ") ;
                        break ;
+               case AWACS_SNAPPER:
+                       sprintf(awacs_name, "PowerMac Snapper ") ;
+                       break ;
                case AWACS_SCREAMER:
                        sprintf(awacs_name, "PowerMac Screamer ") ;
                        break ;
@@ -3069,7 +3129,8 @@ static void __exit dmasound_awacs_cleanup(void)
 {
        switch (awacs_revision) {
                case AWACS_TUMBLER:
-                       awacs_tumbler_cleanup();
+               case AWACS_SNAPPER:
+                       tas_dmasound_cleanup();
                        tas_cleanup();
                        break ;
                case AWACS_DACA:
diff --git a/sound/oss/dmasound/tas3001c.c b/sound/oss/dmasound/tas3001c.c
new file mode 100644 (file)
index 0000000..0185675
--- /dev/null
@@ -0,0 +1,861 @@
+/*
+ * 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/version.h>
+#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;
+
+       if (copy_from_user((void *)&biquad, (const void *)arg, 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((void *)arg, (const void *)&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;
+
+       memset(sync_required,0,sizeof(sync_required));
+
+       if (copy_from_user((void *)&filter_count,
+                          (const void *)arg + offsetof(struct tas_biquad_ctrl_list_t,filter_count),
+                          sizeof(int))) {
+               return -EFAULT;
+       }
+
+       if (copy_from_user((void *)&flags,
+                          (const void *)arg + offsetof(struct tas_biquad_ctrl_list_t,flags),
+                          sizeof(int))) {
+               return -EFAULT;
+       }
+
+       if (cmd & SIOC_IN) {
+       }
+
+       for (i=0; i < filter_count; i++) {
+               if (copy_from_user((void *)&biquad,
+                                  (const void *)arg + offsetof(struct tas_biquad_ctrl_list_t, 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((void *)arg + offsetof(struct tas_biquad_ctrl_list_t, biquads[i]),
+                                        (const void *)&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;
+
+       if (copy_from_user((void *)&drce_ctrl,
+                          (const void *)arg,
+                          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((void *)arg,
+                                (const void *)&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)
+{
+       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, (uint *)(arg));
+               return 0;
+
+       case TAS_READ_EQ_CHANNEL_COUNT:
+               put_user(TAS3001C_BIQUAD_CHANNEL_COUNT, (uint *)(arg));
+               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, (uint *)(arg));
+               return 0;
+
+       case TAS_READ_DRCE_MIN:
+       case TAS_READ_DRCE_MAX: {
+               struct tas_drce_ctrl_t drce_ctrl;
+
+               if (copy_from_user((void *)&drce_ctrl,
+                                  (const void *)arg,
+                                  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((void *)arg,
+                                (const void *)&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
+};
diff --git a/sound/oss/dmasound/tas3001c.h b/sound/oss/dmasound/tas3001c.h
new file mode 100644 (file)
index 0000000..3660da3
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Header file for the i2c/i2s based TA3001c sound chip used
+ * on some Apple hardware. Also known as "tumbler".
+ *
+ *  This file is subject to the terms and conditions of the GNU General Public
+ *  License.  See the file COPYING in the main directory of this archive
+ *  for more details.
+ *
+ * Written by Christopher C. Chimelis <chris@debian.org>
+ */
+
+#ifndef _TAS3001C_H_
+#define _TAS3001C_H_
+
+#include <linux/types.h>
+
+#include "tas_common.h"
+#include "tas_eq_prefs.h"
+
+/*
+ * Macros that correspond to the registers that we write to
+ * when setting the various values.
+ */
+
+#define TAS3001C_VERSION       "0.3"
+#define TAS3001C_DATE          "20011214"
+
+#define I2C_DRIVERNAME_TAS3001C "TAS3001c driver V " TAS3001C_VERSION
+#define I2C_DRIVERID_TAS3001C   (I2C_DRIVERID_TAS_BASE+0)
+
+extern  struct tas_driver_hooks_t tas3001c_hooks;
+extern struct tas_gain_t tas3001c_gain;
+extern struct tas_eq_pref_t *tas3001c_eq_prefs[];
+
+enum tas3001c_reg_t {
+  TAS3001C_REG_MCR                    = 0x01,
+  TAS3001C_REG_DRC                    = 0x02,
+
+  TAS3001C_REG_VOLUME                 = 0x04,
+  TAS3001C_REG_TREBLE                 = 0x05,
+  TAS3001C_REG_BASS                   = 0x06,
+  TAS3001C_REG_MIXER1                 = 0x07,
+  TAS3001C_REG_MIXER2                 = 0x08,
+
+  TAS3001C_REG_LEFT_BIQUAD0           = 0x0a,
+  TAS3001C_REG_LEFT_BIQUAD1           = 0x0b,
+  TAS3001C_REG_LEFT_BIQUAD2           = 0x0c,
+  TAS3001C_REG_LEFT_BIQUAD3           = 0x0d,
+  TAS3001C_REG_LEFT_BIQUAD4           = 0x0e,
+  TAS3001C_REG_LEFT_BIQUAD5           = 0x0f,
+  TAS3001C_REG_LEFT_BIQUAD6           = 0x10,
+  
+  TAS3001C_REG_RIGHT_BIQUAD0          = 0x13,
+  TAS3001C_REG_RIGHT_BIQUAD1          = 0x14,
+  TAS3001C_REG_RIGHT_BIQUAD2          = 0x15,
+  TAS3001C_REG_RIGHT_BIQUAD3          = 0x16,
+  TAS3001C_REG_RIGHT_BIQUAD4          = 0x17,
+  TAS3001C_REG_RIGHT_BIQUAD5          = 0x18,
+  TAS3001C_REG_RIGHT_BIQUAD6          = 0x19,
+
+  TAS3001C_REG_MAX                    = 0x20
+};
+
+#endif /* _TAS3001C_H_ */
diff --git a/sound/oss/dmasound/tas3001c_tables.c b/sound/oss/dmasound/tas3001c_tables.c
new file mode 100644 (file)
index 0000000..681e913
--- /dev/null
@@ -0,0 +1,375 @@
+#include "tas_common.h"
+#include "tas_eq_prefs.h"
+
+static struct tas_drce_t eqp_0e_2_1_drce = {
+  enable:    1,
+  above:     { val: 3.0 * (1<<8), expand: 0 },
+  below:     { val: 1.0 * (1<<8), expand: 0 },
+  threshold: -15.33  * (1<<8),
+  energy:    2.4     * (1<<12),
+  attack:    0.013   * (1<<12),
+  decay:     0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_0e_2_1_biquads[]={
+  { channel: 0, filter: 0, data: { coeff: { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } },
+  { channel: 0, filter: 1, data: { coeff: { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } },
+  { channel: 0, filter: 2, data: { coeff: { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } },
+  { channel: 0, filter: 3, data: { coeff: { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } },
+  { channel: 0, filter: 4, data: { coeff: { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } },
+  { channel: 0, filter: 5, data: { coeff: { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } },
+
+  { channel: 1, filter: 0, data: { coeff: { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } },
+  { channel: 1, filter: 1, data: { coeff: { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } },
+  { channel: 1, filter: 2, data: { coeff: { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } },
+  { channel: 1, filter: 3, data: { coeff: { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } },
+  { channel: 1, filter: 4, data: { coeff: { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } },
+  { channel: 1, filter: 5, data: { coeff: { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } },
+};
+
+static struct tas_eq_pref_t eqp_0e_2_1 = {
+  sample_rate:  44100,
+  device_id:    0x0e,
+  output_id:    TAS_OUTPUT_EXTERNAL_SPKR,
+  speaker_id:   0x01,
+
+  drce:         &eqp_0e_2_1_drce,
+
+  filter_count: 12,
+  biquads:      eqp_0e_2_1_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_10_1_0_drce={
+  enable:    1,
+  above:     { val: 3.0 * (1<<8), expand: 0 },
+  below:     { val: 1.0 * (1<<8), expand: 0 },
+  threshold: -12.46  * (1<<8),
+  energy:    2.4     * (1<<12),
+  attack:    0.013   * (1<<12),
+  decay:     0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_10_1_0_biquads[]={
+  { channel: 0, filter: 0, data: { coeff: { 0x0F4A12, 0xE16BDA, 0x0F4A12, 0xE173F0, 0x0E9C3A } } },
+  { channel: 0, filter: 1, data: { coeff: { 0x02DD54, 0x05BAA8, 0x02DD54, 0xF8001D, 0x037532 } } },
+  { channel: 0, filter: 2, data: { coeff: { 0x0E2FC7, 0xE4D5DC, 0x0D7477, 0xE4D5DC, 0x0BA43F } } },
+  { channel: 0, filter: 3, data: { coeff: { 0x0E7899, 0xE67CCA, 0x0D0E93, 0xE67CCA, 0x0B872D } } },
+  { channel: 0, filter: 4, data: { coeff: { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } },
+  { channel: 0, filter: 5, data: { coeff: { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } },
+
+  { channel: 1, filter: 0, data: { coeff: { 0x0F4A12, 0xE16BDA, 0x0F4A12, 0xE173F0, 0x0E9C3A } } },
+  { channel: 1, filter: 1, data: { coeff: { 0x02DD54, 0x05BAA8, 0x02DD54, 0xF8001D, 0x037532 } } },
+  { channel: 1, filter: 2, data: { coeff: { 0x0E2FC7, 0xE4D5DC, 0x0D7477, 0xE4D5DC, 0x0BA43F } } },
+  { channel: 1, filter: 3, data: { coeff: { 0x0E7899, 0xE67CCA, 0x0D0E93, 0xE67CCA, 0x0B872D } } },
+  { channel: 1, filter: 4, data: { coeff: { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } },
+  { channel: 1, filter: 5, data: { coeff: { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } },
+};
+
+static struct tas_eq_pref_t eqp_10_1_0 = {
+  sample_rate:  44100,
+  device_id:    0x10,
+  output_id:    TAS_OUTPUT_INTERNAL_SPKR,
+  speaker_id:   0x00,
+
+  drce:         &eqp_10_1_0_drce,
+
+  filter_count: 12,
+  biquads:      eqp_10_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_15_2_1_drce={
+  enable:    1,
+  above:     { val: 3.0 * (1<<8), expand: 0 },
+  below:     { val: 1.0 * (1<<8), expand: 0 },
+  threshold: -15.33  * (1<<8),
+  energy:    2.4     * (1<<12),
+  attack:    0.013   * (1<<12),
+  decay:     0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_15_2_1_biquads[]={
+  { channel: 0, filter: 0, data: { coeff: { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } },
+  { channel: 0, filter: 1, data: { coeff: { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } },
+  { channel: 0, filter: 2, data: { coeff: { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } },
+  { channel: 0, filter: 3, data: { coeff: { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } },
+  { channel: 0, filter: 4, data: { coeff: { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } },
+  { channel: 0, filter: 5, data: { coeff: { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } },
+
+  { channel: 1, filter: 0, data: { coeff: { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } },
+  { channel: 1, filter: 1, data: { coeff: { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } },
+  { channel: 1, filter: 2, data: { coeff: { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } },
+  { channel: 1, filter: 3, data: { coeff: { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } },
+  { channel: 1, filter: 4, data: { coeff: { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } },
+  { channel: 1, filter: 5, data: { coeff: { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } },
+};
+
+static struct tas_eq_pref_t eqp_15_2_1 = {
+  sample_rate:  44100,
+  device_id:    0x15,
+  output_id:    TAS_OUTPUT_EXTERNAL_SPKR,
+  speaker_id:   0x01,
+
+  drce:         &eqp_15_2_1_drce,
+
+  filter_count: 12,
+  biquads:      eqp_15_2_1_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_15_1_0_drce={
+  enable:    1,
+  above:     { val: 3.0 * (1<<8), expand: 0 },
+  below:     { val: 1.0 * (1<<8), expand: 0 },
+  threshold: 0.0     * (1<<8),
+  energy:    2.4     * (1<<12),
+  attack:    0.013   * (1<<12),
+  decay:     0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_15_1_0_biquads[]={
+  { channel: 0, filter: 0, data: { coeff: { 0x0FAD08, 0xE0A5EF, 0x0FAD08, 0xE0A79D, 0x0F5BBE } } },
+  { channel: 0, filter: 1, data: { coeff: { 0x04B38D, 0x09671B, 0x04B38D, 0x000F71, 0x02BEC5 } } },
+  { channel: 0, filter: 2, data: { coeff: { 0x0FDD32, 0xE0A56F, 0x0F8A69, 0xE0A56F, 0x0F679C } } },
+  { channel: 0, filter: 3, data: { coeff: { 0x0FD284, 0xE135FB, 0x0F2161, 0xE135FB, 0x0EF3E5 } } },
+  { channel: 0, filter: 4, data: { coeff: { 0x0E81B1, 0xE6283F, 0x0CE49D, 0xE6283F, 0x0B664F } } },
+  { channel: 0, filter: 5, data: { coeff: { 0x0F2D62, 0xE98797, 0x0D1E19, 0xE98797, 0x0C4B7B } } },
+
+  { channel: 1, filter: 0, data: { coeff: { 0x0FAD08, 0xE0A5EF, 0x0FAD08, 0xE0A79D, 0x0F5BBE } } },
+  { channel: 1, filter: 1, data: { coeff: { 0x04B38D, 0x09671B, 0x04B38D, 0x000F71, 0x02BEC5 } } },
+  { channel: 1, filter: 2, data: { coeff: { 0x0FDD32, 0xE0A56F, 0x0F8A69, 0xE0A56F, 0x0F679C } } },
+  { channel: 1, filter: 3, data: { coeff: { 0x0FD284, 0xE135FB, 0x0F2161, 0xE135FB, 0x0EF3E5 } } },
+  { channel: 1, filter: 4, data: { coeff: { 0x0E81B1, 0xE6283F, 0x0CE49D, 0xE6283F, 0x0B664F } } },
+  { channel: 1, filter: 5, data: { coeff: { 0x0F2D62, 0xE98797, 0x0D1E19, 0xE98797, 0x0C4B7B } } },
+};
+
+static struct tas_eq_pref_t eqp_15_1_0 = {
+  sample_rate:  44100,
+  device_id:    0x15,
+  output_id:    TAS_OUTPUT_INTERNAL_SPKR,
+  speaker_id:   0x00,
+
+  drce:         &eqp_15_1_0_drce,
+
+  filter_count: 12,
+  biquads:      eqp_15_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_0f_2_1_drce={
+  enable:    1,
+  above:     { val: 3.0 * (1<<8), expand: 0 },
+  below:     { val: 1.0 * (1<<8), expand: 0 },
+  threshold: -15.33  * (1<<8),
+  energy:    2.4     * (1<<12),
+  attack:    0.013   * (1<<12),
+  decay:     0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_0f_2_1_biquads[]={
+  { channel: 0, filter: 0, data: { coeff: { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } },
+  { channel: 0, filter: 1, data: { coeff: { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } },
+  { channel: 0, filter: 2, data: { coeff: { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } },
+  { channel: 0, filter: 3, data: { coeff: { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } },
+  { channel: 0, filter: 4, data: { coeff: { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } },
+  { channel: 0, filter: 5, data: { coeff: { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } },
+
+  { channel: 1, filter: 0, data: { coeff: { 0x0FE143, 0xE05204, 0x0FCCC5, 0xE05266, 0x0FAE6B } } },
+  { channel: 1, filter: 1, data: { coeff: { 0x102383, 0xE03A03, 0x0FA325, 0xE03A03, 0x0FC6A8 } } },
+  { channel: 1, filter: 2, data: { coeff: { 0x0FF2AB, 0xE06285, 0x0FB20A, 0xE06285, 0x0FA4B5 } } },
+  { channel: 1, filter: 3, data: { coeff: { 0x0F544D, 0xE35971, 0x0D8F3A, 0xE35971, 0x0CE388 } } },
+  { channel: 1, filter: 4, data: { coeff: { 0x13E1D3, 0xF3ECB5, 0x042227, 0xF3ECB5, 0x0803FA } } },
+  { channel: 1, filter: 5, data: { coeff: { 0x0AC119, 0x034181, 0x078AB1, 0x034181, 0x024BCA } } },
+};
+
+static struct tas_eq_pref_t eqp_0f_2_1 = {
+  sample_rate:  44100,
+  device_id:    0x0f,
+  output_id:    TAS_OUTPUT_EXTERNAL_SPKR,
+  speaker_id:   0x01,
+
+  drce:         &eqp_0f_2_1_drce,
+
+  filter_count: 12,
+  biquads:      eqp_0f_2_1_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_0f_1_0_drce={
+  enable:    1,
+  above:     { val: 3.0 * (1<<8), expand: 0 },
+  below:     { val: 1.0 * (1<<8), expand: 0 },
+  threshold: -15.33  * (1<<8),
+  energy:    2.4     * (1<<12),
+  attack:    0.013   * (1<<12),
+  decay:     0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_0f_1_0_biquads[]={
+  { channel: 0, filter: 0, data: { coeff: { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } },
+  { channel: 0, filter: 1, data: { coeff: { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } },
+  { channel: 0, filter: 2, data: { coeff: { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } },
+  { channel: 0, filter: 3, data: { coeff: { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } },
+  { channel: 0, filter: 4, data: { coeff: { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } },
+  { channel: 0, filter: 5, data: { coeff: { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } },
+
+  { channel: 1, filter: 0, data: { coeff: { 0x0FCAD3, 0xE06A58, 0x0FCAD3, 0xE06B09, 0x0F9657 } } },
+  { channel: 1, filter: 1, data: { coeff: { 0x041731, 0x082E63, 0x041731, 0xFD8D08, 0x02CFBD } } },
+  { channel: 1, filter: 2, data: { coeff: { 0x0FFDC7, 0xE0524C, 0x0FBFAA, 0xE0524C, 0x0FBD72 } } },
+  { channel: 1, filter: 3, data: { coeff: { 0x0F3D35, 0xE228CA, 0x0EC7B2, 0xE228CA, 0x0E04E8 } } },
+  { channel: 1, filter: 4, data: { coeff: { 0x0FCEBF, 0xE181C2, 0x0F2656, 0xE181C2, 0x0EF516 } } },
+  { channel: 1, filter: 5, data: { coeff: { 0x0EC417, 0x073E22, 0x0B0633, 0x073E22, 0x09CA4A } } },
+};
+
+static struct tas_eq_pref_t eqp_0f_1_0 = {
+  sample_rate:  44100,
+  device_id:    0x0f,
+  output_id:    TAS_OUTPUT_INTERNAL_SPKR,
+  speaker_id:   0x00,
+
+  drce:         &eqp_0f_1_0_drce,
+
+  filter_count: 12,
+  biquads:      eqp_0f_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static uint tas3001c_master_tab[]={
+              0x0,       0x75,       0x9c,       0xbb,
+             0xdb,       0xfb,      0x11e,      0x143,
+            0x16b,      0x196,      0x1c3,      0x1f5,
+            0x229,      0x263,      0x29f,      0x2e1,
+            0x328,      0x373,      0x3c5,      0x41b,
+            0x478,      0x4dc,      0x547,      0x5b8,
+            0x633,      0x6b5,      0x740,      0x7d5,
+            0x873,      0x91c,      0x9d2,      0xa92,
+            0xb5e,      0xc39,      0xd22,      0xe19,
+            0xf20,     0x1037,     0x1161,     0x129e,
+           0x13ed,     0x1551,     0x16ca,     0x185d,
+           0x1a08,     0x1bcc,     0x1dac,     0x1fa7,
+           0x21c1,     0x23fa,     0x2655,     0x28d6,
+           0x2b7c,     0x2e4a,     0x3141,     0x3464,
+           0x37b4,     0x3b35,     0x3ee9,     0x42d3,
+           0x46f6,     0x4b53,     0x4ff0,     0x54ce,
+           0x59f2,     0x5f5f,     0x6519,     0x6b24,
+           0x7183,     0x783c,     0x7f53,     0x86cc,
+           0x8ead,     0x96fa,     0x9fba,     0xa8f2,
+           0xb2a7,     0xbce1,     0xc7a5,     0xd2fa,
+           0xdee8,     0xeb75,     0xf8aa,    0x1068e,
+          0x1152a,    0x12487,    0x134ad,    0x145a5,
+          0x1577b,    0x16a37,    0x17df5,    0x192bd,
+          0x1a890,    0x1bf7b,    0x1d78d,    0x1f0d1,
+          0x20b55,    0x22727,    0x24456,    0x262f2,
+          0x2830b
+};
+
+static uint tas3001c_mixer_tab[]={
+              0x0,      0x748,      0x9be,      0xbaf,
+            0xda4,      0xfb1,     0x11de,     0x1431,
+           0x16ad,     0x1959,     0x1c37,     0x1f4b,
+           0x2298,     0x2628,     0x29fb,     0x2e12,
+           0x327d,     0x3734,     0x3c47,     0x41b4,
+           0x4787,     0x4dbe,     0x546d,     0x5b86,
+           0x632e,     0x6b52,     0x7400,     0x7d54,
+           0x873b,     0x91c6,     0x9d1a,     0xa920,
+           0xb5e5,     0xc38c,     0xd21b,     0xe18f,
+           0xf1f5,    0x1036a,    0x1160f,    0x129d6,
+          0x13ed0,    0x1550c,    0x16ca0,    0x185c9,
+          0x1a07b,    0x1bcc3,    0x1dab9,    0x1fa75,
+          0x21c0f,    0x23fa3,    0x26552,    0x28d64,
+          0x2b7c9,    0x2e4a2,    0x31411,    0x3463b,
+          0x37b44,    0x3b353,    0x3ee94,    0x42d30,
+          0x46f55,    0x4b533,    0x4fefc,    0x54ce5,
+          0x59f25,    0x5f5f6,    0x65193,    0x6b23c,
+          0x71835,    0x783c3,    0x7f52c,    0x86cc0,
+          0x8eacc,    0x96fa5,    0x9fba0,    0xa8f1a,
+          0xb2a71,    0xbce0a,    0xc7a4a,    0xd2fa0,
+          0xdee7b,    0xeb752,    0xf8a9f,   0x1068e4,
+         0x1152a3,   0x12486a,   0x134ac8,   0x145a55,
+         0x1577ac,   0x16a370,   0x17df51,   0x192bc2,
+         0x1a88f8,   0x1bf7b7,   0x1d78c9,   0x1f0d04,
+         0x20b542,   0x227268,   0x244564,   0x262f26,
+         0x2830af
+};
+
+static uint tas3001c_treble_tab[]={
+             0x96,       0x95,       0x95,       0x94,
+             0x93,       0x92,       0x92,       0x91,
+             0x90,       0x90,       0x8f,       0x8e,
+             0x8d,       0x8d,       0x8c,       0x8b,
+             0x8a,       0x8a,       0x89,       0x88,
+             0x88,       0x87,       0x86,       0x85,
+             0x85,       0x84,       0x83,       0x83,
+             0x82,       0x81,       0x80,       0x80,
+             0x7f,       0x7e,       0x7e,       0x7d,
+             0x7c,       0x7b,       0x7b,       0x7a,
+             0x79,       0x78,       0x78,       0x77,
+             0x76,       0x76,       0x75,       0x74,
+             0x73,       0x73,       0x72,       0x71,
+             0x71,       0x70,       0x6e,       0x6d,
+             0x6d,       0x6c,       0x6b,       0x6a,
+             0x69,       0x68,       0x67,       0x66,
+             0x65,       0x63,       0x62,       0x62,
+             0x60,       0x5f,       0x5d,       0x5c,
+             0x5a,       0x58,       0x56,       0x55,
+             0x53,       0x51,       0x4f,       0x4c,
+             0x4a,       0x48,       0x45,       0x43,
+             0x40,       0x3d,       0x3a,       0x37,
+             0x35,       0x32,       0x2e,       0x2a,
+             0x27,       0x22,       0x1e,       0x1a,
+             0x15,       0x11,        0xc,        0x7,
+              0x1
+};
+
+static uint tas3001c_bass_tab[]={
+             0x86,       0x83,       0x81,       0x7f,
+             0x7d,       0x7b,       0x79,       0x78,
+             0x76,       0x75,       0x74,       0x72,
+             0x71,       0x6f,       0x6e,       0x6d,
+             0x6c,       0x6b,       0x69,       0x67,
+             0x65,       0x64,       0x61,       0x60,
+             0x5e,       0x5d,       0x5c,       0x5b,
+             0x5a,       0x59,       0x58,       0x57,
+             0x56,       0x55,       0x55,       0x54,
+             0x53,       0x52,       0x50,       0x4f,
+             0x4d,       0x4c,       0x4b,       0x49,
+             0x47,       0x45,       0x44,       0x42,
+             0x41,       0x3f,       0x3e,       0x3d,
+             0x3c,       0x3b,       0x39,       0x38,
+             0x37,       0x36,       0x35,       0x34,
+             0x33,       0x31,       0x30,       0x2f,
+             0x2e,       0x2c,       0x2b,       0x2b,
+             0x29,       0x28,       0x27,       0x26,
+             0x25,       0x24,       0x22,       0x21,
+             0x20,       0x1e,       0x1c,       0x19,
+             0x18,       0x18,       0x17,       0x16,
+             0x15,       0x14,       0x13,       0x12,
+             0x11,       0x10,        0xf,        0xe,
+              0xd,        0xb,        0xa,        0x9,
+              0x8,        0x6,        0x4,        0x2,
+              0x1
+};
+
+struct tas_gain_t tas3001c_gain = {
+  master: tas3001c_master_tab,
+  treble: tas3001c_treble_tab,
+  bass:   tas3001c_bass_tab,
+  mixer:  tas3001c_mixer_tab
+};
+
+struct tas_eq_pref_t *tas3001c_eq_prefs[]={
+  &eqp_0e_2_1,
+  &eqp_10_1_0,
+  &eqp_15_2_1,
+  &eqp_15_1_0,
+  &eqp_0f_2_1,
+  &eqp_0f_1_0,
+  NULL
+};
diff --git a/sound/oss/dmasound/tas3004.c b/sound/oss/dmasound/tas3004.c
new file mode 100644 (file)
index 0000000..4974aed
--- /dev/null
@@ -0,0 +1,1124 @@
+/*
+ * 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)
+ *
+ */
+
+#include <linux/version.h>
+#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/interrupt.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 "tas3004.h"
+
+#include "tas_ioctl.h"
+
+/* #define DEBUG_DRCE */
+
+#define TAS3004_BIQUAD_FILTER_COUNT  7
+#define TAS3004_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 tas3004_data_t {
+       struct tas_data_t super;
+       int device_id;
+       int output_id;
+       int speaker_id;
+       struct tas_drce_t drce_state;
+};
+
+#define MAKE_TIME(sec,usec) (((sec)<<12) + (50000+(usec/10)*(1<<12))/100000)
+
+#define MAKE_RATIO(i,f) (((i)<<8) + ((500+(f)*(1<<8))/1000))
+
+
+static const union tas_biquad_t tas3004_eq_unity = {
+       .buf             = { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 },
+};
+
+
+static const struct tas_drce_t tas3004_drce_min = {
+       .enable         = 1,
+       .above          = { .val = MAKE_RATIO(16,0), .expand = 0 },
+       .below          = { .val = MAKE_RATIO(2,0), .expand = 0 },
+       .threshold      = -0x59a0,
+       .energy         = MAKE_TIME(0,  1700),
+       .attack         = MAKE_TIME(0,  1700),
+       .decay          = MAKE_TIME(0,  1700),
+};
+
+
+static const struct tas_drce_t tas3004_drce_max = {
+       .enable         = 1,
+       .above          = { .val = MAKE_RATIO(1,500), .expand = 1 },
+       .below          = { .val = MAKE_RATIO(2,0), .expand = 1 },
+       .threshold      = -0x0,
+       .energy         = MAKE_TIME(2,400000),
+       .attack         = MAKE_TIME(2,400000),
+       .decay          = MAKE_TIME(2,400000),
+};
+
+
+static const unsigned short time_constants[]={
+       MAKE_TIME(0,  1700),
+       MAKE_TIME(0,  3500),
+       MAKE_TIME(0,  6700),
+       MAKE_TIME(0, 13000),
+       MAKE_TIME(0, 26000),
+       MAKE_TIME(0, 53000),
+       MAKE_TIME(0,106000),
+       MAKE_TIME(0,212000),
+       MAKE_TIME(0,425000),
+       MAKE_TIME(0,850000),
+       MAKE_TIME(1,700000),
+       MAKE_TIME(2,400000),
+};
+
+static const unsigned short above_threshold_compression_ratio[]={
+       MAKE_RATIO( 1, 70),
+       MAKE_RATIO( 1,140),
+       MAKE_RATIO( 1,230),
+       MAKE_RATIO( 1,330),
+       MAKE_RATIO( 1,450),
+       MAKE_RATIO( 1,600),
+       MAKE_RATIO( 1,780),
+       MAKE_RATIO( 2,  0),
+       MAKE_RATIO( 2,290),
+       MAKE_RATIO( 2,670),
+       MAKE_RATIO( 3,200),
+       MAKE_RATIO( 4,  0),
+       MAKE_RATIO( 5,330),
+       MAKE_RATIO( 8,  0),
+       MAKE_RATIO(16,  0),
+};
+
+static const unsigned short above_threshold_expansion_ratio[]={
+       MAKE_RATIO(1, 60),
+       MAKE_RATIO(1,130),
+       MAKE_RATIO(1,190),
+       MAKE_RATIO(1,250),
+       MAKE_RATIO(1,310),
+       MAKE_RATIO(1,380),
+       MAKE_RATIO(1,440),
+       MAKE_RATIO(1,500)
+};
+
+static const unsigned short below_threshold_compression_ratio[]={
+       MAKE_RATIO(1, 70),
+       MAKE_RATIO(1,140),
+       MAKE_RATIO(1,230),
+       MAKE_RATIO(1,330),
+       MAKE_RATIO(1,450),
+       MAKE_RATIO(1,600),
+       MAKE_RATIO(1,780),
+       MAKE_RATIO(2,  0)
+};
+
+static const unsigned short below_threshold_expansion_ratio[]={
+       MAKE_RATIO(1, 60),
+       MAKE_RATIO(1,130),
+       MAKE_RATIO(1,190),
+       MAKE_RATIO(1,250),
+       MAKE_RATIO(1,310),
+       MAKE_RATIO(1,380),
+       MAKE_RATIO(1,440),
+       MAKE_RATIO(1,500),
+       MAKE_RATIO(1,560),
+       MAKE_RATIO(1,630),
+       MAKE_RATIO(1,690),
+       MAKE_RATIO(1,750),
+       MAKE_RATIO(1,810),
+       MAKE_RATIO(1,880),
+       MAKE_RATIO(1,940),
+       MAKE_RATIO(2,  0)
+};
+
+static inline int
+search(        unsigned short val,
+       const unsigned short *arr,
+       const int arrsize) {
+       /*
+        * This could be a binary search, but for small tables,
+        * a linear search is likely to be faster
+        */
+
+       int i;
+
+       for (i=0; i < arrsize; i++)
+               if (arr[i] >= val)
+                       goto _1;
+       return arrsize-1;
+ _1:
+       if (i == 0)
+               return 0;
+       return (arr[i]-val < val-arr[i-1]) ? i : i-1;
+}
+
+#define SEARCH(a, b) search(a, b, ARRAY_SIZE(b))
+
+static inline int
+time_index(unsigned short time)
+{
+       return SEARCH(time, time_constants);
+}
+
+
+static inline int
+above_threshold_compression_index(unsigned short ratio)
+{
+       return SEARCH(ratio, above_threshold_compression_ratio);
+}
+
+
+static inline int
+above_threshold_expansion_index(unsigned short ratio)
+{
+       return SEARCH(ratio, above_threshold_expansion_ratio);
+}
+
+
+static inline int
+below_threshold_compression_index(unsigned short ratio)
+{
+       return SEARCH(ratio, below_threshold_compression_ratio);
+}
+
+
+static inline int
+below_threshold_expansion_index(unsigned short ratio)
+{
+       return SEARCH(ratio, below_threshold_expansion_ratio);
+}
+
+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 tas3004_reg_t r)
+{
+       switch(r) {
+       case TAS3004_REG_MCR:
+       case TAS3004_REG_TREBLE:
+       case TAS3004_REG_BASS:
+       case TAS3004_REG_ANALOG_CTRL:
+       case TAS3004_REG_TEST1:
+       case TAS3004_REG_TEST2:
+       case TAS3004_REG_MCR2:
+               return 1;
+
+       case TAS3004_REG_LEFT_LOUD_BIQUAD_GAIN:
+       case TAS3004_REG_RIGHT_LOUD_BIQUAD_GAIN:
+               return 3;
+
+       case TAS3004_REG_DRC:
+       case TAS3004_REG_VOLUME:
+               return 6;
+
+       case TAS3004_REG_LEFT_MIXER:
+       case TAS3004_REG_RIGHT_MIXER:
+               return 9;
+
+       case TAS3004_REG_TEST:
+               return 10;
+
+       case TAS3004_REG_LEFT_BIQUAD0:
+       case TAS3004_REG_LEFT_BIQUAD1:
+       case TAS3004_REG_LEFT_BIQUAD2:
+       case TAS3004_REG_LEFT_BIQUAD3:
+       case TAS3004_REG_LEFT_BIQUAD4:
+       case TAS3004_REG_LEFT_BIQUAD5:
+       case TAS3004_REG_LEFT_BIQUAD6:
+
+       case TAS3004_REG_RIGHT_BIQUAD0:
+       case TAS3004_REG_RIGHT_BIQUAD1:
+       case TAS3004_REG_RIGHT_BIQUAD2:
+       case TAS3004_REG_RIGHT_BIQUAD3:
+       case TAS3004_REG_RIGHT_BIQUAD4:
+       case TAS3004_REG_RIGHT_BIQUAD5:
+       case TAS3004_REG_RIGHT_BIQUAD6:
+
+       case TAS3004_REG_LEFT_LOUD_BIQUAD:
+       case TAS3004_REG_RIGHT_LOUD_BIQUAD:
+               return 15;
+
+       default:
+               return 0;
+       }
+}
+
+static int
+tas3004_write_register(        struct tas3004_data_t *self,
+                       enum tas3004_reg_t reg_num,
+                       char *data,
+                       uint write_mode)
+{
+       if (reg_num==TAS3004_REG_MCR ||
+           reg_num==TAS3004_REG_BASS ||
+           reg_num==TAS3004_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
+tas3004_sync_register( struct tas3004_data_t *self,
+                       enum tas3004_reg_t reg_num)
+{
+       if (reg_num==TAS3004_REG_MCR ||
+           reg_num==TAS3004_REG_BASS ||
+           reg_num==TAS3004_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
+tas3004_read_register( struct tas3004_data_t *self,
+                       enum tas3004_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
+tas3004_fast_load(struct tas3004_data_t *self, int fast)
+{
+       if (fast)
+               self->super.shadow[TAS3004_REG_MCR][0] |= 0x80;
+       else
+               self->super.shadow[TAS3004_REG_MCR][0] &= 0x7f;
+       return tas3004_sync_register(self,TAS3004_REG_MCR);
+}
+
+static uint
+tas3004_supported_mixers(struct tas3004_data_t *self)
+{
+       return SOUND_MASK_VOLUME |
+               SOUND_MASK_PCM |
+               SOUND_MASK_ALTPCM |
+               SOUND_MASK_IMIX |
+               SOUND_MASK_TREBLE |
+               SOUND_MASK_BASS;
+}
+
+static int
+tas3004_mixer_is_stereo(struct tas3004_data_t *self, int mixer)
+{
+       switch(mixer) {
+       case SOUND_MIXER_VOLUME:
+       case SOUND_MIXER_PCM:
+       case SOUND_MIXER_ALTPCM:
+       case SOUND_MIXER_IMIX:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+static uint
+tas3004_stereo_mixers(struct tas3004_data_t *self)
+{
+       uint r = tas3004_supported_mixers(self);
+       uint i;
+       
+       for (i=1; i<SOUND_MIXER_NRDEVICES; i++)
+               if (r&(1<<i) && !tas3004_mixer_is_stereo(self,i))
+                       r &= ~(1<<i);
+       return r;
+}
+
+static int
+tas3004_get_mixer_level(struct tas3004_data_t *self, int mixer, uint *level)
+{
+       if (!self)
+               return -1;
+
+       *level = self->super.mixer[mixer];
+
+       return 0;
+}
+
+static int
+tas3004_set_mixer_level(struct tas3004_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 (!tas3004_mixer_is_stereo(self,mixer))
+               level = tas_mono_to_stereo(level);
+       switch(mixer) {
+       case SOUND_MIXER_VOLUME:
+               temp = tas3004_gain.master[level&0xff];
+               SET_4_20(shadow[TAS3004_REG_VOLUME], 0, temp);
+               temp = tas3004_gain.master[(level>>8)&0xff];
+               SET_4_20(shadow[TAS3004_REG_VOLUME], 3, temp);
+               rc = tas3004_sync_register(self,TAS3004_REG_VOLUME);
+               break;
+       case SOUND_MIXER_IMIX:
+               offset += 3;
+       case SOUND_MIXER_ALTPCM:
+               offset += 3;
+       case SOUND_MIXER_PCM:
+               /*
+                * Don't load these in fast mode. The documentation
+                * says it can be done in either mode, but testing it
+                * shows that fast mode produces ugly clicking.
+               */
+               /* tas3004_fast_load(self,1); */
+               temp = tas3004_gain.mixer[level&0xff];
+               SET_4_20(shadow[TAS3004_REG_LEFT_MIXER], offset, temp);
+               temp = tas3004_gain.mixer[(level>>8)&0xff];
+               SET_4_20(shadow[TAS3004_REG_RIGHT_MIXER], offset, temp);
+               rc = tas3004_sync_register(self,TAS3004_REG_LEFT_MIXER);
+               if (rc == 0)
+                       rc=tas3004_sync_register(self,TAS3004_REG_RIGHT_MIXER);
+               /* tas3004_fast_load(self,0); */
+               break;
+       case SOUND_MIXER_TREBLE:
+               temp = tas3004_gain.treble[level&0xff];
+               shadow[TAS3004_REG_TREBLE][0]=temp&0xff;
+               rc = tas3004_sync_register(self,TAS3004_REG_TREBLE);
+               break;
+       case SOUND_MIXER_BASS:
+               temp = tas3004_gain.bass[level&0xff];
+               shadow[TAS3004_REG_BASS][0]=temp&0xff;
+               rc = tas3004_sync_register(self,TAS3004_REG_BASS);
+               break;
+       default:
+               rc = -1;
+               break;
+       }
+       if (rc < 0)
+               return rc;
+       self->super.mixer[mixer] = level;
+       
+       return 0;
+}
+
+static int
+tas3004_leave_sleep(struct tas3004_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 (tas3004_write_register(self, TAS3004_REG_MCR, &mcr,
+           WRITE_NORMAL | FORCE_WRITE) < 0)
+               return -1;
+
+       tas3004_fast_load(self, 1);
+
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD0);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD1);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD2);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD3);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD4);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD5);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD6);
+
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD0);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD1);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD2);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD3);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD4);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD5);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD6);
+
+       tas3004_fast_load(self, 0);
+
+       (void)tas3004_sync_register(self,TAS3004_REG_VOLUME);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_MIXER);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_MIXER);
+       (void)tas3004_sync_register(self,TAS3004_REG_TREBLE);
+       (void)tas3004_sync_register(self,TAS3004_REG_BASS);
+
+       return 0;
+}
+
+static int
+tas3004_enter_sleep(struct tas3004_data_t *self)
+{
+       if (!self)
+               return -1; 
+       return 0;
+}
+
+static int
+tas3004_sync_biquad(   struct tas3004_data_t *self,
+                       u_int channel,
+                       u_int filter)
+{
+       enum tas3004_reg_t reg;
+
+       if (channel >= TAS3004_BIQUAD_CHANNEL_COUNT ||
+           filter  >= TAS3004_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+       reg=( channel ? TAS3004_REG_RIGHT_BIQUAD0 : TAS3004_REG_LEFT_BIQUAD0 ) + filter;
+
+       return tas3004_sync_register(self,reg);
+}
+
+static int
+tas3004_write_biquad_shadow(   struct tas3004_data_t *self,
+                               u_int channel,
+                               u_int filter,
+                               const union tas_biquad_t *biquad)
+{
+       tas_shadow_t *shadow=self->super.shadow;
+       enum tas3004_reg_t reg;
+
+       if (channel >= TAS3004_BIQUAD_CHANNEL_COUNT ||
+           filter  >= TAS3004_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+       reg=( channel ? TAS3004_REG_RIGHT_BIQUAD0 : TAS3004_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
+tas3004_write_biquad(  struct tas3004_data_t *self,
+                       u_int channel,
+                       u_int filter,
+                       const union tas_biquad_t *biquad)
+{
+       int rc;
+
+       rc=tas3004_write_biquad_shadow(self, channel, filter, biquad);
+       if (rc < 0) return rc;
+
+       return tas3004_sync_biquad(self, channel, filter);
+}
+
+static int
+tas3004_write_biquad_list(     struct tas3004_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) tas3004_fast_load(self,1);
+
+       for (i=0; i<filter_count; i++) {
+               rc=tas3004_write_biquad(self,
+                                       biquads[i].channel,
+                                       biquads[i].filter,
+                                       &biquads[i].data);
+               if (rc < 0) break;
+       }
+
+       if (flags & TAS_BIQUAD_FAST_LOAD) tas3004_fast_load(self,0);
+
+       return rc;
+}
+
+static int
+tas3004_read_biquad(   struct tas3004_data_t *self,
+                       u_int channel,
+                       u_int filter,
+                       union tas_biquad_t *biquad)
+{
+       tas_shadow_t *shadow=self->super.shadow;
+       enum tas3004_reg_t reg;
+
+       if (channel >= TAS3004_BIQUAD_CHANNEL_COUNT ||
+           filter  >= TAS3004_BIQUAD_FILTER_COUNT) return -EINVAL;
+
+       reg=( channel ? TAS3004_REG_RIGHT_BIQUAD0 : TAS3004_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
+tas3004_eq_rw( struct tas3004_data_t *self,
+               u_int cmd,
+               u_long arg)
+{
+       int rc;
+       struct tas_biquad_ctrl_t biquad;
+
+       if (copy_from_user((void *)&biquad, (const void *)arg, sizeof(struct tas_biquad_ctrl_t))) {
+               return -EFAULT;
+       }
+
+       if (cmd & SIOC_IN) {
+               rc=tas3004_write_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+               if (rc != 0) return rc;
+       }
+
+       if (cmd & SIOC_OUT) {
+               rc=tas3004_read_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+               if (rc != 0) return rc;
+
+               if (copy_to_user((void *)arg, (const void *)&biquad, sizeof(struct tas_biquad_ctrl_t))) {
+                       return -EFAULT;
+               }
+
+       }
+       return 0;
+}
+
+static int
+tas3004_eq_list_rw(    struct tas3004_data_t *self,
+                       u_int cmd,
+                       u_long arg)
+{
+       int rc = 0;
+       int filter_count;
+       int flags;
+       int i,j;
+       char sync_required[TAS3004_BIQUAD_CHANNEL_COUNT][TAS3004_BIQUAD_FILTER_COUNT];
+       struct tas_biquad_ctrl_t biquad;
+
+       memset(sync_required,0,sizeof(sync_required));
+
+       if (copy_from_user((void *)&filter_count,
+                          (const void *)arg + offsetof(struct tas_biquad_ctrl_list_t,filter_count),
+                          sizeof(int))) {
+               return -EFAULT;
+       }
+
+       if (copy_from_user((void *)&flags,
+                          (const void *)arg + offsetof(struct tas_biquad_ctrl_list_t,flags),
+                          sizeof(int))) {
+               return -EFAULT;
+       }
+
+       if (cmd & SIOC_IN) {
+       }
+
+       for (i=0; i < filter_count; i++) {
+               if (copy_from_user((void *)&biquad,
+                                  (const void *)arg + offsetof(struct tas_biquad_ctrl_list_t, biquads[i]),
+                                  sizeof(struct tas_biquad_ctrl_t))) {
+                       return -EFAULT;
+               }
+
+               if (cmd & SIOC_IN) {
+                       sync_required[biquad.channel][biquad.filter]=1;
+                       rc=tas3004_write_biquad_shadow(self, biquad.channel, biquad.filter, &biquad.data);
+                       if (rc != 0) return rc;
+               }
+
+               if (cmd & SIOC_OUT) {
+                       rc=tas3004_read_biquad(self, biquad.channel, biquad.filter, &biquad.data);
+                       if (rc != 0) return rc;
+
+                       if (copy_to_user((void *)arg + offsetof(struct tas_biquad_ctrl_list_t, biquads[i]),
+                                        (const void *)&biquad,
+                                        sizeof(struct tas_biquad_ctrl_t))) {
+                               return -EFAULT;
+                       }
+               }
+       }
+
+       if (cmd & SIOC_IN) {
+               /*
+                * This is OK for the tas3004. For the
+                * tas3001c, going into fast load mode causes
+                * the treble and bass to be reset to 0dB, and
+                * volume controls to be muted.
+                */
+               if (flags & TAS_BIQUAD_FAST_LOAD) tas3004_fast_load(self,1);
+               for (i=0; i<TAS3004_BIQUAD_CHANNEL_COUNT; i++) {
+                       for (j=0; j<TAS3004_BIQUAD_FILTER_COUNT; j++) {
+                               if (sync_required[i][j]) {
+                                       rc=tas3004_sync_biquad(self, i, j);
+                                       if (rc < 0) goto out;
+                               }
+                       }
+               }
+       out:
+               if (flags & TAS_BIQUAD_FAST_LOAD)
+                       tas3004_fast_load(self,0);
+       }
+
+       return rc;
+}
+
+static int
+tas3004_update_drce(   struct tas3004_data_t *self,
+                       int flags,
+                       struct tas_drce_t *drce)
+{
+       tas_shadow_t *shadow;
+       int i;
+       shadow=self->super.shadow;
+
+       if (flags & TAS_DRCE_ABOVE_RATIO) {
+               self->drce_state.above.expand = drce->above.expand;
+               if (drce->above.val == (1<<8)) {
+                       self->drce_state.above.val = 1<<8;
+                       shadow[TAS3004_REG_DRC][0] = 0x02;
+                                       
+               } else if (drce->above.expand) {
+                       i=above_threshold_expansion_index(drce->above.val);
+                       self->drce_state.above.val=above_threshold_expansion_ratio[i];
+                       shadow[TAS3004_REG_DRC][0] = 0x0a + (i<<3);
+               } else {
+                       i=above_threshold_compression_index(drce->above.val);
+                       self->drce_state.above.val=above_threshold_compression_ratio[i];
+                       shadow[TAS3004_REG_DRC][0] = 0x08 + (i<<3);
+               }
+       }
+
+       if (flags & TAS_DRCE_BELOW_RATIO) {
+               self->drce_state.below.expand = drce->below.expand;
+               if (drce->below.val == (1<<8)) {
+                       self->drce_state.below.val = 1<<8;
+                       shadow[TAS3004_REG_DRC][1] = 0x02;
+                                       
+               } else if (drce->below.expand) {
+                       i=below_threshold_expansion_index(drce->below.val);
+                       self->drce_state.below.val=below_threshold_expansion_ratio[i];
+                       shadow[TAS3004_REG_DRC][1] = 0x08 + (i<<3);
+               } else {
+                       i=below_threshold_compression_index(drce->below.val);
+                       self->drce_state.below.val=below_threshold_compression_ratio[i];
+                       shadow[TAS3004_REG_DRC][1] = 0x0a + (i<<3);
+               }
+       }
+
+       if (flags & TAS_DRCE_THRESHOLD) {
+               self->drce_state.threshold=quantize_db(drce->threshold);
+               shadow[TAS3004_REG_DRC][2] = db_to_regval(self->drce_state.threshold);
+       }
+
+       if (flags & TAS_DRCE_ENERGY) {
+               i=time_index(drce->energy);
+               self->drce_state.energy=time_constants[i];
+               shadow[TAS3004_REG_DRC][3] = 0x40 + (i<<4);
+       }
+
+       if (flags & TAS_DRCE_ATTACK) {
+               i=time_index(drce->attack);
+               self->drce_state.attack=time_constants[i];
+               shadow[TAS3004_REG_DRC][4] = 0x40 + (i<<4);
+       }
+
+       if (flags & TAS_DRCE_DECAY) {
+               i=time_index(drce->decay);
+               self->drce_state.decay=time_constants[i];
+               shadow[TAS3004_REG_DRC][5] = 0x40 + (i<<4);
+       }
+
+       if (flags & TAS_DRCE_ENABLE) {
+               self->drce_state.enable = drce->enable;
+       }
+
+       if (!self->drce_state.enable) {
+               shadow[TAS3004_REG_DRC][0] |= 0x01;
+       }
+
+#ifdef DEBUG_DRCE
+       printk("DRCE: set [ ENABLE:%x ABOVE:%x/%x BELOW:%x/%x THRESH:%x ENERGY:%x ATTACK:%x DECAY:%x\n",
+              self->drce_state.enable,
+              self->drce_state.above.expand,self->drce_state.above.val,
+              self->drce_state.below.expand,self->drce_state.below.val,
+              self->drce_state.threshold,
+              self->drce_state.energy,
+              self->drce_state.attack,
+              self->drce_state.decay);
+
+       printk("DRCE: reg [ %02x %02x %02x %02x %02x %02x ]\n",
+              (unsigned char)shadow[TAS3004_REG_DRC][0],
+              (unsigned char)shadow[TAS3004_REG_DRC][1],
+              (unsigned char)shadow[TAS3004_REG_DRC][2],
+              (unsigned char)shadow[TAS3004_REG_DRC][3],
+              (unsigned char)shadow[TAS3004_REG_DRC][4],
+              (unsigned char)shadow[TAS3004_REG_DRC][5]);
+#endif
+
+       return tas3004_sync_register(self, TAS3004_REG_DRC);
+}
+
+static int
+tas3004_drce_rw(       struct tas3004_data_t *self,
+                       u_int cmd,
+                       u_long arg)
+{
+       int rc;
+       struct tas_drce_ctrl_t drce_ctrl;
+
+       if (copy_from_user((void *)&drce_ctrl,
+                          (const void *)arg,
+                          sizeof(struct tas_drce_ctrl_t))) {
+               return -EFAULT;
+       }
+
+#ifdef DEBUG_DRCE
+       printk("DRCE: input [ FLAGS:%x ENABLE:%x ABOVE:%x/%x BELOW:%x/%x THRESH:%x ENERGY:%x ATTACK:%x DECAY:%x\n",
+              drce_ctrl.flags,
+              drce_ctrl.data.enable,
+              drce_ctrl.data.above.expand,drce_ctrl.data.above.val,
+              drce_ctrl.data.below.expand,drce_ctrl.data.below.val,
+              drce_ctrl.data.threshold,
+              drce_ctrl.data.energy,
+              drce_ctrl.data.attack,
+              drce_ctrl.data.decay);
+#endif
+
+       if (cmd & SIOC_IN) {
+               rc = tas3004_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_ABOVE_RATIO)
+                       drce_ctrl.data.above = self->drce_state.above;
+               if (drce_ctrl.flags & TAS_DRCE_BELOW_RATIO)
+                       drce_ctrl.data.below = self->drce_state.below;
+               if (drce_ctrl.flags & TAS_DRCE_THRESHOLD)
+                       drce_ctrl.data.threshold = self->drce_state.threshold;
+               if (drce_ctrl.flags & TAS_DRCE_ENERGY)
+                       drce_ctrl.data.energy = self->drce_state.energy;
+               if (drce_ctrl.flags & TAS_DRCE_ATTACK)
+                       drce_ctrl.data.attack = self->drce_state.attack;
+               if (drce_ctrl.flags & TAS_DRCE_DECAY)
+                       drce_ctrl.data.decay = self->drce_state.decay;
+
+               if (copy_to_user((void *)arg,
+                                (const void *)&drce_ctrl,
+                                sizeof(struct tas_drce_ctrl_t))) {
+                       return -EFAULT;
+               }
+       }
+
+       return 0;
+}
+
+static void
+tas3004_update_device_parameters(struct tas3004_data_t *self)
+{
+       char data;
+       int i;
+
+       if (!self) return;
+
+       if (self->output_id == TAS_OUTPUT_HEADPHONES) {
+               /* turn on allPass when headphones are plugged in */
+               data = 0x02;
+       } else {
+               data = 0x00;
+       }
+
+       tas3004_write_register(self, TAS3004_REG_MCR2, &data, WRITE_NORMAL | FORCE_WRITE);
+
+       for (i=0; tas3004_eq_prefs[i]; i++) {
+               struct tas_eq_pref_t *eq = tas3004_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)) {
+
+                       tas3004_update_drce(self, TAS_DRCE_ALL, eq->drce);
+                       tas3004_write_biquad_list(self, eq->filter_count, TAS_BIQUAD_FAST_LOAD, eq->biquads);
+
+                       break;
+               }
+       }
+}
+
+static void
+tas3004_device_change_handler(void *self)
+{
+       if (!self) return;
+
+       tas3004_update_device_parameters((struct tas3004_data_t *)self);
+}
+
+static struct work_struct device_change;
+
+static int
+tas3004_output_device_change(  struct tas3004_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
+tas3004_device_ioctl(  struct tas3004_data_t *self,
+                       u_int cmd,
+                       u_long arg)
+{
+       switch (cmd) {
+       case TAS_READ_EQ:
+       case TAS_WRITE_EQ:
+               return tas3004_eq_rw(self, cmd, arg);
+
+       case TAS_READ_EQ_LIST:
+       case TAS_WRITE_EQ_LIST:
+               return tas3004_eq_list_rw(self, cmd, arg);
+
+       case TAS_READ_EQ_FILTER_COUNT:
+               put_user(TAS3004_BIQUAD_FILTER_COUNT, (uint *)(arg));
+               return 0;
+
+       case TAS_READ_EQ_CHANNEL_COUNT:
+               put_user(TAS3004_BIQUAD_CHANNEL_COUNT, (uint *)(arg));
+               return 0;
+
+       case TAS_READ_DRCE:
+       case TAS_WRITE_DRCE:
+               return tas3004_drce_rw(self, cmd, arg);
+
+       case TAS_READ_DRCE_CAPS:
+               put_user(TAS_DRCE_ENABLE         |
+                        TAS_DRCE_ABOVE_RATIO    |
+                        TAS_DRCE_BELOW_RATIO    |
+                        TAS_DRCE_THRESHOLD      |
+                        TAS_DRCE_ENERGY         |
+                        TAS_DRCE_ATTACK         |
+                        TAS_DRCE_DECAY,
+                        (uint *)(arg));
+               return 0;
+
+       case TAS_READ_DRCE_MIN:
+       case TAS_READ_DRCE_MAX: {
+               struct tas_drce_ctrl_t drce_ctrl;
+               const struct tas_drce_t *drce_copy;
+
+               if (copy_from_user((void *)&drce_ctrl,
+                                  (const void *)arg,
+                                  sizeof(struct tas_drce_ctrl_t))) {
+                       return -EFAULT;
+               }
+
+               if (cmd == TAS_READ_DRCE_MIN) {
+                       drce_copy=&tas3004_drce_min;
+               } else {
+                       drce_copy=&tas3004_drce_max;
+               }
+
+               if (drce_ctrl.flags & TAS_DRCE_ABOVE_RATIO) {
+                       drce_ctrl.data.above=drce_copy->above;
+               }
+               if (drce_ctrl.flags & TAS_DRCE_BELOW_RATIO) {
+                       drce_ctrl.data.below=drce_copy->below;
+               }
+               if (drce_ctrl.flags & TAS_DRCE_THRESHOLD) {
+                       drce_ctrl.data.threshold=drce_copy->threshold;
+               }
+               if (drce_ctrl.flags & TAS_DRCE_ENERGY) {
+                       drce_ctrl.data.energy=drce_copy->energy;
+               }
+               if (drce_ctrl.flags & TAS_DRCE_ATTACK) {
+                       drce_ctrl.data.attack=drce_copy->attack;
+               }
+               if (drce_ctrl.flags & TAS_DRCE_DECAY) {
+                       drce_ctrl.data.decay=drce_copy->decay;
+               }
+
+               if (copy_to_user((void *)arg,
+                                (const void *)&drce_ctrl,
+                                sizeof(struct tas_drce_ctrl_t))) {
+                       return -EFAULT;
+               }
+       }
+       }
+
+       return -EINVAL;
+}
+
+static int
+tas3004_init_mixer(struct tas3004_data_t *self)
+{
+       unsigned char mcr = (1<<6)+(2<<4)+(2<<2);
+
+       /* Make sure something answers on the i2c bus */
+       if (tas3004_write_register(self, TAS3004_REG_MCR, &mcr,
+           WRITE_NORMAL | FORCE_WRITE) < 0)
+               return -1;
+
+       tas3004_fast_load(self, 1);
+
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD0);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD1);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD2);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD3);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD4);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD5);
+       (void)tas3004_sync_register(self,TAS3004_REG_RIGHT_BIQUAD6);
+
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD0);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD1);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD2);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD3);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD4);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD5);
+       (void)tas3004_sync_register(self,TAS3004_REG_LEFT_BIQUAD6);
+
+       tas3004_sync_register(self, TAS3004_REG_DRC);
+
+       tas3004_sync_register(self, TAS3004_REG_MCR2);
+
+       tas3004_fast_load(self, 0);
+
+       tas3004_set_mixer_level(self, SOUND_MIXER_VOLUME, VOL_DEFAULT<<8 | VOL_DEFAULT);
+       tas3004_set_mixer_level(self, SOUND_MIXER_PCM, INPUT_DEFAULT<<8 | INPUT_DEFAULT);
+       tas3004_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0);
+       tas3004_set_mixer_level(self, SOUND_MIXER_IMIX, 0);
+
+       tas3004_set_mixer_level(self, SOUND_MIXER_BASS, BASS_DEFAULT);
+       tas3004_set_mixer_level(self, SOUND_MIXER_TREBLE, TREBLE_DEFAULT);
+
+       return 0;
+}
+
+static int
+tas3004_uninit_mixer(struct tas3004_data_t *self)
+{
+       tas3004_set_mixer_level(self, SOUND_MIXER_VOLUME, 0);
+       tas3004_set_mixer_level(self, SOUND_MIXER_PCM, 0);
+       tas3004_set_mixer_level(self, SOUND_MIXER_ALTPCM, 0);
+       tas3004_set_mixer_level(self, SOUND_MIXER_IMIX, 0);
+
+       tas3004_set_mixer_level(self, SOUND_MIXER_BASS, 0);
+       tas3004_set_mixer_level(self, SOUND_MIXER_TREBLE, 0);
+
+       return 0;
+}
+
+static int
+tas3004_init(struct i2c_client *client)
+{
+       struct tas3004_data_t *self;
+       size_t sz = sizeof(*self) + (TAS3004_REG_MAX*sizeof(tas_shadow_t));
+       char drce_init[] = { 0x69, 0x22, 0x9f, 0xb0, 0x60, 0xa0 };
+       char mcr2 = 0;
+       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 < TAS3004_BIQUAD_CHANNEL_COUNT; i++)
+               for (j = 0; j<TAS3004_BIQUAD_FILTER_COUNT; j++)
+                       tas3004_write_biquad_shadow(self, i, j,
+                                       &tas3004_eq_unity);
+
+       tas3004_write_register(self, TAS3004_REG_MCR2, &mcr2, WRITE_SHADOW);
+       tas3004_write_register(self, TAS3004_REG_DRC, drce_init, WRITE_SHADOW);
+
+       INIT_WORK(&device_change, tas3004_device_change_handler, self);
+       return 0;
+}
+
+static void 
+tas3004_uninit(struct tas3004_data_t *self)
+{
+       tas3004_uninit_mixer(self);
+       kfree(self);
+}
+
+
+struct tas_driver_hooks_t tas3004_hooks = {
+       .init                   = (tas_hook_init_t)tas3004_init,
+       .post_init              = (tas_hook_post_init_t)tas3004_init_mixer,
+       .uninit                 = (tas_hook_uninit_t)tas3004_uninit,
+       .get_mixer_level        = (tas_hook_get_mixer_level_t)tas3004_get_mixer_level,
+       .set_mixer_level        = (tas_hook_set_mixer_level_t)tas3004_set_mixer_level,
+       .enter_sleep            = (tas_hook_enter_sleep_t)tas3004_enter_sleep,
+       .leave_sleep            = (tas_hook_leave_sleep_t)tas3004_leave_sleep,
+       .supported_mixers       = (tas_hook_supported_mixers_t)tas3004_supported_mixers,
+       .mixer_is_stereo        = (tas_hook_mixer_is_stereo_t)tas3004_mixer_is_stereo,
+       .stereo_mixers          = (tas_hook_stereo_mixers_t)tas3004_stereo_mixers,
+       .output_device_change   = (tas_hook_output_device_change_t)tas3004_output_device_change,
+       .device_ioctl           = (tas_hook_device_ioctl_t)tas3004_device_ioctl
+};
diff --git a/sound/oss/dmasound/tas3004.h b/sound/oss/dmasound/tas3004.h
new file mode 100644 (file)
index 0000000..c6d584b
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Header file for the i2c/i2s based TA3004 sound chip used
+ * on some Apple hardware. Also known as "tumbler".
+ *
+ *  This file is subject to the terms and conditions of the GNU General Public
+ *  License.  See the file COPYING in the main directory of this archive
+ *  for more details.
+ *
+ * Written by Christopher C. Chimelis <chris@debian.org>
+ */
+
+#ifndef _TAS3004_H_
+#define _TAS3004_H_
+
+#include <linux/types.h>
+
+#include "tas_common.h"
+#include "tas_eq_prefs.h"
+
+/*
+ * Macros that correspond to the registers that we write to
+ * when setting the various values.
+ */
+
+#define TAS3004_VERSION                "0.3"
+#define TAS3004_DATE           "20011214"
+
+#define I2C_DRIVERNAME_TAS3004 "TAS3004 driver V " TAS3004_VERSION
+#define I2C_DRIVERID_TAS3004    (I2C_DRIVERID_TAS_BASE+1)
+
+extern  struct tas_driver_hooks_t tas3004_hooks;
+extern struct tas_gain_t tas3004_gain;
+extern struct tas_eq_pref_t *tas3004_eq_prefs[];
+
+enum tas3004_reg_t {
+  TAS3004_REG_MCR                    = 0x01,
+  TAS3004_REG_DRC                    = 0x02,
+
+  TAS3004_REG_VOLUME                 = 0x04,
+  TAS3004_REG_TREBLE                 = 0x05,
+  TAS3004_REG_BASS                   = 0x06,
+  TAS3004_REG_LEFT_MIXER             = 0x07,
+  TAS3004_REG_RIGHT_MIXER            = 0x08,
+
+  TAS3004_REG_LEFT_BIQUAD0           = 0x0a,
+  TAS3004_REG_LEFT_BIQUAD1           = 0x0b,
+  TAS3004_REG_LEFT_BIQUAD2           = 0x0c,
+  TAS3004_REG_LEFT_BIQUAD3           = 0x0d,
+  TAS3004_REG_LEFT_BIQUAD4           = 0x0e,
+  TAS3004_REG_LEFT_BIQUAD5           = 0x0f,
+  TAS3004_REG_LEFT_BIQUAD6           = 0x10,
+  
+  TAS3004_REG_RIGHT_BIQUAD0          = 0x13,
+  TAS3004_REG_RIGHT_BIQUAD1          = 0x14,
+  TAS3004_REG_RIGHT_BIQUAD2          = 0x15,
+  TAS3004_REG_RIGHT_BIQUAD3          = 0x16,
+  TAS3004_REG_RIGHT_BIQUAD4          = 0x17,
+  TAS3004_REG_RIGHT_BIQUAD5          = 0x18,
+  TAS3004_REG_RIGHT_BIQUAD6          = 0x19,
+
+  TAS3004_REG_LEFT_LOUD_BIQUAD       = 0x21,
+  TAS3004_REG_RIGHT_LOUD_BIQUAD      = 0x22,
+
+  TAS3004_REG_LEFT_LOUD_BIQUAD_GAIN  = 0x23,
+  TAS3004_REG_RIGHT_LOUD_BIQUAD_GAIN = 0x24,
+
+  TAS3004_REG_TEST                   = 0x29,
+
+  TAS3004_REG_ANALOG_CTRL            = 0x40,
+  TAS3004_REG_TEST1                  = 0x41,
+  TAS3004_REG_TEST2                  = 0x42,
+  TAS3004_REG_MCR2                   = 0x43,
+
+  TAS3004_REG_MAX                    = 0x44
+};
+
+#endif /* _TAS3004_H_ */
diff --git a/sound/oss/dmasound/tas3004_tables.c b/sound/oss/dmasound/tas3004_tables.c
new file mode 100644 (file)
index 0000000..f4dd305
--- /dev/null
@@ -0,0 +1,301 @@
+#include "tas3004.h"
+#include "tas_eq_prefs.h"
+
+static struct tas_drce_t eqp_17_1_0_drce={
+    enable:    1,
+    above:     { val: 3.0 * (1<<8), expand: 0 },
+    below:     { val: 1.0 * (1<<8), expand: 0 },
+    threshold: -19.12  * (1<<8),
+    energy:    2.4     * (1<<12),
+    attack:    0.013   * (1<<12),
+    decay:     0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_17_1_0_biquads[]={
+  { channel: 0, filter: 0, data: { coeff: { 0x0fd0d4, 0xe05e56, 0x0fd0d4, 0xe05ee1, 0x0fa234 } } },
+  { channel: 0, filter: 1, data: { coeff: { 0x0910d7, 0x088e1a, 0x030651, 0x01dcb1, 0x02c892 } } },
+  { channel: 0, filter: 2, data: { coeff: { 0x0ff895, 0xe0970b, 0x0f7f00, 0xe0970b, 0x0f7795 } } },
+  { channel: 0, filter: 3, data: { coeff: { 0x0fd1c4, 0xe1ac22, 0x0ec8cf, 0xe1ac22, 0x0e9a94 } } },
+  { channel: 0, filter: 4, data: { coeff: { 0x0f7c1c, 0xe3cc03, 0x0df786, 0xe3cc03, 0x0d73a2 } } },
+  { channel: 0, filter: 5, data: { coeff: { 0x11fb92, 0xf5a1a0, 0x073cd2, 0xf5a1a0, 0x093865 } } },
+  { channel: 0, filter: 6, data: { coeff: { 0x0e17a9, 0x068b6c, 0x08a0e5, 0x068b6c, 0x06b88e } } },
+
+  { channel: 1, filter: 0, data: { coeff: { 0x0fd0d4, 0xe05e56, 0x0fd0d4, 0xe05ee1, 0x0fa234 } } },
+  { channel: 1, filter: 1, data: { coeff: { 0x0910d7, 0x088e1a, 0x030651, 0x01dcb1, 0x02c892 } } },
+  { channel: 1, filter: 2, data: { coeff: { 0x0ff895, 0xe0970b, 0x0f7f00, 0xe0970b, 0x0f7795 } } },
+  { channel: 1, filter: 3, data: { coeff: { 0x0fd1c4, 0xe1ac22, 0x0ec8cf, 0xe1ac22, 0x0e9a94 } } },
+  { channel: 1, filter: 4, data: { coeff: { 0x0f7c1c, 0xe3cc03, 0x0df786, 0xe3cc03, 0x0d73a2 } } },
+  { channel: 1, filter: 5, data: { coeff: { 0x11fb92, 0xf5a1a0, 0x073cd2, 0xf5a1a0, 0x093865 } } },
+  { channel: 1, filter: 6, data: { coeff: { 0x0e17a9, 0x068b6c, 0x08a0e5, 0x068b6c, 0x06b88e } } }
+};
+
+static struct tas_eq_pref_t eqp_17_1_0 = {
+  sample_rate:  44100,
+  device_id:    0x17,
+  output_id:    TAS_OUTPUT_INTERNAL_SPKR,
+  speaker_id:   0x00,
+
+  drce:         &eqp_17_1_0_drce,
+
+  filter_count: 14,
+  biquads:      eqp_17_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_18_1_0_drce={
+  enable:    1,
+  above:     { val: 3.0 * (1<<8), expand: 0 },
+  below:     { val: 1.0 * (1<<8), expand: 0 },
+  threshold: -13.14  * (1<<8),
+  energy:    2.4     * (1<<12),
+  attack:    0.013   * (1<<12),
+  decay:     0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_18_1_0_biquads[]={
+  { channel: 0, filter: 0, data: { coeff: { 0x0f5514, 0xe155d7, 0x0f5514, 0xe15cfa, 0x0eb14b } } },
+  { channel: 0, filter: 1, data: { coeff: { 0x06ec33, 0x02abe3, 0x015eef, 0xf764d9, 0x03922d } } },
+  { channel: 0, filter: 2, data: { coeff: { 0x0ef5f2, 0xe67d1f, 0x0bcf37, 0xe67d1f, 0x0ac529 } } },
+  { channel: 0, filter: 3, data: { coeff: { 0x0db050, 0xe5be4d, 0x0d0c78, 0xe5be4d, 0x0abcc8 } } },
+  { channel: 0, filter: 4, data: { coeff: { 0x0f1298, 0xe64ec6, 0x0cc03e, 0xe64ec6, 0x0bd2d7 } } },
+  { channel: 0, filter: 5, data: { coeff: { 0x0c641a, 0x06537a, 0x08d155, 0x06537a, 0x053570 } } },
+  { channel: 0, filter: 6, data: { coeff: { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } },
+
+  { channel: 1, filter: 0, data: { coeff: { 0x0f5514, 0xe155d7, 0x0f5514, 0xe15cfa, 0x0eb14b } } },
+  { channel: 1, filter: 1, data: { coeff: { 0x06ec33, 0x02abe3, 0x015eef, 0xf764d9, 0x03922d } } },
+  { channel: 1, filter: 2, data: { coeff: { 0x0ef5f2, 0xe67d1f, 0x0bcf37, 0xe67d1f, 0x0ac529 } } },
+  { channel: 1, filter: 3, data: { coeff: { 0x0db050, 0xe5be4d, 0x0d0c78, 0xe5be4d, 0x0abcc8 } } },
+  { channel: 1, filter: 4, data: { coeff: { 0x0f1298, 0xe64ec6, 0x0cc03e, 0xe64ec6, 0x0bd2d7 } } },
+  { channel: 1, filter: 5, data: { coeff: { 0x0c641a, 0x06537a, 0x08d155, 0x06537a, 0x053570 } } },
+  { channel: 1, filter: 6, data: { coeff: { 0x100000, 0x000000, 0x000000, 0x000000, 0x000000 } } }
+};
+
+static struct tas_eq_pref_t eqp_18_1_0 = {
+  sample_rate:  44100,
+  device_id:    0x18,
+  output_id:    TAS_OUTPUT_INTERNAL_SPKR,
+  speaker_id:   0x00,
+
+  drce:         &eqp_18_1_0_drce,
+
+  filter_count: 14,
+  biquads:      eqp_18_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_1a_1_0_drce={
+  enable:    1,
+  above:     { val: 3.0 * (1<<8), expand: 0 },
+  below:     { val: 1.0 * (1<<8), expand: 0 },
+  threshold: -10.75  * (1<<8),
+  energy:    2.4     * (1<<12),
+  attack:    0.013   * (1<<12),
+  decay:     0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_1a_1_0_biquads[]={
+  { channel: 0, filter: 0, data: { coeff: { 0x0fb8fd, 0xe08e04, 0x0fb8fd, 0xe08f40, 0x0f7336 } } },
+  { channel: 0, filter: 1, data: { coeff: { 0x06371d, 0x0c6e3a, 0x06371d, 0x05bfd3, 0x031ca2 } } },
+  { channel: 0, filter: 2, data: { coeff: { 0x0fa1c0, 0xe18692, 0x0f030e, 0xe18692, 0x0ea4ce } } },
+  { channel: 0, filter: 3, data: { coeff: { 0x0fe495, 0xe17eff, 0x0f0452, 0xe17eff, 0x0ee8e7 } } },
+  { channel: 0, filter: 4, data: { coeff: { 0x100857, 0xe7e71c, 0x0e9599, 0xe7e71c, 0x0e9df1 } } },
+  { channel: 0, filter: 5, data: { coeff: { 0x0fb26e, 0x06a82c, 0x0db2b4, 0x06a82c, 0x0d6522 } } },
+  { channel: 0, filter: 6, data: { coeff: { 0x11419d, 0xf06cbf, 0x0a4f6e, 0xf06cbf, 0x0b910c } } },
+
+  { channel: 1, filter: 0, data: { coeff: { 0x0fb8fd, 0xe08e04, 0x0fb8fd, 0xe08f40, 0x0f7336 } } },
+  { channel: 1, filter: 1, data: { coeff: { 0x06371d, 0x0c6e3a, 0x06371d, 0x05bfd3, 0x031ca2 } } },
+  { channel: 1, filter: 2, data: { coeff: { 0x0fa1c0, 0xe18692, 0x0f030e, 0xe18692, 0x0ea4ce } } },
+  { channel: 1, filter: 3, data: { coeff: { 0x0fe495, 0xe17eff, 0x0f0452, 0xe17eff, 0x0ee8e7 } } },
+  { channel: 1, filter: 4, data: { coeff: { 0x100857, 0xe7e71c, 0x0e9599, 0xe7e71c, 0x0e9df1 } } },
+  { channel: 1, filter: 5, data: { coeff: { 0x0fb26e, 0x06a82c, 0x0db2b4, 0x06a82c, 0x0d6522 } } },
+  { channel: 1, filter: 6, data: { coeff: { 0x11419d, 0xf06cbf, 0x0a4f6e, 0xf06cbf, 0x0b910c } } }
+};
+
+static struct tas_eq_pref_t eqp_1a_1_0 = {
+  sample_rate:  44100,
+  device_id:    0x1a,
+  output_id:    TAS_OUTPUT_INTERNAL_SPKR,
+  speaker_id:   0x00,
+
+  drce:         &eqp_1a_1_0_drce,
+
+  filter_count: 14,
+  biquads:      eqp_1a_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static struct tas_drce_t eqp_1c_1_0_drce={
+  enable:    1,
+  above:     { val: 3.0 * (1<<8), expand: 0 },
+  below:     { val: 1.0 * (1<<8), expand: 0 },
+  threshold: -14.34  * (1<<8),
+  energy:    2.4     * (1<<12),
+  attack:    0.013   * (1<<12),
+  decay:     0.212   * (1<<12),
+};
+
+static struct tas_biquad_ctrl_t eqp_1c_1_0_biquads[]={
+  { channel: 0, filter: 0, data: { coeff: { 0x0f4f95, 0xe160d4, 0x0f4f95, 0xe1686e, 0x0ea6c5 } } },
+  { channel: 0, filter: 1, data: { coeff: { 0x066b92, 0x0290d4, 0x0148a0, 0xf6853f, 0x03bfc7 } } },
+  { channel: 0, filter: 2, data: { coeff: { 0x0f57dc, 0xe51c91, 0x0dd1cb, 0xe51c91, 0x0d29a8 } } },
+  { channel: 0, filter: 3, data: { coeff: { 0x0df1cb, 0xe4fa84, 0x0d7cdc, 0xe4fa84, 0x0b6ea7 } } },
+  { channel: 0, filter: 4, data: { coeff: { 0x0eba36, 0xe6aa48, 0x0b9f52, 0xe6aa48, 0x0a5989 } } },
+  { channel: 0, filter: 5, data: { coeff: { 0x0caf02, 0x05ef9d, 0x084beb, 0x05ef9d, 0x04faee } } },
+  { channel: 0, filter: 6, data: { coeff: { 0x0fc686, 0xe22947, 0x0e4b5d, 0xe22947, 0x0e11e4 } } },
+
+  { channel: 1, filter: 0, data: { coeff: { 0x0f4f95, 0xe160d4, 0x0f4f95, 0xe1686e, 0x0ea6c5 } } },
+  { channel: 1, filter: 1, data: { coeff: { 0x066b92, 0x0290d4, 0x0148a0, 0xf6853f, 0x03bfc7 } } },
+  { channel: 1, filter: 2, data: { coeff: { 0x0f57dc, 0xe51c91, 0x0dd1cb, 0xe51c91, 0x0d29a8 } } },
+  { channel: 1, filter: 3, data: { coeff: { 0x0df1cb, 0xe4fa84, 0x0d7cdc, 0xe4fa84, 0x0b6ea7 } } },
+  { channel: 1, filter: 4, data: { coeff: { 0x0eba36, 0xe6aa48, 0x0b9f52, 0xe6aa48, 0x0a5989 } } },
+  { channel: 1, filter: 5, data: { coeff: { 0x0caf02, 0x05ef9d, 0x084beb, 0x05ef9d, 0x04faee } } },
+  { channel: 1, filter: 6, data: { coeff: { 0x0fc686, 0xe22947, 0x0e4b5d, 0xe22947, 0x0e11e4 } } }
+};
+
+static struct tas_eq_pref_t eqp_1c_1_0 = {
+  sample_rate:  44100,
+  device_id:    0x1c,
+  output_id:    TAS_OUTPUT_INTERNAL_SPKR,
+  speaker_id:   0x00,
+
+  drce:         &eqp_1c_1_0_drce,
+
+  filter_count: 14,
+  biquads:      eqp_1c_1_0_biquads
+};
+
+/* ======================================================================== */
+
+static uint tas3004_master_tab[]={
+              0x0,       0x75,       0x9c,       0xbb,
+             0xdb,       0xfb,      0x11e,      0x143,
+            0x16b,      0x196,      0x1c3,      0x1f5,
+            0x229,      0x263,      0x29f,      0x2e1,
+            0x328,      0x373,      0x3c5,      0x41b,
+            0x478,      0x4dc,      0x547,      0x5b8,
+            0x633,      0x6b5,      0x740,      0x7d5,
+            0x873,      0x91c,      0x9d2,      0xa92,
+            0xb5e,      0xc39,      0xd22,      0xe19,
+            0xf20,     0x1037,     0x1161,     0x129e,
+           0x13ed,     0x1551,     0x16ca,     0x185d,
+           0x1a08,     0x1bcc,     0x1dac,     0x1fa7,
+           0x21c1,     0x23fa,     0x2655,     0x28d6,
+           0x2b7c,     0x2e4a,     0x3141,     0x3464,
+           0x37b4,     0x3b35,     0x3ee9,     0x42d3,
+           0x46f6,     0x4b53,     0x4ff0,     0x54ce,
+           0x59f2,     0x5f5f,     0x6519,     0x6b24,
+           0x7183,     0x783c,     0x7f53,     0x86cc,
+           0x8ead,     0x96fa,     0x9fba,     0xa8f2,
+           0xb2a7,     0xbce1,     0xc7a5,     0xd2fa,
+           0xdee8,     0xeb75,     0xf8aa,    0x1068e,
+          0x1152a,    0x12487,    0x134ad,    0x145a5,
+          0x1577b,    0x16a37,    0x17df5,    0x192bd,
+          0x1a890,    0x1bf7b,    0x1d78d,    0x1f0d1,
+          0x20b55,    0x22727,    0x24456,    0x262f2,
+          0x2830b
+};
+
+static uint tas3004_mixer_tab[]={
+              0x0,      0x748,      0x9be,      0xbaf,
+            0xda4,      0xfb1,     0x11de,     0x1431,
+           0x16ad,     0x1959,     0x1c37,     0x1f4b,
+           0x2298,     0x2628,     0x29fb,     0x2e12,
+           0x327d,     0x3734,     0x3c47,     0x41b4,
+           0x4787,     0x4dbe,     0x546d,     0x5b86,
+           0x632e,     0x6b52,     0x7400,     0x7d54,
+           0x873b,     0x91c6,     0x9d1a,     0xa920,
+           0xb5e5,     0xc38c,     0xd21b,     0xe18f,
+           0xf1f5,    0x1036a,    0x1160f,    0x129d6,
+          0x13ed0,    0x1550c,    0x16ca0,    0x185c9,
+          0x1a07b,    0x1bcc3,    0x1dab9,    0x1fa75,
+          0x21c0f,    0x23fa3,    0x26552,    0x28d64,
+          0x2b7c9,    0x2e4a2,    0x31411,    0x3463b,
+          0x37b44,    0x3b353,    0x3ee94,    0x42d30,
+          0x46f55,    0x4b533,    0x4fefc,    0x54ce5,
+          0x59f25,    0x5f5f6,    0x65193,    0x6b23c,
+          0x71835,    0x783c3,    0x7f52c,    0x86cc0,
+          0x8eacc,    0x96fa5,    0x9fba0,    0xa8f1a,
+          0xb2a71,    0xbce0a,    0xc7a4a,    0xd2fa0,
+          0xdee7b,    0xeb752,    0xf8a9f,   0x1068e4,
+         0x1152a3,   0x12486a,   0x134ac8,   0x145a55,
+         0x1577ac,   0x16a370,   0x17df51,   0x192bc2,
+         0x1a88f8,   0x1bf7b7,   0x1d78c9,   0x1f0d04,
+         0x20b542,   0x227268,   0x244564,   0x262f26,
+         0x2830af
+};
+
+static uint tas3004_treble_tab[]={
+             0x96,       0x95,       0x95,       0x94,
+             0x93,       0x92,       0x92,       0x91,
+             0x90,       0x90,       0x8f,       0x8e,
+             0x8d,       0x8d,       0x8c,       0x8b,
+             0x8a,       0x8a,       0x89,       0x88,
+             0x88,       0x87,       0x86,       0x85,
+             0x85,       0x84,       0x83,       0x83,
+             0x82,       0x81,       0x80,       0x80,
+             0x7f,       0x7e,       0x7e,       0x7d,
+             0x7c,       0x7b,       0x7b,       0x7a,
+             0x79,       0x78,       0x78,       0x77,
+             0x76,       0x76,       0x75,       0x74,
+             0x73,       0x73,       0x72,       0x71,
+             0x71,       0x68,       0x45,       0x5b,
+             0x6d,       0x6c,       0x6b,       0x6a,
+             0x69,       0x68,       0x67,       0x66,
+             0x65,       0x63,       0x62,       0x62,
+             0x60,       0x5e,       0x5c,       0x5b,
+             0x59,       0x57,       0x55,       0x53,
+             0x52,       0x4f,       0x4d,       0x4a,
+             0x48,       0x46,       0x43,       0x40,
+             0x3d,       0x3a,       0x36,       0x33,
+             0x2f,       0x2c,       0x27,       0x23,
+             0x1f,       0x1a,       0x15,        0xf,
+              0x8,        0x5,        0x2,        0x1,
+              0x1
+};
+
+static uint tas3004_bass_tab[]={
+             0x96,       0x95,       0x95,       0x94,
+             0x93,       0x92,       0x92,       0x91,
+             0x90,       0x90,       0x8f,       0x8e,
+             0x8d,       0x8d,       0x8c,       0x8b,
+             0x8a,       0x8a,       0x89,       0x88,
+             0x88,       0x87,       0x86,       0x85,
+             0x85,       0x84,       0x83,       0x83,
+             0x82,       0x81,       0x80,       0x80,
+             0x7f,       0x7e,       0x7e,       0x7d,
+             0x7c,       0x7b,       0x7b,       0x7a,
+             0x79,       0x78,       0x78,       0x77,
+             0x76,       0x76,       0x75,       0x74,
+             0x73,       0x73,       0x72,       0x71,
+             0x70,       0x6f,       0x6e,       0x6d,
+             0x6c,       0x6b,       0x6a,       0x6a,
+             0x69,       0x67,       0x66,       0x66,
+             0x65,       0x63,       0x62,       0x62,
+             0x61,       0x60,       0x5e,       0x5d,
+             0x5b,       0x59,       0x57,       0x55,
+             0x53,       0x51,       0x4f,       0x4c,
+             0x4a,       0x48,       0x46,       0x44,
+             0x41,       0x3e,       0x3b,       0x38,
+             0x36,       0x33,       0x2f,       0x2b,
+             0x28,       0x24,       0x20,       0x1c,
+             0x17,       0x12,        0xd,        0x7,
+              0x1
+};
+
+struct tas_gain_t tas3004_gain={
+  master: tas3004_master_tab,
+  treble: tas3004_treble_tab,
+  bass:   tas3004_bass_tab,
+  mixer:  tas3004_mixer_tab
+};
+
+struct tas_eq_pref_t *tas3004_eq_prefs[]={
+  &eqp_17_1_0,
+  &eqp_18_1_0,
+  &eqp_1a_1_0,
+  &eqp_1c_1_0,
+  NULL
+};
diff --git a/sound/oss/dmasound/tas_common.c b/sound/oss/dmasound/tas_common.c
new file mode 100644 (file)
index 0000000..e291fd0
--- /dev/null
@@ -0,0 +1,212 @@
+#include <linux/version.h>
+#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 <asm/uaccess.h>
+#include <asm/errno.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+
+#include "tas_common.h"
+
+#define CALL0(proc)                                                            \
+       do {                                                                    \
+               struct tas_data_t *self;                                        \
+               if (!tas_client || driver_hooks == NULL)                        \
+                       return -1;                                              \
+               self = dev_get_drvdata(&tas_client->dev);                       \
+               if (driver_hooks->proc)                                         \
+                       return driver_hooks->proc(self);                        \
+               else                                                            \
+                       return -EINVAL;                                         \
+       } while (0)
+
+#define CALL(proc,arg...)                                                      \
+       do {                                                                    \
+               struct tas_data_t *self;                                        \
+               if (!tas_client || driver_hooks == NULL)                        \
+                       return -1;                                              \
+               self = dev_get_drvdata(&tas_client->dev);                       \
+               if (driver_hooks->proc)                                         \
+                       return driver_hooks->proc(self, ## arg);                \
+               else                                                            \
+                       return -EINVAL;                                         \
+       } while (0)
+
+
+static u8 tas_i2c_address = 0x34;
+static struct i2c_client *tas_client;
+static struct device_node* tas_node;
+
+static int tas_attach_adapter(struct i2c_adapter *);
+static int tas_detach_client(struct i2c_client *);
+
+struct i2c_driver tas_driver = {
+       .owner          = THIS_MODULE,
+       .name           = "tas",
+       .flags          = I2C_DF_NOTIFY,
+       .attach_adapter = tas_attach_adapter,
+       .detach_client  = tas_detach_client,
+};
+
+struct tas_driver_hooks_t *driver_hooks;
+
+int
+tas_register_driver(struct tas_driver_hooks_t *hooks)
+{
+       driver_hooks = hooks;
+       return 0;
+}
+
+int
+tas_get_mixer_level(int mixer, uint *level)
+{
+       CALL(get_mixer_level,mixer,level);
+}
+
+int
+tas_set_mixer_level(int mixer,uint level)
+{
+       CALL(set_mixer_level,mixer,level);
+}
+
+int
+tas_enter_sleep(void)
+{
+       CALL0(enter_sleep);
+}
+
+int
+tas_leave_sleep(void)
+{
+       CALL0(leave_sleep);
+}
+
+int
+tas_supported_mixers(void)
+{
+       CALL0(supported_mixers);
+}
+
+int
+tas_mixer_is_stereo(int mixer)
+{
+       CALL(mixer_is_stereo,mixer);
+}
+
+int
+tas_stereo_mixers(void)
+{
+       CALL0(stereo_mixers);
+}
+
+int
+tas_output_device_change(int device_id,int layout_id,int speaker_id)
+{
+       CALL(output_device_change,device_id,layout_id,speaker_id);
+}
+
+int
+tas_device_ioctl(u_int cmd, u_long arg)
+{
+       CALL(device_ioctl,cmd,arg);
+}
+
+int
+tas_post_init(void)
+{
+       CALL0(post_init);
+}
+
+static int
+tas_detect_client(struct i2c_adapter *adapter, int address)
+{
+       static const char *client_name = "tas Digital Equalizer";
+       struct i2c_client *new_client;
+       int rc = -ENODEV;
+
+       if (!driver_hooks) {
+               printk(KERN_ERR "tas_detect_client called with no hooks !\n");
+               return -ENODEV;
+       }
+       
+       new_client = kmalloc(sizeof(*new_client), GFP_KERNEL);
+       if (!new_client)
+               return -ENOMEM;
+       memset(new_client, 0, sizeof(*new_client));
+
+       new_client->addr = address;
+       new_client->adapter = adapter;
+       new_client->driver = &tas_driver;
+       strlcpy(new_client->name, client_name, DEVICE_NAME_SIZE);
+
+        if (driver_hooks->init(new_client))
+               goto bail;
+
+       /* Tell the i2c layer a new client has arrived */
+       if (i2c_attach_client(new_client)) {
+               driver_hooks->uninit(dev_get_drvdata(&new_client->dev));
+               goto bail;
+       }
+
+       tas_client = new_client;
+       return 0;
+ bail:
+       tas_client = NULL;
+       kfree(new_client);
+       return rc;
+}
+
+static int
+tas_attach_adapter(struct i2c_adapter *adapter)
+{
+       if (!strncmp(adapter->name, "mac-io", 6))
+               return tas_detect_client(adapter, tas_i2c_address);
+       return 0;
+}
+
+static int
+tas_detach_client(struct i2c_client *client)
+{
+       if (client == tas_client) {
+               driver_hooks->uninit(dev_get_drvdata(&client->dev));
+
+               i2c_detach_client(client);
+               kfree(client);
+       }
+       return 0;
+}
+
+void
+tas_cleanup(void)
+{
+       i2c_del_driver(&tas_driver);
+}
+
+int __init
+tas_init(int driver_id, const char *driver_name)
+{
+       u32* paddr;
+
+       printk(KERN_INFO "tas driver [%s])\n", driver_name);
+
+       tas_node = find_devices("deq");
+       if (tas_node == NULL)
+               return -ENODEV;
+       paddr = (u32 *)get_property(tas_node, "i2c-address", NULL);
+       if (paddr) {
+               tas_i2c_address = (*paddr) >> 1;
+               printk(KERN_INFO "using i2c address: 0x%x from device-tree\n",
+                               tas_i2c_address);
+       } else    
+               printk(KERN_INFO "using i2c address: 0x%x (default)\n",
+                               tas_i2c_address);
+
+       return i2c_add_driver(&tas_driver);
+}
diff --git a/sound/oss/dmasound/tas_common.h b/sound/oss/dmasound/tas_common.h
new file mode 100644 (file)
index 0000000..e62d6bf
--- /dev/null
@@ -0,0 +1,284 @@
+#ifndef _TAS_COMMON_H_
+#define _TAS_COMMON_H_
+
+#include <linux/i2c.h>
+#include <linux/soundcard.h>
+#include <asm/string.h>
+
+#define I2C_DRIVERID_TAS_BASE   (0xFEBA)
+
+#define SET_4_20(shadow, offset, val)                        \
+       do {                                                 \
+               (shadow)[(offset)+0] = ((val) >> 16) & 0xff; \
+               (shadow)[(offset)+1] = ((val) >> 8)  & 0xff; \
+               (shadow)[(offset)+2] = ((val) >> 0)  & 0xff; \
+       } while (0)
+
+#define GET_4_20(shadow, offset)                             \
+       (((u_int)((shadow)[(offset)+0]) << 16) |             \
+        ((u_int)((shadow)[(offset)+1]) <<  8) |             \
+        ((u_int)((shadow)[(offset)+2]) <<  0))
+
+
+#define TAS_BIQUAD_FAST_LOAD 0x01
+
+#define TAS_DRCE_ENABLE           0x01
+#define TAS_DRCE_ABOVE_RATIO      0x02
+#define TAS_DRCE_BELOW_RATIO      0x04
+#define TAS_DRCE_THRESHOLD        0x08
+#define TAS_DRCE_ENERGY           0x10
+#define TAS_DRCE_ATTACK           0x20
+#define TAS_DRCE_DECAY            0x40
+
+#define TAS_DRCE_ALL              0x7f
+
+
+#define TAS_OUTPUT_HEADPHONES     0x00
+#define TAS_OUTPUT_INTERNAL_SPKR  0x01
+#define TAS_OUTPUT_EXTERNAL_SPKR  0x02
+
+
+union tas_biquad_t {
+       struct {
+               int b0,b1,b2,a1,a2;
+       } coeff;
+       int buf[5];
+};
+
+struct tas_biquad_ctrl_t {
+       u_int channel:4;
+       u_int filter:4;
+
+       union tas_biquad_t data;
+};
+
+struct tas_biquad_ctrl_list_t {
+       int flags;
+       int filter_count;
+       struct tas_biquad_ctrl_t biquads[0];
+};
+
+struct tas_ratio_t {
+       unsigned short val;    /* 8.8                        */
+       unsigned short expand; /* 0 = compress, !0 = expand. */
+};
+
+struct tas_drce_t {
+       unsigned short enable;
+       struct tas_ratio_t above;
+       struct tas_ratio_t below;
+       short threshold;       /* dB,       8.8 signed    */
+       unsigned short energy; /* seconds,  4.12 unsigned */
+       unsigned short attack; /* seconds,  4.12 unsigned */
+       unsigned short decay;  /* seconds,  4.12 unsigned */
+};
+
+struct tas_drce_ctrl_t {
+       uint flags;
+
+       struct tas_drce_t data;
+};
+
+struct tas_gain_t
+{
+  unsigned int *master;
+  unsigned int *treble;
+  unsigned int *bass;
+  unsigned int *mixer;
+};
+
+typedef char tas_shadow_t[16];
+
+struct tas_data_t
+{
+       struct i2c_client *client;
+       tas_shadow_t *shadow;
+       uint mixer[SOUND_MIXER_NRDEVICES];
+};
+
+typedef int (*tas_hook_init_t)(struct i2c_client *);
+typedef int (*tas_hook_post_init_t)(struct tas_data_t *);
+typedef void (*tas_hook_uninit_t)(struct tas_data_t *);
+
+typedef int (*tas_hook_get_mixer_level_t)(struct tas_data_t *,int,uint *);
+typedef int (*tas_hook_set_mixer_level_t)(struct tas_data_t *,int,uint);
+
+typedef int (*tas_hook_enter_sleep_t)(struct tas_data_t *);
+typedef int (*tas_hook_leave_sleep_t)(struct tas_data_t *);
+
+typedef int (*tas_hook_supported_mixers_t)(struct tas_data_t *);
+typedef int (*tas_hook_mixer_is_stereo_t)(struct tas_data_t *,int);
+typedef int (*tas_hook_stereo_mixers_t)(struct tas_data_t *);
+
+typedef int (*tas_hook_output_device_change_t)(struct tas_data_t *,int,int,int);
+typedef int (*tas_hook_device_ioctl_t)(struct tas_data_t *,u_int,u_long);
+
+struct tas_driver_hooks_t {
+       /*
+        * All hardware initialisation must be performed in
+        * post_init(), as tas_dmasound_init() does a hardware reset.
+        *
+        * init() is called before tas_dmasound_init() so that
+        * ouput_device_change() is always called after i2c driver
+        * initialisation. The implication is that
+        * output_device_change() must cope with the fact that it
+        * may be called before post_init().
+        */
+
+       tas_hook_init_t                   init;
+       tas_hook_post_init_t              post_init;
+       tas_hook_uninit_t                 uninit;
+
+       tas_hook_get_mixer_level_t        get_mixer_level;
+       tas_hook_set_mixer_level_t        set_mixer_level;
+
+       tas_hook_enter_sleep_t            enter_sleep;
+       tas_hook_leave_sleep_t            leave_sleep;
+
+       tas_hook_supported_mixers_t       supported_mixers;
+       tas_hook_mixer_is_stereo_t        mixer_is_stereo;
+       tas_hook_stereo_mixers_t          stereo_mixers;
+
+       tas_hook_output_device_change_t   output_device_change;
+       tas_hook_device_ioctl_t           device_ioctl;
+};
+
+enum tas_write_mode_t {
+       WRITE_HW     = 0x01,
+       WRITE_SHADOW = 0x02,
+       WRITE_NORMAL = 0x03,
+       FORCE_WRITE  = 0x04
+};
+
+static inline uint
+tas_mono_to_stereo(uint mono)
+{
+       mono &=0xff;
+       return mono | (mono<<8);
+}
+
+/*
+ * Todo: make these functions a bit more efficient !
+ */
+static inline int
+tas_write_register(    struct tas_data_t *self,
+                       uint reg_num,
+                       uint reg_width,
+                       char *data,
+                       uint write_mode)
+{
+       int rc;
+
+       if (reg_width==0 || data==NULL || self==NULL)
+               return -EINVAL;
+       if (!(write_mode & FORCE_WRITE) &&
+           !memcmp(data,self->shadow[reg_num],reg_width))
+               return 0;
+
+       if (write_mode & WRITE_SHADOW)
+               memcpy(self->shadow[reg_num],data,reg_width);
+       if (write_mode & WRITE_HW) {
+               rc=i2c_smbus_write_block_data(self->client,
+                                             reg_num,
+                                             reg_width,
+                                             data);
+               if (rc < 0) {
+                       printk("tas: I2C block write failed \n");  
+                       return rc; 
+               }
+       }
+       return 0;
+}
+
+static inline int
+tas_sync_register(     struct tas_data_t *self,
+                       uint reg_num,
+                       uint reg_width)
+{
+       int rc;
+
+       if (reg_width==0 || self==NULL)
+               return -EINVAL;
+       rc=i2c_smbus_write_block_data(self->client,
+                                     reg_num,
+                                     reg_width,
+                                     self->shadow[reg_num]);
+       if (rc < 0) {
+               printk("tas: I2C block write failed \n");
+               return rc;
+       }
+       return 0;
+}
+
+static inline int
+tas_write_byte_register(       struct tas_data_t *self,
+                               uint reg_num,
+                               char data,
+                               uint write_mode)
+{
+       if (self==NULL)
+               return -1;
+       if (!(write_mode & FORCE_WRITE) && data != self->shadow[reg_num][0])
+               return 0;
+       if (write_mode & WRITE_SHADOW)
+               self->shadow[reg_num][0]=data;
+       if (write_mode & WRITE_HW) {
+               if (i2c_smbus_write_byte_data(self->client, reg_num, data) < 0) {
+                       printk("tas: I2C byte write failed \n");  
+                       return -1; 
+               }
+       }
+       return 0;
+}
+
+static inline int
+tas_sync_byte_register(        struct tas_data_t *self,
+                       uint reg_num,
+                       uint reg_width)
+{
+       if (reg_width==0 || self==NULL)
+               return -1;
+       if (i2c_smbus_write_byte_data(
+           self->client, reg_num, self->shadow[reg_num][0]) < 0) {
+               printk("tas: I2C byte write failed \n");
+               return -1;
+       }
+       return 0;
+}
+
+static inline int
+tas_read_register(     struct tas_data_t *self,
+                       uint reg_num,
+                       uint reg_width,
+                       char *data)
+{
+       if (reg_width==0 || data==NULL || self==NULL)
+               return -1;
+       memcpy(data,self->shadow[reg_num],reg_width);
+       return 0;
+}
+
+extern int tas_register_driver(struct tas_driver_hooks_t *hooks);
+
+extern int tas_get_mixer_level(int mixer,uint *level);
+extern int tas_set_mixer_level(int mixer,uint level);
+extern int tas_enter_sleep(void);
+extern int tas_leave_sleep(void);
+extern int tas_supported_mixers(void);
+extern int tas_mixer_is_stereo(int mixer);
+extern int tas_stereo_mixers(void);
+extern int tas_output_device_change(int,int,int);
+extern int tas_device_ioctl(u_int, u_long);
+
+extern void tas_cleanup(void);
+extern int tas_init(int driver_id,const char *driver_name);
+extern int tas_post_init(void);
+
+#endif /* _TAS_COMMON_H_ */
+/*
+ * Local Variables:
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/sound/oss/dmasound/tas_eq_prefs.h b/sound/oss/dmasound/tas_eq_prefs.h
new file mode 100644 (file)
index 0000000..3a994ed
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef _TAS_EQ_PREFS_H_
+#define _TAS_EQ_PREFS_H_
+
+struct tas_eq_pref_t {
+       u_int sample_rate;
+       u_int device_id;
+       u_int output_id;
+       u_int speaker_id;
+
+       struct tas_drce_t *drce;
+
+       u_int filter_count;
+       struct tas_biquad_ctrl_t *biquads;
+};
+
+#endif /* _TAS_EQ_PREFS_H_ */
+
+/*
+ * Local Variables:
+ * tab-width: 8
+ * indent-tabs-mode: t
+ * c-basic-offset: 8
+ * End:
+ */
diff --git a/sound/oss/dmasound/tas_ioctl.h b/sound/oss/dmasound/tas_ioctl.h
new file mode 100644 (file)
index 0000000..dccae3a
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef _TAS_IOCTL_H_
+#define _TAS_IOCTL_H_
+
+#include <linux/i2c.h>
+#include <linux/soundcard.h>
+
+
+#define TAS_READ_EQ              _SIOR('t',0,struct tas_biquad_ctrl_t)
+#define TAS_WRITE_EQ             _SIOW('t',0,struct tas_biquad_ctrl_t)
+
+#define TAS_READ_EQ_LIST         _SIOR('t',1,struct tas_biquad_ctrl_t)
+#define TAS_WRITE_EQ_LIST        _SIOW('t',1,struct tas_biquad_ctrl_t)
+
+#define TAS_READ_EQ_FILTER_COUNT  _SIOR('t',2,int)
+#define TAS_READ_EQ_CHANNEL_COUNT _SIOR('t',3,int)
+
+#define TAS_READ_DRCE            _SIOR('t',4,struct tas_drce_ctrl_t)
+#define TAS_WRITE_DRCE           _SIOW('t',4,struct tas_drce_ctrl_t)
+
+#define TAS_READ_DRCE_CAPS       _SIOR('t',5,int)
+#define TAS_READ_DRCE_MIN        _SIOR('t',6,int)
+#define TAS_READ_DRCE_MAX        _SIOR('t',7,int)
+
+#endif
diff --git a/sound/oss/dmasound/trans_16.c b/sound/oss/dmasound/trans_16.c
new file mode 100644 (file)
index 0000000..1f165e4
--- /dev/null
@@ -0,0 +1,679 @@
+/*
+ *  linux/drivers/sound/dmasound/trans_16.c
+ *
+ *  16 bit translation routines.  Only used by Power mac at present.
+ *
+ *  See linux/drivers/sound/dmasound/dmasound_core.c for copyright and
+ *  history prior to 08/02/2001.
+ *
+ *  08/02/2001 Iain Sandoe
+ *             split from dmasound_awacs.c
+ */
+
+#include <linux/soundcard.h>
+#include <asm/uaccess.h>
+#include "dmasound.h"
+
+static short dmasound_alaw2dma16[] ;
+static short dmasound_ulaw2dma16[] ;
+
+static ssize_t pmac_ct_law(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft);
+static ssize_t pmac_ct_s8(const u_char *userPtr, size_t userCount,
+                         u_char frame[], ssize_t *frameUsed,
+                         ssize_t frameLeft);
+static ssize_t pmac_ct_u8(const u_char *userPtr, size_t userCount,
+                         u_char frame[], ssize_t *frameUsed,
+                         ssize_t frameLeft);
+static ssize_t pmac_ct_s16(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft);
+static ssize_t pmac_ct_u16(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft);
+
+static ssize_t pmac_ctx_law(const u_char *userPtr, size_t userCount,
+                           u_char frame[], ssize_t *frameUsed,
+                           ssize_t frameLeft);
+static ssize_t pmac_ctx_s8(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft);
+static ssize_t pmac_ctx_u8(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft);
+static ssize_t pmac_ctx_s16(const u_char *userPtr, size_t userCount,
+                           u_char frame[], ssize_t *frameUsed,
+                           ssize_t frameLeft);
+static ssize_t pmac_ctx_u16(const u_char *userPtr, size_t userCount,
+                           u_char frame[], ssize_t *frameUsed,
+                           ssize_t frameLeft);
+
+static ssize_t pmac_ct_s16_read(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft);
+static ssize_t pmac_ct_u16_read(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft);
+
+/*** Translations ************************************************************/
+
+extern int expand_bal; /* Balance factor for expanding (not volume!) */
+static int expand_data;        /* Data for expanding */
+
+static ssize_t pmac_ct_law(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft)
+{
+       short *table = dmasound.soft.format == AFMT_MU_LAW
+               ? dmasound_ulaw2dma16 : dmasound_alaw2dma16;
+       ssize_t count, used;
+       short *p = (short *) &frame[*frameUsed];
+       int val, stereo = dmasound.soft.stereo;
+
+       frameLeft >>= 2;
+       if (stereo)
+               userCount >>= 1;
+       used = count = min_t(unsigned long, userCount, frameLeft);
+       while (count > 0) {
+               u_char data;
+               if (get_user(data, userPtr++))
+                       return -EFAULT;
+               val = table[data];
+               *p++ = val;
+               if (stereo) {
+                       if (get_user(data, userPtr++))
+                               return -EFAULT;
+                       val = table[data];
+               }
+               *p++ = val;
+               count--;
+       }
+       *frameUsed += used * 4;
+       return stereo? used * 2: used;
+}
+
+
+static ssize_t pmac_ct_s8(const u_char *userPtr, size_t userCount,
+                         u_char frame[], ssize_t *frameUsed,
+                         ssize_t frameLeft)
+{
+       ssize_t count, used;
+       short *p = (short *) &frame[*frameUsed];
+       int val, stereo = dmasound.soft.stereo;
+
+       frameLeft >>= 2;
+       if (stereo)
+               userCount >>= 1;
+       used = count = min_t(unsigned long, userCount, frameLeft);
+       while (count > 0) {
+               u_char data;
+               if (get_user(data, userPtr++))
+                       return -EFAULT;
+               val = data << 8;
+               *p++ = val;
+               if (stereo) {
+                       if (get_user(data, userPtr++))
+                               return -EFAULT;
+                       val = data << 8;
+               }
+               *p++ = val;
+               count--;
+       }
+       *frameUsed += used * 4;
+       return stereo? used * 2: used;
+}
+
+
+static ssize_t pmac_ct_u8(const u_char *userPtr, size_t userCount,
+                         u_char frame[], ssize_t *frameUsed,
+                         ssize_t frameLeft)
+{
+       ssize_t count, used;
+       short *p = (short *) &frame[*frameUsed];
+       int val, stereo = dmasound.soft.stereo;
+
+       frameLeft >>= 2;
+       if (stereo)
+               userCount >>= 1;
+       used = count = min_t(unsigned long, userCount, frameLeft);
+       while (count > 0) {
+               u_char data;
+               if (get_user(data, userPtr++))
+                       return -EFAULT;
+               val = (data ^ 0x80) << 8;
+               *p++ = val;
+               if (stereo) {
+                       if (get_user(data, userPtr++))
+                               return -EFAULT;
+                       val = (data ^ 0x80) << 8;
+               }
+               *p++ = val;
+               count--;
+       }
+       *frameUsed += used * 4;
+       return stereo? used * 2: used;
+}
+
+
+static ssize_t pmac_ct_s16(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft)
+{
+       ssize_t count, used;
+       int stereo = dmasound.soft.stereo;
+       short *fp = (short *) &frame[*frameUsed];
+
+       frameLeft >>= 2;
+       userCount >>= (stereo? 2: 1);
+       used = count = min_t(unsigned long, userCount, frameLeft);
+       if (!stereo) {
+               short *up = (short *) userPtr;
+               while (count > 0) {
+                       short data;
+                       if (get_user(data, up++))
+                               return -EFAULT;
+                       *fp++ = data;
+                       *fp++ = data;
+                       count--;
+               }
+       } else {
+               if (copy_from_user(fp, userPtr, count * 4))
+                       return -EFAULT;
+       }
+       *frameUsed += used * 4;
+       return stereo? used * 4: used * 2;
+}
+
+static ssize_t pmac_ct_u16(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft)
+{
+       ssize_t count, used;
+       int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000);
+       int stereo = dmasound.soft.stereo;
+       short *fp = (short *) &frame[*frameUsed];
+       short *up = (short *) userPtr;
+
+       frameLeft >>= 2;
+       userCount >>= (stereo? 2: 1);
+       used = count = min_t(unsigned long, userCount, frameLeft);
+       while (count > 0) {
+               short data;
+               if (get_user(data, up++))
+                       return -EFAULT;
+               data ^= mask;
+               *fp++ = data;
+               if (stereo) {
+                       if (get_user(data, up++))
+                               return -EFAULT;
+                       data ^= mask;
+               }
+               *fp++ = data;
+               count--;
+       }
+       *frameUsed += used * 4;
+       return stereo? used * 4: used * 2;
+}
+
+
+static ssize_t pmac_ctx_law(const u_char *userPtr, size_t userCount,
+                           u_char frame[], ssize_t *frameUsed,
+                           ssize_t frameLeft)
+{
+       unsigned short *table = (unsigned short *)
+               (dmasound.soft.format == AFMT_MU_LAW
+                ? dmasound_ulaw2dma16 : dmasound_alaw2dma16);
+       unsigned int data = expand_data;
+       unsigned int *p = (unsigned int *) &frame[*frameUsed];
+       int bal = expand_bal;
+       int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+       int utotal, ftotal;
+       int stereo = dmasound.soft.stereo;
+
+       frameLeft >>= 2;
+       if (stereo)
+               userCount >>= 1;
+       ftotal = frameLeft;
+       utotal = userCount;
+       while (frameLeft) {
+               u_char c;
+               if (bal < 0) {
+                       if (userCount == 0)
+                               break;
+                       if (get_user(c, userPtr++))
+                               return -EFAULT;
+                       data = table[c];
+                       if (stereo) {
+                               if (get_user(c, userPtr++))
+                                       return -EFAULT;
+                               data = (data << 16) + table[c];
+                       } else
+                               data = (data << 16) + data;
+                       userCount--;
+                       bal += hSpeed;
+               }
+               *p++ = data;
+               frameLeft--;
+               bal -= sSpeed;
+       }
+       expand_bal = bal;
+       expand_data = data;
+       *frameUsed += (ftotal - frameLeft) * 4;
+       utotal -= userCount;
+       return stereo? utotal * 2: utotal;
+}
+
+static ssize_t pmac_ctx_s8(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft)
+{
+       unsigned int *p = (unsigned int *) &frame[*frameUsed];
+       unsigned int data = expand_data;
+       int bal = expand_bal;
+       int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+       int stereo = dmasound.soft.stereo;
+       int utotal, ftotal;
+
+       frameLeft >>= 2;
+       if (stereo)
+               userCount >>= 1;
+       ftotal = frameLeft;
+       utotal = userCount;
+       while (frameLeft) {
+               u_char c;
+               if (bal < 0) {
+                       if (userCount == 0)
+                               break;
+                       if (get_user(c, userPtr++))
+                               return -EFAULT;
+                       data = c << 8;
+                       if (stereo) {
+                               if (get_user(c, userPtr++))
+                                       return -EFAULT;
+                               data = (data << 16) + (c << 8);
+                       } else
+                               data = (data << 16) + data;
+                       userCount--;
+                       bal += hSpeed;
+               }
+               *p++ = data;
+               frameLeft--;
+               bal -= sSpeed;
+       }
+       expand_bal = bal;
+       expand_data = data;
+       *frameUsed += (ftotal - frameLeft) * 4;
+       utotal -= userCount;
+       return stereo? utotal * 2: utotal;
+}
+
+
+static ssize_t pmac_ctx_u8(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft)
+{
+       unsigned int *p = (unsigned int *) &frame[*frameUsed];
+       unsigned int data = expand_data;
+       int bal = expand_bal;
+       int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+       int stereo = dmasound.soft.stereo;
+       int utotal, ftotal;
+
+       frameLeft >>= 2;
+       if (stereo)
+               userCount >>= 1;
+       ftotal = frameLeft;
+       utotal = userCount;
+       while (frameLeft) {
+               u_char c;
+               if (bal < 0) {
+                       if (userCount == 0)
+                               break;
+                       if (get_user(c, userPtr++))
+                               return -EFAULT;
+                       data = (c ^ 0x80) << 8;
+                       if (stereo) {
+                               if (get_user(c, userPtr++))
+                                       return -EFAULT;
+                               data = (data << 16) + ((c ^ 0x80) << 8);
+                       } else
+                               data = (data << 16) + data;
+                       userCount--;
+                       bal += hSpeed;
+               }
+               *p++ = data;
+               frameLeft--;
+               bal -= sSpeed;
+       }
+       expand_bal = bal;
+       expand_data = data;
+       *frameUsed += (ftotal - frameLeft) * 4;
+       utotal -= userCount;
+       return stereo? utotal * 2: utotal;
+}
+
+
+static ssize_t pmac_ctx_s16(const u_char *userPtr, size_t userCount,
+                           u_char frame[], ssize_t *frameUsed,
+                           ssize_t frameLeft)
+{
+       unsigned int *p = (unsigned int *) &frame[*frameUsed];
+       unsigned int data = expand_data;
+       unsigned short *up = (unsigned short *) userPtr;
+       int bal = expand_bal;
+       int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+       int stereo = dmasound.soft.stereo;
+       int utotal, ftotal;
+
+       frameLeft >>= 2;
+       userCount >>= (stereo? 2: 1);
+       ftotal = frameLeft;
+       utotal = userCount;
+       while (frameLeft) {
+               unsigned short c;
+               if (bal < 0) {
+                       if (userCount == 0)
+                               break;
+                       if (get_user(data, up++))
+                               return -EFAULT;
+                       if (stereo) {
+                               if (get_user(c, up++))
+                                       return -EFAULT;
+                               data = (data << 16) + c;
+                       } else
+                               data = (data << 16) + data;
+                       userCount--;
+                       bal += hSpeed;
+               }
+               *p++ = data;
+               frameLeft--;
+               bal -= sSpeed;
+       }
+       expand_bal = bal;
+       expand_data = data;
+       *frameUsed += (ftotal - frameLeft) * 4;
+       utotal -= userCount;
+       return stereo? utotal * 4: utotal * 2;
+}
+
+
+static ssize_t pmac_ctx_u16(const u_char *userPtr, size_t userCount,
+                           u_char frame[], ssize_t *frameUsed,
+                           ssize_t frameLeft)
+{
+       int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000);
+       unsigned int *p = (unsigned int *) &frame[*frameUsed];
+       unsigned int data = expand_data;
+       unsigned short *up = (unsigned short *) userPtr;
+       int bal = expand_bal;
+       int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
+       int stereo = dmasound.soft.stereo;
+       int utotal, ftotal;
+
+       frameLeft >>= 2;
+       userCount >>= (stereo? 2: 1);
+       ftotal = frameLeft;
+       utotal = userCount;
+       while (frameLeft) {
+               unsigned short c;
+               if (bal < 0) {
+                       if (userCount == 0)
+                               break;
+                       if (get_user(data, up++))
+                               return -EFAULT;
+                       data ^= mask;
+                       if (stereo) {
+                               if (get_user(c, up++))
+                                       return -EFAULT;
+                               data = (data << 16) + (c ^ mask);
+                       } else
+                               data = (data << 16) + data;
+                       userCount--;
+                       bal += hSpeed;
+               }
+               *p++ = data;
+               frameLeft--;
+               bal -= sSpeed;
+       }
+       expand_bal = bal;
+       expand_data = data;
+       *frameUsed += (ftotal - frameLeft) * 4;
+       utotal -= userCount;
+       return stereo? utotal * 4: utotal * 2;
+}
+
+/* data in routines... */
+
+static ssize_t pmac_ct_s8_read(const u_char *userPtr, size_t userCount,
+                         u_char frame[], ssize_t *frameUsed,
+                         ssize_t frameLeft)
+{
+       ssize_t count, used;
+       short *p = (short *) &frame[*frameUsed];
+       int val, stereo = dmasound.soft.stereo;
+
+       frameLeft >>= 2;
+       if (stereo)
+               userCount >>= 1;
+       used = count = min_t(unsigned long, userCount, frameLeft);
+       while (count > 0) {
+               u_char data;
+
+               val = *p++;
+               data = val >> 8;
+               if (put_user(data, (u_char *)userPtr++))
+                       return -EFAULT;
+               if (stereo) {
+                       val = *p;
+                       data = val >> 8;
+                       if (put_user(data, (u_char *)userPtr++))
+                               return -EFAULT;
+               }
+               p++;
+               count--;
+       }
+       *frameUsed += used * 4;
+       return stereo? used * 2: used;
+}
+
+
+static ssize_t pmac_ct_u8_read(const u_char *userPtr, size_t userCount,
+                         u_char frame[], ssize_t *frameUsed,
+                         ssize_t frameLeft)
+{
+       ssize_t count, used;
+       short *p = (short *) &frame[*frameUsed];
+       int val, stereo = dmasound.soft.stereo;
+
+       frameLeft >>= 2;
+       if (stereo)
+               userCount >>= 1;
+       used = count = min_t(unsigned long, userCount, frameLeft);
+       while (count > 0) {
+               u_char data;
+
+               val = *p++;
+               data = (val >> 8) ^ 0x80;
+               if (put_user(data, (u_char *)userPtr++))
+                       return -EFAULT;
+               if (stereo) {
+                       val = *p;
+                       data = (val >> 8) ^ 0x80;
+                       if (put_user(data, (u_char *)userPtr++))
+                               return -EFAULT;
+               }
+               p++;
+               count--;
+       }
+       *frameUsed += used * 4;
+       return stereo? used * 2: used;
+}
+
+static ssize_t pmac_ct_s16_read(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft)
+{
+       ssize_t count, used;
+       int stereo = dmasound.soft.stereo;
+       short *fp = (short *) &frame[*frameUsed];
+
+       frameLeft >>= 2;
+       userCount >>= (stereo? 2: 1);
+       used = count = min_t(unsigned long, userCount, frameLeft);
+       if (!stereo) {
+               short *up = (short *) userPtr;
+               while (count > 0) {
+                       short data;
+                       data = *fp;
+                       if (put_user(data, up++))
+                               return -EFAULT;
+                       fp+=2;
+                       count--;
+               }
+       } else {
+               if (copy_to_user((u_char *)userPtr, fp, count * 4))
+                       return -EFAULT;
+       }
+       *frameUsed += used * 4;
+       return stereo? used * 4: used * 2;
+}
+
+static ssize_t pmac_ct_u16_read(const u_char *userPtr, size_t userCount,
+                          u_char frame[], ssize_t *frameUsed,
+                          ssize_t frameLeft)
+{
+       ssize_t count, used;
+       int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000);
+       int stereo = dmasound.soft.stereo;
+       short *fp = (short *) &frame[*frameUsed];
+       short *up = (short *) userPtr;
+
+       frameLeft >>= 2;
+       userCount >>= (stereo? 2: 1);
+       used = count = min_t(unsigned long, userCount, frameLeft);
+       while (count > 0) {
+               int data;
+
+               data = *fp++;
+               data ^= mask;
+               if (put_user(data, up++))
+                       return -EFAULT;
+               if (stereo) {
+                       data = *fp;
+                       data ^= mask;
+                       if (put_user(data, up++))
+                               return -EFAULT;
+               }
+               fp++;
+               count--;
+       }
+       *frameUsed += used * 4;
+       return stereo? used * 4: used * 2;
+}
+
+TRANS transAwacsNormal = {
+       ct_ulaw:        pmac_ct_law,
+       ct_alaw:        pmac_ct_law,
+       ct_s8:          pmac_ct_s8,
+       ct_u8:          pmac_ct_u8,
+       ct_s16be:       pmac_ct_s16,
+       ct_u16be:       pmac_ct_u16,
+       ct_s16le:       pmac_ct_s16,
+       ct_u16le:       pmac_ct_u16,
+};
+
+TRANS transAwacsExpand = {
+       ct_ulaw:        pmac_ctx_law,
+       ct_alaw:        pmac_ctx_law,
+       ct_s8:          pmac_ctx_s8,
+       ct_u8:          pmac_ctx_u8,
+       ct_s16be:       pmac_ctx_s16,
+       ct_u16be:       pmac_ctx_u16,
+       ct_s16le:       pmac_ctx_s16,
+       ct_u16le:       pmac_ctx_u16,
+};
+
+TRANS transAwacsNormalRead = {
+       ct_s8:          pmac_ct_s8_read,
+       ct_u8:          pmac_ct_u8_read,
+       ct_s16be:       pmac_ct_s16_read,
+       ct_u16be:       pmac_ct_u16_read,
+       ct_s16le:       pmac_ct_s16_read,
+       ct_u16le:       pmac_ct_u16_read,
+};
+
+/* translation tables */
+/* 16 bit mu-law */
+
+static short dmasound_ulaw2dma16[] = {
+       -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956,
+       -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764,
+       -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412,
+       -11900, -11388, -10876, -10364, -9852,  -9340,  -8828,  -8316,
+       -7932,  -7676,  -7420,  -7164,  -6908,  -6652,  -6396,  -6140,
+       -5884,  -5628,  -5372,  -5116,  -4860,  -4604,  -4348,  -4092,
+       -3900,  -3772,  -3644,  -3516,  -3388,  -3260,  -3132,  -3004,
+       -2876,  -2748,  -2620,  -2492,  -2364,  -2236,  -2108,  -1980,
+       -1884,  -1820,  -1756,  -1692,  -1628,  -1564,  -1500,  -1436,
+       -1372,  -1308,  -1244,  -1180,  -1116,  -1052,  -988,   -924,
+       -876,   -844,   -812,   -780,   -748,   -716,   -684,   -652,
+       -620,   -588,   -556,   -524,   -492,   -460,   -428,   -396,
+       -372,   -356,   -340,   -324,   -308,   -292,   -276,   -260,
+       -244,   -228,   -212,   -196,   -180,   -164,   -148,   -132,
+       -120,   -112,   -104,   -96,    -88,    -80,    -72,    -64,
+       -56,    -48,    -40,    -32,    -24,    -16,    -8,     0,
+       32124,  31100,  30076,  29052,  28028,  27004,  25980,  24956,
+       23932,  22908,  21884,  20860,  19836,  18812,  17788,  16764,
+       15996,  15484,  14972,  14460,  13948,  13436,  12924,  12412,
+       11900,  11388,  10876,  10364,  9852,   9340,   8828,   8316,
+       7932,   7676,   7420,   7164,   6908,   6652,   6396,   6140,
+       5884,   5628,   5372,   5116,   4860,   4604,   4348,   4092,
+       3900,   3772,   3644,   3516,   3388,   3260,   3132,   3004,
+       2876,   2748,   2620,   2492,   2364,   2236,   2108,   1980,
+       1884,   1820,   1756,   1692,   1628,   1564,   1500,   1436,
+       1372,   1308,   1244,   1180,   1116,   1052,   988,    924,
+       876,    844,    812,    780,    748,    716,    684,    652,
+       620,    588,    556,    524,    492,    460,    428,    396,
+       372,    356,    340,    324,    308,    292,    276,    260,
+       244,    228,    212,    196,    180,    164,    148,    132,
+       120,    112,    104,    96,     88,     80,     72,     64,
+       56,     48,     40,     32,     24,     16,     8,      0,
+};
+
+/* 16 bit A-law */
+
+static short dmasound_alaw2dma16[] = {
+       -5504,  -5248,  -6016,  -5760,  -4480,  -4224,  -4992,  -4736,
+       -7552,  -7296,  -8064,  -7808,  -6528,  -6272,  -7040,  -6784,
+       -2752,  -2624,  -3008,  -2880,  -2240,  -2112,  -2496,  -2368,
+       -3776,  -3648,  -4032,  -3904,  -3264,  -3136,  -3520,  -3392,
+       -22016, -20992, -24064, -23040, -17920, -16896, -19968, -18944,
+       -30208, -29184, -32256, -31232, -26112, -25088, -28160, -27136,
+       -11008, -10496, -12032, -11520, -8960,  -8448,  -9984,  -9472,
+       -15104, -14592, -16128, -15616, -13056, -12544, -14080, -13568,
+       -344,   -328,   -376,   -360,   -280,   -264,   -312,   -296,
+       -472,   -456,   -504,   -488,   -408,   -392,   -440,   -424,
+       -88,    -72,    -120,   -104,   -24,    -8,     -56,    -40,
+       -216,   -200,   -248,   -232,   -152,   -136,   -184,   -168,
+       -1376,  -1312,  -1504,  -1440,  -1120,  -1056,  -1248,  -1184,
+       -1888,  -1824,  -2016,  -1952,  -1632,  -1568,  -1760,  -1696,
+       -688,   -656,   -752,   -720,   -560,   -528,   -624,   -592,
+       -944,   -912,   -1008,  -976,   -816,   -784,   -880,   -848,
+       5504,   5248,   6016,   5760,   4480,   4224,   4992,   4736,
+       7552,   7296,   8064,   7808,   6528,   6272,   7040,   6784,
+       2752,   2624,   3008,   2880,   2240,   2112,   2496,   2368,
+       3776,   3648,   4032,   3904,   3264,   3136,   3520,   3392,
+       22016,  20992,  24064,  23040,  17920,  16896,  19968,  18944,
+       30208,  29184,  32256,  31232,  26112,  25088,  28160,  27136,
+       11008,  10496,  12032,  11520,  8960,   8448,   9984,   9472,
+       15104,  14592,  16128,  15616,  13056,  12544,  14080,  13568,
+       344,    328,    376,    360,    280,    264,    312,    296,
+       472,    456,    504,    488,    408,    392,    440,    424,
+       88,     72,     120,    104,    24,     8,      56,     40,
+       216,    200,    248,    232,    152,    136,    184,    168,
+       1376,   1312,   1504,   1440,   1120,   1056,   1248,   1184,
+       1888,   1824,   2016,   1952,   1632,   1568,   1760,   1696,
+       688,    656,    752,    720,    560,    528,    624,    592,
+       944,    912,    1008,   976,    816,    784,    880,    848,
+};