summaryrefslogtreecommitdiff
path: root/sound/oss/dmasound/tas3004.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/oss/dmasound/tas3004.c')
-rw-r--r--sound/oss/dmasound/tas3004.c1140
1 files changed, 1140 insertions, 0 deletions
diff --git a/sound/oss/dmasound/tas3004.c b/sound/oss/dmasound/tas3004.c
new file mode 100644
index 000000000000..82eaaca2db9a
--- /dev/null
+++ b/sound/oss/dmasound/tas3004.c
@@ -0,0 +1,1140 @@
+/*
+ * 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>:
+ *
+ * Input support by Renzo Davoli <renzo@cs.unibo.it>
+ *
+ */
+
+#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 ||
+ reg_num==TAS3004_REG_ANALOG_CTRL) {
+ 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 ||
+ reg_num==TAS3004_REG_ANALOG_CTRL) {
+ 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 |
+ SOUND_MASK_MIC |
+ SOUND_MASK_LINE;
+}
+
+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;
+ case SOUND_MIXER_MIC:
+ if ((level&0xff)>0) {
+ software_input_volume = SW_INPUT_VOLUME_SCALE * (level&0xff);
+ if (self->super.mixer[mixer] == 0) {
+ self->super.mixer[SOUND_MIXER_LINE] = 0;
+ shadow[TAS3004_REG_ANALOG_CTRL][0]=0xc2;
+ rc = tas3004_sync_register(self,TAS3004_REG_ANALOG_CTRL);
+ } else rc=0;
+ } else {
+ self->super.mixer[SOUND_MIXER_LINE] = SW_INPUT_VOLUME_DEFAULT;
+ software_input_volume = SW_INPUT_VOLUME_SCALE *
+ (self->super.mixer[SOUND_MIXER_LINE]&0xff);
+ shadow[TAS3004_REG_ANALOG_CTRL][0]=0x00;
+ rc = tas3004_sync_register(self,TAS3004_REG_ANALOG_CTRL);
+ }
+ break;
+ case SOUND_MIXER_LINE:
+ if (self->super.mixer[SOUND_MIXER_MIC] == 0) {
+ software_input_volume = SW_INPUT_VOLUME_SCALE * (level&0xff);
+ rc=0;
+ }
+ 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);
+ (void)tas3004_sync_register(self,TAS3004_REG_ANALOG_CTRL);
+
+ 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)
+{
+ void __user *argp = (void __user *)arg;
+ int rc;
+ struct tas_biquad_ctrl_t biquad;
+
+ if (copy_from_user((void *)&biquad, argp, 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(argp, &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;
+ struct tas_biquad_ctrl_list_t __user *argp = (void __user *)arg;
+
+ memset(sync_required,0,sizeof(sync_required));
+
+ if (copy_from_user(&filter_count, &argp->filter_count, sizeof(int)))
+ return -EFAULT;
+
+ if (copy_from_user(&flags, &argp->flags, sizeof(int)))
+ return -EFAULT;
+
+ if (cmd & SIOC_IN) {
+ }
+
+ for (i=0; i < filter_count; i++) {
+ if (copy_from_user(&biquad, &argp->biquads[i],
+ sizeof(struct tas_biquad_ctrl_t))) {
+ return -EFAULT;
+ }
+
+ if (cmd & SIOC_IN) {
+ sync_required[biquad.channel][biquad.filter]=1;
+ rc=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(&argp->biquads[i], &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;
+ void __user *argp = (void __user *)arg;
+
+ if (copy_from_user(&drce_ctrl, argp, sizeof(struct tas_drce_ctrl_t)))
+ return -EFAULT;
+
+#ifdef DEBUG_DRCE
+ printk("DRCE: 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(argp, &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)
+{
+ uint __user *argp = (void __user *)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, argp);
+ return 0;
+
+ case TAS_READ_EQ_CHANNEL_COUNT:
+ put_user(TAS3004_BIQUAD_CHANNEL_COUNT, argp);
+ 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,
+ argp);
+ 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(&drce_ctrl, argp,
+ 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(argp, &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);
+
+ tas3004_set_mixer_level(self, SOUND_MIXER_LINE,SW_INPUT_VOLUME_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);
+
+ tas3004_set_mixer_level(self, SOUND_MIXER_LINE, 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
+};