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,
# 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
/* ------- - --- ----- - ------ */
#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 */
#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 */
#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)
#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_ */
--- /dev/null
+/*
+ * 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);
+}
* 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.
#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;
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
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);
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;
}
-/*** AE - TUMBLER START *********************************************************/
+/*** AE - TUMBLER / SNAPPER START ************************************************/
int gpio_audio_reset, gpio_audio_reset_pol;
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",
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 ************************************************/
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;
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);
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
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 ;
};
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 ;
/*
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 */
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 ;
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;
}
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 */
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])));
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();
}
{
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;
emergency_in_use = 0 ; /* done that */
--write_sq.count;
--write_sq.active;
+ i_nowrap++;
if (++i >= write_sq.max_count)
i = 0;
}
}
/* 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;
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;
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--)
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.
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 */
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
/* 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();
/* 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();
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);
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]);
/* 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:
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);
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;
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);
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;
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);
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 */
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:
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;
* 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
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 ;
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;
* 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) {
/* 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 ;
#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 ;
#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 ;
}
}
/* 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 */
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 ;
}
}
/* 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
/* 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 */
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 ;
}
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 ;
}
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 ;
{
switch (awacs_revision) {
case AWACS_TUMBLER:
- awacs_tumbler_cleanup();
+ case AWACS_SNAPPER:
+ tas_dmasound_cleanup();
tas_cleanup();
break ;
case AWACS_DACA:
--- /dev/null
+/*
+ * 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
+};
--- /dev/null
+/*
+ * 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_ */
--- /dev/null
+#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
+};
--- /dev/null
+/*
+ * 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
+};
--- /dev/null
+/*
+ * 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_ */
--- /dev/null
+#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
+};
--- /dev/null
+#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);
+}
--- /dev/null
+#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:
+ */
--- /dev/null
+#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:
+ */
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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,
+};