lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Date:	Wed, 16 Jun 2010 13:40:57 -0500
From:	<jesse.marroquin@...im-ic.com>
To:	<lrg@...mlogic.co.uk>, <broonie@...nsource.wolfsonmicro.com>,
	<perex@...ex.cz>, <tiwai@...e.de>, <peter.ujfalusi@...ia.com>,
	<barry.song@...log.com>, <jy0922.shim@...sung.com>,
	<morimoto.kuninori@...esas.com>
CC:	<alsa-devel@...a-project.org>, <linux-kernel@...r.kernel.org>,
	Jesse Marroquin <jesse.marroquin@...im-ic.com>,
	Peter Hsiang <peter.hsiang@...im-ic.com>
Subject: [PATCH] ASoC: Add max9888 CODEC driver

From: Jesse Marroquin <jesse.marroquin@...im-ic.com>

This patch adds max9888 CODEC driver.

Signed-off-by: Jesse Marroquin <jesse.marroquin@...im-ic.com>
Signed-off-by: Peter Hsiang <peter.hsiang@...im-ic.com>
---
 sound/soc/codecs/Kconfig   |    4 +
 sound/soc/codecs/Makefile  |    2 +
 sound/soc/codecs/max9888.c | 1939 ++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/max9888.h |  180 ++++
 4 files changed, 2125 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/codecs/max9888.c
 create mode 100644 sound/soc/codecs/max9888.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index c37c844..7bbdf74 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -71,6 +71,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_WM9705 if SND_SOC_AC97_BUS
 	select SND_SOC_WM9712 if SND_SOC_AC97_BUS
 	select SND_SOC_WM9713 if SND_SOC_AC97_BUS
+	select SND_SOC_MAX9888 if I2C
         help
           Normally ASoC codec drivers are only built if a machine driver which
           uses them is also built since they are only usable with a machine
@@ -273,6 +274,9 @@ config SND_SOC_WM9712
 config SND_SOC_WM9713
 	tristate
 
+config SND_SOC_MAX9888
+	tristate
+
 # Amp
 config SND_SOC_MAX9877
 	tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 4a9c205..e2eab0f 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -57,6 +57,7 @@ snd-soc-wm9705-objs := wm9705.o
 snd-soc-wm9712-objs := wm9712.o
 snd-soc-wm9713-objs := wm9713.o
 snd-soc-wm-hubs-objs := wm_hubs.o
+snd-soc-max9888-objs := max9888.o
 
 # Amp
 snd-soc-max9877-objs := max9877.o
@@ -123,6 +124,7 @@ obj-$(CONFIG_SND_SOC_WM9705)	+= snd-soc-wm9705.o
 obj-$(CONFIG_SND_SOC_WM9712)	+= snd-soc-wm9712.o
 obj-$(CONFIG_SND_SOC_WM9713)	+= snd-soc-wm9713.o
 obj-$(CONFIG_SND_SOC_WM_HUBS)	+= snd-soc-wm-hubs.o
+obj-$(CONFIG_SND_SOC_MAX9888)	+= snd-soc-max9888.o
 
 # Amp
 obj-$(CONFIG_SND_SOC_MAX9877)	+= snd-soc-max9877.o
diff --git a/sound/soc/codecs/max9888.c b/sound/soc/codecs/max9888.c
new file mode 100644
index 0000000..c81f177
--- /dev/null
+++ b/sound/soc/codecs/max9888.c
@@ -0,0 +1,1939 @@
+/*
+ * max9888.c -- MAX9888 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "max9888.h"
+
+#define AUDIO_NAME		"MAX9888"
+#define MAX9888_DRIVER_VERSION	"1.0"
+
+#ifdef MAX9888_DEBUG
+#define dprintk(x...) printk(KERN_WARNING x)
+#else
+#define dprintk(x...)
+#endif
+
+#define err(format, arg...) \
+	printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg)
+
+#define info(format, arg...) \
+	printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg)
+
+#define warn(format, arg...) \
+	printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg)
+
+struct snd_soc_codec_device soc_codec_dev_max9888;
+
+/* codec private data */
+struct max9888_priv {
+	unsigned int sysclk;
+	unsigned int dai1_rate;
+	unsigned int dai1_fmt;
+	unsigned int reg_14_val;
+	unsigned int dai2_rate;
+	unsigned int dai2_fmt;
+	unsigned int reg_1c_val;
+	unsigned int rev_version;
+	struct snd_soc_codec codec;
+};
+
+/*
+ * Equalizer DSP filter coefficients generated from the evkit software tool
+ */
+static unsigned int param_eq_band_1_voice[] = {
+	0x2027, 0xC001, 0x3FFB, 0x0042, 0x0184 };
+static unsigned int param_eq_band_2_voice[] = {
+	0x2027, 0xC003, 0x3FE7, 0x012F, 0x037B };
+static unsigned int param_eq_band_3_voice[] = {
+	0x3EC6, 0xC024, 0x3C2E, 0x043D, 0x15C7 };
+static unsigned int param_eq_band_4_voice[] = {
+	0x3EC6, 0xC261, 0x344F, 0x1145, 0x24DF };
+static unsigned int param_eq_band_5_voice[] = {
+	0x2027, 0xEEB0, 0x38F9, 0x3D9C, 0x1D26 };
+static unsigned int param_eq_band_1_music[] = {
+	0x1027, 0xC001, 0x3F9F, 0x006C, 0x06F2, };
+static unsigned int param_eq_band_2_music[] = {
+	0x2894, 0xC003, 0x3F32, 0x0110, 0x0A1A, };
+static unsigned int param_eq_band_3_music[] = {
+	0x2D25, 0xC072, 0x39F3, 0x0782, 0x1B29, };
+static unsigned int param_eq_band_4_music[] = {
+	0x1463, 0xC59A, 0x2876, 0x1A2E, 0x3195, };
+static unsigned int param_eq_band_5_music[] = {
+	0x4000, 0xD468, 0x30EA, 0x2EDB, 0x2944, };
+
+static unsigned int param_eq_band_1_flat[] = {
+	0x2027, 0xc001, 0x3ff1, 0x006a, 0x02b4 };
+static unsigned int param_eq_band_2_flat[] = {
+	0x2027, 0xc008, 0x3fcc, 0x01e4, 0x0512 };
+static unsigned int param_eq_band_3_flat[] = {
+	0x2027, 0xc052, 0x3f77, 0x0661, 0x083e };
+static unsigned int param_eq_band_4_flat[] = {
+	0x2027, 0xc2dd, 0x3e60, 0x12eb, 0x0e54 };
+static unsigned int param_eq_band_5_flat[] = {
+	0x2027, 0xe611, 0x36fa, 0x3a82, 0x20c2 };
+
+static unsigned int param_eq_band_1_trebel_reduce[] = {
+	0x2027, 0xC001, 0x3FFB, 0x0042, 0x0184, };
+static unsigned int param_eq_band_2_trebel_reduce[] = {
+	0x2027, 0xC003, 0x3FE7, 0x012F, 0x037B, };
+static unsigned int param_eq_band_3_trebel_reduce[] = {
+	0x2027, 0xC028, 0x3FA4, 0x0471, 0x06BC, };
+static unsigned int param_eq_band_4_trebel_reduce[] = {
+	0x16AE, 0xC22A, 0x2FAF, 0x1080, 0x2AAF, };
+static unsigned int param_eq_band_5_trebel_reduce[] = {
+	0x1027, 0xEBF3, 0xFC34, 0x3CC6, 0x3FE3, };
+
+static unsigned int param_eq_band_1_loudness[] = {
+	0x3F62, 0xC001, 0x3FDF, 0x0042, 0x040B, };
+static unsigned int param_eq_band_2_loudness[] = {
+	0x2D25, 0xC003, 0x3F08, 0x0101, 0x0B17, };
+static unsigned int param_eq_band_3_loudness[] = {
+	0x2027, 0xC028, 0x3FA4, 0x0471, 0x06BC, };
+static unsigned int param_eq_band_4_loudness[] = {
+	0x2D25, 0xC245, 0x3290, 0x10E2, 0x273B, };
+static unsigned int param_eq_band_5_loudness[] = {
+	0x4000, 0xEBF3, 0x149E, 0x3CC6, 0x3C96, };
+
+/*
+ * Dynamic high pass excursion limiter filter coefficients.
+ * The coefficients define the user programmable frequency response for the
+ * excursion limiter.
+ * Use the PC evkit software to generate the coefficients and put it here.
+ */
+static unsigned int param_ex_resp_a[] = { 0, 0, 0, 0, 0 };
+static unsigned int param_ex_resp_b[] = { 0, 0, 0, 0, 0 };
+static unsigned int param_ex_resp_c[] = { 0, 0, 0, 0, 0 };
+
+/*
+ * Read the MAX9888 register space
+ */
+static unsigned int max9888_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+	struct i2c_msg msg[2];
+	struct i2c_client *client;
+	u8 data[2];
+	int ret = 0;
+
+	client = (struct i2c_client *)codec->control_data;
+	data[0] = reg;
+	msg[0].addr = client->addr;
+	msg[0].flags = 0;
+	msg[0].buf = &data[0];
+	msg[0].len = 1;
+
+	msg[1].addr = client->addr;
+	msg[1].flags = I2C_M_RD;
+	msg[1].buf = &data[1];
+	msg[1].len = 1;
+#if defined(CONFIG_I2C)
+	ret = i2c_transfer(client->adapter, &msg[0], 2);
+#endif
+	return (ret == 2) ? data[1] : -EIO;
+}
+
+/*
+ * Write to the MAX9888 register space
+ */
+static int max9888_write(struct snd_soc_codec *codec, unsigned int reg,
+			 unsigned int value)
+{
+	u8 data[2];
+
+	data[0] = reg;
+	data[1] = value;
+
+	dprintk("%s 0x%02x=0x%02x\n", __func__, reg, value);
+	if (codec->hw_write(codec->control_data, data, 2) == 2)
+		return 0;
+	else
+		return -EIO;
+}
+
+#define INA1_PGA_BIT	0x01
+#define INA2_PGA_BIT	0x02
+#define INB1_PGA_BIT	0x04
+#define INB2_PGA_BIT	0x08
+
+/*
+ * The INx1 and INx2 PGA's share a power control signal.
+ * This function OR's the two power events to keep an unpowered INx
+ * from turning off it's counterpart.
+ * The control names are used to identify the PGA.
+ */
+int max9888_pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k,
+			 int event)
+{
+	struct snd_soc_codec *codec = w->codec;
+	unsigned int val;
+	unsigned int pga;
+	unsigned int mask;
+	static int power_state;
+
+	if (w->reg != M9888_REG_4A_PWR_EN_IN)
+		return -EINVAL;
+
+	if (strncmp(w->name, "INA1", 4) == 0) {
+		pga = INA1_PGA_BIT;
+		mask = INA1_PGA_BIT | INA2_PGA_BIT;
+	} else if (strncmp(w->name, "INA2", 4) == 0) {
+		pga = INA2_PGA_BIT;
+		mask = INA1_PGA_BIT | INA2_PGA_BIT;
+	} else if (strncmp(w->name, "INB1", 4) == 0) {
+		pga = INB1_PGA_BIT;
+		mask = INB1_PGA_BIT | INB2_PGA_BIT;
+	} else if (strncmp(w->name, "INB2", 4) == 0) {
+		pga = INB2_PGA_BIT;
+		mask = INB1_PGA_BIT | INB2_PGA_BIT;
+	} else {
+		return -EINVAL;
+	}
+
+	switch (event) {
+	case SND_SOC_DAPM_POST_PMU:
+
+		power_state |= pga;
+
+		/* Turn on, avoiding unnecessary writes */
+		val = max9888_read(codec, w->reg);
+
+		if (!(val & (1 << w->shift))) {
+			val |= (1 << w->shift);
+			max9888_write(codec, w->reg, val | MBEN);
+		}
+		break;
+
+	case SND_SOC_DAPM_PRE_PMD:
+		power_state &= ~pga;
+
+		/* Turn off if both are off, avoiding unnecessary writes */
+		if (!(power_state & mask)) {
+			val = max9888_read(codec, w->reg);
+			if (val & (1 << w->shift)) {
+				val &= ~(1 << w->shift);
+				max9888_write(codec, w->reg, val & ~MBEN);
+			}
+		}
+		break;
+	default:
+		val = max9888_read(codec, w->reg);
+		max9888_write(codec, w->reg, val | MBEN);
+		break;
+	}
+
+	return 0;
+}
+
+/*
+ * Define a few static eq settings
+ */
+static const char *max9888_eq[] = {
+	"voice", "music", "flat", "treble reduce", "loudness" };
+
+static const struct soc_enum max9888_eq_enum[] = {
+	SOC_ENUM_SINGLE_EXT(5, max9888_eq),
+};
+
+enum max9888_eq_enumeration {
+	MAX9888_EQ_VOICE = 0,
+	MAX9888_EQ_MUSIC,
+	MAX9888_EQ_FLAT,
+	MAX9888_EQ_TREBLE_REDUCE,
+	MAX9888_EQ_LOUDNESS
+};
+
+static int max9888_eq1_value = MAX9888_EQ_FLAT;
+static int max9888_eq2_value = MAX9888_EQ_FLAT;
+
+/*
+ * Do work of loading regs from table
+ */
+int m9888_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+		  unsigned int band, unsigned int *coefs)
+{
+	unsigned int eq_reg;
+	unsigned int i;
+
+	if (band > 4)
+		return 1;
+
+	if (dai > 1)
+		return 1;
+
+	/* Load the base register address */
+	eq_reg = dai ? M9888_REG_82_DAI2_EQ_BASE : M9888_REG_50_DAI1_EQ_BASE;
+
+	/* Add the band address offset, note adjustment for word address */
+	eq_reg += band * (REGS_PER_BAND << 1);
+
+	/* Step through the registers and coefs */
+	for (i = 0; i < REGS_PER_BAND; i++) {
+		max9888_write(codec, eq_reg++, HIGH_BYTE(coefs[i]));
+		max9888_write(codec, eq_reg++, LOW_BYTE(coefs[i]));
+	}
+
+	return 0;
+}
+
+/*
+ * Load selected eq from coefficient table
+ */
+int m9888_eq_control(struct snd_soc_codec *codec, unsigned int dai,
+		     unsigned int curve)
+{
+	switch (curve) {
+	case MAX9888_EQ_VOICE:
+		m9888_eq_band(codec, dai, 0, param_eq_band_1_voice);
+		m9888_eq_band(codec, dai, 1, param_eq_band_2_voice);
+		m9888_eq_band(codec, dai, 2, param_eq_band_3_voice);
+		m9888_eq_band(codec, dai, 3, param_eq_band_4_voice);
+		m9888_eq_band(codec, dai, 4, param_eq_band_5_voice);
+		break;
+	case MAX9888_EQ_MUSIC:
+		m9888_eq_band(codec, dai, 0, param_eq_band_1_music);
+		m9888_eq_band(codec, dai, 1, param_eq_band_2_music);
+		m9888_eq_band(codec, dai, 2, param_eq_band_3_music);
+		m9888_eq_band(codec, dai, 3, param_eq_band_4_music);
+		m9888_eq_band(codec, dai, 4, param_eq_band_5_music);
+		break;
+	case MAX9888_EQ_FLAT:
+		m9888_eq_band(codec, dai, 0, param_eq_band_1_flat);
+		m9888_eq_band(codec, dai, 1, param_eq_band_2_flat);
+		m9888_eq_band(codec, dai, 2, param_eq_band_3_flat);
+		m9888_eq_band(codec, dai, 3, param_eq_band_4_flat);
+		m9888_eq_band(codec, dai, 4, param_eq_band_5_flat);
+		break;
+	case MAX9888_EQ_TREBLE_REDUCE:
+		m9888_eq_band(codec, dai, 0, param_eq_band_1_trebel_reduce);
+		m9888_eq_band(codec, dai, 1, param_eq_band_2_trebel_reduce);
+		m9888_eq_band(codec, dai, 2, param_eq_band_3_trebel_reduce);
+		m9888_eq_band(codec, dai, 3, param_eq_band_4_trebel_reduce);
+		m9888_eq_band(codec, dai, 4, param_eq_band_5_trebel_reduce);
+		break;
+	case MAX9888_EQ_LOUDNESS:
+		m9888_eq_band(codec, dai, 0, param_eq_band_1_loudness);
+		m9888_eq_band(codec, dai, 1, param_eq_band_2_loudness);
+		m9888_eq_band(codec, dai, 2, param_eq_band_3_loudness);
+		m9888_eq_band(codec, dai, 3, param_eq_band_4_loudness);
+		m9888_eq_band(codec, dai, 4, param_eq_band_5_loudness);
+		break;
+	}
+
+	return 1;
+}
+
+/*
+ * Load new eq settings
+ */
+static int max9888_eq1_set(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned char reg;
+
+	max9888_eq1_value = ucontrol->value.integer.value[0];
+
+	reg = max9888_read(codec, M9888_REG_47_CFG_LEVEL);
+	reg &= ~EQ1EN;
+	max9888_write(codec, M9888_REG_47_CFG_LEVEL, reg);
+
+	m9888_eq_control(codec, 0, max9888_eq1_value);
+
+	reg |= EQ1EN;
+	max9888_write(codec, M9888_REG_47_CFG_LEVEL, reg);
+
+	return 1;
+}
+
+/*
+ * Return current eq setting
+ */
+static int max9888_eq1_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = max9888_eq1_value;
+	return 0;
+}
+
+/*
+ * Load new eq settings
+ */
+static int max9888_eq2_set(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	unsigned char reg;
+
+	max9888_eq2_value = ucontrol->value.integer.value[0];
+
+	reg = max9888_read(codec, M9888_REG_47_CFG_LEVEL);
+	reg &= ~EQ2EN;
+	max9888_write(codec, M9888_REG_47_CFG_LEVEL, reg);
+
+	m9888_eq_control(codec, 1, max9888_eq2_value);
+
+	reg |= EQ2EN;
+	max9888_write(codec, M9888_REG_47_CFG_LEVEL, reg);
+
+	return 1;
+}
+
+/*
+ * Return current eq setting
+ */
+static int max9888_eq2_get(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = max9888_eq2_value;
+	return 0;
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max9888_ex_mode[] = {
+	"disable",
+	"100Hz",
+	"400Hz",
+	"600Hz",
+	"800Hz",
+	"1000Hz",
+	"200-400Hz",
+	"400-600Hz",
+	"400-800Hz",
+	"user-400Hz",
+	"user-600Hz",
+	"user-800Hz",
+	"user-1000Hz"
+};
+
+static const unsigned int ex_mode_table[] = {
+	0x00,
+	(0 << 4) | 3,
+	(1 << 4) | 0,
+	(2 << 4) | 0,
+	(3 << 4) | 0,
+	(4 << 4) | 0,
+	(1 << 4) | 1,
+	(2 << 4) | 2,
+	(3 << 4) | 2,
+	(1 << 4) | 3,
+	(2 << 4) | 3,
+	(3 << 4) | 3,
+	(4 << 4) | 3
+};
+
+static const struct soc_enum max9888_ex_mode_enum[] = {
+	SOC_ENUM_SINGLE_EXT(13, max9888_ex_mode),
+};
+
+static unsigned int max9888_ex_mode_value;
+
+/*
+ * Excursion limiter mode - set mode
+ */
+static int max9888_ex_mode_set(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	max9888_ex_mode_value = ucontrol->value.integer.value[0];
+
+	if (max9888_ex_mode_value <= 13) {
+		max9888_write(codec, M9888_REG_3F_SPKDHP,
+			      ex_mode_table[max9888_ex_mode_value]);
+	}
+
+	return 1;
+}
+
+/*
+ * Excursion limiter mode - get mode
+ */
+static int max9888_ex_mode_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = max9888_ex_mode_value;
+	return 0;
+}
+
+/*
+ * Functions and control structures for loading
+ * static coefficients into the bi-quad filter.
+ *
+ * As it is the user has a choice of three filters - a, b, or c.
+ */
+static const char *max9888_ex_resp[] = { "a", "b", "c" };
+
+static const struct soc_enum max9888_ex_resp_enum[] = {
+	SOC_ENUM_SINGLE_EXT(3, max9888_ex_resp),
+};
+
+enum max9888_ex_resp_enumeration {
+	MAX9888_BIQUAD_A = 0,
+	MAX9888_BIQUAD_B,
+	MAX9888_BIQUAD_C
+};
+
+static int max9888_ex1_resp_value = MAX9888_BIQUAD_A;
+static int max9888_ex2_resp_value = MAX9888_BIQUAD_A;
+
+/*
+ * Load user programmable mode excursion limiter filter coefficients
+ */
+static void max9888_ex_resp_control(struct snd_soc_codec *codec, int reg,
+				    unsigned int *param)
+{
+	max9888_write(codec, reg, HIGH_BYTE(param[0]));
+	max9888_write(codec, ++reg, LOW_BYTE(param[0]));
+	max9888_write(codec, ++reg, HIGH_BYTE(param[1]));
+	max9888_write(codec, ++reg, LOW_BYTE(param[1]));
+	max9888_write(codec, ++reg, HIGH_BYTE(param[2]));
+	max9888_write(codec, ++reg, LOW_BYTE(param[2]));
+	max9888_write(codec, ++reg, HIGH_BYTE(param[3]));
+	max9888_write(codec, ++reg, LOW_BYTE(param[3]));
+	max9888_write(codec, ++reg, HIGH_BYTE(param[4]));
+	max9888_write(codec, ++reg, LOW_BYTE(param[4]));
+}
+
+/*
+ * Select and load excursion limiter user mode coefficient selection
+ */
+static int max9888_ex1_resp_set(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	max9888_ex1_resp_value = ucontrol->value.integer.value[0];
+
+	switch (max9888_ex1_resp_value) {
+	case MAX9888_BIQUAD_A:
+		max9888_ex_resp_control(codec, M9888_REG_B4_DAI1_BIQUAD_BASE,
+					param_ex_resp_a);
+		break;
+
+	case MAX9888_BIQUAD_B:
+		max9888_ex_resp_control(codec, M9888_REG_B4_DAI1_BIQUAD_BASE,
+					param_ex_resp_b);
+		break;
+
+	case MAX9888_BIQUAD_C:
+		max9888_ex_resp_control(codec, M9888_REG_B4_DAI1_BIQUAD_BASE,
+					param_ex_resp_c);
+		break;
+	}
+
+	return 1;
+}
+
+/*
+ * Return current excursion limiter user mode coefficient selection
+ */
+static int max9888_ex1_resp_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = max9888_ex1_resp_value;
+	return 0;
+}
+
+/*
+ * Select and load excursion limiter user mode coefficient selection
+ */
+static int max9888_ex2_resp_set(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	max9888_ex2_resp_value = ucontrol->value.integer.value[0];
+
+	switch (max9888_ex2_resp_value) {
+	case MAX9888_BIQUAD_A:
+		max9888_ex_resp_control(codec, M9888_REG_BE_DAI2_BIQUAD_BASE,
+					param_ex_resp_a);
+		break;
+
+	case MAX9888_BIQUAD_B:
+		max9888_ex_resp_control(codec, M9888_REG_BE_DAI2_BIQUAD_BASE,
+					param_ex_resp_b);
+		break;
+
+	case MAX9888_BIQUAD_C:
+		max9888_ex_resp_control(codec, M9888_REG_BE_DAI2_BIQUAD_BASE,
+					param_ex_resp_c);
+		break;
+	}
+
+	return 1;
+}
+
+/*
+ * Return current excursion limiter user mode coefficient selection
+ */
+static int max9888_ex2_resp_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	ucontrol->value.integer.value[0] = max9888_ex2_resp_value;
+	return 0;
+}
+
+/*
+ * Control mic1 analog amplifier circuit on/off, and gain
+ * Typical setting: register value of 0x7F for 30dB mic gain
+ */
+void m9888_mic1_gain_control(struct snd_soc_codec *codec, unsigned int gain,
+			     unsigned int power_on)
+{
+	/* power off the amplifier */
+	if (power_on == 0) {
+		max9888_write(codec, M9888_REG_31_LVL_MIC1, 0x00);
+		return;
+	}
+	/* 0dB..19dB */
+	if (gain < 20) {
+		max9888_write(codec, M9888_REG_31_LVL_MIC1,
+			      (1 << 5) | (20 - gain));
+		return;
+	}
+	/* 20dB..29dB */
+	if (gain < 30) {
+		max9888_write(codec, M9888_REG_31_LVL_MIC1,
+			      (2 << 5) | (40 - gain));
+		return;
+	}
+	/* 30dB..49B */
+	if (gain < 50) {
+		max9888_write(codec, M9888_REG_31_LVL_MIC1,
+			      (3 << 5) | (50 - gain));
+		return;
+	}
+	/* set to maximum 50dB */
+	max9888_write(codec, M9888_REG_31_LVL_MIC1, (3 << 5) | 0);
+}
+
+/*
+ * Control mic2 analog amplifier circuit on/off, and gain
+ * Typical setting: register value of 0x7F for 30dB mic gain
+ */
+void m9888_mic2_gain_control(struct snd_soc_codec *codec, unsigned int gain,
+			     unsigned int power_on)
+{
+	/* power off the amplifier */
+	if (power_on == 0) {
+		max9888_write(codec, M9888_REG_32_LVL_MIC2, 0x00);
+		return;
+	}
+	/* 0dB..19dB */
+	if (gain < 20) {
+		max9888_write(codec, M9888_REG_32_LVL_MIC2,
+			      (1 << 5) | (20 - gain));
+		return;
+	}
+	/* 20dB..29dB */
+	if (gain < 30) {
+		max9888_write(codec, M9888_REG_32_LVL_MIC2,
+			      (2 << 5) | (40 - gain));
+		return;
+	}
+	/* 30dB..49B */
+	if (gain < 50) {
+		max9888_write(codec, M9888_REG_32_LVL_MIC2,
+			      (3 << 5) | (50 - gain));
+		return;
+	}
+	/* set to maximum 50dB */
+	max9888_write(codec, M9888_REG_32_LVL_MIC2, (3 << 5) | 0);
+}
+
+/*
+ * Set headphone output volume and mute setting
+ */
+void headphone_set_volume(struct snd_soc_codec *codec, u8 vol, u8 mute)
+{
+
+	if (vol > 0x1F)
+		vol = 0x1F;
+
+	if (mute > 1)
+		mute = 1;
+
+	max9888_write(codec, M9888_REG_38_LVL_HEADPHONE_L,
+		      (mute << 7) | (vol << 0));
+	max9888_write(codec, M9888_REG_39_LVL_HEADPHONE_R,
+		      (mute << 7) | (vol << 0));
+}
+
+/*
+ * set receiver output volume and mute setting
+ */
+void receiver_set_volume(struct snd_soc_codec *codec, u8 vol, u8 mute)
+{
+
+	if (vol > 0x1F)
+		vol = 0x1F;
+
+	if (mute > 1)
+		mute = 1;
+
+	max9888_write(codec, M9888_REG_3A_LVL_RECEIVER,
+		      (mute << 7) | (vol << 0));
+}
+
+/*
+ * set speaker output volume and mute setting
+ */
+void speaker_set_volume(struct snd_soc_codec *codec, u8 vol, u8 mute)
+{
+
+	if (vol > 0x1F)
+		vol = 0x1F;
+
+	if (mute > 1)
+		mute = 1;
+
+	max9888_write(codec, M9888_REG_3B_LVL_SPEAKER_L,
+		      (mute << 7) | (vol << 0));
+	max9888_write(codec, M9888_REG_3C_LVL_SPEAKER_R,
+		      (mute << 7) | (vol << 0));
+}
+
+/*
+ * configure speaker ALC
+ */
+void m9888_speaker_ALC_control(struct snd_soc_codec *codec)
+{
+	/* compressor enable */
+	max9888_write(codec, M9888_REG_41_SPKALC_COMP, (0 << 7) |
+		      (7 << 4) | (0 << 3) | (0 << 0));
+}
+
+/*
+ * Control mic AGC for both microphone inputs.
+ */
+void m9888_mic_AGC_control(struct snd_soc_codec *codec)
+{
+	max9888_write(codec, M9888_REG_3D_MICAGC_CFG, (0 << 7) |
+		      (0 << 4) | (0 << 2) | (0 << 0));
+
+	max9888_write(codec, M9888_REG_3E_MICAGC_THRESHOLD, (0 << 4) |
+		      (0 << 0));
+}
+
+/*
+ * Mute the speaker
+ */
+static const char *max9888_hp_spk_mute[] = { "disable", "enable" };
+
+static const struct soc_enum max9888_hp_spk_mute_enum[] = {
+	SOC_ENUM_SINGLE_EXT(2, max9888_hp_spk_mute),
+};
+
+#define MAX9888_HP_SPK_MUTE_DISABLE	0
+#define MAX9888_HP_SPK_MUTE_ENABLE	1
+
+static int max9888_spk_mute_set(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+	u8 lReg = max9888_read(codec, M9888_REG_3B_LVL_SPEAKER_L);
+	u8 rReg = max9888_read(codec, M9888_REG_3C_LVL_SPEAKER_R);
+
+	if (ucontrol->value.integer.value[0] == MAX9888_HP_SPK_MUTE_ENABLE) {
+		/* Enable mute for both channels */
+		lReg |= SPXM;
+		rReg |= SPXM;
+	} else if (ucontrol->value.integer.value[0]
+			== MAX9888_HP_SPK_MUTE_DISABLE) {
+		/* Disable mute for both channels */
+		lReg &= ~SPXM;
+		rReg &= ~SPXM;
+	} else {
+		return -1;
+	}
+
+	max9888_write(codec, M9888_REG_3B_LVL_SPEAKER_L, lReg);
+	max9888_write(codec, M9888_REG_3C_LVL_SPEAKER_R, rReg);
+
+	return 1;
+}
+
+static int max9888_spk_mute_get(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	u8 reg = max9888_read(codec, M9888_REG_3B_LVL_SPEAKER_L);
+
+	ucontrol->value.integer.value[0] = (reg & SPXM) ? 1 : 0;
+
+	return 0;
+}
+
+/*
+ * Mute the headphone
+ */
+static int max9888_hp_mute_set(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	u8 lReg = max9888_read(codec, M9888_REG_38_LVL_HEADPHONE_L);
+	u8 rReg = max9888_read(codec, M9888_REG_39_LVL_HEADPHONE_R);
+
+	if (MAX9888_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) {
+		/* Enable mute for both channels */
+		lReg |= HPXM;
+		rReg |= HPXM;
+	} else {
+		/* Disable mute for both channels */
+		lReg &= ~HPXM;
+		rReg &= ~HPXM;
+	}
+
+	max9888_write(codec, M9888_REG_38_LVL_HEADPHONE_L, lReg);
+	max9888_write(codec, M9888_REG_39_LVL_HEADPHONE_R, rReg);
+
+	return 1;
+}
+
+/*
+ * Get current headphone mute hardware register setting
+ */
+static int max9888_hp_mute_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+	u8 reg = max9888_read(codec, M9888_REG_38_LVL_HEADPHONE_L);
+
+	ucontrol->value.integer.value[0] = (reg & HPXM) ? 1 : 0;
+	return 0;
+}
+
+static const char *max9888_rx_mute[] = { "disable", "enable" };
+static const char *max9888_fltr_mode[] = { "voice", "music" };
+static const char *max9888_highrate[] = { ">50khz", "<50khz" };
+
+static const char *max9888_dai1_fltr[] = {
+	"disable", "fc=258/fs=16k", "fc=500/fs=16k",
+	"fc=258/fs=8k", "fc=500/fs=8k", "fc=200" };
+static const char *max9888_dc_block[] = { "disable", "enable" };
+static const char *max9888_exlimit_threshold[] = {
+	"0.6v", "1.2v", "1.8v", "2.4v", "3.0", "3.6v", "4.2v", "4.8v" };
+
+static const struct soc_enum max9888_enum[] = {
+	SOC_ENUM_SINGLE(M9888_REG_3A_LVL_RECEIVER, 7, 2, max9888_rx_mute),
+	SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 7, 2, max9888_fltr_mode),
+	SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 3, 2, max9888_highrate),
+	SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 0, 6, max9888_dai1_fltr),
+	SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 4, 6, max9888_dai1_fltr),
+	SOC_ENUM_SINGLE(M9888_REG_20_DAI2_FILTERS, 3, 2, max9888_highrate),
+	SOC_ENUM_SINGLE(M9888_REG_20_DAI2_FILTERS, 0, 2, max9888_dc_block),
+	SOC_ENUM_SINGLE(M9888_REG_40_SPKDHP_THRESHOLD, 0, 8,
+			max9888_exlimit_threshold)
+};
+
+static const struct snd_kcontrol_new max9888_snd_controls[] = {
+
+	/* analog output levels */
+	SOC_DOUBLE_R("hp_vol", M9888_REG_38_LVL_HEADPHONE_L,
+		     M9888_REG_39_LVL_HEADPHONE_R, 0, 31, 0),
+	SOC_DOUBLE_R("spk_vol", M9888_REG_3B_LVL_SPEAKER_L,
+		     M9888_REG_3C_LVL_SPEAKER_R, 0, 31, 0),
+	SOC_SINGLE("rec_vol", M9888_REG_3A_LVL_RECEIVER, 0, 31, 0),
+
+	SOC_ENUM_EXT("hp_mute", max9888_hp_spk_mute_enum, max9888_hp_mute_get,
+		     max9888_hp_mute_set),
+	SOC_ENUM_EXT("spk_mute", max9888_hp_spk_mute_enum, max9888_spk_mute_get,
+		     max9888_spk_mute_set),
+	SOC_ENUM("rec_mute", max9888_enum[0]),
+
+	/* analog input levels */
+	SOC_SINGLE("mic1_gain", M9888_REG_31_LVL_MIC1, 0, 31, 1),
+	SOC_SINGLE("mic2_gain", M9888_REG_32_LVL_MIC2, 0, 31, 1),
+
+	SOC_SINGLE("mic1_pre", M9888_REG_31_LVL_MIC1, 5, 3, 0),
+	SOC_SINGLE("mic2_pre", M9888_REG_32_LVL_MIC2, 5, 3, 0),
+
+	SOC_SINGLE("ina_gain", M9888_REG_33_LVL_INA, 0, 7, 1),
+	SOC_SINGLE("inb_gain", M9888_REG_34_LVL_INB, 0, 7, 1),
+
+	/* digital gains */
+	SOC_SINGLE("adcl_dvol", M9888_REG_2F_LVL_ADC_L, 0, 15, 0),
+	SOC_SINGLE("adcr_dvol", M9888_REG_30_LVL_ADC_R, 0, 15, 0),
+
+	SOC_SINGLE("adcl_dgain", M9888_REG_2F_LVL_ADC_L, 4, 3, 0),
+	SOC_SINGLE("adcr_dgain", M9888_REG_30_LVL_ADC_R, 4, 3, 0),
+
+	/* equalizer */
+	SOC_SINGLE("eq1_enable", M9888_REG_47_CFG_LEVEL, 0, 1, 0),
+	SOC_SINGLE("eq2_enable", M9888_REG_47_CFG_LEVEL, 1, 1, 0),
+
+	SOC_SINGLE("eq1_atten", M9888_REG_2C_LVL_DAI1_PLAY_EQ, 0, 15, 0),
+	SOC_SINGLE("eq2_atten", M9888_REG_2E_LVL_DAI2_PLAY_EQ, 0, 15, 0),
+
+	SOC_ENUM_EXT("eq1_resp", max9888_eq_enum, max9888_eq1_get,
+		     max9888_eq1_set),
+	SOC_ENUM_EXT("eq2_resp", max9888_eq_enum, max9888_eq2_get,
+		     max9888_eq2_set),
+
+	/* excursion limiter */
+	SOC_ENUM_EXT("exlmt_mode", max9888_ex_mode_enum, max9888_ex_mode_get,
+		     max9888_ex_mode_set),
+	SOC_ENUM("exlmt_thresh", max9888_enum[7]),
+	SOC_ENUM_EXT("exlmt1_resp", max9888_ex_resp_enum, max9888_ex1_resp_get,
+		     max9888_ex1_resp_set),
+	SOC_ENUM_EXT("exlmt2_resp", max9888_ex_resp_enum, max9888_ex2_resp_get,
+		     max9888_ex2_resp_set),
+
+	/* voice/music filters */
+	SOC_ENUM("dai1_flt_mode", max9888_enum[1]),
+	SOC_ENUM("dai1_flt_dac", max9888_enum[3]),
+	SOC_ENUM("dai1_flt_adc", max9888_enum[4]),
+
+	SOC_ENUM("dai1_highrate", max9888_enum[2]),
+	SOC_ENUM("dai2_highrate", max9888_enum[5]),
+	SOC_ENUM("dai2_dcblock", max9888_enum[6]),
+
+	/* automatic level control (for both DAI1/DAI2) */
+	SOC_SINGLE("alc_enable", M9888_REG_41_SPKALC_COMP, 7, 1, 0),
+	SOC_SINGLE("alc_thresh", M9888_REG_41_SPKALC_COMP, 0, 7, 0),
+	SOC_SINGLE("alc_multiband", M9888_REG_41_SPKALC_COMP, 3, 1, 0),
+	SOC_SINGLE("alc_rlstime", M9888_REG_41_SPKALC_COMP, 4, 7, 0),
+
+	/* power limiter */
+	SOC_SINGLE("pwlmt_thresh", M9888_REG_42_PWRLMT_CFG, 4, 15, 0),
+	SOC_SINGLE("pwlmt_weight", M9888_REG_42_PWRLMT_CFG, 0, 7, 0),
+	SOC_SINGLE("pwlmt_time1", M9888_REG_43_PWRLMT_TIME, 0, 15, 0),
+	SOC_SINGLE("pwlmt_time2", M9888_REG_43_PWRLMT_TIME, 4, 15, 0),
+
+	/* THD distortion limiter */
+	SOC_SINGLE("thdlmt_thresh", M9888_REG_44_THDLMT_CFG, 4, 15, 0),
+	SOC_SINGLE("thdlmt_time", M9888_REG_44_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max9888_left_speaker_mixer_controls[] = {
+	SOC_DAPM_SINGLE("left_dac", M9888_REG_29_MIX_SPEAKER, 7, 1, 0),
+	SOC_DAPM_SINGLE("right_dac", M9888_REG_29_MIX_SPEAKER, 6, 1, 0),
+	SOC_DAPM_SINGLE("preout3", M9888_REG_29_MIX_SPEAKER, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max9888_right_speaker_mixer_controls[] = {
+	SOC_DAPM_SINGLE("left_dac", M9888_REG_29_MIX_SPEAKER, 3, 1, 0),
+	SOC_DAPM_SINGLE("right_dac", M9888_REG_29_MIX_SPEAKER, 2, 1, 0),
+	SOC_DAPM_SINGLE("preout2", M9888_REG_29_MIX_SPEAKER, 0, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max9888_left_headphone_mixer_controls[] = {
+	SOC_DAPM_SINGLE("left_dac", M9888_REG_27_MIX_HEADPHONE, 7, 1, 0),
+	SOC_DAPM_SINGLE("right_dac", M9888_REG_27_MIX_HEADPHONE, 6, 1, 0),
+	SOC_DAPM_SINGLE("preout1", M9888_REG_27_MIX_HEADPHONE, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new
+		max9888_right_headphone_mixer_controls[] = {
+	SOC_DAPM_SINGLE("left_dac", M9888_REG_27_MIX_HEADPHONE, 3, 1, 0),
+	SOC_DAPM_SINGLE("right_dac", M9888_REG_27_MIX_HEADPHONE, 2, 1, 0),
+	SOC_DAPM_SINGLE("preout2", M9888_REG_27_MIX_HEADPHONE, 0, 1, 0),
+};
+
+/* Earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max9888_receiver_mixer_controls[] = {
+	SOC_DAPM_SINGLE("left_dac", M9888_REG_28_MIX_RECEIVER, 3, 1, 0),
+	SOC_DAPM_SINGLE("right_dac", M9888_REG_28_MIX_RECEIVER, 2, 1, 0),
+	SOC_DAPM_SINGLE("preout1", M9888_REG_28_MIX_RECEIVER, 1, 1, 0),
+	SOC_DAPM_SINGLE("preout2", M9888_REG_28_MIX_RECEIVER, 0, 1, 0),
+};
+
+/* Pre-output mixer 1 switch */
+static const struct snd_kcontrol_new max9888_preout_mixer_1_controls[] = {
+	SOC_DAPM_SINGLE("ina1", M9888_REG_24_MIX_PREOUT_A, 3, 1, 0),
+	SOC_DAPM_SINGLE("ina2", M9888_REG_24_MIX_PREOUT_A, 2, 1, 0),
+	SOC_DAPM_SINGLE("inb1", M9888_REG_24_MIX_PREOUT_A, 1, 1, 0),
+	SOC_DAPM_SINGLE("inb2", M9888_REG_24_MIX_PREOUT_A, 0, 1, 0),
+};
+
+/* Pre-output mixer 2 switch */
+static const struct snd_kcontrol_new max9888_preout_mixer_2_controls[] = {
+	SOC_DAPM_SINGLE("ina1", M9888_REG_25_MIX_PREOUT_B, 3, 1, 0),
+	SOC_DAPM_SINGLE("ina2", M9888_REG_25_MIX_PREOUT_B, 2, 1, 0),
+	SOC_DAPM_SINGLE("inb1", M9888_REG_25_MIX_PREOUT_B, 1, 1, 0),
+	SOC_DAPM_SINGLE("inb2", M9888_REG_25_MIX_PREOUT_B, 0, 1, 0),
+};
+
+/* Pre-output mixer 3 switch */
+static const struct snd_kcontrol_new max9888_preout_mixer_3_controls[] = {
+	SOC_DAPM_SINGLE("ina1", M9888_REG_26_MIX_PREOUT_C, 3, 1, 0),
+	SOC_DAPM_SINGLE("ina2", M9888_REG_26_MIX_PREOUT_C, 2, 1, 0),
+	SOC_DAPM_SINGLE("inb1", M9888_REG_26_MIX_PREOUT_C, 1, 1, 0),
+	SOC_DAPM_SINGLE("inb2", M9888_REG_26_MIX_PREOUT_C, 0, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max9888_left_ADC_mixer_controls[] = {
+	SOC_DAPM_SINGLE("mic1", M9888_REG_22_MIX_ADC_LEFT, 7, 1, 0),
+	SOC_DAPM_SINGLE("mic2", M9888_REG_22_MIX_ADC_LEFT, 6, 1, 0),
+	SOC_DAPM_SINGLE("ina1", M9888_REG_22_MIX_ADC_LEFT, 3, 1, 0),
+	SOC_DAPM_SINGLE("ina2", M9888_REG_22_MIX_ADC_LEFT, 2, 1, 0),
+	SOC_DAPM_SINGLE("inb1", M9888_REG_22_MIX_ADC_LEFT, 1, 1, 0),
+	SOC_DAPM_SINGLE("inb2", M9888_REG_22_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max9888_right_ADC_mixer_controls[] = {
+	SOC_DAPM_SINGLE("mic1", M9888_REG_23_MIX_ADC_RIGHT, 7, 1, 0),
+	SOC_DAPM_SINGLE("mic2", M9888_REG_23_MIX_ADC_RIGHT, 6, 1, 0),
+	SOC_DAPM_SINGLE("ina1", M9888_REG_23_MIX_ADC_RIGHT, 3, 1, 0),
+	SOC_DAPM_SINGLE("ina2", M9888_REG_23_MIX_ADC_RIGHT, 2, 1, 0),
+	SOC_DAPM_SINGLE("inb1", M9888_REG_23_MIX_ADC_RIGHT, 1, 1, 0),
+	SOC_DAPM_SINGLE("inb2", M9888_REG_23_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget max9888_dapm_widgets[] = {
+
+	SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M9888_REG_4A_PWR_EN_IN, 1, 0),
+	SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M9888_REG_4A_PWR_EN_IN, 0, 0),
+
+	SND_SOC_DAPM_DAC("DACL", "HiFi Playback", M9888_REG_4B_PWR_EN_OUT, 1,
+			 0),
+	SND_SOC_DAPM_DAC("DACR", "HiFi Playback", M9888_REG_4B_PWR_EN_OUT, 0,
+			 0),
+
+	SND_SOC_DAPM_PGA("HP Left Out", M9888_REG_4B_PWR_EN_OUT, 7, 0, NULL, 0),
+	SND_SOC_DAPM_PGA("HP Right Out", M9888_REG_4B_PWR_EN_OUT, 6, 0, NULL,
+			 0),
+
+	SND_SOC_DAPM_PGA("SPK Left Out", M9888_REG_4B_PWR_EN_OUT, 5, 0, NULL,
+			 0),
+	SND_SOC_DAPM_PGA("SPK Right Out", M9888_REG_4B_PWR_EN_OUT, 4, 0, NULL,
+			 0),
+
+	SND_SOC_DAPM_PGA("REC Out", M9888_REG_4B_PWR_EN_OUT, 3, 0, NULL, 0),
+
+	SND_SOC_DAPM_MIXER("hp_left_mixer", SND_SOC_NOPM, 0, 0,
+			   &max9888_left_headphone_mixer_controls[0],
+			   ARRAY_SIZE(max9888_left_headphone_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("hp_right_mixer", SND_SOC_NOPM, 0, 0,
+			   &max9888_right_headphone_mixer_controls[0],
+			   ARRAY_SIZE(max9888_right_headphone_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("spk_left_mixer", SND_SOC_NOPM, 0, 0,
+			   &max9888_left_speaker_mixer_controls[0],
+			   ARRAY_SIZE(max9888_left_speaker_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("spk_right_mixer", SND_SOC_NOPM, 0, 0,
+			   &max9888_right_speaker_mixer_controls[0],
+			   ARRAY_SIZE(max9888_left_speaker_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("rec_mixer", SND_SOC_NOPM, 0, 0,
+			   &max9888_receiver_mixer_controls[0],
+			   ARRAY_SIZE(max9888_receiver_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("preout1_mixer", SND_SOC_NOPM, 0, 0,
+			   &max9888_preout_mixer_1_controls[0],
+			   ARRAY_SIZE(max9888_preout_mixer_1_controls)),
+
+	SND_SOC_DAPM_MIXER("preout2_mixer", SND_SOC_NOPM, 0, 0,
+			   &max9888_preout_mixer_2_controls[0],
+			   ARRAY_SIZE(max9888_preout_mixer_2_controls)),
+
+	SND_SOC_DAPM_MIXER("preout3_mixer", SND_SOC_NOPM, 0, 0,
+			   &max9888_preout_mixer_3_controls[0],
+			   ARRAY_SIZE(max9888_preout_mixer_1_controls)),
+
+	SND_SOC_DAPM_MIXER("left_adc_mixer", SND_SOC_NOPM, 0, 0,
+			   &max9888_left_ADC_mixer_controls[0],
+			   ARRAY_SIZE(max9888_left_ADC_mixer_controls)),
+
+	SND_SOC_DAPM_MIXER("right_adc_mixer", SND_SOC_NOPM, 0, 0,
+			   &max9888_right_ADC_mixer_controls[0],
+			   ARRAY_SIZE(max9888_right_ADC_mixer_controls)),
+
+	SND_SOC_DAPM_PGA_E("INA1 Input", M9888_REG_4A_PWR_EN_IN,
+			       7, 0, NULL, 0, max9888_pga_event,
+			       SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+	SND_SOC_DAPM_PGA_E("INA2 Input", M9888_REG_4A_PWR_EN_IN,
+			       7, 0, NULL, 0, max9888_pga_event,
+			       SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+	SND_SOC_DAPM_PGA_E("INB1 Input", M9888_REG_4A_PWR_EN_IN,
+			       6, 0, NULL, 0, max9888_pga_event,
+			       SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+	SND_SOC_DAPM_PGA_E("INB2 Input", M9888_REG_4A_PWR_EN_IN,
+			       6, 0, NULL, 0, max9888_pga_event,
+			       SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+	SND_SOC_DAPM_MICBIAS("Mic Bias", M9888_REG_4A_PWR_EN_IN, 3, 0),
+	SND_SOC_DAPM_MICBIAS("Digmic_l", M9888_REG_46_CFG_MIC, 5, 0),
+	SND_SOC_DAPM_MICBIAS("Digmic_r", M9888_REG_46_CFG_MIC, 4, 0),
+
+	SND_SOC_DAPM_OUTPUT("HPL"),
+	SND_SOC_DAPM_OUTPUT("HPR"),
+	SND_SOC_DAPM_OUTPUT("SPKL"),
+	SND_SOC_DAPM_OUTPUT("SPKR"),
+	SND_SOC_DAPM_OUTPUT("REC"),
+
+	SND_SOC_DAPM_INPUT("DIGMIC"),
+	SND_SOC_DAPM_INPUT("MIC1"),
+	SND_SOC_DAPM_INPUT("MIC2"),
+	SND_SOC_DAPM_INPUT("INA1"),
+	SND_SOC_DAPM_INPUT("INA2"),
+	SND_SOC_DAPM_INPUT("INB1"),
+	SND_SOC_DAPM_INPUT("INB2"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+	/* Left headphone output mixer */
+	{"hp_left_mixer", "left_dac", "DACL"},
+	{"hp_left_mixer", "right_dac", "DACR"},
+	{"hp_left_mixer", "preout1", "preout1_mixer"},
+
+	/* Right headphone output mixer */
+	{"hp_right_mixer", "left_dac", "DACL"},
+	{"hp_right_mixer", "right_dac", "DACR"},
+	{"hp_right_mixer", "preout2", "preout2_mixer"},
+
+	/* Left speaker output mixer */
+	{"spk_left_mixer", "left_dac", "DACL"},
+	{"spk_left_mixer", "right_dac", "DACR"},
+	{"spk_left_mixer", "preout3", "preout3_mixer"},
+
+	/* Right speaker output mixer */
+	{"spk_right_mixer", "left_dac", "DACL"},
+	{"spk_right_mixer", "right_dac", "DACR"},
+	{"spk_right_mixer", "preout2", "preout3_mixer"},
+
+	/* Earpiece/Receiver output mixer */
+	{"rec_mixer", "left_dac", "DACL"},
+	{"rec_mixer", "right_dac", "DACR"},
+	{"rec_mixer", "preout1", "preout1_mixer"},
+	{"rec_mixer", "preout2", "preout2_mixer"},
+
+	{"HP Left Out", NULL, "hp_left_mixer"},
+	{"HPL", NULL, "HP Left Out"},
+
+	{"HP Right Out", NULL, "hp_right_mixer"},
+	{"HPR", NULL, "HP Right Out"},
+
+	{"SPK Left Out", NULL, "spk_left_mixer"},
+	{"SPKL", NULL, "SPK Left Out"},
+
+	{"SPK Right Out", NULL, "spk_right_mixer"},
+	{"SPKR", NULL, "SPK Right Out"},
+
+	{"REC Out", NULL, "rec_mixer"},
+	{"REC", NULL, "REC Out"},
+
+	/* Left ADC input mixer */
+	{"left_adc_mixer", "mic1", "Mic Bias"},
+	{"left_adc_mixer", "mic2", "Mic Bias"},
+	{"left_adc_mixer", "ina1", "INA1 Input"},
+	{"left_adc_mixer", "ina2", "INA2 Input"},
+	{"left_adc_mixer", "inb1", "INB1 Input"},
+	{"left_adc_mixer", "inb2", "INB2 Input"},
+
+	/* Right ADC input mixer */
+	{"right_adc_mixer", "mic1", "Mic Bias"},
+	{"right_adc_mixer", "mic2", "Mic Bias"},
+	{"right_adc_mixer", "ina1", "INA1 Input"},
+	{"right_adc_mixer", "ina2", "INA2 Input"},
+	{"right_adc_mixer", "inb1", "INB1 Input"},
+	{"right_adc_mixer", "inb2", "INB2 Input"},
+
+	/* Pre-output 1 mixer */
+	{"preout1_mixer", "ina1", "INA1 Input"},
+	{"preout1_mixer", "ina2", "INA2 Input"},
+	{"preout1_mixer", "inb1", "INB1 Input"},
+	{"preout1_mixer", "inb2", "INB2 Input"},
+
+	/* Pre-output 2 mixer */
+	{"preout2_mixer", "ina1", "INA1 Input"},
+	{"preout2_mixer", "ina2", "INA2 Input"},
+	{"preout2_mixer", "inb1", "INB1 Input"},
+	{"preout2_mixer", "inb2", "INB2 Input"},
+
+	/* Pre-output 3 mixer */
+	{"preout3_mixer", "ina1", "INA1 Input"},
+	{"preout3_mixer", "ina2", "INA2 Input"},
+	{"preout3_mixer", "inb1", "INB1 Input"},
+	{"preout3_mixer", "inb2", "INB2 Input"},
+
+	/* inputs */
+	{"ADCL", NULL, "left_adc_mixer"},
+	{"ADCR", NULL, "right_adc_mixer"},
+
+	{"INA1 Input", NULL, "INA1"},
+	{"INA2 Input", NULL, "INA2"},
+	{"INB1 Input", NULL, "INB1"},
+	{"INB2 Input", NULL, "INB2"},
+
+	{"Digmic_l", NULL, "DIGMIC"},
+	{"Digmic_r", NULL, "DIGMIC"},
+	{"Mic Bias", NULL, "MIC1"},
+	{"Mic Bias", NULL, "MIC2"},
+};
+
+/*
+ * Add widgets
+ */
+static int max9888_add_widgets(struct snd_soc_codec *codec)
+{
+	snd_soc_dapm_new_controls(codec, max9888_dapm_widgets,
+				  ARRAY_SIZE(max9888_dapm_widgets));
+
+	/* set up audio path interconnects */
+	snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+	snd_soc_dapm_new_widgets(codec);
+	return 0;
+}
+
+/*
+ * Set bias level
+ */
+static int max9888_set_bias_level(struct snd_soc_codec *codec,
+				  enum snd_soc_bias_level level)
+{
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+	case SND_SOC_BIAS_PREPARE:
+	case SND_SOC_BIAS_STANDBY:
+		max9888_write(codec, M9888_REG_4C_PWR_SYS, SHDN|JDWK);
+		break;
+	case SND_SOC_BIAS_OFF:
+		max9888_write(codec, M9888_REG_4C_PWR_SYS, JDWK);
+		break;
+	}
+	codec->bias_level = level;
+	return 0;
+}
+
+/*
+ * mute CODEC
+ */
+int max9888_mute(struct snd_soc_dai *dai, int mute)
+{
+	struct snd_soc_codec *codec = dai->codec;
+
+	if (mute)
+		max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	else
+		max9888_set_bias_level(codec, SND_SOC_BIAS_ON);
+	return 0;
+}
+
+/*
+ * Power down CODEC
+ */
+void max9888_shutdown(struct snd_pcm_substream *substream,
+			 struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->card->codec;
+
+	max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+/*
+ * Setup DAI1 format
+ */
+static int max9888_dai1_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct max9888_priv *max9888 = codec->private_data;
+
+	if (fmt != max9888->dai1_fmt) {
+		max9888->dai1_fmt = fmt;
+
+		/* DAI clock master/slave wrt the codec */
+		switch (fmt & SND_SOC_DAIFMT_CBS_CFS) {
+		case SND_SOC_DAIFMT_CBS_CFS:
+			/* codec clk & frm slave */
+			max9888->reg_14_val &= ~(1 << 7);
+			max9888_write(codec, M9888_REG_12_DAI1_CLKCFG_HI, 0x80);
+			max9888_write(codec, M9888_REG_13_DAI1_CLKCFG_LO, 0x00);
+			break;
+		case SND_SOC_DAIFMT_CBM_CFM:
+			/* codec clk & frm master */
+			max9888->reg_14_val |= (1 << 7);
+			break;
+		case SND_SOC_DAIFMT_CBS_CFM:
+			/* codec clk slave & frm master */
+		case SND_SOC_DAIFMT_CBM_CFS:
+			/* codec clk master & frame slave */
+			info("Clock mode unsupported");
+			return -1;
+		}
+
+		/* I2S or TDM */
+		if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S)
+			max9888->reg_14_val &= ~(1 << 2);
+		else
+			max9888->reg_14_val |= (1 << 2);
+
+		/* DAI hardware signal inversions */
+		switch (fmt & SND_SOC_DAIFMT_NB_NF) {
+		case SND_SOC_DAIFMT_NB_NF:
+			max9888->reg_14_val &= ~(1 << 5);
+			max9888->reg_14_val &= ~(1 << 6);
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			max9888->reg_14_val |= (1 << 5);
+			max9888->reg_14_val |= (1 << 6);
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			max9888->reg_14_val |= (1 << 5);
+			max9888->reg_14_val &= ~(1 << 6);
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			max9888->reg_14_val |= (1 << 5);
+			max9888->reg_14_val &= ~(1 << 6);
+			break;
+		}
+		max9888_write(codec, M9888_REG_14_DAI1_FORMAT,
+			      max9888->reg_14_val);
+
+		max9888_write(codec, M9888_REG_15_DAI1_CLOCK,
+			      (0 << 6) | (0 << 0));
+
+		max9888_write(codec, M9888_REG_16_DAI1_IOCFG,
+			      (1 << 6) |
+			      (0 << 5) |
+			      (0 << 4) |
+			      (0 << 3) | (0 << 2) | (1 << 1) | (1 << 0));
+
+		max9888_write(codec, M9888_REG_17_DAI1_TDM,
+			      (0 << 6) | (1 << 4) | (0 << 0));
+	}
+
+	return 0;
+}
+
+/*
+ * Setup DAI2 format
+ */
+static int max9888_dai2_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct max9888_priv *max9888 = codec->private_data;
+
+	if (fmt != max9888->dai2_fmt) {
+		max9888->dai2_fmt = fmt;
+		/* DAI clock master/slave wrt the codec  */
+		switch (fmt & SND_SOC_DAIFMT_CBS_CFS) {
+		case SND_SOC_DAIFMT_CBS_CFS:
+			/* codec clk & frm slave */
+			max9888->reg_1c_val &= ~(1 << 7);
+			max9888_write(codec, M9888_REG_1A_DAI2_CLKCFG_HI, 0x80);
+			max9888_write(codec, M9888_REG_1B_DAI2_CLKCFG_LO, 0x00);
+			break;
+		case SND_SOC_DAIFMT_CBM_CFM:
+			/* codec clk & frm master */
+			max9888->reg_1c_val |= (1 << 7);
+			break;
+		case SND_SOC_DAIFMT_CBS_CFM:
+			/* codec clk slave & frm master */
+		case SND_SOC_DAIFMT_CBM_CFS:
+			/* codec clk master & frame slave */
+			info("Clock mode unsupported");
+			return -1;
+		}
+
+		/* I2S or TDM */
+		if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S)
+			max9888->reg_1c_val &= ~(1 << 2);
+		else
+			max9888->reg_1c_val |= (1 << 2);
+
+		/* DAI hardware signal inversions */
+		switch (fmt & SND_SOC_DAIFMT_NB_NF) {
+		case SND_SOC_DAIFMT_NB_NF:
+			max9888->reg_1c_val &= ~(1 << 5);
+			max9888->reg_1c_val &= ~(1 << 6);
+			break;
+		case SND_SOC_DAIFMT_NB_IF:
+			max9888->reg_1c_val |= (1 << 5);
+			max9888->reg_1c_val |= (1 << 6);
+			break;
+		case SND_SOC_DAIFMT_IB_NF:
+			max9888->reg_1c_val |= (1 << 5);
+			max9888->reg_1c_val &= ~(1 << 6);
+			break;
+		case SND_SOC_DAIFMT_IB_IF:
+			max9888->reg_1c_val |= (1 << 5);
+			max9888->reg_1c_val &= ~(1 << 6);
+			break;
+		}
+		max9888_write(codec, M9888_REG_1C_DAI2_FORMAT,
+			      max9888->reg_1c_val);
+
+		max9888_write(codec, M9888_REG_1D_DAI2_CLOCK, (0 << 0));
+
+		max9888_write(codec, M9888_REG_1E_DAI2_IOCFG,
+			      (2 << 6) |
+			      (0 << 4) |
+			      (0 << 3) | (0 << 2) | (1 << 1) | (1 << 0));
+
+		max9888_write(codec, M9888_REG_1F_DAI2_TDM,
+			      (0 << 6) | (1 << 4) | (0 << 0));
+	}
+
+	return 0;
+}
+
+/*
+ * Setup clock
+ */
+static int max9888_dai_set_sysclk(struct snd_soc_dai *codec_dai,
+				  int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_codec *codec = codec_dai->codec;
+	struct max9888_priv *max9888 = codec->private_data;
+
+	if (freq != max9888->sysclk) {
+		max9888->sysclk = freq;
+
+		/* setup clocks for slave mode,
+		and using the PLL anyclock feature
+		PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+		0x02 (when master clk is 20MHz to 30MHz) */
+		if ((freq >= 10000000) && (freq < 20000000)) {
+			max9888_write(codec, M9888_REG_10_SYS_CLK, 0x10);
+		} else if ((freq >= 20000000) && (freq < 30000000)) {
+			max9888_write(codec, M9888_REG_10_SYS_CLK, 0x20);
+		} else {
+			info("Invalid master clock frequency %u", freq);
+			return 1;
+		}
+
+		/* these registers writes take effect with SHDN cycle */
+		if (max9888_read(codec, M9888_REG_4C_PWR_SYS) & 0x80) {
+			max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x00);
+			max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x80);
+		}
+	}
+
+	max9888->sysclk = freq;
+	return 0;
+}
+
+struct rate_table_struct {
+	u32 rate;
+	u16 sr1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct rate_table_struct rate_table[] = {
+	{8000, 0x1},
+	{11025, 0x2},
+	{16000, 0x3},
+	{22050, 0x4},
+	{24000, 0x5},
+	{32000, 0x6},
+	{44100, 0x7},
+	{48000, 0x8},
+	{88200, 0x9},
+	{96000, 0xA},
+};
+
+static inline int rate_index(int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+		if (rate_table[i].rate >= rate)
+			return i;
+	}
+	return 0;
+}
+
+/*
+ * Setup hw params, sample rate
+ */
+static int max9888_dai1_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->card->codec;
+	struct max9888_priv *max9888 = codec->private_data;
+	unsigned int rate;
+	unsigned int reg14val;
+	u16 reg11val;
+
+	rate = params_rate(params);
+
+	/* data 16/24 bit width */
+	reg14val = max9888->reg_14_val;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		reg14val &= ~(1 << 0);
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		reg14val |= (1 << 0);
+		break;
+	}
+
+	if ((rate != max9888->dai1_rate) || (reg14val != max9888->reg_14_val)) {
+		/* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
+		reg11val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
+		max9888_write(codec, M9888_REG_11_DAI1_CLKMODE, reg11val);
+		max9888->dai1_rate = rate;
+
+		max9888_write(codec, M9888_REG_14_DAI1_FORMAT, reg14val);
+		max9888->reg_14_val = reg14val;
+
+		/* these registers writes take effect with SHDN cycle */
+		if (max9888_read(codec, M9888_REG_4C_PWR_SYS) & 0x80) {
+			max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x00);
+			max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x80);
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Setup hw params, sample rate
+ */
+static int max9888_dai2_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *params,
+				  struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct snd_soc_device *socdev = rtd->socdev;
+	struct snd_soc_codec *codec = socdev->card->codec;
+	struct max9888_priv *max9888 = codec->private_data;
+	unsigned int rate;
+	unsigned int reg1Cval;
+	u16 reg19val;
+
+	rate = params_rate(params);
+
+	/* data 16/24 bit width */
+	reg1Cval = max9888->reg_1c_val;
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		reg1Cval &= ~(1 << 0);
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		reg1Cval |= (1 << 0);
+		break;
+	}
+
+	if ((rate != max9888->dai2_rate) || (reg1Cval != max9888->reg_1c_val)) {
+		/* set DAI2 SR2 value for the DSP; FREQ1:0=anyclock */
+		reg19val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
+		max9888_write(codec, M9888_REG_19_DAI2_CLKMODE, reg19val);
+		max9888->dai1_rate = rate;
+
+		max9888_write(codec, M9888_REG_1C_DAI2_FORMAT, reg1Cval);
+		max9888->reg_1c_val = reg1Cval;
+
+		/* these registers writes take effect with SHDN cycle */
+		if (max9888_read(codec, M9888_REG_4C_PWR_SYS) & 0x80) {
+			max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x00);
+			max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x80);
+		}
+	}
+
+	return 0;
+}
+
+#define MAX9888_RATES	SNDRV_PCM_RATE_8000_96000
+#define MAX9888_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max9888_dai_ops_hifi_mode = {
+	.shutdown = max9888_shutdown,
+	.digital_mute = max9888_mute,
+	.set_sysclk = max9888_dai_set_sysclk,
+	.set_fmt = max9888_dai1_set_fmt,
+	.hw_params = max9888_dai1_hw_params
+};
+
+static struct snd_soc_dai_ops max9888_dai_ops_voice_mode = {
+	.shutdown = max9888_shutdown,
+	.digital_mute = max9888_mute,
+	.set_sysclk = max9888_dai_set_sysclk,
+	.set_fmt = max9888_dai2_set_fmt,
+	.hw_params = max9888_dai2_hw_params
+};
+
+struct snd_soc_dai max9888_dai[] = {
+/* DAI HIFI mode 1 */
+	{
+	 .name = "max9888 DAI0",
+	 .playback = {
+		      .stream_name = "Playback",
+		      .channels_min = 1,
+		      .channels_max = 2,
+		      .rates = MAX9888_RATES,
+		      .formats = MAX9888_FORMATS,
+		      },
+	 .capture = {
+		     .stream_name = "Capture",
+		     .channels_min = 1,
+		     .channels_max = 2,
+		     .rates = MAX9888_RATES,
+		     .formats = MAX9888_FORMATS,
+		     },
+	 .ops = &max9888_dai_ops_hifi_mode,
+	 },
+
+/* DAI Voice mode 2 */
+	{.name = "max9888 DAI1",
+	 .id = 2,
+	 .playback = {
+		      .stream_name = "Voice Playback",
+		      .channels_min = 1,
+		      .channels_max = 1,
+		      .rates = MAX9888_RATES,
+		      .formats = MAX9888_FORMATS,},
+	 .capture = {
+		     .stream_name = "Capture",
+		     .channels_min = 1,
+		     .channels_max = 1,
+		     .rates = MAX9888_RATES,
+		     .formats = MAX9888_FORMATS,},
+	 .ops = &max9888_dai_ops_voice_mode,
+	 },
+};
+EXPORT_SYMBOL_GPL(max9888_dai);
+
+
+/*
+ * Restore previous state if power was removed.
+ * As it is now the CODEC is brought out of shut down.
+ */
+static void max9888_init_private_data(struct max9888_priv *max9888)
+{
+	max9888->sysclk = (unsigned)-1;
+	max9888->dai1_rate = (unsigned)-1;
+	max9888->dai2_rate = (unsigned)-1;
+	max9888->dai1_fmt = (unsigned)-1;
+	max9888->dai2_fmt = (unsigned)-1;
+
+	max9888->reg_14_val =
+	    max9888->reg_1c_val =
+	    (0 << 7) |
+	    (0 << 6) | (0 << 5) | (1 << 4) | (0 << 2) | (0 << 1) | (0 << 0);
+}
+
+/*
+ * The current state should be saved if power is removed.
+ * As it is now the CODEC is just shut down.
+ */
+static int max9888_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec = socdev->card->codec;
+	dprintk("%s\n", __func__);
+	/* we only need to suspend if we are a valid card */
+	if (!codec->card)
+		return 0;
+
+	max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	return 0;
+}
+
+/*
+ * Restore previous state if power was removed.
+ * As it is now the CODEC is brought out of shut down.
+ */
+static int max9888_resume(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec = socdev->card->codec;
+	struct max9888_priv *max9888 = codec->private_data;
+
+	max9888_init_private_data(max9888);
+
+	max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+	max9888_set_bias_level(codec, SND_SOC_BIAS_ON);
+
+	return 0;
+}
+
+/*
+ * Initialize the codec.
+ */
+static struct snd_soc_codec *max9888_codec;
+
+static int max9888_register(struct max9888_priv *max9888)
+{
+	int ret, i;
+	struct snd_soc_codec *codec = &max9888->codec;
+
+	if (max9888_codec) {
+		dev_err(codec->dev, "Multiple max9888 devices not supported\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	mutex_init(&codec->mutex);
+	INIT_LIST_HEAD(&codec->dapm_widgets);
+	INIT_LIST_HEAD(&codec->dapm_paths);
+
+	codec->name = "MAX9888";
+	codec->owner = THIS_MODULE;
+	codec->read = max9888_read;
+	codec->write = max9888_write;
+	codec->bias_level = SND_SOC_BIAS_STANDBY;
+	codec->set_bias_level = max9888_set_bias_level;
+	codec->dai = max9888_dai;
+	codec->num_dai = 1;
+	codec->private_data = max9888;
+
+	/* disable device via dapm interface */
+	max9888->rev_version = max9888_read(codec, M9888_REG_FF_REV_ID);
+	max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	max9888_init_private_data(max9888);
+
+	/* Jack detection function only works well on Revision C */
+	if (max9888->rev_version == MAX9888_REVC) {
+		max9888_write(codec, M9888_REG_0F_IRQ_ENABLE, IJDET);
+		max9888_write(codec, M9888_REG_49_CFG_JACKDET,
+				JDETEN | JDEB_200ms);
+		max9888_write(codec, M9888_REG_4C_PWR_SYS, JDWK);
+	}
+
+	max9888_write(codec, M9888_REG_22_MIX_ADC_LEFT, MIX_MIC1 | MIX_INA1);
+	max9888_write(codec, M9888_REG_23_MIX_ADC_RIGHT, MIX_MIC2 | MIX_INA1);
+	max9888_write(codec, M9888_REG_21_MIX_DAC,
+		      MIX_DAI1L_TO_DACL | MIX_DAI2L_TO_DACL | MIX_DAI1R_TO_DACR
+		      | MIX_DAI2R_TO_DACR);
+	max9888_write(codec, M9888_REG_29_MIX_SPEAKER,
+		      MIX_LDAC_TO_SPL | MIX_RDAC_TO_SPR);
+	max9888_write(codec, M9888_REG_27_MIX_HEADPHONE,
+		      MIX_LDAC_TO_HPL | MIX_RDAC_TO_HPR);
+
+	headphone_set_volume(codec, 0x1f, 0);
+	speaker_set_volume(codec, 0x1f, 0);
+	m9888_mic1_gain_control(codec, 20, 1);
+	m9888_mic2_gain_control(codec, 20, 1);
+
+	/* power on device */
+	max9888_codec = codec;
+	for (i = 0; i < codec->num_dai; i++)
+		max9888_dai[i].dev = codec->dev;
+
+	ret = snd_soc_register_codec(codec);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+		goto err;
+	}
+
+	ret = snd_soc_register_dais(&max9888_dai[0], codec->num_dai);
+	if (ret != 0) {
+		dev_err(codec->dev, "Failed to register DAIs: %d\n", ret);
+		goto err_codec;
+	}
+
+	return ret;
+
+err_codec:
+	snd_soc_unregister_codec(codec);
+err:
+	kfree(max9888);
+
+	return ret;
+}
+
+static void max9888_unregister(struct max9888_priv *max9888)
+{
+	struct snd_soc_codec *codec = &max9888->codec;
+	snd_soc_unregister_dais(max9888_dai, codec->num_dai);
+	snd_soc_unregister_codec(codec);
+	kfree(max9888);
+	max9888_codec = NULL;
+}
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
+
+/* Magic definition of all other variables and things */
+I2C_CLIENT_INSMOD;
+
+static struct i2c_driver max9888_i2c_driver;
+
+static int max9888_i2c_probe(struct i2c_client *i2c,
+			     const struct i2c_device_id *id)
+{
+	struct snd_soc_codec *codec;
+	struct max9888_priv *max9888;
+
+	max9888 = kzalloc(sizeof(struct max9888_priv), GFP_KERNEL);
+	if (max9888 == NULL)
+		return -ENOMEM;
+
+	codec = &max9888->codec;
+	codec->hw_write = (hw_write_t) i2c_master_send;
+	codec->control_data = i2c;
+	i2c_set_clientdata(i2c, max9888);
+
+	codec->dev = &i2c->dev;
+
+	return max9888_register(max9888);
+}
+
+static int max9888_i2c_remove(struct i2c_client *client)
+{
+	struct max9888_priv *max9888 = i2c_get_clientdata(client);
+	max9888_unregister(max9888);
+	return 0;
+}
+
+static const struct i2c_device_id max9888_i2c_id[] = {
+	{"max9888", 0},
+	{}
+};
+
+/*
+ * gumstix i2c codec control layer
+ */
+static struct i2c_driver max9888_i2c_driver = {
+	.driver = {
+		   .name = "MAX9888 I2C CODEC",
+		   .owner = THIS_MODULE,
+		   },
+	.probe = max9888_i2c_probe,
+	.remove = max9888_i2c_remove,
+	.id_table = max9888_i2c_id,
+};
+#endif
+
+/*
+ * Make sure that a max9888 is attached to the I2C bus.
+ */
+
+static int max9888_probe(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec;
+	struct max9888_priv *max9888;
+	int ret = 0;
+
+	if (!max9888_codec) {
+		dev_err(&pdev->dev, "max9888 codec not yet registered\n");
+		return -EINVAL;
+	}
+
+	socdev->card->codec = max9888_codec;
+	codec = max9888_codec;
+	max9888 = codec->private_data;
+
+	if (max9888->rev_version == MAX9888_REVB)
+		info("RevB Audio CODEC %s", MAX9888_DRIVER_VERSION);
+	else if (max9888->rev_version == MAX9888_REVC)
+		info("RevC Audio CODEC %s", MAX9888_DRIVER_VERSION);
+	else
+		warn("Not supported MAX9888 : %d", max9888->rev_version);
+
+	/* register pcms */
+	ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+	if (ret < 0) {
+		printk(KERN_ERR "max9888: failed to create pcms\n");
+		goto pcm_err;
+	}
+
+	snd_soc_add_controls(codec, max9888_snd_controls,
+			     ARRAY_SIZE(max9888_snd_controls));
+	max9888_add_widgets(codec);
+	ret = snd_soc_init_card(socdev);
+	if (ret < 0) {
+		printk(KERN_ERR "max9888: failed to register card\n");
+		goto card_err;
+	}
+
+	return 0;
+
+card_err:
+	snd_soc_free_pcms(socdev);
+	snd_soc_dapm_free(socdev);
+
+pcm_err:
+	return ret;
+}
+
+/*
+ * Driver is being unloaded, power down the codec and free allocated resources
+ */
+static int max9888_remove(struct platform_device *pdev)
+{
+	struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+	struct snd_soc_codec *codec = socdev->card->codec;
+
+	if (codec->control_data)
+		max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+	snd_soc_free_pcms(socdev);
+	snd_soc_dapm_free(socdev);
+
+	return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_max9888 = {
+	.probe = max9888_probe,
+	.remove = max9888_remove,
+	.suspend = max9888_suspend,
+	.resume = max9888_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_max9888);
+
+static int __init max9888_modinit(void)
+{
+	int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	ret = i2c_add_driver(&max9888_i2c_driver);
+	if (ret != 0)
+		pr_err("Failed to register max9888 I2C driver: %d\n", ret);
+#endif
+	return 0;
+}
+
+module_init(max9888_modinit);
+
+static void __exit max9888_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+	i2c_del_driver(&max9888_i2c_driver);
+#endif
+}
+
+module_exit(max9888_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX9888 driver");
+MODULE_AUTHOR("Jesse Marroquin <jesse.marroquin@...im-ic.com>");
+MODULE_AUTHOR("Peter Hsiang <peter.hsiang@...im-ic.com>");
+MODULE_AUTHOR("BooJin Kim <boojin.kim@...sung.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max9888.h b/sound/soc/codecs/max9888.h
new file mode 100644
index 0000000..1053854
--- /dev/null
+++ b/sound/soc/codecs/max9888.h
@@ -0,0 +1,180 @@
+/*
+ * max9888.h -- MAX9888 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_MAX9888_H
+#define __LINUX_MAX9888_H __FILE__
+
+/*
+ *
+ * MAX9888 Registers Definition and Bit Fields
+ *
+ */
+#define M9888_REG_00_IRQ_STATUS		0x00
+#define M9888_REG_01_MIC_STATUS		0x01
+#define M9888_REG_02_JACK_STAUS		0x02
+#define M9888_REG_0F_IRQ_ENABLE		0x0F
+#define IJDET			(1<<1)
+
+#define M9888_REG_10_SYS_CLK		0x10
+#define M9888_REG_11_DAI1_CLKMODE	0x11
+#define M9888_REG_12_DAI1_CLKCFG_HI	0x12
+#define M9888_REG_13_DAI1_CLKCFG_LO	0x13
+#define M9888_REG_14_DAI1_FORMAT	0x14
+#define M9888_REG_15_DAI1_CLOCK		0x15
+#define M9888_REG_16_DAI1_IOCFG		0x16
+#define M9888_REG_17_DAI1_TDM		0x17
+#define M9888_REG_18_DAI1_FILTERS	0x18
+#define M9888_REG_19_DAI2_CLKMODE	0x19
+#define M9888_REG_1A_DAI2_CLKCFG_HI	0x1A
+#define M9888_REG_1B_DAI2_CLKCFG_LO	0x1B
+#define M9888_REG_1C_DAI2_FORMAT	0x1C
+#define M9888_REG_1D_DAI2_CLOCK		0x1D
+#define M9888_REG_1E_DAI2_IOCFG		0x1E
+#define M9888_REG_1F_DAI2_TDM		0x1F
+#define M9888_REG_20_DAI2_FILTERS	0x20
+
+#define M9888_REG_21_MIX_DAC		0x21
+#define MIX_DAI1L_TO_DACL	(1<<7)
+#define MIX_DAI1R_TO_DACL	(1<<6)
+#define MIX_DAI2L_TO_DACL	(1<<5)
+#define MIX_DAI2R_TO_DACL	(1<<4)
+#define MIX_DAI1L_TO_DACR	(1<<3)
+#define MIX_DAI1R_TO_DACR	(1<<2)
+#define MIX_DAI2L_TO_DACR	(1<<1)
+#define MIX_DAI2R_TO_DACR	(1<<0)
+
+#define M9888_REG_22_MIX_ADC_LEFT	0x22
+#define M9888_REG_23_MIX_ADC_RIGHT	0x23
+#define MIX_MIC1		(1<<7)
+#define MIX_MIC2		(1<<6)
+#define MIX_INA1		(1<<3)
+#define MIX_INA2		(1<<2)
+#define MIX_INB1		(1<<1)
+#define MIX_INB2		(1<<0)
+#define MIX_INA_DIFFERENTIAL	(1<<2)
+#define MIX_INB_DIFFERENTIAL	(1<<0)
+
+#define M9888_REG_24_MIX_PREOUT_A	0x24
+#define M9888_REG_25_MIX_PREOUT_B	0x25
+#define M9888_REG_26_MIX_PREOUT_C	0x26
+#define MIX_INA1		(1<<3)
+#define MIX_INA2		(1<<2)
+#define MIX_INB1		(1<<1)
+#define MIX_INB2		(1<<0)
+#define MIX_INA_DIFFERENTIAL	(1<<2)
+#define MIX_INB_DIFFERENTIAL	(1<<0)
+
+#define M9888_REG_27_MIX_HEADPHONE	0x27
+#define MIX_LDAC_TO_HPL		(3<<6)
+#define MIX_RDAC_TO_HPR		(3<<2)
+
+#define M9888_REG_28_MIX_RECEIVER	0x28
+#define M9888_REG_29_MIX_SPEAKER	0x29
+#define MIX_LDAC_TO_SPL		(1<<7)
+#define MIX_RDAC_TO_SPR		(1<<2)
+
+#define M9888_REG_2A_LVL_SIDETONE	0x2A
+#define M9888_REG_2B_LVL_DAI1_PLAY	0x2B
+#define M9888_REG_2C_LVL_DAI1_PLAY_EQ	0x2C
+#define M9888_REG_2D_LVL_DAI2_PLAY	0x2D
+#define M9888_REG_2E_LVL_DAI2_PLAY_EQ	0x2E
+#define M9888_REG_2F_LVL_ADC_L		0x2F
+#define M9888_REG_30_LVL_ADC_R		0x30
+#define M9888_REG_31_LVL_MIC1		0x31
+#define M9888_REG_32_LVL_MIC2		0x32
+#define M9888_REG_33_LVL_INA		0x33
+#define M9888_REG_34_LVL_INB		0x34
+#define M9888_REG_35_LVL_PREOUT_A	0x35
+#define M9888_REG_36_LVL_PREOUT_B	0x36
+#define M9888_REG_37_LVL_PREOUT_C	0x37
+#define M9888_REG_38_LVL_HEADPHONE_L	0x38
+#define M9888_REG_39_LVL_HEADPHONE_R	0x39
+#define HPXM			(1<<7)
+
+#define M9888_REG_3A_LVL_RECEIVER	0x3A
+#define RECM			(1<<7)
+
+#define M9888_REG_3B_LVL_SPEAKER_L	0x3B
+#define M9888_REG_3C_LVL_SPEAKER_R	0x3C
+#define SPXM			(1<<7)
+
+#define M9888_REG_3D_MICAGC_CFG		0x3D
+#define M9888_REG_3E_MICAGC_THRESHOLD	0x3E
+#define M9888_REG_3F_SPKDHP		0x3F
+#define M9888_REG_40_SPKDHP_THRESHOLD	0x40
+#define M9888_REG_41_SPKALC_COMP	0x41
+#define M9888_REG_42_PWRLMT_CFG		0x42
+#define M9888_REG_43_PWRLMT_TIME	0x43
+#define M9888_REG_44_THDLMT_CFG		0x44
+#define M9888_REG_45_CFG_AUDIO_IN	0x45
+#define M9888_REG_46_CFG_MIC		0x46
+#define DIGMICLEN		(1<<5)
+#define DIGMICREN		(1<<4)
+
+#define M9888_REG_47_CFG_LEVEL		0x47
+#define VSEN			(1<<6)
+#define ZDEN			(1<<5)
+#define EQ2EN			(1<<1)
+#define EQ1EN			(1<<0)
+
+#define M9888_REG_48_CFG_BYPASS		0x48
+#define M9888_REG_49_CFG_JACKDET	0x49
+#define JDETEN			(1<<7)
+#define JDEB_25ms		(0<<0)
+#define JDEB_50ms		(1<<0)
+#define JDEB_100ms		(2<<0)
+#define JDEB_200ms		(3<<0)
+
+#define M9888_REG_4A_PWR_EN_IN		0x4A
+#define INAEN			(1<<7)
+#define INBEN			(1<<6)
+#define MBEN			(1<<3)
+#define ADLEN			(1<<1)
+#define ADREN			(1<<0)
+
+#define M9888_REG_4B_PWR_EN_OUT		0x4B
+#define HPLEN			(1<<7)
+#define HPREN			(1<<6)
+#define SPLEN			(1<<5)
+#define SPREN			(1<<4)
+#define RECEN			(1<<3)
+#define DALEN			(1<<1)
+#define DAREN			(1<<0)
+
+#define M9888_REG_4C_PWR_SYS		0x4C
+#define SHDN			(1<<7)
+#define JDWK			(1<<0)
+
+#define M9888_REG_50_DAI1_EQ_BASE	0x50
+#define M9888_REG_82_DAI2_EQ_BASE	0x82
+#define M9888_REG_B4_DAI1_BIQUAD_BASE	0xB4
+#define M9888_REG_BE_DAI2_BIQUAD_BASE	0xBE
+#define M9888_REG_FF_REV_ID		0xFF
+
+#define MAX9888_REVB			0x42
+#define MAX9888_REVC			0x43
+
+#define REGS_PER_BAND			5
+#define REGS_PER_BIQUAD			5
+
+#define HIGH_BYTE(w)			((w >> 8) & 0x00ff)
+#define LOW_BYTE(w)			(w & 0x00ff)
+
+#define MAX9888_DAI_HIFI		0
+#define MAX9888_DAI_VOICE		1
+
+struct max9888_setup_data {
+	unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai max9888_dai[2];
+extern struct snd_soc_codec_device soc_codec_dev_max9888;
+
+#endif /* __LINUX_MAX9888_H */
-- 
1.5.5.6

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ