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