| /* |
| * 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; |
| struct work_struct change; |
| }; |
| |
| #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(struct work_struct *work) |
| { |
| struct tas3004_data_t *self; |
| self = container_of(work, struct tas3004_data_t, change); |
| tas3004_update_device_parameters(self); |
| } |
| |
| 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(&self->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 = kzalloc(sz, GFP_KERNEL); |
| if (!self) |
| return -ENOMEM; |
| |
| 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(&self->change, tas3004_device_change_handler); |
| 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 |
| }; |