All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-08-31 21:08 Peter Hsiang
  2010-09-01 11:14   ` Mark Brown
  0 siblings, 1 reply; 46+ messages in thread
From: Peter Hsiang @ 2010-08-31 21:08 UTC (permalink / raw)
  To: Jaroslav Kysela, Takashi Iwai, Peter Hsiang, Jesse Marroquin,
	Liam Girdwood, Mark Brown, Peter Ujfalusi, Joonyoung Shim,
	alsa-devel, linux-kernel

This patch adds the MAX98088 CODEC driver.

Signed-off-by: Peter Hsiang <peter.hsiang at maxim-ic.com>
---
 include/sound/max98088.h    |   87 ++
 sound/soc/codecs/Kconfig    |    4 +
 sound/soc/codecs/Makefile   |    2 +
 sound/soc/codecs/max98088.c | 2871 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/max98088.h |  161 +++
 5 files changed, 3125 insertions(+), 0 deletions(-)
 create mode 100644 include/sound/max98088.h
 create mode 100644 sound/soc/codecs/max98088.c
 create mode 100644 sound/soc/codecs/max98088.h

diff --git a/include/sound/max98088.h b/include/sound/max98088.h
new file mode 100644
index 0000000..7a6c53c
--- /dev/null
+++ b/include/sound/max98088.h
@@ -0,0 +1,87 @@
+/*
+ * Platform data for MAX98088
+ *
+ * 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 as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __SOUND_MAX98088_PDATA_H__
+#define __SOUND_MAX98088_PDATA_H__
+
+#define EQ_CFG_MAX 32
+
+/* Equalizer filter response configuration */
+struct max98088_eq_cfg {
+       const char *name;
+       unsigned int rate;
+       u16 band1[5];
+       u16 band2[5];
+       u16 band3[5];
+       u16 band4[5];
+       u16 band5[5];
+};
+
+/* Speaker excursion limiter filter response configurations */
+#define EX_CFG_MAX 32
+
+struct max98088_excursion_cfg {
+       const char *name;
+       unsigned int rate;
+       u16 resp[5];
+};
+
+/* codec platform data */
+struct max98088_pdata {
+
+       /* Equalizers for DAI1 and DAI2 */
+       struct max98088_eq_cfg *eq1_cfg;
+       struct max98088_eq_cfg *eq2_cfg;
+       unsigned int eq1_cfgcnt;
+       unsigned int eq2_cfgcnt;
+
+       /* Excursion limiters for DAI1 and DAI2 */
+       struct max98088_excursion_cfg *ex1_cfg;
+       struct max98088_excursion_cfg *ex2_cfg;
+       unsigned int ex1_cfgcnt;
+       unsigned int ex2_cfgcnt;
+
+       /* Receiver output can be configured as power amplifier or LINE out */
+       /* Set receiver_mode to:
+        * 0 = amplifier output, or
+        * 1 = LINE level output
+        */
+       unsigned int receiver_mode;
+
+       /* Analog/digital microphone configuration:
+        * 0 = analog microphone input (normal setting)
+        * 1 = digital microhpone input
+        */
+       unsigned int digmic_left_enable;
+       unsigned int digmic_right_enable;
+
+       /* Normal microphone connection, or external connection through INA
+        * 0 = normal connection
+        * 1 = INA1 input
+        * 2 = INA2 input
+        */
+       unsigned int extmic_mode;
+
+       /* Bypass option for INA to MIC1 connection
+        * 0 = normal setting
+        * 1 = bypass enabled
+        */
+       unsigned int ina_to_mic1_bypass;
+
+       /* Bypass option for MIC1 to MIC2 connection
+        * 0 = normal setting
+        * 1 = bypass enabled
+        */
+       unsigned int mic1_to_mic2_bypass;
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 155c127..e9a3c74 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_DA7210 if I2C
        select SND_SOC_JZ4740 if SOC_JZ4740
        select SND_SOC_MAX9877 if I2C
+       select SND_SOC_MAX98088 if I2C
        select SND_SOC_PCM3008
        select SND_SOC_SPDIF
        select SND_SOC_SSM2602 if I2C
@@ -156,6 +157,9 @@ config SND_SOC_L3
 config SND_SOC_DA7210
         tristate

+config SND_SOC_MAX98088
+       tristate
+
 config SND_SOC_PCM3008
        tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 10d468e..fe9d104 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -15,6 +15,7 @@ snd-soc-cs4270-objs := cs4270.o
 snd-soc-cx20442-objs := cx20442.o
 snd-soc-da7210-objs := da7210.o
 snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
 snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-spdif-objs := spdif_transciever.o
 snd-soc-ssm2602-objs := ssm2602.o
@@ -87,6 +88,7 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
 obj-$(CONFIG_SND_SOC_DA7210)   += snd-soc-da7210.o
 obj-$(CONFIG_SND_SOC_L3)       += snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_JZ4740_CODEC)     += snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088)  += snd-soc-max98088.o
 obj-$(CONFIG_SND_SOC_PCM3008)  += snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_SPDIF)    += snd-soc-spdif.o
 obj-$(CONFIG_SND_SOC_SSM2602)  += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
new file mode 100644
index 0000000..72f599f
--- /dev/null
+++ b/sound/soc/codecs/max98088.c
@@ -0,0 +1,2871 @@
+/*
+ * max98088.c -- MAX98088 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 <linux/slab.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+static struct snd_soc_codec *max98088_codec;
+struct snd_soc_codec_device soc_codec_dev_max98088;
+
+/* configurations associated with each channel of DAI stream */
+struct max98088_cdata {
+       unsigned int rate;
+       unsigned int fmt;
+
+       /* Equalizer parameters */
+       int eq_textcnt;
+       const char *eq_texts[EQ_CFG_MAX];
+       int eq_sel;
+       struct soc_enum eq_enum;
+
+       /* Excursion limiter parameters */
+       int ex_textcnt;
+       const char *ex_texts[EX_CFG_MAX];
+       int ex_sel;
+       struct soc_enum ex_enum;
+};
+
+/* codec private data */
+struct max98088_priv {
+       struct snd_soc_codec codec;
+       struct max98088_pdata *pdata;
+       u8 reg_cache[M98088_REG_CNT];
+       unsigned int sysclk;
+       struct max98088_cdata dai[2];
+       u8 power_state;
+       unsigned int ex_mode;
+       unsigned int digmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+       0x00, /* 00 IRQ status */
+       0x00, /* 01 MIC status */
+       0x00, /* 02 jack status */
+       0x00, /* 03 battery voltage */
+       0x00, /* 04 */
+       0x00, /* 05 */
+       0x00, /* 06 */
+       0x00, /* 07 */
+       0x00, /* 08 */
+       0x00, /* 09 */
+       0x00, /* 0A */
+       0x00, /* 0B */
+       0x00, /* 0C */
+       0x00, /* 0D */
+       0x00, /* 0E */
+       0x00, /* 0F interrupt enable */
+
+       0x00, /* 10 master clock */
+       0x00, /* 11 DAI1 clock mode */
+       0x00, /* 12 DAI1 clock control */
+       0x00, /* 13 DAI1 clock control */
+       0x00, /* 14 DAI1 format */
+       0x00, /* 15 DAI1 clock */
+       0x00, /* 16 DAI1 config */
+       0x00, /* 17 DAI1 TDM */
+       0x00, /* 18 DAI1 filters */
+       0x00, /* 19 DAI2 clock mode */
+       0x00, /* 1A DAI2 clock control */
+       0x00, /* 1B DAI2 clock control */
+       0x00, /* 1C DAI2 format */
+       0x00, /* 1D DAI2 clock */
+       0x00, /* 1E DAI2 config */
+       0x00, /* 1F DAI2 TDM */
+
+       0x00, /* 20 DAI2 filters */
+       0x00, /* 21 data config */
+       0x00, /* 22 DAC mixer */
+       0x00, /* 23 left ADC mixer */
+       0x00, /* 24 right ADC mixer */
+       0x00, /* 25 left HP mixer */
+       0x00, /* 26 right HP mixer */
+       0x00, /* 27 HP control */
+       0x00, /* 28 left REC mixer */
+       0x00, /* 29 right REC mixer */
+       0x00, /* 2A REC control */
+       0x00, /* 2B left SPK mixer */
+       0x00, /* 2C right SPK mixer */
+       0x00, /* 2D SPK control */
+       0x00, /* 2E sidetone */
+       0x00, /* 2F DAI1 playback level */
+
+       0x00, /* 30 DAI1 playback level */
+       0x00, /* 31 DAI2 playback level */
+       0x00, /* 32 DAI2 playbakc level */
+       0x00, /* 33 left ADC level */
+       0x00, /* 34 right ADC level */
+       0x00, /* 35 MIC1 level */
+       0x00, /* 36 MIC2 level */
+       0x00, /* 37 INA level */
+       0x00, /* 38 INB level */
+       0x00, /* 39 left HP volume */
+       0x00, /* 3A right HP volume */
+       0x00, /* 3B left REC volume */
+       0x00, /* 3C right REC volume */
+       0x00, /* 3D left SPK volume */
+       0x00, /* 3E right SPK volume */
+       0x00, /* 3F MIC config */
+
+       0x00, /* 40 MIC threshold */
+       0x00, /* 41 excursion limiter filter */
+       0x00, /* 42 excursion limiter threshold */
+       0x00, /* 43 ALC */
+       0x00, /* 44 power limiter threshold */
+       0x00, /* 45 power limiter config */
+       0x00, /* 46 distortion limiter config */
+       0x00, /* 47 audio input */
+       0x00, /* 48 microphone */
+       0x00, /* 49 level control */
+       0x00, /* 4A bypass switches */
+       0x00, /* 4B jack detect */
+       0x00, /* 4C input enable */
+       0x00, /* 4D output enable */
+       0xF0, /* 4E bias control */
+       0x00, /* 4F DAC power */
+
+       0x0F, /* 50 DAC power */
+       0x00, /* 51 system */
+       0x00, /* 52 DAI1 EQ1 */
+       0x00, /* 53 DAI1 EQ1 */
+       0x00, /* 54 DAI1 EQ1 */
+       0x00, /* 55 DAI1 EQ1 */
+       0x00, /* 56 DAI1 EQ1 */
+       0x00, /* 57 DAI1 EQ1 */
+       0x00, /* 58 DAI1 EQ1 */
+       0x00, /* 59 DAI1 EQ1 */
+       0x00, /* 5A DAI1 EQ1 */
+       0x00, /* 5B DAI1 EQ1 */
+       0x00, /* 5C DAI1 EQ2 */
+       0x00, /* 5D DAI1 EQ2 */
+       0x00, /* 5E DAI1 EQ2 */
+       0x00, /* 5F DAI1 EQ2 */
+
+       0x00, /* 60 DAI1 EQ2 */
+       0x00, /* 61 DAI1 EQ2 */
+       0x00, /* 62 DAI1 EQ2 */
+       0x00, /* 63 DAI1 EQ2 */
+       0x00, /* 64 DAI1 EQ2 */
+       0x00, /* 65 DAI1 EQ2 */
+       0x00, /* 66 DAI1 EQ3 */
+       0x00, /* 67 DAI1 EQ3 */
+       0x00, /* 68 DAI1 EQ3 */
+       0x00, /* 69 DAI1 EQ3 */
+       0x00, /* 6A DAI1 EQ3 */
+       0x00, /* 6B DAI1 EQ3 */
+       0x00, /* 6C DAI1 EQ3 */
+       0x00, /* 6D DAI1 EQ3 */
+       0x00, /* 6E DAI1 EQ3 */
+       0x00, /* 6F DAI1 EQ3 */
+
+       0x00, /* 70 DAI1 EQ4 */
+       0x00, /* 71 DAI1 EQ4 */
+       0x00, /* 72 DAI1 EQ4 */
+       0x00, /* 73 DAI1 EQ4 */
+       0x00, /* 74 DAI1 EQ4 */
+       0x00, /* 75 DAI1 EQ4 */
+       0x00, /* 76 DAI1 EQ4 */
+       0x00, /* 77 DAI1 EQ4 */
+       0x00, /* 78 DAI1 EQ4 */
+       0x00, /* 79 DAI1 EQ4 */
+       0x00, /* 7A DAI1 EQ5 */
+       0x00, /* 7B DAI1 EQ5 */
+       0x00, /* 7C DAI1 EQ5 */
+       0x00, /* 7D DAI1 EQ5 */
+       0x00, /* 7E DAI1 EQ5 */
+       0x00, /* 7F DAI1 EQ5 */
+
+       0x00, /* 80 DAI1 EQ5 */
+       0x00, /* 81 DAI1 EQ5 */
+       0x00, /* 82 DAI1 EQ5 */
+       0x00, /* 83 DAI1 EQ5 */
+       0x00, /* 84 DAI2 EQ1 */
+       0x00, /* 85 DAI2 EQ1 */
+       0x00, /* 86 DAI2 EQ1 */
+       0x00, /* 87 DAI2 EQ1 */
+       0x00, /* 88 DAI2 EQ1 */
+       0x00, /* 89 DAI2 EQ1 */
+       0x00, /* 8A DAI2 EQ1 */
+       0x00, /* 8B DAI2 EQ1 */
+       0x00, /* 8C DAI2 EQ1 */
+       0x00, /* 8D DAI2 EQ1 */
+       0x00, /* 8E DAI2 EQ2 */
+       0x00, /* 8F DAI2 EQ2 */
+
+       0x00, /* 90 DAI2 EQ2 */
+       0x00, /* 91 DAI2 EQ2 */
+       0x00, /* 92 DAI2 EQ2 */
+       0x00, /* 93 DAI2 EQ2 */
+       0x00, /* 94 DAI2 EQ2 */
+       0x00, /* 95 DAI2 EQ2 */
+       0x00, /* 96 DAI2 EQ2 */
+       0x00, /* 97 DAI2 EQ2 */
+       0x00, /* 98 DAI2 EQ3 */
+       0x00, /* 99 DAI2 EQ3 */
+       0x00, /* 9A DAI2 EQ3 */
+       0x00, /* 9B DAI2 EQ3 */
+       0x00, /* 9C DAI2 EQ3 */
+       0x00, /* 9D DAI2 EQ3 */
+       0x00, /* 9E DAI2 EQ3 */
+       0x00, /* 9F DAI2 EQ3 */
+
+       0x00, /* A0 DAI2 EQ3 */
+       0x00, /* A1 DAI2 EQ3 */
+       0x00, /* A2 DAI2 EQ4 */
+       0x00, /* A3 DAI2 EQ4 */
+       0x00, /* A4 DAI2 EQ4 */
+       0x00, /* A5 DAI2 EQ4 */
+       0x00, /* A6 DAI2 EQ4 */
+       0x00, /* A7 DAI2 EQ4 */
+       0x00, /* A8 DAI2 EQ4 */
+       0x00, /* A9 DAI2 EQ4 */
+       0x00, /* AA DAI2 EQ4 */
+       0x00, /* AB DAI2 EQ4 */
+       0x00, /* AC DAI2 EQ5 */
+       0x00, /* AD DAI2 EQ5 */
+       0x00, /* AE DAI2 EQ5 */
+       0x00, /* AF DAI2 EQ5 */
+
+       0x00, /* B0 DAI2 EQ5 */
+       0x00, /* B1 DAI2 EQ5 */
+       0x00, /* B2 DAI2 EQ5 */
+       0x00, /* B3 DAI2 EQ5 */
+       0x00, /* B4 DAI2 EQ5 */
+       0x00, /* B5 DAI2 EQ5 */
+       0x00, /* B6 DAI1 biquad */
+       0x00, /* B7 DAI1 biquad */
+       0x00, /* B8 DAI1 biquad */
+       0x00, /* B9 DAI1 biquad */
+       0x00, /* BA DAI1 biquad */
+       0x00, /* BB DAI1 biquad */
+       0x00, /* BC DAI1 biquad */
+       0x00, /* BD DAI1 biquad */
+       0x00, /* BE DAI1 biquad */
+       0x00, /* BF DAI1 biquad */
+
+       0x00, /* C0 DAI2 biquad */
+       0x00, /* C1 DAI2 biquad */
+       0x00, /* C2 DAI2 biquad */
+       0x00, /* C3 DAI2 biquad */
+       0x00, /* C4 DAI2 biquad */
+       0x00, /* C5 DAI2 biquad */
+       0x00, /* C6 DAI2 biquad */
+       0x00, /* C7 DAI2 biquad */
+       0x00, /* C8 DAI2 biquad */
+       0x00, /* C9 DAI2 biquad */
+       0x00, /* CA */
+       0x00, /* CB */
+       0x00, /* CC */
+       0x00, /* CD */
+       0x00, /* CE */
+       0x00, /* CF */
+
+       0x00, /* D0 */
+       0x00, /* D1 */
+       0x00, /* D2 */
+       0x00, /* D3 */
+       0x00, /* D4 */
+       0x00, /* D5 */
+       0x00, /* D6 */
+       0x00, /* D7 */
+       0x00, /* D8 */
+       0x00, /* D9 */
+       0x00, /* DA */
+       0x70, /* DB */
+       0x00, /* DC */
+       0x00, /* DD */
+       0x00, /* DE */
+       0x00, /* DF */
+
+       0x00, /* E0 */
+       0x00, /* E1 */
+       0x00, /* E2 */
+       0x00, /* E3 */
+       0x00, /* E4 */
+       0x00, /* E5 */
+       0x00, /* E6 */
+       0x00, /* E7 */
+       0x00, /* E8 */
+       0x00, /* E9 */
+       0x00, /* EA */
+       0x00, /* EB */
+       0x00, /* EC */
+       0x00, /* ED */
+       0x00, /* EE */
+       0x00, /* EF */
+
+       0x00, /* F0 */
+       0x00, /* F1 */
+       0x00, /* F2 */
+       0x00, /* F3 */
+       0x00, /* F4 */
+       0x00, /* F5 */
+       0x00, /* F6 */
+       0x00, /* F7 */
+       0x00, /* F8 */
+       0x00, /* F9 */
+       0x00, /* FA */
+       0x00, /* FB */
+       0x00, /* FC */
+       0x00, /* FD */
+       0x00, /* FE */
+       0x00, /* FF */
+};
+
+static struct {
+       int readable;
+       int writable;
+       int vol;
+} max98088_access[M98088_REG_CNT] = {
+       { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+       { 0xFF, 0x00, 1 }, /* 01 MIC status */
+       { 0xFF, 0x00, 1 }, /* 02 jack status */
+       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+       { 0xFF, 0xFF, 0 }, /* 04 */
+       { 0xFF, 0xFF, 0 }, /* 05 */
+       { 0xFF, 0xFF, 0 }, /* 06 */
+       { 0xFF, 0xFF, 0 }, /* 07 */
+       { 0xFF, 0xFF, 0 }, /* 08 */
+       { 0xFF, 0xFF, 0 }, /* 09 */
+       { 0xFF, 0xFF, 0 }, /* 0A */
+       { 0xFF, 0xFF, 0 }, /* 0B */
+       { 0xFF, 0xFF, 0 }, /* 0C */
+       { 0xFF, 0xFF, 0 }, /* 0D */
+       { 0xFF, 0xFF, 0 }, /* 0E */
+       { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+       { 0xFF, 0xFF, 0 }, /* 10 master clock */
+       { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+       { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+       { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+       { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+       { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+       { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+       { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+       { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+       { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+       { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+       { 0xFF, 0xFF, 0 }, /* 21 data config */
+       { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+       { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 27 HP control */
+       { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 2A REC control */
+       { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+       { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+       { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+       { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+       { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+       { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+       { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+       { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+       { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+       { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+       { 0xFF, 0xFF, 0 }, /* 37 INA level */
+       { 0xFF, 0xFF, 0 }, /* 38 INB level */
+       { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+       { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+       { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+       { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 43 ALC */
+       { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+       { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+       { 0xFF, 0xFF, 0 }, /* 47 audio input */
+       { 0xFF, 0xFF, 0 }, /* 48 microphone */
+       { 0xFF, 0xFF, 0 }, /* 49 level control */
+       { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+       { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+       { 0xFF, 0xFF, 0 }, /* 4C input enable */
+       { 0xFF, 0xFF, 0 }, /* 4D output enable */
+       { 0xFF, 0xFF, 0 }, /* 4E bias control */
+       { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+       { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+       { 0xFF, 0xFF, 0 }, /* 51 system */
+       { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+       { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+       { 0x00, 0x00, 0 }, /* CA */
+       { 0x00, 0x00, 0 }, /* CB */
+       { 0x00, 0x00, 0 }, /* CC */
+       { 0x00, 0x00, 0 }, /* CD */
+       { 0x00, 0x00, 0 }, /* CE */
+       { 0x00, 0x00, 0 }, /* CF */
+
+       { 0x00, 0x00, 0 }, /* D0 */
+       { 0x00, 0x00, 0 }, /* D1 */
+       { 0x00, 0x00, 0 }, /* D2 */
+       { 0x00, 0x00, 0 }, /* D3 */
+       { 0x00, 0x00, 0 }, /* D4 */
+       { 0x00, 0x00, 0 }, /* D5 */
+       { 0x00, 0x00, 0 }, /* D6 */
+       { 0x00, 0x00, 0 }, /* D7 */
+       { 0x00, 0x00, 0 }, /* D8 */
+       { 0x00, 0x00, 0 }, /* D9 */
+       { 0x00, 0x00, 0 }, /* DA */
+       { 0x70, 0x70, 0 }, /* DB */
+       { 0x00, 0x00, 0 }, /* DC */
+       { 0x00, 0x00, 0 }, /* DD */
+       { 0x00, 0x00, 0 }, /* DE */
+       { 0x00, 0x00, 0 }, /* DF */
+
+       { 0x00, 0x00, 0 }, /* E0 */
+       { 0x00, 0x00, 0 }, /* E1 */
+       { 0x00, 0x00, 0 }, /* E2 */
+       { 0x00, 0x00, 0 }, /* E3 */
+       { 0x00, 0x00, 0 }, /* E4 */
+       { 0x00, 0x00, 0 }, /* E5 */
+       { 0x00, 0x00, 0 }, /* E6 */
+       { 0x00, 0x00, 0 }, /* E7 */
+       { 0x00, 0x00, 0 }, /* E8 */
+       { 0x00, 0x00, 0 }, /* E9 */
+       { 0x00, 0x00, 0 }, /* EA */
+       { 0x00, 0x00, 0 }, /* EB */
+       { 0x00, 0x00, 0 }, /* EC */
+       { 0x00, 0x00, 0 }, /* ED */
+       { 0x00, 0x00, 0 }, /* EE */
+       { 0x00, 0x00, 0 }, /* EF */
+
+       { 0x00, 0x00, 0 }, /* F0 */
+       { 0x00, 0x00, 0 }, /* F1 */
+       { 0x00, 0x00, 0 }, /* F2 */
+       { 0x00, 0x00, 0 }, /* F3 */
+       { 0x00, 0x00, 0 }, /* F4 */
+       { 0x00, 0x00, 0 }, /* F5 */
+       { 0x00, 0x00, 0 }, /* F6 */
+       { 0x00, 0x00, 0 }, /* F7 */
+       { 0x00, 0x00, 0 }, /* F8 */
+       { 0x00, 0x00, 0 }, /* F9 */
+       { 0x00, 0x00, 0 }, /* FA */
+       { 0x00, 0x00, 0 }, /* FB */
+       { 0x00, 0x00, 0 }, /* FC */
+       { 0x00, 0x00, 0 }, /* FD */
+       { 0x00, 0x00, 0 }, /* FE */
+       { 0xFF, 0x00, 1 }, /* FF */
+};
+
+
+static int max98088_volatile_register(unsigned int reg)
+{
+       return max98088_access[reg].vol;
+}
+
+/*
+ * Read the MAX98088 I2C register space
+ * Note: this driver source code is backward compatible to kernel
+ * version 2.6.32.
+ */
+static unsigned int max98088_read(struct snd_soc_codec *codec,
+                                 unsigned int reg)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct i2c_msg msg[2];
+       struct i2c_client *client;
+       u8 data[2];
+       int ret;
+
+       client = (struct i2c_client *)codec->control_data;
+
+       if (max98088_volatile_register(reg)) {
+               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;
+
+               ret = i2c_transfer(client->adapter, &msg[0], 2);
+               return (ret == 2) ? data[1] : -EIO;
+       } else {
+               if (reg > (ARRAY_SIZE(max98088_reg)))
+                       return -EIO;
+               return max98088->reg_cache[reg];
+       }
+}
+
+/* Write to the MAX98088 register space (cached) */
+static int max98088_write(struct snd_soc_codec *codec, unsigned int reg,
+                         unsigned int value)
+{
+       u8 data[2];
+       struct max98088_priv *max98088 = codec->private_data;
+
+       data[0] = reg;
+       data[1] = value;
+       if (codec->hw_write(codec->control_data, data, 2) == 2) {
+               max98088->reg_cache[reg] =
+                       (value & max98088_access[reg].writable);
+               return 0;
+       } else {
+               return -EIO;
+       }
+}
+
+/* Write to the MAX98088 I2C register space */
+static int max98088_hw_write(struct snd_soc_codec *codec, unsigned int reg,
+                            unsigned int value)
+{
+       u8 data[2];
+
+       data[0] = reg;
+       data[1] = value;
+       if (codec->hw_write(codec->control_data, data, 2) == 2)
+               return 0;
+       else
+               return -EIO;
+}
+
+/*
+ * For kernels compiled without unsigned long long int division
+ */
+unsigned long long int ulldiv(unsigned long long int dividend,
+                             unsigned long long int divisor)
+{
+       unsigned long long int quotient = 0;
+       int shift = 1;
+
+       if (divisor == 0)
+               return 0;
+
+       /* result is 1.0 if divisor and dividend are equal */
+       if (divisor == dividend)
+               return 1;
+
+       /* Normalize divisor */
+       while (!(divisor & 0x8000000000000000ULL)) {
+               divisor <<= 1;
+               ++shift;
+       }
+
+       /* Shift and subtract */
+       while (shift--) {
+               quotient <<= 1;
+
+               if (divisor <= dividend) {
+                       dividend -= divisor;
+                       ++quotient;
+               }
+               divisor >>= 1;
+       }
+
+       /* Round up */
+       if (dividend > divisor)
+               ++quotient;
+
+       return quotient;
+}
+
+#define INA1_PGA_BIT 0x01
+#define INA2_PGA_BIT 0x02
+#define INB1_PGA_BIT 0x04
+#define INB2_PGA_BIT 0x08
+/*
+ * The INx1 and INx2 PGAs 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.
+ */
+static int max98088_pga_event(struct snd_soc_dapm_widget *w,
+                             struct snd_kcontrol *k, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       u8 *state = &max98088->power_state;
+       unsigned int val;
+       unsigned int pga;
+       unsigned int mask;
+
+       BUG_ON(w->reg != M98088_REG_4C_PWR_EN_IN);
+
+       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;
+       }
+
+       if (event == SND_SOC_DAPM_POST_PMU) {
+               /* ON */
+               *state |= pga;
+
+               /* Turn on, avoiding unnecessary writes */
+               val = max98088_read(codec, w->reg);
+               if (!(val & (1 << w->shift))) {
+                       val |= (1 << w->shift);
+                       max98088_write(codec, w->reg, val);
+               }
+       } else if (event == SND_SOC_DAPM_POST_PMD) {
+               /* OFF */
+               *state &= ~pga;
+
+               /* Turn off if both are off, avoiding unnecessary writes */
+               if (!(*state & mask)) {
+                       val = max98088_read(codec, w->reg);
+                       if (val & (1 << w->shift)) {
+                               val &= ~(1 << w->shift);
+                               max98088_write(codec, w->reg, val);
+                       }
+               }
+       } else {
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+                   unsigned int band, u16 *coefs)
+{
+       unsigned int eq_reg;
+       unsigned int i;
+
+       if (band > 4)
+               return;
+
+       if (dai > 1)
+               return;
+
+       /* Load the base register address */
+       eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+       /* Add the band address offset, note adjustment for word address */
+       eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+       /* Step through the registers and coefs */
+       for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+               max98088_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+               max98088_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+       }
+
+       return;
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_ex_mode[] = {
+       "Off",
+       "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,           /* disabled */
+       (0<<4)|3,       /* 100Hz */
+       (1<<4)|0,       /* 400Hz */
+       (2<<4)|0,       /* 600Hz */
+       (3<<4)|0,       /* 800Hz */
+       (4<<4)|0,       /* 1000Hz */
+       (1<<4)|1,       /* 200-400Hz */
+       (2<<4)|2,       /* 400-600Hz */
+       (3<<4)|2,       /* 400-800Hz */
+       (1<<4)|3,       /* user-400Hz */
+       (2<<4)|3,       /* user-600Hz */
+       (3<<4)|3,       /* user-800Hz */
+       (4<<4)|3    /* user-1000Hz */
+};
+
+static const struct soc_enum max98088_ex_mode_enum[] = {
+       SOC_ENUM_SINGLE_EXT(13, max98088_ex_mode),
+};
+
+/*
+ * Excursion limiter mode - set mode
+ */
+static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->ex_mode;
+
+       *mode = ucontrol->value.integer.value[0];
+
+       if (*mode <= ARRAY_SIZE(ex_mode_table))
+               max98088_write(codec, M98088_REG_41_SPKDHP,
+                       ex_mode_table[*mode]);
+
+       return 0;
+}
+
+/*
+ * Excursion limiter mode - get mode
+ */
+static int max98088_ex_mode_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->ex_mode;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+/*
+ * Load user programmable mode excursion limiter filter coefficients
+ */
+static void max98088_ex_resp_control(struct snd_soc_codec *codec,
+                                    int reg, u16 *param)
+{
+       max98088_write(codec,   reg, M98088_BYTE1(param[0]));
+       max98088_write(codec, ++reg, M98088_BYTE0(param[0]));
+       max98088_write(codec, ++reg, M98088_BYTE1(param[1]));
+       max98088_write(codec, ++reg, M98088_BYTE0(param[1]));
+       max98088_write(codec, ++reg, M98088_BYTE1(param[2]));
+       max98088_write(codec, ++reg, M98088_BYTE0(param[2]));
+       max98088_write(codec, ++reg, M98088_BYTE1(param[3]));
+       max98088_write(codec, ++reg, M98088_BYTE0(param[3]));
+       max98088_write(codec, ++reg, M98088_BYTE1(param[4]));
+       max98088_write(codec, ++reg, M98088_BYTE0(param[4]));
+}
+
+static const char *max98088_hp_spk_mute[] = {"disable", "enable"};
+static const struct soc_enum max98088_hp_spk_mute_enum[] = {
+       SOC_ENUM_SINGLE_EXT(2, max98088_hp_spk_mute),
+};
+/* These defines correlate to the position of the like named value in
+ * max98088_hp_spk_mute[] */
+#define MAX98088_HP_SPK_MUTE_DISABLE 0
+#define MAX98088_HP_SPK_MUTE_ENABLE 1
+
+/*
+ * Mute the speaker
+ */
+static int max98088_spk_mute_set(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 lReg = max98088_read(codec, M98088_REG_3D_LVL_SPK_L);
+       u8 rReg = max98088_read(codec, M98088_REG_3E_LVL_SPK_R);
+
+       if (MAX98088_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) {
+               lReg |= M98088_SP_MUTE;
+               rReg |= M98088_SP_MUTE;
+       } else {
+               lReg &= ~M98088_SP_MUTE;
+               rReg &= ~M98088_SP_MUTE;
+       }
+
+       max98088_write(codec, M98088_REG_3D_LVL_SPK_L, lReg);
+       max98088_write(codec, M98088_REG_3E_LVL_SPK_R, rReg);
+
+       return 0;
+}
+
+/*
+ * Get speaker mute status
+ */
+static int max98088_spk_mute_get(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 reg = max98088_read(codec, M98088_REG_3D_LVL_SPK_L);
+
+       ucontrol->value.integer.value[0] = (reg & M98088_SP_MUTE) ? 1 : 0;
+
+       return 0;
+}
+
+/*
+ * Mute the headphone
+ */
+static int max98088_hp_mute_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 lReg = max98088_read(codec, M98088_REG_39_LVL_HP_L);
+       u8 rReg = max98088_read(codec, M98088_REG_3A_LVL_HP_R);
+
+       if (MAX98088_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) {
+               lReg |= M98088_HP_MUTE;
+               rReg |= M98088_HP_MUTE;
+       } else {
+               lReg &= ~M98088_HP_MUTE;
+               rReg &= ~M98088_HP_MUTE;
+       }
+
+       max98088_write(codec, M98088_REG_39_LVL_HP_L, lReg);
+       max98088_write(codec, M98088_REG_3A_LVL_HP_R, rReg);
+
+       return 0;
+}
+
+/*
+ * Get current headphone mute status
+ */
+static int max98088_hp_mute_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 reg = max98088_read(codec, M98088_REG_39_LVL_HP_L);
+
+       ucontrol->value.integer.value[0] = (reg & M98088_HP_MUTE) ? 1 : 0;
+
+       return 0;
+}
+
+/*
+ * Mute the receiver
+ */
+static int max98088_rec_mute_set(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 lReg = max98088_read(codec, M98088_REG_3B_LVL_REC_L);
+       u8 rReg = max98088_read(codec, M98088_REG_3C_LVL_REC_R);
+
+       if (MAX98088_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) {
+               lReg |= M98088_REC_MUTE;
+               rReg |= M98088_REC_MUTE;
+       } else {
+               lReg &= ~M98088_REC_MUTE;
+               rReg &= ~M98088_REC_MUTE;
+       }
+
+       max98088_write(codec, M98088_REG_3B_LVL_REC_L, lReg);
+       max98088_write(codec, M98088_REG_3C_LVL_REC_R, rReg);
+
+       return 0;
+}
+
+/*
+ * Get current receiver mute status
+ */
+static int max98088_rec_mute_get(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 reg = max98088_read(codec, M98088_REG_3B_LVL_REC_L);
+
+       ucontrol->value.integer.value[0] = (reg & M98088_REC_MUTE) ? 1 : 0;
+       return 0;
+}
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+       "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+               max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_dai1_fltr[] = {
+       "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+       "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static const char *max98088_dcblk[] =  {"Off", "On"};
+static const struct soc_enum max98088_dcblk_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_20_DAI2_FILTERS, 0, 2, max98088_dcblk),
+};
+
+/* amixer controls (non-dapm) */
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+       /* analog output levels */
+
+       SOC_DOUBLE_R("Headphone volume", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+       SOC_DOUBLE_R("Speaker volume", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+       SOC_DOUBLE_R("Receiver volume", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+       SOC_ENUM_EXT("Headphone mute", max98088_hp_spk_mute_enum,
+               max98088_hp_mute_get, max98088_hp_mute_set),
+       SOC_ENUM_EXT("Speaker mute", max98088_hp_spk_mute_enum,
+               max98088_spk_mute_get, max98088_spk_mute_set),
+       SOC_ENUM_EXT("Receiver mute", max98088_hp_spk_mute_enum,
+               max98088_rec_mute_get, max98088_rec_mute_set),
+
+       /* analog input levels */
+
+       SOC_SINGLE("MIC1 gain", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+       SOC_SINGLE("MIC2 gain", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+       SOC_SINGLE("MIC1 pre", M98088_REG_35_LVL_MIC1, 5, 3, 0),
+       SOC_SINGLE("MIC2 pre", M98088_REG_36_LVL_MIC2, 5, 3, 0),
+
+       SOC_SINGLE("INA gain", M98088_REG_37_LVL_INA, 0, 7, 1),
+       SOC_SINGLE("INB gain", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+       /* ADC input digital gains and volume control */
+
+       SOC_SINGLE("ADCL volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+       SOC_SINGLE("ADCR volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+       SOC_SINGLE("ADCL gain", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+       SOC_SINGLE("ADCR gain", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+       /* equalizer */
+
+       SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+       SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+       /* excursion limiter */
+
+       SOC_ENUM_EXT("EX limiter mode", max98088_ex_mode_enum,
+               max98088_ex_mode_get, max98088_ex_mode_set),
+       SOC_ENUM("EX limiter threshold", max98088_ex_thresh_enum),
+
+       /* voice/music filters */
+
+       SOC_ENUM("DAI1 filter mode", max98088_filter_mode_enum),
+       SOC_ENUM("DAI1 DAC filter", max98088_dai1_dac_filter_enum),
+       SOC_ENUM("DAI1 ADC filter", max98088_dai1_adc_filter_enum),
+       SOC_ENUM("DAI2 DC block", max98088_dcblk_enum),
+
+       /* automatic level control (for both DAI1/DAI2) */
+
+       SOC_SINGLE("ALC switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+       SOC_SINGLE("ALC threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+       SOC_SINGLE("ALC multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+       SOC_SINGLE("ALC release time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+       /* power limiter */
+
+       SOC_SINGLE("PWR limiter threshold", M98088_REG_44_PWRLMT_CFG,
+               4, 15, 0),
+       SOC_SINGLE("PWR limiter weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+       SOC_SINGLE("PWR limiter time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+       SOC_SINGLE("PWR limiter time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+       /* THD distortion limiter */
+
+       SOC_SINGLE("THD limiter thresh", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+       SOC_SINGLE("THD limiter time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* DAPM sub-tables of the main dapm_widgets */
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_receiver_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_hp_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       u16 status;
+
+       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
+
+       /* powering down headphone gracefully */
+       status = max98088_read(codec, M98088_REG_4D_PWR_EN_OUT);
+       if ((status & M98088_HPEN) == M98088_HPEN) {
+               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
+                       (status & ~M98088_HPEN));
+       }
+       schedule_timeout_interruptible(msecs_to_jiffies(20));
+
+       return 0;
+}
+
+/* DAPM widgets top level */
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+       SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+       SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+       SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+       SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+       SND_SOC_DAPM_PGA_E("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+               7, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+       SND_SOC_DAPM_PGA_E("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+               6, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+
+       SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+               5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+               4, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+               3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+               2, 0, NULL, 0),
+
+       SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_left_receiver_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_receiver_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_right_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_MICBIAS("Mic Bias", M98088_REG_4C_PWR_EN_IN, 3, 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("RECL"),
+       SND_SOC_DAPM_OUTPUT("RECR"),
+
+       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"),
+};
+
+/* DAPM AUDIO_MAP: */
+static const struct snd_soc_dapm_route audio_map[] = {
+       /* Left headphone output mixer */
+       {"Left HP Mixer", "Left DAC1", "DACL1"},
+       {"Left HP Mixer", "Left DAC2", "DACL2"},
+       {"Left HP Mixer", "Right DAC1", "DACR1"},
+       {"Left HP Mixer", "Right DAC2", "DACR2"},
+       {"Left HP Mixer", "MIC1", "Mic Bias"},
+       {"Left HP Mixer", "MIC2", "Mic Bias"},
+       {"Left HP Mixer", "INA1", "INA1 Input"},
+       {"Left HP Mixer", "INA2", "INA2 Input"},
+       {"Left HP Mixer", "INB1", "INB1 Input"},
+       {"Left HP Mixer", "INB2", "INB2 Input"},
+
+       /* Right headphone output mixer */
+       {"Right HP Mixer", "Left DAC1", "DACL1"},
+       {"Right HP Mixer", "Left DAC2", "DACL2" },
+       {"Right HP Mixer", "Right DAC1", "DACR1"},
+       {"Right HP Mixer", "Right DAC2", "DACR2"},
+       {"Right HP Mixer", "MIC1", "Mic Bias"},
+       {"Right HP Mixer", "MIC2", "Mic Bias"},
+       {"Right HP Mixer", "INA1", "INA1 Input"},
+       {"Right HP Mixer", "INA2", "INA2 Input"},
+       {"Right HP Mixer", "INB1", "INB1 Input"},
+       {"Right HP Mixer", "INB2", "INB2 Input"},
+
+       /* Left speaker output mixer */
+       {"Left SPK Mixer", "Left DAC1", "DACL1"},
+       {"Left SPK Mixer", "Left DAC2", "DACL2"},
+       {"Left SPK Mixer", "Right DAC1", "DACR1"},
+       {"Left SPK Mixer", "Right DAC2", "DACR2"},
+       {"Left SPK Mixer", "MIC1", "Mic Bias"},
+       {"Left SPK Mixer", "MIC2", "Mic Bias"},
+       {"Left SPK Mixer", "INA1", "INA1 Input"},
+       {"Left SPK Mixer", "INA2", "INA2 Input"},
+       {"Left SPK Mixer", "INB1", "INB1 Input"},
+       {"Left SPK Mixer", "INB2", "INB2 Input"},
+
+       /* Right speaker output mixer */
+       {"Right SPK Mixer", "Left DAC1", "DACL1"},
+       {"Right SPK Mixer", "Left DAC2", "DACL2"},
+       {"Right SPK Mixer", "Right DAC1", "DACR1"},
+       {"Right SPK Mixer", "Right DAC2", "DACR2"},
+       {"Right SPK Mixer", "MIC1", "Mic Bias"},
+       {"Right SPK Mixer", "MIC2", "Mic Bias"},
+       {"Right SPK Mixer", "INA1", "INA1 Input"},
+       {"Right SPK Mixer", "INA2", "INA2 Input"},
+       {"Right SPK Mixer", "INB1", "INB1 Input"},
+       {"Right SPK Mixer", "INB2", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Left REC Mixer", "Left DAC1", "DACL1"},
+       {"Left REC Mixer", "Left DAC2", "DACL2"},
+       {"Left REC Mixer", "Right DAC1", "DACR1"},
+       {"Left REC Mixer", "Right DAC2", "DACR2"},
+       {"Left REC Mixer", "MIC1", "Mic Bias"},
+       {"Left REC Mixer", "MIC2", "Mic Bias"},
+       {"Left REC Mixer", "INA1", "INA1 Input"},
+       {"Left REC Mixer", "INA2", "INA2 Input"},
+       {"Left REC Mixer", "INB1", "INB1 Input"},
+       {"Left REC Mixer", "INB2", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Right REC Mixer", "Left DAC1", "DACL1"},
+       {"Right REC Mixer", "Left DAC2", "DACL2"},
+       {"Right REC Mixer", "Right DAC1", "DACR1"},
+       {"Right REC Mixer", "Right DAC2", "DACR2"},
+       {"Right REC Mixer", "MIC1", "Mic Bias"},
+       {"Right REC Mixer", "MIC2", "Mic Bias"},
+       {"Right REC Mixer", "INA1", "INA1 Input"},
+       {"Right REC Mixer", "INA2", "INA2 Input"},
+       {"Right REC Mixer", "INB1", "INB1 Input"},
+       {"Right REC Mixer", "INB2", "INB2 Input"},
+
+       {"HP Left Out", NULL, "Left HP Mixer"},
+       {"HP Right Out", NULL, "Right HP Mixer"},
+       {"SPK Left Out", NULL, "Left SPK Mixer"},
+       {"SPK Right Out", NULL, "Right SPK Mixer"},
+       {"REC Left Out", NULL, "Left REC Mixer"},
+       {"REC Right Out", NULL, "Right REC Mixer"},
+
+       {"HPL", NULL, "HP Left Out"},
+       {"HPR", NULL, "HP Right Out"},
+       {"SPKL", NULL, "SPK Left Out"},
+       {"SPKR", NULL, "SPK Right Out"},
+       {"RECL", NULL, "REC Left Out"},
+       {"RECR", NULL, "REC Right 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"},
+
+       /* 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"},
+
+       {"Mic Bias", NULL, "MIC1"},
+       {"Mic Bias", NULL, "MIC2"},
+};
+
+/*
+ * Add widgets
+ */
+static int max98088_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, max98088_dapm_widgets,
+                                 ARRAY_SIZE(max98088_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;
+}
+
+/*
+ * Setup DAI1 format
+ */
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       u8 regval;
+
+       cdata = &max98088->dai[0];
+
+       if (fmt != cdata->fmt) {
+               cdata->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 */
+                       /* MAS: slave */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               M98088_DAI_MAS, 0);
+                       /* slave mode PLL */
+                       max98088_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                               0x80);
+                       max98088_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM: /* codec clk, frm master */
+                       /* MAS: master */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               0, M98088_DAI_MAS);
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slv, frm mas */
+               case SND_SOC_DAIFMT_CBM_CFS: /* codec clk mas, frm slv */
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;;
+               }
+
+               /* I2S or TDM */
+               if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S) {
+                       /* TDM: I2S */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               M98088_DAI_TDM, 0);
+               } else {
+                       /* TDM: PCM/TDM */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               0, M98088_DAI_TDM);
+               }
+
+               /* DAI hardware signal inversions */
+               switch (fmt & SND_SOC_DAIFMT_NB_NF) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       /* BCI: normal bclk (rise) */
+                       /* WCI: normal frame */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               M98088_DAI_BCI|M98088_DAI_WCI, 0);
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       /* BCI: normal bclk (rise) */
+                       /* WCI: invert frame */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               M98088_DAI_BCI, M98088_DAI_WCI);
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       /* BCI: invert bclk (fall) */
+                       /* WCI: normal frame */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               M98088_DAI_WCI, M98088_DAI_BCI);
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       /* BCI: invert bclk (fall) */
+                       /* WCI: invert frame */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               0, M98088_DAI_BCI|M98088_DAI_WCI);
+                       break;
+               }
+
+               regval = (1<<0); /* BSEL : 64*LRCLK (for master mode only) */
+               if (max98088->digmic_mode)
+                       regval |= (1<<6); /* OSR : oversample ratio */
+               max98088_write(codec, M98088_REG_15_DAI1_CLOCK, regval);
+
+               max98088_write(codec, M98088_REG_16_DAI1_IOCFG,
+                       (1<<6) |  /* SEL : map DAI1 to S1 */
+                       (0<<5) |  /* LTEN : ADC->DAC loop-through enable */
+                       (0<<4) |  /* LBEN : loopback (0=disable, 1=enable) */
+                       (0<<3) |  /* DMONO : DAC SDIN (0=stereo, 1=mono) */
+                       (0<<2) |  /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive) */
+                       (1<<1) |  /* SDOEN : serial data out ENABLE */
+                       (1<<0));  /* SDIEN : serial data in ENABLE */
+
+               max98088_write(codec, M98088_REG_17_DAI1_TDM,
+                       (0<<6) |  /* SLOTL : L in 1st slot */
+                       (1<<4) |  /* SLOTR : R in 2nd slot */
+                       (0<<0));  /* SLOTDLY : no delay */
+       }
+
+       return 0;
+}
+
+/*
+ * Setup DAI2 format
+ */
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (fmt != cdata->fmt) {
+               cdata->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 */
+                       /* MAS: slave */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               M98088_DAI_MAS, 0);
+                       /* slave mode PLL */
+                       max98088_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                               0x80);
+                       max98088_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */
+                       /* MAS: master */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               0, M98088_DAI_MAS);
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slv & frm mas */
+               case SND_SOC_DAIFMT_CBM_CFS: /* codec clk mas & frm slv */
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;;
+               }
+
+               /* I2S or TDM */
+               if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S) {
+                       /* TDM: I2S */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               M98088_DAI_TDM, 0);
+               } else {
+                       /* TDM: PCM/TDM */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               0, M98088_DAI_TDM);
+               }
+
+               /* DAI hardware signal inversions */
+               switch (fmt & SND_SOC_DAIFMT_NB_NF) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       /* BCI: normal bclk (rise) */
+                       /* WCI: normal frame */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               M98088_DAI_BCI|M98088_DAI_WCI, 0);
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       /* BCI: normal bclk (rise) */
+                       /* WCI: invert frame */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               M98088_DAI_BCI, M98088_DAI_WCI);
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       /* BCI: invert bclk (fall) */
+                       /* WCI: normal frame */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               M98088_DAI_WCI, M98088_DAI_BCI);
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       /* BCI: invert bclk (fall) */
+                       /* WCI: invert frame */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               0, M98088_DAI_BCI|M98088_DAI_WCI);
+                       break;
+               }
+
+               max98088_write(codec, M98088_REG_1D_DAI2_CLOCK,
+                       (1<<0)); /* BSEL: 64*LRCLK (for master mode only) */
+
+               max98088_write(codec, M98088_REG_1E_DAI2_IOCFG,
+                       (2<<6) |  /* SEL : map DAI2 to S2 */
+                       (0<<4) |  /* LBEN : loopback (0=disable) */
+                       (0<<3) |  /* DMONO : DAC SDIN input */
+                       (0<<2) |  /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive) */
+                       (1<<1) |  /* SDOEN : serial data out ENABLE */
+                       (1<<0));  /* SDIEN : serial data in ENABLE */
+
+               max98088_write(codec, M98088_REG_1F_DAI2_TDM,
+                       (0<<6) |  /* SLOTL : L in 1st slot */
+                       (1<<4) |  /* SLOTR : R in 2nd slot */
+                       (0<<0));  /* SLOTDLY : no delay */
+       }
+
+       return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+                                  int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+
+       if (freq != max98088->sysclk) {
+               max98088->sysclk = freq; /* remember current sysclk */
+
+               /* setup clocks for slave mode, and using the PLL
+                * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+                *         0x02 (when master clk is 20MHz to 30MHz)..
+                */
+               if ((freq >= 10000000) && (freq < 20000000)) {
+                       max98088_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+               } else if ((freq >= 20000000) && (freq < 30000000)) {
+                       max98088_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+               } else {
+                       dev_err(codec->dev, "Invalid master clock frequency\n");
+                       return -EINVAL;
+               }
+
+               /* If codec is currently running, toggle reset */
+               if (max98088_read(codec, M98088_REG_51_PWR_SYS)
+                       & M98088_SHDNRUN) {
+                       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                               M98088_SHDNRUN, 0);
+                       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                               0, M98088_SHDNRUN);
+               }
+       }
+
+       dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+       max98088->sysclk = freq;
+       return 0;
+}
+
+
+struct rate_table_struct {
+       u32 rate;
+       u8  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 and sample rate */
+static int max98088_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 max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 reg11val;
+       u16 ni;
+
+       cdata = &max98088->dai[0];
+
+       rate = params_rate(params);
+
+       /* data 16/24 bit width */
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               /* WS: 16bit */
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               /* WS: 24bit */
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       0, M98088_DAI_WS);
+               break;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
+               reg11val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
+               max98088_write(codec, M98088_REG_11_DAI1_CLKMODE, reg11val);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (max98088_read(codec, M98088_REG_14_DAI1_FORMAT)
+               & M98088_DAI_MAS) {
+               BUG_ON(max98088->sysclk == 0);
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               max98088_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               max98088_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       (1<<3), 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       0, (1<<3));
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+/* Setup hw params and sample rate */
+static int max98088_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 max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 reg19val;
+       u16 ni;
+
+       cdata = &max98088->dai[1];
+
+       rate = params_rate(params);
+
+       /* data 16/24 bit width */
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               /* WS: 16bit */
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               /* WS: 24bit */
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       0, M98088_DAI_WS);
+               break;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI2 SR2 value for the DSP; FREQ1:0=anyclock */
+               reg19val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
+               max98088_write(codec, M98088_REG_19_DAI2_CLKMODE, reg19val);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (max98088_read(codec, M98088_REG_1C_DAI2_FORMAT)
+               & M98088_DAI_MAS) {
+               BUG_ON(max98088->sysclk == 0);
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               max98088_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               max98088_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       (1<<3), 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       0, (1<<3));
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       int i;
+
+       /* write back cached values if they're writeable and
+        * different from the hardware default.
+        */
+       for (i = 1; i < ARRAY_SIZE(max98088->reg_cache); i++) {
+               if (!max98088_access[i].writable)
+                       continue;
+
+               if (max98088->reg_cache[i] == max98088_reg[i])
+                       continue;
+
+               snd_soc_write(codec, i, max98088->reg_cache[i]);
+       }
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+                                 enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN);
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN);
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               max98088_sync_cache(codec);
+               max98088_write(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN|M98088_PWRSV);
+               break;
+       case SND_SOC_BIAS_OFF:
+               max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai1_set_fmt,
+       .hw_params = max98088_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai2_set_fmt,
+       .hw_params = max98088_dai2_hw_params,
+};
+
+struct snd_soc_dai max98088_dai[] = {
+{
+       .name = "HiFi",
+       .playback = {
+               .stream_name = "HiFi Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .capture = {
+               .stream_name = "HiFi Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+        .ops = &max98088_dai1_ops,
+},
+{
+       .name = "Aux",
+       .playback = {
+               .stream_name = "Aux Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .ops = &max98088_dai2_ops,
+}
+};
+EXPORT_SYMBOL_GPL(max98088_dai);
+
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+       sel = cdata->eq_sel;
+
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               if (strcmp(pdata->eq1_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq1_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq1_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq1_cfg[best].name,
+               pdata->eq1_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+       coef_set = &pdata->eq1_cfg[sel];
+
+       m98088_eq_band(codec, 0, 0, coef_set->band1);
+       m98088_eq_band(codec, 0, 1, coef_set->band2);
+       m98088_eq_band(codec, 0, 2, coef_set->band3);
+       m98088_eq_band(codec, 0, 3, coef_set->band4);
+       m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->eq_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               if (strcmp(pdata->eq2_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq2_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq2_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq2_cfg[best].name,
+               pdata->eq2_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+       coef_set = &pdata->eq2_cfg[sel];
+
+       m98088_eq_band(codec, 1, 0, coef_set->band1);
+       m98088_eq_band(codec, 1, 1, coef_set->band2);
+       m98088_eq_band(codec, 1, 2, coef_set->band3);
+       m98088_eq_band(codec, 1, 3, coef_set->band4);
+       m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+               save);
+}
+
+
+static int max98088_put_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[0];
+
+       if (sel >= pdata->eq1_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq1(codec);
+       return 0;
+}
+
+static int max98088_put_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[1];
+
+       if (sel >= pdata->eq2_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq2(codec);
+       return 0;
+}
+
+static int max98088_get_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static int max98088_get_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static void max98088_handle_eq1_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq1control =
+               SOC_ENUM_EXT("EQ1 Mode",
+                       max98088->dai[0].eq_enum,
+                       max98088_get_eq1_enum,
+                       max98088_put_eq1_enum);
+
+       cdata = &max98088->dai[0];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq1_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               /* ...and remember the new version. */
+               cdata->eq_texts[i] = pdata->eq1_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ1 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       BUG_ON(&max98088->codec == 0);
+       BUG_ON((max98088->codec).card == 0);
+
+       ret = snd_soc_add_controls(&max98088->codec, &eq1control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_eq2_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq2control =
+               SOC_ENUM_EXT("EQ2 Mode",
+                       max98088->dai[1].eq_enum,
+                       max98088_get_eq2_enum,
+                       max98088_put_eq2_enum);
+
+       cdata = &max98088->dai[1];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq2_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               cdata->eq_texts[i] = pdata->eq2_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ2 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       ret = snd_soc_add_controls(&max98088->codec, &eq2control, 1);
+       if (ret != 0)
+               printk(KERN_ERR "Failed to add EQ control: %d\n", ret);
+}
+
+
+static void max98088_setup_ex1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       struct max98088_excursion_cfg *coef_set;
+       int best, best_val, i, sel, fs;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !cdata->ex_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->ex_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->ex1_cfgcnt; i++) {
+               if (strcmp(pdata->ex1_cfg[i].name,
+                          cdata->ex_texts[sel]) == 0 &&
+                   abs(pdata->ex1_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->ex1_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->ex1_cfg[best].name,
+               pdata->ex1_cfg[best].rate, fs);
+
+       coef_set = &pdata->ex1_cfg[sel];
+
+       max98088_ex_resp_control(codec, M98088_REG_B6_DAI1_BIQUAD_BASE,
+               coef_set->resp);
+}
+
+static int max98088_put_ex1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[0];
+
+       if (sel >= pdata->ex1_cfgcnt)
+               return -EINVAL;
+
+       cdata->ex_sel = sel;
+       max98088_setup_ex1(codec);
+       return 0;
+}
+
+
+static int max98088_get_ex1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+       ucontrol->value.enumerated.item[0] = cdata->ex_sel;
+
+       return 0;
+}
+
+static void max98088_handle_ex1_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new ex1control =
+               SOC_ENUM_EXT("EX1 Limiter Config",
+                            max98088->dai[0].ex_enum,
+                            max98088_get_ex1_enum,
+                            max98088_put_ex1_enum);
+
+       cdata = &max98088->dai[0];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->ex_textcnt = 0;
+       for (i = 0; i < pdata->ex1_cfgcnt; i++) {
+               for (j = 0; j < cdata->ex_textcnt; j++) {
+                       if (strcmp(pdata->ex1_cfg[i].name,
+                                  cdata->ex_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->ex_textcnt)
+                       continue;
+
+               /* ...and remember the new version. */
+               cdata->ex_texts[i] = pdata->ex1_cfg[i].name;
+               cdata->ex_textcnt++;
+
+               if (cdata->ex_textcnt >= EX_CFG_MAX) {
+                       dev_err(codec->dev, "Too many limiter configs\n");
+                       cdata->ex_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EX1 configurations\n",
+               cdata->ex_textcnt);
+
+       /* now point the soc_enum to .texts array */
+       cdata->ex_enum.texts = cdata->ex_texts;
+       cdata->ex_enum.max = cdata->ex_textcnt;
+       ret = snd_soc_add_controls(&max98088->codec, &ex1control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add limiter control\n");
+}
+
+
+static void max98088_setup_ex2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       struct max98088_excursion_cfg *coef_set;
+       int best, best_val, i, sel, fs;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !cdata->ex_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->ex_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->ex2_cfgcnt; i++) {
+               if (strcmp(pdata->ex2_cfg[i].name,
+                          cdata->ex_texts[sel]) == 0 &&
+                   abs(pdata->ex2_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->ex2_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->ex2_cfg[best].name,
+               pdata->ex2_cfg[best].rate, fs);
+
+       coef_set = &pdata->ex2_cfg[sel];
+
+       max98088_ex_resp_control(codec, M98088_REG_C0_DAI2_BIQUAD_BASE,
+               coef_set->resp);
+}
+
+static int max98088_put_ex2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[1];
+
+       if (sel >= pdata->ex2_cfgcnt)
+               return -EINVAL;
+
+       cdata->ex_sel = sel;
+       max98088_setup_ex2(codec);
+       return 0;
+}
+
+static int max98088_get_ex2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+       ucontrol->value.enumerated.item[0] = cdata->ex_sel;
+
+       return 0;
+}
+
+static void max98088_handle_ex2_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new ex2control =
+               SOC_ENUM_EXT("EX2 Limiter Config",
+                            max98088->dai[1].ex_enum,
+                            max98088_get_ex2_enum,
+                            max98088_put_ex2_enum);
+
+       cdata = &max98088->dai[1];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->ex_textcnt = 0;
+       for (i = 0; i < pdata->ex2_cfgcnt; i++) {
+               for (j = 0; j < cdata->ex_textcnt; j++) {
+                       if (strcmp(pdata->ex2_cfg[i].name,
+                                  cdata->ex_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->ex_textcnt)
+                       continue;
+
+               /* ...and remember the new version. */
+               cdata->ex_texts[i] = pdata->ex2_cfg[i].name;
+               cdata->ex_textcnt++;
+
+               if (cdata->ex_textcnt >= EX_CFG_MAX) {
+                       dev_err(codec->dev, "Too many limiter configs\n");
+                       cdata->ex_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EX2 configurations\n",
+               cdata->ex_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->ex_enum.texts = cdata->ex_texts;
+       cdata->ex_enum.max = cdata->ex_textcnt;
+
+       ret = snd_soc_add_controls(&max98088->codec, &ex2control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add limiter control\n");
+}
+
+static void max98088_handle_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       u8 regval;
+
+       if (!pdata)
+               return;
+
+       /* Configure digital mic / external mic */
+       /* Digital mic needs REG_15 OSR1=1 */
+       regval = ((0<<6) | /* digital mic clock freq = PCLK/8 */
+                 (0<<5) | /* DIGMICL enable */
+                 (0<<4) | /* DIGMICR enable */
+                 (0<<0)); /* external mic enable */
+
+       if (pdata->digmic_left_enable)
+               regval |= (1<<5); /* digital left mic enabled */
+
+       if (pdata->digmic_right_enable)
+               regval |= (1<<4); /* digital right mic enabled */
+
+       max98088->digmic_mode = (regval ? 1 : 0);
+
+       if (pdata->extmic_mode < 3)
+               regval |= (pdata->extmic_mode << 0); /* external mic enabled */
+
+       max98088_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+       /* configure external mic to mic1 bypass, mic1 to mic2 bypass */
+       regval = 0;
+
+       if (pdata->ina_to_mic1_bypass)
+               regval |= (1<<7);
+
+       if (pdata->mic1_to_mic2_bypass)
+               regval |= (1<<4);
+
+       max98088_write(codec, M98088_REG_4A_CFG_BYPASS, regval);
+
+       /* configure receiver output */
+       regval = ((pdata->receiver_mode) ? (1<<7) : 0);
+       snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+               (1<<7), regval);
+
+       /* configure equalizers */
+       if (pdata->eq1_cfgcnt)
+               max98088_handle_eq1_pdata(max98088);
+
+       if (pdata->eq2_cfgcnt)
+               max98088_handle_eq2_pdata(max98088);
+
+       /* configure excursion limiters */
+       if (pdata->ex1_cfgcnt)
+               max98088_handle_ex1_pdata(max98088);
+
+       if (pdata->ex2_cfgcnt)
+               max98088_handle_ex2_pdata(max98088);
+}
+
+static int max98088_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;
+
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static int max98088_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+       int i;
+       u8 *cache = codec->reg_cache;
+
+       /* toggle reset the shutdown bit of the codec hardware */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       /* Sync reg_cache with the hardware */
+       for (i = 0; i < M98088_REG_CNT; i++) {
+               if (i == M98088_REG_51_PWR_SYS)
+                       continue;
+
+               if (!max98088_access[i].writable)
+                       continue;
+
+               max98088_hw_write(codec, i, cache[i]);
+       }
+
+       /* now enter into the resume mode bias level */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+}
+
+/*
+ * Make sure that a max98088 is attached to the I2C bus.
+ */
+static int max98088_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+       struct max98088_priv *max98088;
+       int ret = 0;
+
+       dev_printk(KERN_INFO, &pdev->dev, "MAX98088 Audio CODEC\n");
+
+       if (!max98088_codec) {
+               dev_err(&pdev->dev, "Codec device is not registered\n");
+               return -EINVAL;
+       }
+
+       socdev->card->codec = max98088_codec;
+       codec = max98088_codec;
+       max98088 = codec->private_data;
+
+       /* register pcms */
+       ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to create pcms: %d\n", ret);
+               goto pcm_err;
+       }
+
+       snd_soc_add_controls(codec, max98088_snd_controls,
+                            ARRAY_SIZE(max98088_snd_controls));
+
+       /* install additional amixer controls: EQ and excursion limiter */
+       if (max98088->pdata)
+               max98088_handle_pdata(max98088);
+       else
+               dev_err(&pdev->dev, "No platform data\n");
+
+       max98088_add_widgets(codec);
+
+       ret = snd_soc_init_card(socdev);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "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;
+}
+
+static int max98088_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)
+               max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+
+       return 0;
+}
+
+/* expose to machine driver */
+struct snd_soc_codec_device soc_codec_dev_max98088 = {
+       .probe   = max98088_probe,
+       .remove  = max98088_remove,
+       .suspend = max98088_suspend,
+       .resume  = max98088_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_max98088);
+
+
+static int max98088_register(struct max98088_priv *max98088,
+                            enum snd_soc_control_type control)
+
+{
+       int ret, i, version;
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_cdata *cdata;
+
+       if (max98088_codec) {
+               dev_err(codec->dev, "Another MAX98088 is registered\n");
+               ret = -EINVAL;
+               goto err;
+       }
+
+       mutex_init(&codec->mutex);
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       codec->name = "MAX98088";
+       codec->owner = THIS_MODULE;
+       codec->read = max98088_read;
+       codec->write = max98088_write;
+
+       /* setup DAPM event bias level and handler function */
+       codec->bias_level = SND_SOC_BIAS_STANDBY;
+       codec->set_bias_level = max98088_set_bias_level;
+
+       codec->dai = max98088_dai;
+       codec->num_dai = ARRAY_SIZE(max98088_dai);
+       codec->private_data = max98088;
+
+       codec->reg_cache_size = M98088_REG_CNT;
+       codec->reg_cache = &max98088->reg_cache;
+       codec->volatile_register = max98088_volatile_register;
+
+       memcpy(codec->reg_cache, max98088_reg, sizeof(max98088_reg));
+
+       version = max98088_read(codec, M98088_REG_FF_REV_ID);
+       dev_dbg(codec->dev, "MAX98088 revision 0x%02X\n", version);
+
+       /* initalize private data */
+
+       max98088->sysclk = (unsigned)-1;
+
+       cdata = &max98088->dai[0];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+       cdata->ex_textcnt = 0;
+       cdata->ex_sel = 0;
+
+       cdata = &max98088->dai[1];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+       cdata->ex_textcnt = 0;
+       cdata->ex_sel = 0;
+
+       max98088_write(codec, M98088_REG_14_DAI1_FORMAT, M98088_DAI_DLY);
+       max98088_write(codec, M98088_REG_1C_DAI2_FORMAT, M98088_DAI_DLY);
+
+       max98088->power_state = 0; /* INA INB power enable state */
+       max98088->ex_mode = 0; /* excursion limiter mode */
+       max98088->digmic_mode = 0; /* 0=normal, 1=digital mic used */
+
+       /* disable device via dapm interface */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       /* initialize registers cache to hardware default */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       max98088_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+       max98088_write(codec, M98088_REG_22_MIX_DAC,
+               M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+               M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+       max98088_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+       max98088_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+       /* power on device */
+       max98088_codec = codec;
+       for (i = 0; i < codec->num_dai; i++)
+               max98088_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(&max98088_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(max98088);
+
+       return ret;
+}
+
+static void max98088_unregister(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       snd_soc_unregister_dais(max98088_dai, codec->num_dai);
+       snd_soc_unregister_codec(codec);
+       kfree(max98088);
+       max98088_codec = NULL;
+}
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+                            const struct i2c_device_id *id)
+{
+       struct snd_soc_codec *codec;
+       struct max98088_priv *max98088;
+
+       max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+       if (max98088 == NULL)
+               return -ENOMEM;
+
+       /* codec structure is inside the private data */
+       codec = &(max98088->codec);
+
+       codec->private_data = max98088;
+
+       codec->hw_write = (hw_write_t) i2c_master_send;
+       i2c_set_clientdata(i2c, max98088);
+       codec->control_data = i2c;
+
+       max98088->pdata = i2c->dev.platform_data;
+
+       codec->dev = &i2c->dev;
+
+       return max98088_register(max98088, SND_SOC_I2C);
+}
+
+static int max98088_i2c_remove(struct i2c_client *client)
+{
+       struct max98088_priv *max98088 = i2c_get_clientdata(client);
+       max98088_unregister(max98088);
+       return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+       { "max98088", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+
+static struct i2c_driver max98088_i2c_driver = {
+       .driver = {
+               .name = "MAX98088 I2C Codec",
+               .owner = THIS_MODULE,
+       },
+       .probe  = max98088_i2c_probe,
+       .remove = __devexit_p(max98088_i2c_remove),
+       .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&max98088_i2c_driver);
+       if (ret)
+               pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+       return ret;
+}
+
+static void __exit max98088_exit(void)
+{
+       i2c_del_driver(&max98088_i2c_driver);
+}
+
+module_init(max98088_init);
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang <peter.hsiang@maxim-ic.com>");
+MODULE_AUTHOR("Jesse Marroquin <jesse.marroquin@maxim-ic.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h
new file mode 100644
index 0000000..f7ef4c2
--- /dev/null
+++ b/sound/soc/codecs/max98088.h
@@ -0,0 +1,161 @@
+/*
+ * max98088.h -- MAX98088 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 _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS            0x00
+#define M98088_REG_01_MIC_STATUS            0x01
+#define M98088_REG_02_JACK_STAUS            0x02
+#define M98088_REG_03_BATTERY_VOLTAGE       0x03
+#define M98088_REG_0F_IRQ_ENABLE            0x0F
+#define M98088_REG_10_SYS_CLK               0x10
+#define M98088_REG_11_DAI1_CLKMODE          0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI        0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO        0x13
+#define M98088_REG_14_DAI1_FORMAT           0x14
+#define M98088_REG_15_DAI1_CLOCK            0x15
+#define M98088_REG_16_DAI1_IOCFG            0x16
+#define M98088_REG_17_DAI1_TDM              0x17
+#define M98088_REG_18_DAI1_FILTERS          0x18
+#define M98088_REG_19_DAI2_CLKMODE          0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI        0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO        0x1B
+#define M98088_REG_1C_DAI2_FORMAT           0x1C
+#define M98088_REG_1D_DAI2_CLOCK            0x1D
+#define M98088_REG_1E_DAI2_IOCFG            0x1E
+#define M98088_REG_1F_DAI2_TDM              0x1F
+#define M98088_REG_20_DAI2_FILTERS          0x20
+#define M98088_REG_21_SRC                   0x21
+#define M98088_REG_22_MIX_DAC               0x22
+#define M98088_REG_23_MIX_ADC_LEFT          0x23
+#define M98088_REG_24_MIX_ADC_RIGHT         0x24
+#define M98088_REG_25_MIX_HP_LEFT           0x25
+#define M98088_REG_26_MIX_HP_RIGHT          0x26
+#define M98088_REG_27_MIX_HP_CNTL           0x27
+#define M98088_REG_28_MIX_REC_LEFT          0x28
+#define M98088_REG_29_MIX_REC_RIGHT         0x29
+#define M98088_REG_2A_MIC_REC_CNTL          0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT          0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT         0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL          0x2D
+#define M98088_REG_2E_LVL_SIDETONE          0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY         0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ      0x30
+#define M98088_REG_31_LVL_DAI2_PLAY         0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ      0x32
+#define M98088_REG_33_LVL_ADC_L             0x33
+#define M98088_REG_34_LVL_ADC_R             0x34
+#define M98088_REG_35_LVL_MIC1              0x35
+#define M98088_REG_36_LVL_MIC2              0x36
+#define M98088_REG_37_LVL_INA               0x37
+#define M98088_REG_38_LVL_INB               0x38
+#define M98088_REG_39_LVL_HP_L              0x39
+#define M98088_REG_3A_LVL_HP_R              0x3A
+#define M98088_REG_3B_LVL_REC_L             0x3B
+#define M98088_REG_3C_LVL_REC_R             0x3C
+#define M98088_REG_3D_LVL_SPK_L             0x3D
+#define M98088_REG_3E_LVL_SPK_R             0x3E
+#define M98088_REG_3F_MICAGC_CFG            0x3F
+#define M98088_REG_40_MICAGC_THRESH         0x40
+#define M98088_REG_41_SPKDHP                0x41
+#define M98088_REG_42_SPKDHP_THRESH         0x42
+#define M98088_REG_43_SPKALC_COMP           0x43
+#define M98088_REG_44_PWRLMT_CFG            0x44
+#define M98088_REG_45_PWRLMT_TIME           0x45
+#define M98088_REG_46_THDLMT_CFG            0x46
+#define M98088_REG_47_CFG_AUDIO_IN          0x47
+#define M98088_REG_48_CFG_MIC               0x48
+#define M98088_REG_49_CFG_LEVEL             0x49
+#define M98088_REG_4A_CFG_BYPASS            0x4A
+#define M98088_REG_4B_CFG_JACKDET           0x4B
+#define M98088_REG_4C_PWR_EN_IN             0x4C
+#define M98088_REG_4D_PWR_EN_OUT            0x4D
+#define M98088_REG_4E_BIAS_CNTL             0x4E
+#define M98088_REG_4F_DAC_BIAS1             0x4F
+#define M98088_REG_50_DAC_BIAS2             0x50
+#define M98088_REG_51_PWR_SYS               0x51
+#define M98088_REG_52_DAI1_EQ_BASE          0x52
+#define M98088_REG_84_DAI2_EQ_BASE          0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE      0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE      0xC0
+#define M98088_REG_FF_REV_ID                0xFF
+
+#define M98088_REG_CNT                      (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+       #define M98088_DAI_MAS                  (1<<7)
+       #define M98088_DAI_WCI                  (1<<6)
+       #define M98088_DAI_BCI                  (1<<5)
+       #define M98088_DAI_DLY                  (1<<4)
+       #define M98088_DAI_TDM                  (1<<2)
+       #define M98088_DAI_FSW                  (1<<1)
+       #define M98088_DAI_WS                   (1<<0)
+
+/* M98088_REG_22_MIX_DAC */
+       #define M98088_DAI1L_TO_DACL            (1<<7)
+       #define M98088_DAI1R_TO_DACL            (1<<6)
+       #define M98088_DAI2L_TO_DACL            (1<<5)
+       #define M98088_DAI2R_TO_DACL            (1<<4)
+       #define M98088_DAI1L_TO_DACR            (1<<3)
+       #define M98088_DAI1R_TO_DACR            (1<<2)
+       #define M98088_DAI2L_TO_DACR            (1<<1)
+       #define M98088_DAI2R_TO_DACR            (1<<0)
+
+/* M98088_REG_3A_LVL_HEADPHONE_R */
+       #define M98088_HP_MUTE                  (1<<7)
+
+/* M98088_REG_3C_LVL_RECEIVER_R */
+       #define M98088_REC_MUTE                 (1<<7)
+
+/* M98088_REG_3E_LVL_SPEAKER_R */
+       #define M98088_SP_MUTE                  (1<<7)
+
+/* M98088_REG_49_CFG_LEVEL */
+       #define M98088_VSEN                     (1<<6)
+       #define M98088_ZDEN                     (1<<5)
+       #define M98088_EQ2EN                    (1<<1)
+       #define M98088_EQ1EN                    (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+       #define M98088_HPLEN                    (1<<7)
+       #define M98088_HPREN                    (1<<6)
+       #define M98088_HPEN                     ((1<<7)|(1<<6))
+       #define M98088_SPLEN                    (1<<5)
+       #define M98088_SPREN                    (1<<4)
+       #define M98088_RECEN                    (1<<3)
+       #define M98088_DALEN                    (1<<1)
+       #define M98088_DAREN                    (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+       #define M98088_SHDNRUN                  (1<<7)
+       #define M98088_PERFMODE                 (1<<3)
+       #define M98088_HPPLYBACK                (1<<2)
+       #define M98088_PWRSV8K                  (1<<1)
+       #define M98088_PWRSV                    (1<<0)
+
+#define M98088_COEFS_PER_BAND               5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+struct max98088_setup_data {
+       unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai max98088_dai[];
+extern struct snd_soc_codec_device soc_codec_dev_max98088;
+
+#endif
--
1.6.3.3


^ permalink raw reply related	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-08-31 21:08 [PATCH] ASoC: Add max98088 CODEC driver Peter Hsiang
@ 2010-09-01 11:14   ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-01 11:14 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Jesse Marroquin, Liam Girdwood,
	Peter Ujfalusi, Joonyoung Shim, alsa-devel, linux-kernel

On Tue, Aug 31, 2010 at 02:08:30PM -0700, Peter Hsiang wrote:
> This patch adds the MAX98088 CODEC driver.

The major issue with this is that the driver needs to be updated to
current ASoC versions - you should always be working against the trees
that are in linux-next for new drivers.  For ASoC you should be looking
at:

  git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git

> Signed-off-by: Peter Hsiang <peter.hsiang at maxim-ic.com>

Please write your e-mail address properly - it'll end up in the git logs
anyway.  It'd also be nice to credit your sources when you've taken code
from other drivers :)

> +#define EQ_CFG_MAX 32

> +/* Speaker excursion limiter filter response configurations */
> +#define EX_CFG_MAX 32

Multiple definitions of this.

> +       /* Set receiver_mode to:
> +        * 0 = amplifier output, or
> +        * 1 = LINE level output
> +        */
> +       unsigned int receiver_mode;

Better to use bools or bitfields.

> +       /* Bypass option for INA to MIC1 connection
> +        * 0 = normal setting
> +        * 1 = bypass enabled
> +        */
> +       unsigned int ina_to_mic1_bypass;
> +
> +       /* Bypass option for MIC1 to MIC2 connection

What are these - they sound like something I'd expect to be configurable
at runtime?

> @@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS
>         select SND_SOC_DA7210 if I2C
>         select SND_SOC_JZ4740 if SOC_JZ4740
>         select SND_SOC_MAX9877 if I2C
> +       select SND_SOC_MAX98088 if I2C

The sorting should be lexical.

> +/*
> + * Read the MAX98088 I2C register space
> + * Note: this driver source code is backward compatible to kernel
> + * version 2.6.32.
> + */
> +static unsigned int max98088_read(struct snd_soc_codec *codec,
> +                                 unsigned int reg)

Just use soc-cache.  Compatibility code for older kernels isn't really
acceptable for mainline due to the increased maintainance burden and it
will at best result in a driver that doesn't work as well as possible.

> +       client = (struct i2c_client *)codec->control_data;

No need to cast away from void.

> +/*
> + * For kernels compiled without unsigned long long int division
> + */
> +unsigned long long int ulldiv(unsigned long long int dividend,
> +                             unsigned long long int divisor)
> +{
> +       unsigned long long int quotient = 0;
> +       int shift = 1;
> +
> +       if (divisor == 0)
> +               return 0;
> +
> +       /* result is 1.0 if divisor and dividend are equal */
> +       if (divisor == dividend)
> +               return 1;
> +
> +       /* Normalize divisor */
> +       while (!(divisor & 0x8000000000000000ULL)) {
> +               divisor <<= 1;
> +               ++shift;
> +       }
> +
> +       /* Shift and subtract */
> +       while (shift--) {
> +               quotient <<= 1;
> +
> +               if (divisor <= dividend) {
> +                       dividend -= divisor;
> +                       ++quotient;
> +               }
> +               divisor >>= 1;
> +       }
> +
> +       /* Round up */
> +       if (dividend > divisor)
> +               ++quotient;
> +
> +       return quotient;
> +}

> +/*
> + * Load equalizer DSP coefficient configurations registers
> + */
> +void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
> +                   unsigned int band, u16 *coefs)
> +{
> +       unsigned int eq_reg;
> +       unsigned int i;
> +
> +       if (band > 4)
> +               return;
> +
> +       if (dai > 1)
> +               return;

These look like they should be BUG_ON().

> +/*
> + * Excursion limiter modes
> + */
> +static const char *max98088_ex_mode[] = {
> +       "Off",
> +       "100Hz",
> +       "400Hz",
> +       "600Hz",
> +       "800Hz",
> +       "1000Hz",
> +       "200-400Hz",
> +       "400-600Hz",
> +       "400-800Hz",
> +       "user-400Hz",
> +       "user-600Hz",
> +       "user-800Hz",
> +       "user-1000Hz"

These user options look very suspicous.

> +static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
> +                               struct snd_ctl_elem_value *ucontrol)
> +{
> +       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
> +       struct max98088_priv *max98088 = codec->private_data;
> +       unsigned int *mode = &max98088->ex_mode;
> +
> +       *mode = ucontrol->value.integer.value[0];
> +
> +       if (*mode <= ARRAY_SIZE(ex_mode_table))
> +               max98088_write(codec, M98088_REG_41_SPKDHP,
> +                       ex_mode_table[*mode]);

Report the error if there's an invalid input.

> +static const char *max98088_hp_spk_mute[] = {"disable", "enable"};
> +static const struct soc_enum max98088_hp_spk_mute_enum[] = {
> +       SOC_ENUM_SINGLE_EXT(2, max98088_hp_spk_mute),

Why are you doing this rather than just using a standard switch control?
Even if there's a reason to not use one of the standard register
controls this should be presented to the application layer as a switch
rather than as an enumeration.

The same issue applies to the other outputs.

> +static const char *max98088_dcblk[] =  {"Off", "On"};
> +static const struct soc_enum max98088_dcblk_enum[] = {
> +       SOC_ENUM_SINGLE(M98088_REG_20_DAI2_FILTERS, 0, 2, max98088_dcblk),
> +};

Again, present a switch to userspace.

> +       SOC_DOUBLE_R("Headphone volume", M98088_REG_39_LVL_HP_L,
> +               M98088_REG_3A_LVL_HP_R, 0, 31, 0),

Volume, not volume.

> +       SOC_SINGLE("MIC1 gain", M98088_REG_35_LVL_MIC1, 0, 31, 1),
> +       SOC_SINGLE("MIC2 gain", M98088_REG_36_LVL_MIC2, 0, 31, 1),

> +       SOC_SINGLE("MIC1 pre", M98088_REG_35_LVL_MIC1, 5, 3, 0),
> +       SOC_SINGLE("MIC2 pre", M98088_REG_36_LVL_MIC2, 5, 3, 0),
> +
> +       SOC_SINGLE("INA gain", M98088_REG_37_LVL_INA, 0, 7, 1),
> +       SOC_SINGLE("INB gain", M98088_REG_38_LVL_INB, 0, 7, 1),

These should all be named " Volume", as should all your other " gain" or
" pre" controls.

> +       SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
> +       SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),

Capitalise switch.

> +/* Left speaker mixer switch */
> +static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
> +       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),

All of these should be " Switch".

> +static int max98088_hp_event(struct snd_soc_dapm_widget *w,
> +                            struct snd_kcontrol *kcontrol, int event)
> +{
> +       struct snd_soc_codec *codec = w->codec;
> +       u16 status;
> +
> +       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
> +
> +       /* powering down headphone gracefully */
> +       status = max98088_read(codec, M98088_REG_4D_PWR_EN_OUT);
> +       if ((status & M98088_HPEN) == M98088_HPEN) {
> +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> +                       (status & ~M98088_HPEN));
> +       }
> +       schedule_timeout_interruptible(msecs_to_jiffies(20));

This should be non-interruptible since there's no handling of any actual
interruptions.

> +/* DAPM AUDIO_MAP: */
> +static const struct snd_soc_dapm_route audio_map[] = {
> +       /* Left headphone output mixer */
> +       {"Left HP Mixer", "Left DAC1", "DACL1"},
> +       {"Left HP Mixer", "Left DAC2", "DACL2"},
> +       {"Left HP Mixer", "Right DAC1", "DACR1"},
> +       {"Left HP Mixer", "Right DAC2", "DACR2"},
> +       {"Left HP Mixer", "MIC1", "Mic Bias"},
> +       {"Left HP Mixer", "MIC2", "Mic Bias"},

This looks wrong - I'd expect this to be switching the microphone input
to the mixer, not the bias.  Mixing the bias in would just present a DC
level which isn't going to be great.  Similarly for all your other
microphone input switching.

> +               /* DAI clock master/slave wrt the codec */
> +               switch (fmt & SND_SOC_DAIFMT_CBS_CFS) {

You should be using the masks for your switch statements.  Using
specific values is at best harder to read and at worst will lead to
false positives.  This applies to all your comparisons in this function.

> +                       /* BCI: normal bclk (rise) */
> +                       /* WCI: invert frame */
> +                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
> +                               M98088_DAI_BCI, M98088_DAI_WCI);
> +                       break;
> +               case SND_SOC_DAIFMT_IB_NF:
> +                       /* BCI: invert bclk (fall) */
> +                       /* WCI: normal frame */
> +                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
> +                               M98088_DAI_WCI, M98088_DAI_BCI);

This code (and most of the other update_bits() usages round here) looks
wrong - I'd expect to see a constant bitmask being supplied as the mask
argument then the value changing to set different values.

> +               max98088_write(codec, M98088_REG_16_DAI1_IOCFG,
> +                       (1<<6) |  /* SEL : map DAI1 to S1 */
> +                       (0<<5) |  /* LTEN : ADC->DAC loop-through enable */
> +                       (0<<4) |  /* LBEN : loopback (0=disable, 1=enable) */
> +                       (0<<3) |  /* DMONO : DAC SDIN (0=stereo, 1=mono) */

These look like things that should be configured by the user, especially
LTEN.

> +                       (0<<2) |  /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive) */
> +                       (1<<1) |  /* SDOEN : serial data out ENABLE */
> +                       (1<<0));  /* SDIEN : serial data in ENABLE */

These look like they ought to be DAPM widgets for the AIF.

> +static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
> +                                  int clk_id, unsigned int freq, int dir)
> +{
> +       struct snd_soc_codec *codec = dai->codec;
> +       struct max98088_priv *max98088 = codec->private_data;
> +
> +       if (freq != max98088->sysclk) {

It would be clearer to just return in this case so you don't have so
much indentation.

> +               /* If codec is currently running, toggle reset */
> +               if (max98088_read(codec, M98088_REG_51_PWR_SYS)
> +                       & M98088_SHDNRUN) {
> +                       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
> +                               M98088_SHDNRUN, 0);
> +                       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
> +                               0, M98088_SHDNRUN);

Will this reset the whole chip?

> +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;

This won't indicate if there's a failure to find a match.

> +       /* data 16/24 bit width */
> +       switch (params_format(params)) {
> +       case SNDRV_PCM_FORMAT_S16_LE:
> +               /* WS: 16bit */
> +               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
> +                       M98088_DAI_WS, 0);
> +               break;
> +       case SNDRV_PCM_FORMAT_S24_LE:
> +               /* WS: 24bit */
> +               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
> +                       0, M98088_DAI_WS);
> +               break;
> +       }

Again, your snd_soc_update_bits() usage seems confused.  You're also not
checking for invalid inputs.  There are other instances of this in the
driver, I'll not comment them all.

> +       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
> +
> +       if (rate != cdata->rate) {
> +               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
> +               reg11val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
> +               max98088_write(codec, M98088_REG_11_DAI1_CLKMODE, reg11val);
> +               cdata->rate = rate;

Just directly use snd_soc_update_bits() - it will suppress null changes.

> +       /* Configure NI when operating as master */
> +       if (max98088_read(codec, M98088_REG_14_DAI1_FORMAT)
> +               & M98088_DAI_MAS) {
> +               BUG_ON(max98088->sysclk == 0);

BUG_ON() is unfriendly here - this is a machine driver configuration
error and normally BUG_ON() would be an internal error in the driver.
Better error reporting please.

> +static int max98088_set_bias_level(struct snd_soc_codec *codec,
> +                                 enum snd_soc_bias_level level)
> +{
> +       switch (level) {
> +       case SND_SOC_BIAS_ON:
> +               max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN);
> +               break;
> +
> +       case SND_SOC_BIAS_PREPARE:
> +               max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN);
> +               break;

Drop the _ON cacse if it's the same.  Also, how will this interact with
the management of SHDNRUN in hw_params()?

> +       case SND_SOC_BIAS_STANDBY:
> +               max98088_sync_cache(codec);
> +               max98088_write(codec, M98088_REG_51_PWR_SYS,
> +                       M98088_SHDNRUN|M98088_PWRSV);
> +               break;

Do you really want to sync the cache every time we drop into standby?

> +       /* Configure digital mic / external mic */
> +       /* Digital mic needs REG_15 OSR1=1 */
> +       regval = ((0<<6) | /* digital mic clock freq = PCLK/8 */
> +                 (0<<5) | /* DIGMICL enable */
> +                 (0<<4) | /* DIGMICR enable */
> +                 (0<<0)); /* external mic enable */

These look like they ought to be managed via DAPM?

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-01 11:14   ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-01 11:14 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Joonyoung Shim, Takashi Iwai, Peter Ujfalusi,
	linux-kernel, Jesse Marroquin, Liam Girdwood

On Tue, Aug 31, 2010 at 02:08:30PM -0700, Peter Hsiang wrote:
> This patch adds the MAX98088 CODEC driver.

The major issue with this is that the driver needs to be updated to
current ASoC versions - you should always be working against the trees
that are in linux-next for new drivers.  For ASoC you should be looking
at:

  git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git

> Signed-off-by: Peter Hsiang <peter.hsiang at maxim-ic.com>

Please write your e-mail address properly - it'll end up in the git logs
anyway.  It'd also be nice to credit your sources when you've taken code
from other drivers :)

> +#define EQ_CFG_MAX 32

> +/* Speaker excursion limiter filter response configurations */
> +#define EX_CFG_MAX 32

Multiple definitions of this.

> +       /* Set receiver_mode to:
> +        * 0 = amplifier output, or
> +        * 1 = LINE level output
> +        */
> +       unsigned int receiver_mode;

Better to use bools or bitfields.

> +       /* Bypass option for INA to MIC1 connection
> +        * 0 = normal setting
> +        * 1 = bypass enabled
> +        */
> +       unsigned int ina_to_mic1_bypass;
> +
> +       /* Bypass option for MIC1 to MIC2 connection

What are these - they sound like something I'd expect to be configurable
at runtime?

> @@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS
>         select SND_SOC_DA7210 if I2C
>         select SND_SOC_JZ4740 if SOC_JZ4740
>         select SND_SOC_MAX9877 if I2C
> +       select SND_SOC_MAX98088 if I2C

The sorting should be lexical.

> +/*
> + * Read the MAX98088 I2C register space
> + * Note: this driver source code is backward compatible to kernel
> + * version 2.6.32.
> + */
> +static unsigned int max98088_read(struct snd_soc_codec *codec,
> +                                 unsigned int reg)

Just use soc-cache.  Compatibility code for older kernels isn't really
acceptable for mainline due to the increased maintainance burden and it
will at best result in a driver that doesn't work as well as possible.

> +       client = (struct i2c_client *)codec->control_data;

No need to cast away from void.

> +/*
> + * For kernels compiled without unsigned long long int division
> + */
> +unsigned long long int ulldiv(unsigned long long int dividend,
> +                             unsigned long long int divisor)
> +{
> +       unsigned long long int quotient = 0;
> +       int shift = 1;
> +
> +       if (divisor == 0)
> +               return 0;
> +
> +       /* result is 1.0 if divisor and dividend are equal */
> +       if (divisor == dividend)
> +               return 1;
> +
> +       /* Normalize divisor */
> +       while (!(divisor & 0x8000000000000000ULL)) {
> +               divisor <<= 1;
> +               ++shift;
> +       }
> +
> +       /* Shift and subtract */
> +       while (shift--) {
> +               quotient <<= 1;
> +
> +               if (divisor <= dividend) {
> +                       dividend -= divisor;
> +                       ++quotient;
> +               }
> +               divisor >>= 1;
> +       }
> +
> +       /* Round up */
> +       if (dividend > divisor)
> +               ++quotient;
> +
> +       return quotient;
> +}

> +/*
> + * Load equalizer DSP coefficient configurations registers
> + */
> +void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
> +                   unsigned int band, u16 *coefs)
> +{
> +       unsigned int eq_reg;
> +       unsigned int i;
> +
> +       if (band > 4)
> +               return;
> +
> +       if (dai > 1)
> +               return;

These look like they should be BUG_ON().

> +/*
> + * Excursion limiter modes
> + */
> +static const char *max98088_ex_mode[] = {
> +       "Off",
> +       "100Hz",
> +       "400Hz",
> +       "600Hz",
> +       "800Hz",
> +       "1000Hz",
> +       "200-400Hz",
> +       "400-600Hz",
> +       "400-800Hz",
> +       "user-400Hz",
> +       "user-600Hz",
> +       "user-800Hz",
> +       "user-1000Hz"

These user options look very suspicous.

> +static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
> +                               struct snd_ctl_elem_value *ucontrol)
> +{
> +       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
> +       struct max98088_priv *max98088 = codec->private_data;
> +       unsigned int *mode = &max98088->ex_mode;
> +
> +       *mode = ucontrol->value.integer.value[0];
> +
> +       if (*mode <= ARRAY_SIZE(ex_mode_table))
> +               max98088_write(codec, M98088_REG_41_SPKDHP,
> +                       ex_mode_table[*mode]);

Report the error if there's an invalid input.

> +static const char *max98088_hp_spk_mute[] = {"disable", "enable"};
> +static const struct soc_enum max98088_hp_spk_mute_enum[] = {
> +       SOC_ENUM_SINGLE_EXT(2, max98088_hp_spk_mute),

Why are you doing this rather than just using a standard switch control?
Even if there's a reason to not use one of the standard register
controls this should be presented to the application layer as a switch
rather than as an enumeration.

The same issue applies to the other outputs.

> +static const char *max98088_dcblk[] =  {"Off", "On"};
> +static const struct soc_enum max98088_dcblk_enum[] = {
> +       SOC_ENUM_SINGLE(M98088_REG_20_DAI2_FILTERS, 0, 2, max98088_dcblk),
> +};

Again, present a switch to userspace.

> +       SOC_DOUBLE_R("Headphone volume", M98088_REG_39_LVL_HP_L,
> +               M98088_REG_3A_LVL_HP_R, 0, 31, 0),

Volume, not volume.

> +       SOC_SINGLE("MIC1 gain", M98088_REG_35_LVL_MIC1, 0, 31, 1),
> +       SOC_SINGLE("MIC2 gain", M98088_REG_36_LVL_MIC2, 0, 31, 1),

> +       SOC_SINGLE("MIC1 pre", M98088_REG_35_LVL_MIC1, 5, 3, 0),
> +       SOC_SINGLE("MIC2 pre", M98088_REG_36_LVL_MIC2, 5, 3, 0),
> +
> +       SOC_SINGLE("INA gain", M98088_REG_37_LVL_INA, 0, 7, 1),
> +       SOC_SINGLE("INB gain", M98088_REG_38_LVL_INB, 0, 7, 1),

These should all be named " Volume", as should all your other " gain" or
" pre" controls.

> +       SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
> +       SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),

Capitalise switch.

> +/* Left speaker mixer switch */
> +static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
> +       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),

All of these should be " Switch".

> +static int max98088_hp_event(struct snd_soc_dapm_widget *w,
> +                            struct snd_kcontrol *kcontrol, int event)
> +{
> +       struct snd_soc_codec *codec = w->codec;
> +       u16 status;
> +
> +       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
> +
> +       /* powering down headphone gracefully */
> +       status = max98088_read(codec, M98088_REG_4D_PWR_EN_OUT);
> +       if ((status & M98088_HPEN) == M98088_HPEN) {
> +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> +                       (status & ~M98088_HPEN));
> +       }
> +       schedule_timeout_interruptible(msecs_to_jiffies(20));

This should be non-interruptible since there's no handling of any actual
interruptions.

> +/* DAPM AUDIO_MAP: */
> +static const struct snd_soc_dapm_route audio_map[] = {
> +       /* Left headphone output mixer */
> +       {"Left HP Mixer", "Left DAC1", "DACL1"},
> +       {"Left HP Mixer", "Left DAC2", "DACL2"},
> +       {"Left HP Mixer", "Right DAC1", "DACR1"},
> +       {"Left HP Mixer", "Right DAC2", "DACR2"},
> +       {"Left HP Mixer", "MIC1", "Mic Bias"},
> +       {"Left HP Mixer", "MIC2", "Mic Bias"},

This looks wrong - I'd expect this to be switching the microphone input
to the mixer, not the bias.  Mixing the bias in would just present a DC
level which isn't going to be great.  Similarly for all your other
microphone input switching.

> +               /* DAI clock master/slave wrt the codec */
> +               switch (fmt & SND_SOC_DAIFMT_CBS_CFS) {

You should be using the masks for your switch statements.  Using
specific values is at best harder to read and at worst will lead to
false positives.  This applies to all your comparisons in this function.

> +                       /* BCI: normal bclk (rise) */
> +                       /* WCI: invert frame */
> +                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
> +                               M98088_DAI_BCI, M98088_DAI_WCI);
> +                       break;
> +               case SND_SOC_DAIFMT_IB_NF:
> +                       /* BCI: invert bclk (fall) */
> +                       /* WCI: normal frame */
> +                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
> +                               M98088_DAI_WCI, M98088_DAI_BCI);

This code (and most of the other update_bits() usages round here) looks
wrong - I'd expect to see a constant bitmask being supplied as the mask
argument then the value changing to set different values.

> +               max98088_write(codec, M98088_REG_16_DAI1_IOCFG,
> +                       (1<<6) |  /* SEL : map DAI1 to S1 */
> +                       (0<<5) |  /* LTEN : ADC->DAC loop-through enable */
> +                       (0<<4) |  /* LBEN : loopback (0=disable, 1=enable) */
> +                       (0<<3) |  /* DMONO : DAC SDIN (0=stereo, 1=mono) */

These look like things that should be configured by the user, especially
LTEN.

> +                       (0<<2) |  /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive) */
> +                       (1<<1) |  /* SDOEN : serial data out ENABLE */
> +                       (1<<0));  /* SDIEN : serial data in ENABLE */

These look like they ought to be DAPM widgets for the AIF.

> +static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
> +                                  int clk_id, unsigned int freq, int dir)
> +{
> +       struct snd_soc_codec *codec = dai->codec;
> +       struct max98088_priv *max98088 = codec->private_data;
> +
> +       if (freq != max98088->sysclk) {

It would be clearer to just return in this case so you don't have so
much indentation.

> +               /* If codec is currently running, toggle reset */
> +               if (max98088_read(codec, M98088_REG_51_PWR_SYS)
> +                       & M98088_SHDNRUN) {
> +                       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
> +                               M98088_SHDNRUN, 0);
> +                       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
> +                               0, M98088_SHDNRUN);

Will this reset the whole chip?

> +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;

This won't indicate if there's a failure to find a match.

> +       /* data 16/24 bit width */
> +       switch (params_format(params)) {
> +       case SNDRV_PCM_FORMAT_S16_LE:
> +               /* WS: 16bit */
> +               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
> +                       M98088_DAI_WS, 0);
> +               break;
> +       case SNDRV_PCM_FORMAT_S24_LE:
> +               /* WS: 24bit */
> +               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
> +                       0, M98088_DAI_WS);
> +               break;
> +       }

Again, your snd_soc_update_bits() usage seems confused.  You're also not
checking for invalid inputs.  There are other instances of this in the
driver, I'll not comment them all.

> +       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
> +
> +       if (rate != cdata->rate) {
> +               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
> +               reg11val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
> +               max98088_write(codec, M98088_REG_11_DAI1_CLKMODE, reg11val);
> +               cdata->rate = rate;

Just directly use snd_soc_update_bits() - it will suppress null changes.

> +       /* Configure NI when operating as master */
> +       if (max98088_read(codec, M98088_REG_14_DAI1_FORMAT)
> +               & M98088_DAI_MAS) {
> +               BUG_ON(max98088->sysclk == 0);

BUG_ON() is unfriendly here - this is a machine driver configuration
error and normally BUG_ON() would be an internal error in the driver.
Better error reporting please.

> +static int max98088_set_bias_level(struct snd_soc_codec *codec,
> +                                 enum snd_soc_bias_level level)
> +{
> +       switch (level) {
> +       case SND_SOC_BIAS_ON:
> +               max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN);
> +               break;
> +
> +       case SND_SOC_BIAS_PREPARE:
> +               max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN);
> +               break;

Drop the _ON cacse if it's the same.  Also, how will this interact with
the management of SHDNRUN in hw_params()?

> +       case SND_SOC_BIAS_STANDBY:
> +               max98088_sync_cache(codec);
> +               max98088_write(codec, M98088_REG_51_PWR_SYS,
> +                       M98088_SHDNRUN|M98088_PWRSV);
> +               break;

Do you really want to sync the cache every time we drop into standby?

> +       /* Configure digital mic / external mic */
> +       /* Digital mic needs REG_15 OSR1=1 */
> +       regval = ((0<<6) | /* digital mic clock freq = PCLK/8 */
> +                 (0<<5) | /* DIGMICL enable */
> +                 (0<<4) | /* DIGMICR enable */
> +                 (0<<0)); /* external mic enable */

These look like they ought to be managed via DAPM?

^ permalink raw reply	[flat|nested] 46+ messages in thread

* RE: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-01 11:14   ` Mark Brown
  (?)
@ 2010-09-02 23:30   ` Peter Hsiang
  2010-09-03 10:17       ` Mark Brown
  -1 siblings, 1 reply; 46+ messages in thread
From: Peter Hsiang @ 2010-09-02 23:30 UTC (permalink / raw)
  To: Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Jesse Marroquin, Liam Girdwood,
	Peter Ujfalusi, Joonyoung Shim, alsa-devel, linux-kernel

On Wed, Sep 01, 2010, Mark Brown wrote:
> On Tue, Aug 31, 2010 at 02:08:30PM -0700, Peter Hsiang wrote:
> > This patch adds the MAX98088 CODEC driver.
>
> The major issue with this is that the driver needs to be updated to
> current ASoC versions - you should always be working against the trees
> that are in linux-next for new drivers.  For ASoC you should be looking
> at:
>
>   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git

Thanks for the git location you provided.  What version of Linux
kernel does this git/broonie/sound-2.6.git version correspond to?
Is it newer than even the latest kernel-next?

Some of the ASoC API functions and features in the latest version of
the kernel you require us to use are not in the popular stable kernel
version 2.6.32 that people are using today to build products.
Our intent is to release driver code that will work in both the current
and latest kernel versions.

Would it not be a good idea to use the API functions that exist in
both the very latest and the currently popular kernel version?

Does kernel.org provide a channel for releasing codec drivers that
can be backward compatible to a popular earlier kernel version?

>
> > Signed-off-by: Peter Hsiang <peter.hsiang at maxim-ic.com>
>
> Please write your e-mail address properly - it'll end up in the git logs
> anyway.  It'd also be nice to credit your sources when you've taken code
> from other drivers :)
>
> > +#define EQ_CFG_MAX 32
>
> > +/* Speaker excursion limiter filter response configurations */
> > +#define EX_CFG_MAX 32
>
> Multiple definitions of this.

These are not multiple definitions.
They are 2 different ones - the equalizer, and excursion limiter.

>
> > +       /* Set receiver_mode to:
> > +        * 0 = amplifier output, or
> > +        * 1 = LINE level output
> > +        */
> > +       unsigned int receiver_mode;
>
> Better to use bools or bitfields.
>
> > +       /* Bypass option for INA to MIC1 connection
> > +        * 0 = normal setting
> > +        * 1 = bypass enabled
> > +        */
> > +       unsigned int ina_to_mic1_bypass;
> > +
> > +       /* Bypass option for MIC1 to MIC2 connection
>
> What are these - they sound like something I'd expect to be configurable
> at runtime?
>
> > @@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS
> >         select SND_SOC_DA7210 if I2C
> >         select SND_SOC_JZ4740 if SOC_JZ4740
> >         select SND_SOC_MAX9877 if I2C
> > +       select SND_SOC_MAX98088 if I2C
>
> The sorting should be lexical.

Ok, thanks

>
> > +/*
> > + * Read the MAX98088 I2C register space
> > + * Note: this driver source code is backward compatible to kernel
> > + * version 2.6.32.
> > + */
> > +static unsigned int max98088_read(struct snd_soc_codec *codec,
> > +                                 unsigned int reg)
>
> Just use soc-cache.  Compatibility code for older kernels isn't really
> acceptable for mainline due to the increased maintainance burden and it
> will at best result in a driver that doesn't work as well as possible.

This integrated registers feature is not available in 2.6.32
Is it ok with you to keep it this way for this release?  Thanks.

>
> > +       client = (struct i2c_client *)codec->control_data;
>
> No need to cast away from void.

Ok will do - but no harm in being informative :)

>
> > +/*
> > + * For kernels compiled without unsigned long long int division
> > + */
> > +unsigned long long int ulldiv(unsigned long long int dividend,
> > +                             unsigned long long int divisor)
> > +{
> > +       unsigned long long int quotient = 0;
> > +       int shift = 1;
> > +
> > +       if (divisor == 0)
> > +               return 0;
> > +
> > +       /* result is 1.0 if divisor and dividend are equal */
> > +       if (divisor == dividend)
> > +               return 1;
> > +
> > +       /* Normalize divisor */
> > +       while (!(divisor & 0x8000000000000000ULL)) {
> > +               divisor <<= 1;
> > +               ++shift;
> > +       }
> > +
> > +       /* Shift and subtract */
> > +       while (shift--) {
> > +               quotient <<= 1;
> > +
> > +               if (divisor <= dividend) {
> > +                       dividend -= divisor;
> > +                       ++quotient;
> > +               }
> > +               divisor >>= 1;
> > +       }
> > +
> > +       /* Round up */
> > +       if (dividend > divisor)
> > +               ++quotient;
> > +
> > +       return quotient;
> > +}
>
> > +/*
> > + * Load equalizer DSP coefficient configurations registers
> > + */
> > +void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
> > +                   unsigned int band, u16 *coefs)
> > +{
> > +       unsigned int eq_reg;
> > +       unsigned int i;
> > +
> > +       if (band > 4)
> > +               return;
> > +
> > +       if (dai > 1)
> > +               return;
>
> These look like they should be BUG_ON().

Yes, good idea.

>
> > +/*
> > + * Excursion limiter modes
> > + */
> > +static const char *max98088_ex_mode[] = {
> > +       "Off",
> > +       "100Hz",
> > +       "400Hz",
> > +       "600Hz",
> > +       "800Hz",
> > +       "1000Hz",
> > +       "200-400Hz",
> > +       "400-600Hz",
> > +       "400-800Hz",
> > +       "user-400Hz",
> > +       "user-600Hz",
> > +       "user-800Hz",
> > +       "user-1000Hz"
>
> These user options look very suspicous.

The first 8 options are settings pre-defined in hardware that the
user can choose from.
The last 4 are user programmable options, and thus the name 'user'.

>
> > +static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
> > +                               struct snd_ctl_elem_value *ucontrol)
> > +{
> > +       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
> > +       struct max98088_priv *max98088 = codec->private_data;
> > +       unsigned int *mode = &max98088->ex_mode;
> > +
> > +       *mode = ucontrol->value.integer.value[0];
> > +
> > +       if (*mode <= ARRAY_SIZE(ex_mode_table))
> > +               max98088_write(codec, M98088_REG_41_SPKDHP,
> > +                       ex_mode_table[*mode]);
>
> Report the error if there's an invalid input.

Ok

>
> > +static const char *max98088_hp_spk_mute[] = {"disable", "enable"};
> > +static const struct soc_enum max98088_hp_spk_mute_enum[] = {
> > +       SOC_ENUM_SINGLE_EXT(2, max98088_hp_spk_mute),
>
> Why are you doing this rather than just using a standard switch control?
> Even if there's a reason to not use one of the standard register
> controls this should be presented to the application layer as a switch
> rather than as an enumeration.

We will use a switch control.  Thanks

>
> The same issue applies to the other outputs.
>
> > +static const char *max98088_dcblk[] =  {"Off", "On"};
> > +static const struct soc_enum max98088_dcblk_enum[] = {
> > +       SOC_ENUM_SINGLE(M98088_REG_20_DAI2_FILTERS, 0, 2,
> max98088_dcblk),
> > +};
>
> Again, present a switch to userspace.

Yes

>
> > +       SOC_DOUBLE_R("Headphone volume", M98088_REG_39_LVL_HP_L,
> > +               M98088_REG_3A_LVL_HP_R, 0, 31, 0),
>
> Volume, not volume.
>
> > +       SOC_SINGLE("MIC1 gain", M98088_REG_35_LVL_MIC1, 0, 31, 1),
> > +       SOC_SINGLE("MIC2 gain", M98088_REG_36_LVL_MIC2, 0, 31, 1),
>
> > +       SOC_SINGLE("MIC1 pre", M98088_REG_35_LVL_MIC1, 5, 3, 0),
> > +       SOC_SINGLE("MIC2 pre", M98088_REG_36_LVL_MIC2, 5, 3, 0),
> > +
> > +       SOC_SINGLE("INA gain", M98088_REG_37_LVL_INA, 0, 7, 1),
> > +       SOC_SINGLE("INB gain", M98088_REG_38_LVL_INB, 0, 7, 1),
>
> These should all be named " Volume", as should all your other " gain" or
> " pre" controls.

Ok
>
> > +       SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
> > +       SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
>
> Capitalise switch.

I noticed that by capitalizing the first letter of the 2nd word,
the 2nd word is dropped out by the system when amixer lists them.
"EQ1 Switch" becomes just "EQ1", while "EQ1 switch" is "EQ1 switch".
Have you seen this behavior before?
Is this a known issue with some version of amixer or ASoC code base?

>
> > +/* Left speaker mixer switch */
> > +static const struct snd_kcontrol_new
> max98088_left_speaker_mixer_controls[] = {
> > +       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2B_MIX_SPK_LEFT, 7, 1,
> 0),
>
> All of these should be " Switch".

Ok

>
> > +static int max98088_hp_event(struct snd_soc_dapm_widget *w,
> > +                            struct snd_kcontrol *kcontrol, int event)
> > +{
> > +       struct snd_soc_codec *codec = w->codec;
> > +       u16 status;
> > +
> > +       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
> > +
> > +       /* powering down headphone gracefully */
> > +       status = max98088_read(codec, M98088_REG_4D_PWR_EN_OUT);
> > +       if ((status & M98088_HPEN) == M98088_HPEN) {
> > +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> > +                       (status & ~M98088_HPEN));
> > +       }
> > +       schedule_timeout_interruptible(msecs_to_jiffies(20));
>
> This should be non-interruptible since there's no handling of any actual
> interruptions.

Ok

>
> > +/* DAPM AUDIO_MAP: */
> > +static const struct snd_soc_dapm_route audio_map[] = {
> > +       /* Left headphone output mixer */
> > +       {"Left HP Mixer", "Left DAC1", "DACL1"},
> > +       {"Left HP Mixer", "Left DAC2", "DACL2"},
> > +       {"Left HP Mixer", "Right DAC1", "DACR1"},
> > +       {"Left HP Mixer", "Right DAC2", "DACR2"},
> > +       {"Left HP Mixer", "MIC1", "Mic Bias"},
> > +       {"Left HP Mixer", "MIC2", "Mic Bias"},
>
> This looks wrong - I'd expect this to be switching the microphone input
> to the mixer, not the bias.  Mixing the bias in would just present a DC
> level which isn't going to be great.  Similarly for all your other
> microphone input switching.

I'll look at switching the microphone input like you suggested. Thanks.
No worries, when turning on the mic bias, the hardware will not
add DC into the digital samples.  It's handled well.

>
> > +               /* DAI clock master/slave wrt the codec */
> > +               switch (fmt & SND_SOC_DAIFMT_CBS_CFS) {
>
> You should be using the masks for your switch statements.  Using
> specific values is at best harder to read and at worst will lead to
> false positives.  This applies to all your comparisons in this function.

This method was inherited from another released driver.
We will adopt the new method.

>
> > +                       /* BCI: normal bclk (rise) */
> > +                       /* WCI: invert frame */
> > +                       snd_soc_update_bits(codec,
> M98088_REG_14_DAI1_FORMAT,
> > +                               M98088_DAI_BCI, M98088_DAI_WCI);
> > +                       break;
> > +               case SND_SOC_DAIFMT_IB_NF:
> > +                       /* BCI: invert bclk (fall) */
> > +                       /* WCI: normal frame */
> > +                       snd_soc_update_bits(codec,
> M98088_REG_14_DAI1_FORMAT,
> > +                               M98088_DAI_WCI, M98088_DAI_BCI);
>
> This code (and most of the other update_bits() usages round here) looks
> wrong - I'd expect to see a constant bitmask being supplied as the mask
> argument then the value changing to set different values.

The M98088_DAI_BCI and M98088_DAI_WCI are bit field masks.
Is this what you expect?  If not, could you clarify?  Thanks!

>
> > +               max98088_write(codec, M98088_REG_16_DAI1_IOCFG,
> > +                       (1<<6) |  /* SEL : map DAI1 to S1 */
> > +                       (0<<5) |  /* LTEN : ADC->DAC loop-through enable
> */
> > +                       (0<<4) |  /* LBEN : loopback (0=disable,
> 1=enable) */
> > +                       (0<<3) |  /* DMONO : DAC SDIN (0=stereo, 1=mono)
> */
>
> These look like things that should be configured by the user, especially
> LTEN.
>
> > +                       (0<<2) |  /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive)
> */
> > +                       (1<<1) |  /* SDOEN : serial data out ENABLE */
> > +                       (1<<0));  /* SDIEN : serial data in ENABLE */
>
> These look like they ought to be DAPM widgets for the AIF.

Good idea. Which widget(s) will drive SDOEN, or SDIEN?

>
> > +static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
> > +                                  int clk_id, unsigned int freq, int
> dir)
> > +{
> > +       struct snd_soc_codec *codec = dai->codec;
> > +       struct max98088_priv *max98088 = codec->private_data;
> > +
> > +       if (freq != max98088->sysclk) {
>
> It would be clearer to just return in this case so you don't have so
> much indentation.

Good point.  Thanks for this suggestion on the source indentation.

>
> > +               /* If codec is currently running, toggle reset */
> > +               if (max98088_read(codec, M98088_REG_51_PWR_SYS)
> > +                       & M98088_SHDNRUN) {
> > +                       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
> > +                               M98088_SHDNRUN, 0);
> > +                       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
> > +                               0, M98088_SHDNRUN);
>
> Will this reset the whole chip?

It's not a chip reset.
I will correct the comments to make it clearer.  Thanks.

>
> > +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;
>
> This won't indicate if there's a failure to find a match.

You are right.
This will default to the hardware default setting of 0 if the sample
rate specified by the user were invalid.
I'll take a look this.

>
> > +       /* data 16/24 bit width */
> > +       switch (params_format(params)) {
> > +       case SNDRV_PCM_FORMAT_S16_LE:
> > +               /* WS: 16bit */
> > +               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
> > +                       M98088_DAI_WS, 0);
> > +               break;
> > +       case SNDRV_PCM_FORMAT_S24_LE:
> > +               /* WS: 24bit */
> > +               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
> > +                       0, M98088_DAI_WS);
> > +               break;
> > +       }
>
> Again, your snd_soc_update_bits() usage seems confused.  You're also not
> checking for invalid inputs.  There are other instances of this in the
> driver, I'll not comment them all.

It uses the bit field mask to control the desired bits.

>
> > +       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN,
> 0);
> > +
> > +       if (rate != cdata->rate) {
> > +               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
> > +               reg11val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
> > +               max98088_write(codec, M98088_REG_11_DAI1_CLKMODE,
> reg11val);
> > +               cdata->rate = rate;
>
> Just directly use snd_soc_update_bits() - it will suppress null changes.

Good idea.

>
> > +       /* Configure NI when operating as master */
> > +       if (max98088_read(codec, M98088_REG_14_DAI1_FORMAT)
> > +               & M98088_DAI_MAS) {
> > +               BUG_ON(max98088->sysclk == 0);
>
> BUG_ON() is unfriendly here - this is a machine driver configuration
> error and normally BUG_ON() would be an internal error in the driver.
> Better error reporting please.

Ok

>
> > +static int max98088_set_bias_level(struct snd_soc_codec *codec,
> > +                                 enum snd_soc_bias_level level)
> > +{
> > +       switch (level) {
> > +       case SND_SOC_BIAS_ON:
> > +               max98088_write(codec, M98088_REG_51_PWR_SYS,
> M98088_SHDNRUN);
> > +               break;
> > +
> > +       case SND_SOC_BIAS_PREPARE:
> > +               max98088_write(codec, M98088_REG_51_PWR_SYS,
> M98088_SHDNRUN);
> > +               break;
>
> Drop the _ON cacse if it's the same.  Also, how will this interact with
> the management of SHDNRUN in hw_params()?

Ok

>
> > +       case SND_SOC_BIAS_STANDBY:
> > +               max98088_sync_cache(codec);
> > +               max98088_write(codec, M98088_REG_51_PWR_SYS,
> > +                       M98088_SHDNRUN|M98088_PWRSV);
> > +               break;
>
> Do you really want to sync the cache every time we drop into standby?

Good point.

>
> > +       /* Configure digital mic / external mic */
> > +       /* Digital mic needs REG_15 OSR1=1 */
> > +       regval = ((0<<6) | /* digital mic clock freq = PCLK/8 */
> > +                 (0<<5) | /* DIGMICL enable */
> > +                 (0<<4) | /* DIGMICR enable */
> > +                 (0<<0)); /* external mic enable */
>
> These look like they ought to be managed via DAPM?


^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-02 23:30   ` Peter Hsiang
@ 2010-09-03 10:17       ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-03 10:17 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Jesse Marroquin, Liam Girdwood,
	Peter Ujfalusi, Joonyoung Shim, alsa-devel, linux-kernel

On Thu, Sep 02, 2010 at 04:30:14PM -0700, Peter Hsiang wrote:
> On Wed, Sep 01, 2010, Mark Brown wrote:

> >   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git

> Thanks for the git location you provided.  What version of Linux
> kernel does this git/broonie/sound-2.6.git version correspond to?
> Is it newer than even the latest kernel-next?

This is what's going into -next, it will often be trivially ahead of
-next with the patches addded since -next was last built but not
meaningfully so.

> Some of the ASoC API functions and features in the latest version of
> the kernel you require us to use are not in the popular stable kernel
> version 2.6.32 that people are using today to build products.
> Our intent is to release driver code that will work in both the current
> and latest kernel versions.

This is not how Linux development works, and in many cases will be
impossible as internal APIs are frequently removed from the
Linux kernel or changed incompatibly.

> Would it not be a good idea to use the API functions that exist in
> both the very latest and the currently popular kernel version?

Like I say this is just not possible in many cases.

> Does kernel.org provide a channel for releasing codec drivers that
> can be backward compatible to a popular earlier kernel version?

No, and it's not clear that a single approach can be adopted since newer
devices often drive enhancements to the core APIs so the approaches that
work best to integrate with older kernels vary depending on the particular 
devices and kernel versions involved.

> > > +       /* Bypass option for INA to MIC1 connection
> > > +        * 0 = normal setting
> > > +        * 1 = bypass enabled
> > > +        */
> > > +       unsigned int ina_to_mic1_bypass;
> > > +
> > > +       /* Bypass option for MIC1 to MIC2 connection

> > What are these - they sound like something I'd expect to be configurable
> > at runtime?

Please engage with questions like this.

> > > +/*
> > > + * Read the MAX98088 I2C register space
> > > + * Note: this driver source code is backward compatible to kernel
> > > + * version 2.6.32.
> > > + */
> > > +static unsigned int max98088_read(struct snd_soc_codec *codec,
> > > +                                 unsigned int reg)

> > Just use soc-cache.  Compatibility code for older kernels isn't really
> > acceptable for mainline due to the increased maintainance burden and it
> > will at best result in a driver that doesn't work as well as possible.

> This integrated registers feature is not available in 2.6.32
> Is it ok with you to keep it this way for this release?  Thanks.

No, you should use the features of the current kernel.  For your
backport you can do things like supply soc-cache.c as well.

> > > +       client = (struct i2c_client *)codec->control_data;

> > No need to cast away from void.

> Ok will do - but no harm in being informative :)

There is actually - if you have a cast then if you have done something
broken by mistake the compiler is much less likely to generate a warning
since the cast says you know what you're doing.

> > > +       "user-400Hz",
> > > +       "user-600Hz",
> > > +       "user-800Hz",
> > > +       "user-1000Hz"

> > These user options look very suspicous.

> The first 8 options are settings pre-defined in hardware that the
> user can choose from.
> The last 4 are user programmable options, and thus the name 'user'.

What happens if the user didn't supply any programmable options?  Would
it not be better to allow the user to specify meaningful text here?

> > > +       SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
> > > +       SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),

> > Capitalise switch.

> I noticed that by capitalizing the first letter of the 2nd word,
> the 2nd word is dropped out by the system when amixer lists them.
> "EQ1 Switch" becomes just "EQ1", while "EQ1 switch" is "EQ1 switch".
> Have you seen this behavior before?
> Is this a known issue with some version of amixer or ASoC code base?

This is not a problem, this is intended behaviour.  ALSA applications
interpret the control names and use this naming information to present
an appropriate UI to users.

> > > +static const struct snd_soc_dapm_route audio_map[] = {
> > > +       /* Left headphone output mixer */
> > > +       {"Left HP Mixer", "Left DAC1", "DACL1"},
> > > +       {"Left HP Mixer", "Left DAC2", "DACL2"},
> > > +       {"Left HP Mixer", "Right DAC1", "DACR1"},
> > > +       {"Left HP Mixer", "Right DAC2", "DACR2"},
> > > +       {"Left HP Mixer", "MIC1", "Mic Bias"},
> > > +       {"Left HP Mixer", "MIC2", "Mic Bias"},

> > This looks wrong - I'd expect this to be switching the microphone input
> > to the mixer, not the bias.  Mixing the bias in would just present a DC
> > level which isn't going to be great.  Similarly for all your other
> > microphone input switching.

> I'll look at switching the microphone input like you suggested. Thanks.
> No worries, when turning on the mic bias, the hardware will not
> add DC into the digital samples.  It's handled well.

As I said I rather suspect that this is because the microphone input is
being switched into the path rather than the microphone bias.

> > > +                       /* BCI: normal bclk (rise) */
> > > +                       /* WCI: invert frame */
> > > +                       snd_soc_update_bits(codec,
> > M98088_REG_14_DAI1_FORMAT,
> > > +                               M98088_DAI_BCI, M98088_DAI_WCI);
> > > +                       break;
> > > +               case SND_SOC_DAIFMT_IB_NF:
> > > +                       /* BCI: invert bclk (fall) */
> > > +                       /* WCI: normal frame */
> > > +                       snd_soc_update_bits(codec,
> > M98088_REG_14_DAI1_FORMAT,
> > > +                               M98088_DAI_WCI, M98088_DAI_BCI);

> > This code (and most of the other update_bits() usages round here) looks
> > wrong - I'd expect to see a constant bitmask being supplied as the mask
> > argument then the value changing to set different values.

> The M98088_DAI_BCI and M98088_DAI_WCI are bit field masks.
> Is this what you expect?  If not, could you clarify?  Thanks!

As I said above I'd expect to see a constant mask being supplied and
varying values.  Could you please explain what you think the code above
does?

> > > +                       (0<<2) |  /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive)
> > */
> > > +                       (1<<1) |  /* SDOEN : serial data out ENABLE */
> > > +                       (1<<0));  /* SDIEN : serial data in ENABLE */

> > These look like they ought to be DAPM widgets for the AIF.

> Good idea. Which widget(s) will drive SDOEN, or SDIEN?

The AIF widgets.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-03 10:17       ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-03 10:17 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Joonyoung Shim, Takashi Iwai, Peter Ujfalusi,
	linux-kernel, Jesse Marroquin, Liam Girdwood

On Thu, Sep 02, 2010 at 04:30:14PM -0700, Peter Hsiang wrote:
> On Wed, Sep 01, 2010, Mark Brown wrote:

> >   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git

> Thanks for the git location you provided.  What version of Linux
> kernel does this git/broonie/sound-2.6.git version correspond to?
> Is it newer than even the latest kernel-next?

This is what's going into -next, it will often be trivially ahead of
-next with the patches addded since -next was last built but not
meaningfully so.

> Some of the ASoC API functions and features in the latest version of
> the kernel you require us to use are not in the popular stable kernel
> version 2.6.32 that people are using today to build products.
> Our intent is to release driver code that will work in both the current
> and latest kernel versions.

This is not how Linux development works, and in many cases will be
impossible as internal APIs are frequently removed from the
Linux kernel or changed incompatibly.

> Would it not be a good idea to use the API functions that exist in
> both the very latest and the currently popular kernel version?

Like I say this is just not possible in many cases.

> Does kernel.org provide a channel for releasing codec drivers that
> can be backward compatible to a popular earlier kernel version?

No, and it's not clear that a single approach can be adopted since newer
devices often drive enhancements to the core APIs so the approaches that
work best to integrate with older kernels vary depending on the particular 
devices and kernel versions involved.

> > > +       /* Bypass option for INA to MIC1 connection
> > > +        * 0 = normal setting
> > > +        * 1 = bypass enabled
> > > +        */
> > > +       unsigned int ina_to_mic1_bypass;
> > > +
> > > +       /* Bypass option for MIC1 to MIC2 connection

> > What are these - they sound like something I'd expect to be configurable
> > at runtime?

Please engage with questions like this.

> > > +/*
> > > + * Read the MAX98088 I2C register space
> > > + * Note: this driver source code is backward compatible to kernel
> > > + * version 2.6.32.
> > > + */
> > > +static unsigned int max98088_read(struct snd_soc_codec *codec,
> > > +                                 unsigned int reg)

> > Just use soc-cache.  Compatibility code for older kernels isn't really
> > acceptable for mainline due to the increased maintainance burden and it
> > will at best result in a driver that doesn't work as well as possible.

> This integrated registers feature is not available in 2.6.32
> Is it ok with you to keep it this way for this release?  Thanks.

No, you should use the features of the current kernel.  For your
backport you can do things like supply soc-cache.c as well.

> > > +       client = (struct i2c_client *)codec->control_data;

> > No need to cast away from void.

> Ok will do - but no harm in being informative :)

There is actually - if you have a cast then if you have done something
broken by mistake the compiler is much less likely to generate a warning
since the cast says you know what you're doing.

> > > +       "user-400Hz",
> > > +       "user-600Hz",
> > > +       "user-800Hz",
> > > +       "user-1000Hz"

> > These user options look very suspicous.

> The first 8 options are settings pre-defined in hardware that the
> user can choose from.
> The last 4 are user programmable options, and thus the name 'user'.

What happens if the user didn't supply any programmable options?  Would
it not be better to allow the user to specify meaningful text here?

> > > +       SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
> > > +       SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),

> > Capitalise switch.

> I noticed that by capitalizing the first letter of the 2nd word,
> the 2nd word is dropped out by the system when amixer lists them.
> "EQ1 Switch" becomes just "EQ1", while "EQ1 switch" is "EQ1 switch".
> Have you seen this behavior before?
> Is this a known issue with some version of amixer or ASoC code base?

This is not a problem, this is intended behaviour.  ALSA applications
interpret the control names and use this naming information to present
an appropriate UI to users.

> > > +static const struct snd_soc_dapm_route audio_map[] = {
> > > +       /* Left headphone output mixer */
> > > +       {"Left HP Mixer", "Left DAC1", "DACL1"},
> > > +       {"Left HP Mixer", "Left DAC2", "DACL2"},
> > > +       {"Left HP Mixer", "Right DAC1", "DACR1"},
> > > +       {"Left HP Mixer", "Right DAC2", "DACR2"},
> > > +       {"Left HP Mixer", "MIC1", "Mic Bias"},
> > > +       {"Left HP Mixer", "MIC2", "Mic Bias"},

> > This looks wrong - I'd expect this to be switching the microphone input
> > to the mixer, not the bias.  Mixing the bias in would just present a DC
> > level which isn't going to be great.  Similarly for all your other
> > microphone input switching.

> I'll look at switching the microphone input like you suggested. Thanks.
> No worries, when turning on the mic bias, the hardware will not
> add DC into the digital samples.  It's handled well.

As I said I rather suspect that this is because the microphone input is
being switched into the path rather than the microphone bias.

> > > +                       /* BCI: normal bclk (rise) */
> > > +                       /* WCI: invert frame */
> > > +                       snd_soc_update_bits(codec,
> > M98088_REG_14_DAI1_FORMAT,
> > > +                               M98088_DAI_BCI, M98088_DAI_WCI);
> > > +                       break;
> > > +               case SND_SOC_DAIFMT_IB_NF:
> > > +                       /* BCI: invert bclk (fall) */
> > > +                       /* WCI: normal frame */
> > > +                       snd_soc_update_bits(codec,
> > M98088_REG_14_DAI1_FORMAT,
> > > +                               M98088_DAI_WCI, M98088_DAI_BCI);

> > This code (and most of the other update_bits() usages round here) looks
> > wrong - I'd expect to see a constant bitmask being supplied as the mask
> > argument then the value changing to set different values.

> The M98088_DAI_BCI and M98088_DAI_WCI are bit field masks.
> Is this what you expect?  If not, could you clarify?  Thanks!

As I said above I'd expect to see a constant mask being supplied and
varying values.  Could you please explain what you think the code above
does?

> > > +                       (0<<2) |  /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive)
> > */
> > > +                       (1<<1) |  /* SDOEN : serial data out ENABLE */
> > > +                       (1<<0));  /* SDIEN : serial data in ENABLE */

> > These look like they ought to be DAPM widgets for the AIF.

> Good idea. Which widget(s) will drive SDOEN, or SDIEN?

The AIF widgets.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* RE: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-03 10:17       ` Mark Brown
@ 2010-09-22  2:49         ` Peter Hsiang
  -1 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-22  2:49 UTC (permalink / raw)
  To: Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Jesse Marroquin, Liam Girdwood,
	Peter Ujfalusi, Joonyoung Shim, alsa-devel, linux-kernel

On Fri, Sep 03, 2010, Mark Brown wrote:
> On Thu, Sep 02, 2010 at 04:30:14PM -0700, Peter Hsiang wrote:
> > On Wed, Sep 01, 2010, Mark Brown wrote:
> 
> > > > +/*
> > > > + * Read the MAX98088 I2C register space
> > > > + * Note: this driver source code is backward compatible to kernel
> > > > + * version 2.6.32.
> > > > + */
> > > > +static unsigned int max98088_read(struct snd_soc_codec *codec,
> > > > +                                 unsigned int reg)
> 
> > > Just use soc-cache.  Compatibility code for older kernels isn't really
> > > acceptable for mainline due to the increased maintainance burden and it
> > > will at best result in a driver that doesn't work as well as possible.
> 
> > This integrated registers feature is not available in 2.6.32
> > Is it ok with you to keep it this way for this release?  Thanks.
> 
> No, you should use the features of the current kernel.  For your
> backport you can do things like supply soc-cache.c as well.

The soc-cache.c in the latest kernel-next version supports codecs
with volatile registers in 16 bit mode, but not for the 8 bit mode.
See snd_soc_8_8_write.

The source code comments confirmed this:
"Note at present this code can not be used by CODECs with 
volatile registers."  

For this codec (with volatile registers), should we use the read
and write functions in the codec driver until the 8-bit mode is
supported by Linux soc-cache.c?


^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-22  2:49         ` Peter Hsiang
  0 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-22  2:49 UTC (permalink / raw)
  To: Mark Brown
  Cc: alsa-devel, Joonyoung, Shim, Takashi Iwai, Peter Ujfalusi,
	linux-kernel, Marroquin, Liam Girdwood, Jesse

On Fri, Sep 03, 2010, Mark Brown wrote:
> On Thu, Sep 02, 2010 at 04:30:14PM -0700, Peter Hsiang wrote:
> > On Wed, Sep 01, 2010, Mark Brown wrote:
> 
> > > > +/*
> > > > + * Read the MAX98088 I2C register space
> > > > + * Note: this driver source code is backward compatible to kernel
> > > > + * version 2.6.32.
> > > > + */
> > > > +static unsigned int max98088_read(struct snd_soc_codec *codec,
> > > > +                                 unsigned int reg)
> 
> > > Just use soc-cache.  Compatibility code for older kernels isn't really
> > > acceptable for mainline due to the increased maintainance burden and it
> > > will at best result in a driver that doesn't work as well as possible.
> 
> > This integrated registers feature is not available in 2.6.32
> > Is it ok with you to keep it this way for this release?  Thanks.
> 
> No, you should use the features of the current kernel.  For your
> backport you can do things like supply soc-cache.c as well.

The soc-cache.c in the latest kernel-next version supports codecs
with volatile registers in 16 bit mode, but not for the 8 bit mode.
See snd_soc_8_8_write.

The source code comments confirmed this:
"Note at present this code can not be used by CODECs with 
volatile registers."  

For this codec (with volatile registers), should we use the read
and write functions in the codec driver until the 8-bit mode is
supported by Linux soc-cache.c?

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-22  2:49         ` Peter Hsiang
@ 2010-09-22 10:38           ` Mark Brown
  -1 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-22 10:38 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Jesse Marroquin, Liam Girdwood,
	Peter Ujfalusi, Joonyoung Shim, alsa-devel, linux-kernel

On Tue, Sep 21, 2010 at 07:49:30PM -0700, Peter Hsiang wrote:
> On Fri, Sep 03, 2010, Mark Brown wrote:

> > No, you should use the features of the current kernel.  For your
> > backport you can do things like supply soc-cache.c as well.

> The soc-cache.c in the latest kernel-next version supports codecs
> with volatile registers in 16 bit mode, but not for the 8 bit mode.
> See snd_soc_8_8_write.

> The source code comments confirmed this:
> "Note at present this code can not be used by CODECs with 
> volatile registers."  

> For this codec (with volatile registers), should we use the read
> and write functions in the codec driver until the 8-bit mode is
> supported by Linux soc-cache.c?

You should modify soc-cache.c to support volatile registers if you need
this; it's not been implemented because nobody needed it yet (someone
may get to it before you get round to resubmitting, but if they haven't
then just extend the core functionality).

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-22 10:38           ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-22 10:38 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Joonyoung Shim, Takashi Iwai, Peter Ujfalusi,
	linux-kernel, Jesse Marroquin, Liam Girdwood

On Tue, Sep 21, 2010 at 07:49:30PM -0700, Peter Hsiang wrote:
> On Fri, Sep 03, 2010, Mark Brown wrote:

> > No, you should use the features of the current kernel.  For your
> > backport you can do things like supply soc-cache.c as well.

> The soc-cache.c in the latest kernel-next version supports codecs
> with volatile registers in 16 bit mode, but not for the 8 bit mode.
> See snd_soc_8_8_write.

> The source code comments confirmed this:
> "Note at present this code can not be used by CODECs with 
> volatile registers."  

> For this codec (with volatile registers), should we use the read
> and write functions in the codec driver until the 8-bit mode is
> supported by Linux soc-cache.c?

You should modify soc-cache.c to support volatile registers if you need
this; it's not been implemented because nobody needed it yet (someone
may get to it before you get round to resubmitting, but if they haven't
then just extend the core functionality).

^ permalink raw reply	[flat|nested] 46+ messages in thread

* RE: [PATCH] ASoC: Add max98088 CODEC driver
  2010-10-15 10:55       ` Mark Brown
@ 2010-10-15 17:23         ` Peter Hsiang
  -1 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-10-15 17:23 UTC (permalink / raw)
  To: Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Friday, Oct 15, 2010, Mark Brown wrote:
> On Wed, Oct 13, 2010 at 08:30:43PM -0700, Peter Hsiang wrote:
> > This patch adds the MAX98088 CODEC driver.
> >
> > Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
> 
> Applied, thanks.

Thanks!

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-10-15 17:23         ` Peter Hsiang
  0 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-10-15 17:23 UTC (permalink / raw)
  To: Mark Brown
  Cc: alsa-devel, Jesse, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Marroquin, Liam, Girdwood

On Friday, Oct 15, 2010, Mark Brown wrote:
> On Wed, Oct 13, 2010 at 08:30:43PM -0700, Peter Hsiang wrote:
> > This patch adds the MAX98088 CODEC driver.
> >
> > Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
> 
> Applied, thanks.

Thanks!

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-10-14  3:30   ` Peter Hsiang
@ 2010-10-15 10:55       ` Mark Brown
  2010-10-15 10:55       ` Mark Brown
  1 sibling, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-10-15 10:55 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Wed, Oct 13, 2010 at 08:30:43PM -0700, Peter Hsiang wrote:
> This patch adds the MAX98088 CODEC driver.
> 
> Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>

Applied, thanks.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-10-15 10:55       ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-10-15 10:55 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Jesse Marroquin, Liam Girdwood

On Wed, Oct 13, 2010 at 08:30:43PM -0700, Peter Hsiang wrote:
> This patch adds the MAX98088 CODEC driver.
> 
> Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>

Applied, thanks.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-10-14  3:30   ` Peter Hsiang
@ 2010-10-15 10:04       ` Liam Girdwood
  2010-10-15 10:55       ` Mark Brown
  1 sibling, 0 replies; 46+ messages in thread
From: Liam Girdwood @ 2010-10-15 10:04 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Mark Brown, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Wed, 2010-10-13 at 20:30 -0700, Peter Hsiang wrote:
> This patch adds the MAX98088 CODEC driver.
> 
> Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
> ---
>  include/sound/max98088.h    |   50 +
>  sound/soc/codecs/Kconfig    |    4 +
>  sound/soc/codecs/Makefile   |    2 +
>  sound/soc/codecs/max98088.c | 2097 +++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/codecs/max98088.h |  193 ++++
>  5 files changed, 2346 insertions(+), 0 deletions(-)
>  create mode 100644 include/sound/max98088.h
>  create mode 100644 sound/soc/codecs/max98088.c
>  create mode 100644 sound/soc/codecs/max98088.h
> 

Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
-- 
Freelance Developer, SlimLogic Ltd
ASoC and Voltage Regulator Maintainer.
http://www.slimlogic.co.uk


^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-10-15 10:04       ` Liam Girdwood
  0 siblings, 0 replies; 46+ messages in thread
From: Liam Girdwood @ 2010-10-15 10:04 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Takashi Iwai, Brown, Peter Ujfalusi, linux-kernel,
	Mark, Jesse Marroquin

On Wed, 2010-10-13 at 20:30 -0700, Peter Hsiang wrote:
> This patch adds the MAX98088 CODEC driver.
> 
> Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
> ---
>  include/sound/max98088.h    |   50 +
>  sound/soc/codecs/Kconfig    |    4 +
>  sound/soc/codecs/Makefile   |    2 +
>  sound/soc/codecs/max98088.c | 2097 +++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/codecs/max98088.h |  193 ++++
>  5 files changed, 2346 insertions(+), 0 deletions(-)
>  create mode 100644 include/sound/max98088.h
>  create mode 100644 sound/soc/codecs/max98088.c
>  create mode 100644 sound/soc/codecs/max98088.h
> 

Acked-by: Liam Girdwood <lrg@slimlogic.co.uk>
-- 
Freelance Developer, SlimLogic Ltd
ASoC and Voltage Regulator Maintainer.
http://www.slimlogic.co.uk

^ permalink raw reply	[flat|nested] 46+ messages in thread

* [PATCH] ASoC: Add max98088 CODEC driver
  2010-10-13  1:20 ` Peter Hsiang
  2010-10-13  1:47   ` Joe Perches
  2010-10-13 10:32     ` Mark Brown
@ 2010-10-14  3:30   ` Peter Hsiang
  2010-10-15 10:04       ` Liam Girdwood
  2010-10-15 10:55       ` Mark Brown
  2 siblings, 2 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-10-14  3:30 UTC (permalink / raw)
  To: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, Peter Ujfalusi
  Cc: alsa-devel, linux-kernel, Jesse Marroquin

This patch adds the MAX98088 CODEC driver.

Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
---
 include/sound/max98088.h    |   50 +
 sound/soc/codecs/Kconfig    |    4 +
 sound/soc/codecs/Makefile   |    2 +
 sound/soc/codecs/max98088.c | 2097 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/max98088.h |  193 ++++
 5 files changed, 2346 insertions(+), 0 deletions(-)
 create mode 100644 include/sound/max98088.h
 create mode 100644 sound/soc/codecs/max98088.c
 create mode 100644 sound/soc/codecs/max98088.h

diff --git a/include/sound/max98088.h b/include/sound/max98088.h
new file mode 100644
index 0000000..59edf30
--- /dev/null
+++ b/include/sound/max98088.h
@@ -0,0 +1,50 @@
+/*
+ * Platform data for MAX98088
+ *
+ * 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 as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __SOUND_MAX98088_PDATA_H__
+#define __SOUND_MAX98088_PDATA_H__
+
+/* Equalizer filter response configuration */
+struct max98088_eq_cfg {
+       const char *name;
+       unsigned int rate;
+       u16 band1[5];
+       u16 band2[5];
+       u16 band3[5];
+       u16 band4[5];
+       u16 band5[5];
+};
+
+/* codec platform data */
+struct max98088_pdata {
+
+       /* Equalizers for DAI1 and DAI2 */
+       struct max98088_eq_cfg *eq_cfg;
+       unsigned int eq_cfgcnt;
+
+       /* Receiver output can be configured as power amplifier or LINE out */
+       /* Set receiver_mode to:
+        * 0 = amplifier output, or
+        * 1 = LINE level output
+        */
+       unsigned int receiver_mode:1;
+
+       /* Analog/digital microphone configuration:
+        * 0 = analog microphone input (normal setting)
+        * 1 = digital microphone input
+        */
+       unsigned int digmic_left_mode:1;
+       unsigned int digmic_right_mode:1;
+
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index ff7b922..53af7d2 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -27,6 +27,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_CS4270 if I2C
        select SND_SOC_DA7210 if I2C
        select SND_SOC_JZ4740 if SOC_JZ4740
+       select SND_SOC_MAX98088 if I2C
        select SND_SOC_MAX9877 if I2C
        select SND_SOC_PCM3008
        select SND_SOC_SPDIF
@@ -158,6 +159,9 @@ config SND_SOC_L3
 config SND_SOC_DA7210
         tristate

+config SND_SOC_MAX98088
+       tristate
+
 config SND_SOC_PCM3008
        tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index af4d4c4..2d4941d 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -15,6 +15,7 @@ snd-soc-cs4270-objs := cs4270.o
 snd-soc-cx20442-objs := cx20442.o
 snd-soc-da7210-objs := da7210.o
 snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
 snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-spdif-objs := spdif_transciever.o
 snd-soc-ssm2602-objs := ssm2602.o
@@ -89,6 +90,7 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
 obj-$(CONFIG_SND_SOC_DA7210)   += snd-soc-da7210.o
 obj-$(CONFIG_SND_SOC_L3)       += snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_JZ4740_CODEC)     += snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088)  += snd-soc-max98088.o
 obj-$(CONFIG_SND_SOC_PCM3008)  += snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_SPDIF)    += snd-soc-spdif.o
 obj-$(CONFIG_SND_SOC_SSM2602)  += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
new file mode 100644
index 0000000..92bc2ec
--- /dev/null
+++ b/sound/soc/codecs/max98088.c
@@ -0,0 +1,2097 @@
+/*
+ * max98088.c -- MAX98088 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 <linux/slab.h>
+#include <asm/div64.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+struct max98088_cdata {
+       unsigned int rate;
+       unsigned int fmt;
+       int eq_sel;
+};
+
+struct max98088_priv {
+       u8 reg_cache[M98088_REG_CNT];
+       void *control_data;
+       struct max98088_pdata *pdata;
+       unsigned int sysclk;
+       struct max98088_cdata dai[2];
+       int eq_textcnt;
+       const char **eq_texts;
+       struct soc_enum eq_enum;
+       u8 ina_state;
+       u8 inb_state;
+       unsigned int ex_mode;
+       unsigned int digmic;
+       unsigned int mic1pre;
+       unsigned int mic2pre;
+       unsigned int extmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+       0x00, /* 00 IRQ status */
+       0x00, /* 01 MIC status */
+       0x00, /* 02 jack status */
+       0x00, /* 03 battery voltage */
+       0x00, /* 04 */
+       0x00, /* 05 */
+       0x00, /* 06 */
+       0x00, /* 07 */
+       0x00, /* 08 */
+       0x00, /* 09 */
+       0x00, /* 0A */
+       0x00, /* 0B */
+       0x00, /* 0C */
+       0x00, /* 0D */
+       0x00, /* 0E */
+       0x00, /* 0F interrupt enable */
+
+       0x00, /* 10 master clock */
+       0x00, /* 11 DAI1 clock mode */
+       0x00, /* 12 DAI1 clock control */
+       0x00, /* 13 DAI1 clock control */
+       0x00, /* 14 DAI1 format */
+       0x00, /* 15 DAI1 clock */
+       0x00, /* 16 DAI1 config */
+       0x00, /* 17 DAI1 TDM */
+       0x00, /* 18 DAI1 filters */
+       0x00, /* 19 DAI2 clock mode */
+       0x00, /* 1A DAI2 clock control */
+       0x00, /* 1B DAI2 clock control */
+       0x00, /* 1C DAI2 format */
+       0x00, /* 1D DAI2 clock */
+       0x00, /* 1E DAI2 config */
+       0x00, /* 1F DAI2 TDM */
+
+       0x00, /* 20 DAI2 filters */
+       0x00, /* 21 data config */
+       0x00, /* 22 DAC mixer */
+       0x00, /* 23 left ADC mixer */
+       0x00, /* 24 right ADC mixer */
+       0x00, /* 25 left HP mixer */
+       0x00, /* 26 right HP mixer */
+       0x00, /* 27 HP control */
+       0x00, /* 28 left REC mixer */
+       0x00, /* 29 right REC mixer */
+       0x00, /* 2A REC control */
+       0x00, /* 2B left SPK mixer */
+       0x00, /* 2C right SPK mixer */
+       0x00, /* 2D SPK control */
+       0x00, /* 2E sidetone */
+       0x00, /* 2F DAI1 playback level */
+
+       0x00, /* 30 DAI1 playback level */
+       0x00, /* 31 DAI2 playback level */
+       0x00, /* 32 DAI2 playbakc level */
+       0x00, /* 33 left ADC level */
+       0x00, /* 34 right ADC level */
+       0x00, /* 35 MIC1 level */
+       0x00, /* 36 MIC2 level */
+       0x00, /* 37 INA level */
+       0x00, /* 38 INB level */
+       0x00, /* 39 left HP volume */
+       0x00, /* 3A right HP volume */
+       0x00, /* 3B left REC volume */
+       0x00, /* 3C right REC volume */
+       0x00, /* 3D left SPK volume */
+       0x00, /* 3E right SPK volume */
+       0x00, /* 3F MIC config */
+
+       0x00, /* 40 MIC threshold */
+       0x00, /* 41 excursion limiter filter */
+       0x00, /* 42 excursion limiter threshold */
+       0x00, /* 43 ALC */
+       0x00, /* 44 power limiter threshold */
+       0x00, /* 45 power limiter config */
+       0x00, /* 46 distortion limiter config */
+       0x00, /* 47 audio input */
+       0x00, /* 48 microphone */
+       0x00, /* 49 level control */
+       0x00, /* 4A bypass switches */
+       0x00, /* 4B jack detect */
+       0x00, /* 4C input enable */
+       0x00, /* 4D output enable */
+       0xF0, /* 4E bias control */
+       0x00, /* 4F DAC power */
+
+       0x0F, /* 50 DAC power */
+       0x00, /* 51 system */
+       0x00, /* 52 DAI1 EQ1 */
+       0x00, /* 53 DAI1 EQ1 */
+       0x00, /* 54 DAI1 EQ1 */
+       0x00, /* 55 DAI1 EQ1 */
+       0x00, /* 56 DAI1 EQ1 */
+       0x00, /* 57 DAI1 EQ1 */
+       0x00, /* 58 DAI1 EQ1 */
+       0x00, /* 59 DAI1 EQ1 */
+       0x00, /* 5A DAI1 EQ1 */
+       0x00, /* 5B DAI1 EQ1 */
+       0x00, /* 5C DAI1 EQ2 */
+       0x00, /* 5D DAI1 EQ2 */
+       0x00, /* 5E DAI1 EQ2 */
+       0x00, /* 5F DAI1 EQ2 */
+
+       0x00, /* 60 DAI1 EQ2 */
+       0x00, /* 61 DAI1 EQ2 */
+       0x00, /* 62 DAI1 EQ2 */
+       0x00, /* 63 DAI1 EQ2 */
+       0x00, /* 64 DAI1 EQ2 */
+       0x00, /* 65 DAI1 EQ2 */
+       0x00, /* 66 DAI1 EQ3 */
+       0x00, /* 67 DAI1 EQ3 */
+       0x00, /* 68 DAI1 EQ3 */
+       0x00, /* 69 DAI1 EQ3 */
+       0x00, /* 6A DAI1 EQ3 */
+       0x00, /* 6B DAI1 EQ3 */
+       0x00, /* 6C DAI1 EQ3 */
+       0x00, /* 6D DAI1 EQ3 */
+       0x00, /* 6E DAI1 EQ3 */
+       0x00, /* 6F DAI1 EQ3 */
+
+       0x00, /* 70 DAI1 EQ4 */
+       0x00, /* 71 DAI1 EQ4 */
+       0x00, /* 72 DAI1 EQ4 */
+       0x00, /* 73 DAI1 EQ4 */
+       0x00, /* 74 DAI1 EQ4 */
+       0x00, /* 75 DAI1 EQ4 */
+       0x00, /* 76 DAI1 EQ4 */
+       0x00, /* 77 DAI1 EQ4 */
+       0x00, /* 78 DAI1 EQ4 */
+       0x00, /* 79 DAI1 EQ4 */
+       0x00, /* 7A DAI1 EQ5 */
+       0x00, /* 7B DAI1 EQ5 */
+       0x00, /* 7C DAI1 EQ5 */
+       0x00, /* 7D DAI1 EQ5 */
+       0x00, /* 7E DAI1 EQ5 */
+       0x00, /* 7F DAI1 EQ5 */
+
+       0x00, /* 80 DAI1 EQ5 */
+       0x00, /* 81 DAI1 EQ5 */
+       0x00, /* 82 DAI1 EQ5 */
+       0x00, /* 83 DAI1 EQ5 */
+       0x00, /* 84 DAI2 EQ1 */
+       0x00, /* 85 DAI2 EQ1 */
+       0x00, /* 86 DAI2 EQ1 */
+       0x00, /* 87 DAI2 EQ1 */
+       0x00, /* 88 DAI2 EQ1 */
+       0x00, /* 89 DAI2 EQ1 */
+       0x00, /* 8A DAI2 EQ1 */
+       0x00, /* 8B DAI2 EQ1 */
+       0x00, /* 8C DAI2 EQ1 */
+       0x00, /* 8D DAI2 EQ1 */
+       0x00, /* 8E DAI2 EQ2 */
+       0x00, /* 8F DAI2 EQ2 */
+
+       0x00, /* 90 DAI2 EQ2 */
+       0x00, /* 91 DAI2 EQ2 */
+       0x00, /* 92 DAI2 EQ2 */
+       0x00, /* 93 DAI2 EQ2 */
+       0x00, /* 94 DAI2 EQ2 */
+       0x00, /* 95 DAI2 EQ2 */
+       0x00, /* 96 DAI2 EQ2 */
+       0x00, /* 97 DAI2 EQ2 */
+       0x00, /* 98 DAI2 EQ3 */
+       0x00, /* 99 DAI2 EQ3 */
+       0x00, /* 9A DAI2 EQ3 */
+       0x00, /* 9B DAI2 EQ3 */
+       0x00, /* 9C DAI2 EQ3 */
+       0x00, /* 9D DAI2 EQ3 */
+       0x00, /* 9E DAI2 EQ3 */
+       0x00, /* 9F DAI2 EQ3 */
+
+       0x00, /* A0 DAI2 EQ3 */
+       0x00, /* A1 DAI2 EQ3 */
+       0x00, /* A2 DAI2 EQ4 */
+       0x00, /* A3 DAI2 EQ4 */
+       0x00, /* A4 DAI2 EQ4 */
+       0x00, /* A5 DAI2 EQ4 */
+       0x00, /* A6 DAI2 EQ4 */
+       0x00, /* A7 DAI2 EQ4 */
+       0x00, /* A8 DAI2 EQ4 */
+       0x00, /* A9 DAI2 EQ4 */
+       0x00, /* AA DAI2 EQ4 */
+       0x00, /* AB DAI2 EQ4 */
+       0x00, /* AC DAI2 EQ5 */
+       0x00, /* AD DAI2 EQ5 */
+       0x00, /* AE DAI2 EQ5 */
+       0x00, /* AF DAI2 EQ5 */
+
+       0x00, /* B0 DAI2 EQ5 */
+       0x00, /* B1 DAI2 EQ5 */
+       0x00, /* B2 DAI2 EQ5 */
+       0x00, /* B3 DAI2 EQ5 */
+       0x00, /* B4 DAI2 EQ5 */
+       0x00, /* B5 DAI2 EQ5 */
+       0x00, /* B6 DAI1 biquad */
+       0x00, /* B7 DAI1 biquad */
+       0x00, /* B8 DAI1 biquad */
+       0x00, /* B9 DAI1 biquad */
+       0x00, /* BA DAI1 biquad */
+       0x00, /* BB DAI1 biquad */
+       0x00, /* BC DAI1 biquad */
+       0x00, /* BD DAI1 biquad */
+       0x00, /* BE DAI1 biquad */
+       0x00, /* BF DAI1 biquad */
+
+       0x00, /* C0 DAI2 biquad */
+       0x00, /* C1 DAI2 biquad */
+       0x00, /* C2 DAI2 biquad */
+       0x00, /* C3 DAI2 biquad */
+       0x00, /* C4 DAI2 biquad */
+       0x00, /* C5 DAI2 biquad */
+       0x00, /* C6 DAI2 biquad */
+       0x00, /* C7 DAI2 biquad */
+       0x00, /* C8 DAI2 biquad */
+       0x00, /* C9 DAI2 biquad */
+       0x00, /* CA */
+       0x00, /* CB */
+       0x00, /* CC */
+       0x00, /* CD */
+       0x00, /* CE */
+       0x00, /* CF */
+
+       0x00, /* D0 */
+       0x00, /* D1 */
+       0x00, /* D2 */
+       0x00, /* D3 */
+       0x00, /* D4 */
+       0x00, /* D5 */
+       0x00, /* D6 */
+       0x00, /* D7 */
+       0x00, /* D8 */
+       0x00, /* D9 */
+       0x00, /* DA */
+       0x70, /* DB */
+       0x00, /* DC */
+       0x00, /* DD */
+       0x00, /* DE */
+       0x00, /* DF */
+
+       0x00, /* E0 */
+       0x00, /* E1 */
+       0x00, /* E2 */
+       0x00, /* E3 */
+       0x00, /* E4 */
+       0x00, /* E5 */
+       0x00, /* E6 */
+       0x00, /* E7 */
+       0x00, /* E8 */
+       0x00, /* E9 */
+       0x00, /* EA */
+       0x00, /* EB */
+       0x00, /* EC */
+       0x00, /* ED */
+       0x00, /* EE */
+       0x00, /* EF */
+
+       0x00, /* F0 */
+       0x00, /* F1 */
+       0x00, /* F2 */
+       0x00, /* F3 */
+       0x00, /* F4 */
+       0x00, /* F5 */
+       0x00, /* F6 */
+       0x00, /* F7 */
+       0x00, /* F8 */
+       0x00, /* F9 */
+       0x00, /* FA */
+       0x00, /* FB */
+       0x00, /* FC */
+       0x00, /* FD */
+       0x00, /* FE */
+       0x00, /* FF */
+};
+
+static struct {
+       int readable;
+       int writable;
+       int vol;
+} max98088_access[M98088_REG_CNT] = {
+       { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+       { 0xFF, 0x00, 1 }, /* 01 MIC status */
+       { 0xFF, 0x00, 1 }, /* 02 jack status */
+       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+       { 0xFF, 0xFF, 0 }, /* 04 */
+       { 0xFF, 0xFF, 0 }, /* 05 */
+       { 0xFF, 0xFF, 0 }, /* 06 */
+       { 0xFF, 0xFF, 0 }, /* 07 */
+       { 0xFF, 0xFF, 0 }, /* 08 */
+       { 0xFF, 0xFF, 0 }, /* 09 */
+       { 0xFF, 0xFF, 0 }, /* 0A */
+       { 0xFF, 0xFF, 0 }, /* 0B */
+       { 0xFF, 0xFF, 0 }, /* 0C */
+       { 0xFF, 0xFF, 0 }, /* 0D */
+       { 0xFF, 0xFF, 0 }, /* 0E */
+       { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+       { 0xFF, 0xFF, 0 }, /* 10 master clock */
+       { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+       { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+       { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+       { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+       { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+       { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+       { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+       { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+       { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+       { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+       { 0xFF, 0xFF, 0 }, /* 21 data config */
+       { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+       { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 27 HP control */
+       { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 2A REC control */
+       { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+       { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+       { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+       { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+       { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+       { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+       { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+       { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+       { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+       { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+       { 0xFF, 0xFF, 0 }, /* 37 INA level */
+       { 0xFF, 0xFF, 0 }, /* 38 INB level */
+       { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+       { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+       { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+       { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 43 ALC */
+       { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+       { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+       { 0xFF, 0xFF, 0 }, /* 47 audio input */
+       { 0xFF, 0xFF, 0 }, /* 48 microphone */
+       { 0xFF, 0xFF, 0 }, /* 49 level control */
+       { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+       { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+       { 0xFF, 0xFF, 0 }, /* 4C input enable */
+       { 0xFF, 0xFF, 0 }, /* 4D output enable */
+       { 0xFF, 0xFF, 0 }, /* 4E bias control */
+       { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+       { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+       { 0xFF, 0xFF, 0 }, /* 51 system */
+       { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+       { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+       { 0x00, 0x00, 0 }, /* CA */
+       { 0x00, 0x00, 0 }, /* CB */
+       { 0x00, 0x00, 0 }, /* CC */
+       { 0x00, 0x00, 0 }, /* CD */
+       { 0x00, 0x00, 0 }, /* CE */
+       { 0x00, 0x00, 0 }, /* CF */
+
+       { 0x00, 0x00, 0 }, /* D0 */
+       { 0x00, 0x00, 0 }, /* D1 */
+       { 0x00, 0x00, 0 }, /* D2 */
+       { 0x00, 0x00, 0 }, /* D3 */
+       { 0x00, 0x00, 0 }, /* D4 */
+       { 0x00, 0x00, 0 }, /* D5 */
+       { 0x00, 0x00, 0 }, /* D6 */
+       { 0x00, 0x00, 0 }, /* D7 */
+       { 0x00, 0x00, 0 }, /* D8 */
+       { 0x00, 0x00, 0 }, /* D9 */
+       { 0x00, 0x00, 0 }, /* DA */
+       { 0x00, 0x00, 0 }, /* DB */
+       { 0x00, 0x00, 0 }, /* DC */
+       { 0x00, 0x00, 0 }, /* DD */
+       { 0x00, 0x00, 0 }, /* DE */
+       { 0x00, 0x00, 0 }, /* DF */
+
+       { 0x00, 0x00, 0 }, /* E0 */
+       { 0x00, 0x00, 0 }, /* E1 */
+       { 0x00, 0x00, 0 }, /* E2 */
+       { 0x00, 0x00, 0 }, /* E3 */
+       { 0x00, 0x00, 0 }, /* E4 */
+       { 0x00, 0x00, 0 }, /* E5 */
+       { 0x00, 0x00, 0 }, /* E6 */
+       { 0x00, 0x00, 0 }, /* E7 */
+       { 0x00, 0x00, 0 }, /* E8 */
+       { 0x00, 0x00, 0 }, /* E9 */
+       { 0x00, 0x00, 0 }, /* EA */
+       { 0x00, 0x00, 0 }, /* EB */
+       { 0x00, 0x00, 0 }, /* EC */
+       { 0x00, 0x00, 0 }, /* ED */
+       { 0x00, 0x00, 0 }, /* EE */
+       { 0x00, 0x00, 0 }, /* EF */
+
+       { 0x00, 0x00, 0 }, /* F0 */
+       { 0x00, 0x00, 0 }, /* F1 */
+       { 0x00, 0x00, 0 }, /* F2 */
+       { 0x00, 0x00, 0 }, /* F3 */
+       { 0x00, 0x00, 0 }, /* F4 */
+       { 0x00, 0x00, 0 }, /* F5 */
+       { 0x00, 0x00, 0 }, /* F6 */
+       { 0x00, 0x00, 0 }, /* F7 */
+       { 0x00, 0x00, 0 }, /* F8 */
+       { 0x00, 0x00, 0 }, /* F9 */
+       { 0x00, 0x00, 0 }, /* FA */
+       { 0x00, 0x00, 0 }, /* FB */
+       { 0x00, 0x00, 0 }, /* FC */
+       { 0x00, 0x00, 0 }, /* FD */
+       { 0x00, 0x00, 0 }, /* FE */
+       { 0xFF, 0x00, 1 }, /* FF */
+};
+
+static int max98088_volatile_register(unsigned int reg)
+{
+       return max98088_access[reg].vol;
+}
+
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+                   unsigned int band, u16 *coefs)
+{
+       unsigned int eq_reg;
+       unsigned int i;
+
+       BUG_ON(band > 4);
+       BUG_ON(dai > 1);
+
+       /* Load the base register address */
+       eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+       /* Add the band address offset, note adjustment for word address */
+       eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+       /* Step through the registers and coefs */
+       for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+               snd_soc_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+               snd_soc_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+       }
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_exmode_texts[] = {
+       "Off", "100Hz", "400Hz", "600Hz", "800Hz", "1000Hz", "200-400Hz",
+       "400-600Hz", "400-800Hz",
+};
+
+static const unsigned int max98088_exmode_values[] = {
+       0x00, 0x43, 0x10, 0x20, 0x30, 0x40, 0x11, 0x22, 0x32
+};
+
+static const struct soc_enum max98088_exmode_enum =
+       SOC_VALUE_ENUM_SINGLE(M98088_REG_41_SPKDHP, 0, 127,
+                             ARRAY_SIZE(max98088_exmode_texts),
+                             max98088_exmode_texts,
+                             max98088_exmode_values);
+static const struct snd_kcontrol_new max98088_exmode_controls =
+       SOC_DAPM_VALUE_ENUM("Route", max98088_exmode_enum);
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+       "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+               max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_extmic_text[] = { "None", "MIC1", "MIC2" };
+
+static const struct soc_enum max98088_extmic_enum =
+       SOC_ENUM_SINGLE(M98088_REG_48_CFG_MIC, 0, 3, max98088_extmic_text);
+
+static const struct snd_kcontrol_new max98088_extmic_mux =
+       SOC_DAPM_ENUM("External MIC Mux", max98088_extmic_enum);
+
+static const char *max98088_dai1_fltr[] = {
+       "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+       "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int sel = ucontrol->value.integer.value[0];
+
+       max98088->mic1pre = sel;
+       snd_soc_update_bits(codec, M98088_REG_35_LVL_MIC1, M98088_MICPRE_MASK,
+               (1+sel)<<M98088_MICPRE_SHIFT);
+
+       return 0;
+}
+
+static int max98088_mic1pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       ucontrol->value.integer.value[0] = max98088->mic1pre;
+       return 0;
+}
+
+static int max98088_mic2pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int sel = ucontrol->value.integer.value[0];
+
+       max98088->mic2pre = sel;
+       snd_soc_update_bits(codec, M98088_REG_36_LVL_MIC2, M98088_MICPRE_MASK,
+               (1+sel)<<M98088_MICPRE_SHIFT);
+
+       return 0;
+}
+
+static int max98088_mic2pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       ucontrol->value.integer.value[0] = max98088->mic2pre;
+       return 0;
+}
+
+static const unsigned int max98088_micboost_tlv[] = {
+       TLV_DB_RANGE_HEAD(2),
+       0, 1, TLV_DB_SCALE_ITEM(0, 2000, 0),
+       2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+       SOC_DOUBLE_R("Headphone Volume", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+       SOC_DOUBLE_R("Speaker Volume", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+       SOC_DOUBLE_R("Receiver Volume", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+       SOC_DOUBLE_R("Headphone Switch", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 7, 1, 1),
+       SOC_DOUBLE_R("Speaker Switch", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 7, 1, 1),
+       SOC_DOUBLE_R("Receiver Switch", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 7, 1, 1),
+
+       SOC_SINGLE("MIC1 Volume", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+       SOC_SINGLE("MIC2 Volume", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+       SOC_SINGLE_EXT_TLV("MIC1 Boost Volume",
+                       M98088_REG_35_LVL_MIC1, 5, 2, 0,
+                       max98088_mic1pre_get, max98088_mic1pre_set,
+                       max98088_micboost_tlv),
+       SOC_SINGLE_EXT_TLV("MIC2 Boost Volume",
+                       M98088_REG_36_LVL_MIC2, 5, 2, 0,
+                       max98088_mic2pre_get, max98088_mic2pre_set,
+                       max98088_micboost_tlv),
+
+       SOC_SINGLE("INA Volume", M98088_REG_37_LVL_INA, 0, 7, 1),
+       SOC_SINGLE("INB Volume", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+       SOC_SINGLE("ADCL Volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+       SOC_SINGLE("ADCR Volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+       SOC_SINGLE("ADCL Boost Volume", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+       SOC_SINGLE("ADCR Boost Volume", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+       SOC_SINGLE("EQ1 Switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+       SOC_SINGLE("EQ2 Switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+       SOC_ENUM("EX Limiter Threshold", max98088_ex_thresh_enum),
+
+       SOC_ENUM("DAI1 Filter Mode", max98088_filter_mode_enum),
+       SOC_ENUM("DAI1 DAC Filter", max98088_dai1_dac_filter_enum),
+       SOC_ENUM("DAI1 ADC Filter", max98088_dai1_adc_filter_enum),
+       SOC_SINGLE("DAI2 DC Block Switch", M98088_REG_20_DAI2_FILTERS,
+               0, 1, 0),
+
+       SOC_SINGLE("ALC Switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+       SOC_SINGLE("ALC Threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+       SOC_SINGLE("ALC Multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+       SOC_SINGLE("ALC Release Time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+       SOC_SINGLE("PWR Limiter Threshold", M98088_REG_44_PWRLMT_CFG,
+               4, 15, 0),
+       SOC_SINGLE("PWR Limiter Weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+       SOC_SINGLE("PWR Limiter Time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+       SOC_SINGLE("PWR Limiter Time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+       SOC_SINGLE("THD Limiter Threshold", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+       SOC_SINGLE("THD Limiter Time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_mic_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               if (w->reg == M98088_REG_35_LVL_MIC1) {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic1pre)<<M98088_MICPRE_SHIFT);
+               } else {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic2pre)<<M98088_MICPRE_SHIFT);
+               }
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK, 0);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * The line inputs are 2-channel stereo inputs with the left
+ * and right channels sharing a common PGA power control signal.
+ */
+static int max98088_line_pga(struct snd_soc_dapm_widget *w,
+                            int event, int line, u8 channel)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       u8 *state;
+
+       BUG_ON(!((channel == 1) || (channel == 2)));
+
+       switch (line) {
+       case LINE_INA:
+               state = &max98088->ina_state;
+               break;
+       case LINE_INB:
+               state = &max98088->inb_state;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               *state |= channel;
+               snd_soc_update_bits(codec, w->reg,
+                       (1 << w->shift), (1 << w->shift));
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               *state &= ~channel;
+               if (*state == 0) {
+                       snd_soc_update_bits(codec, w->reg,
+                               (1 << w->shift), 0);
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int max98088_pga_ina1_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INA, 1);
+}
+
+static int max98088_pga_ina2_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INA, 2);
+}
+
+static int max98088_pga_inb1_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INB, 1);
+}
+
+static int max98088_pga_inb2_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INB, 2);
+}
+
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+       SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+       SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+       SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+       SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+       SND_SOC_DAPM_PGA("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+               7, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+               6, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+               5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+               4, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+               3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+               2, 0, NULL, 0),
+
+       SND_SOC_DAPM_MUX("External MIC", SND_SOC_NOPM, 0, 0,
+               &max98088_extmic_mux),
+
+       SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_left_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_right_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_PGA_E("MIC1 Input", M98088_REG_35_LVL_MIC1,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("MIC2 Input", M98088_REG_36_LVL_MIC2,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_ina1_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_ina2_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_inb1_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_inb2_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_MICBIAS("MICBIAS", M98088_REG_4C_PWR_EN_IN, 3, 0),
+
+       SND_SOC_DAPM_MUX("EX Limiter Mode", SND_SOC_NOPM, 0, 0,
+               &max98088_exmode_controls),
+
+       SND_SOC_DAPM_OUTPUT("HPL"),
+       SND_SOC_DAPM_OUTPUT("HPR"),
+       SND_SOC_DAPM_OUTPUT("SPKL"),
+       SND_SOC_DAPM_OUTPUT("SPKR"),
+       SND_SOC_DAPM_OUTPUT("RECL"),
+       SND_SOC_DAPM_OUTPUT("RECR"),
+
+       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 */
+       {"Left HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left HP Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right headphone output mixer */
+       {"Right HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right HP Mixer", "Left DAC2 Switch", "DACL2"  },
+       {"Right HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Left speaker output mixer */
+       {"Left SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right speaker output mixer */
+       {"Right SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Left REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Right REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       {"HP Left Out", NULL, "Left HP Mixer"},
+       {"HP Right Out", NULL, "Right HP Mixer"},
+       {"SPK Left Out", NULL, "Left SPK Mixer"},
+       {"SPK Right Out", NULL, "Right SPK Mixer"},
+       {"REC Left Out", NULL, "Left REC Mixer"},
+       {"REC Right Out", NULL, "Right REC Mixer"},
+
+       {"HPL", NULL, "HP Left Out"},
+       {"HPR", NULL, "HP Right Out"},
+       {"SPKL", NULL, "SPK Left Out"},
+       {"SPKR", NULL, "SPK Right Out"},
+       {"RECL", NULL, "REC Left Out"},
+       {"RECR", NULL, "REC Right Out"},
+
+       /* Left ADC input mixer */
+       {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right ADC input mixer */
+       {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right ADC Mixer", "INB2 Switch", "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"},
+       {"MIC1 Input", NULL, "MIC1"},
+       {"MIC2 Input", NULL, "MIC2"},
+};
+
+static int max98088_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, max98088_dapm_widgets,
+                                 ARRAY_SIZE(max98088_dapm_widgets));
+
+       snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+       snd_soc_add_controls(codec, max98088_snd_controls,
+                            ARRAY_SIZE(max98088_snd_controls));
+
+       snd_soc_dapm_new_widgets(codec);
+       return 0;
+}
+
+/* codec mclk clock divider coefficients */
+static const struct {
+       u32 rate;
+       u8  sr;
+} rate_table[] = {
+       {8000,  0x10},
+       {11025, 0x20},
+       {16000, 0x30},
+       {22050, 0x40},
+       {24000, 0x50},
+       {32000, 0x60},
+       {44100, 0x70},
+       {48000, 0x80},
+       {88200, 0x90},
+       {96000, 0xA0},
+};
+
+static inline int rate_value(int rate, u8 *value)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+               if (rate_table[i].rate >= rate) {
+                       *value = rate_table[i].sr;
+                       return 0;
+               }
+       }
+       *value = rate_table[0].sr;
+       return -EINVAL;
+}
+
+static int max98088_dai1_hw_params(struct snd_pcm_substream *substream,
+                                  struct snd_pcm_hw_params *params,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       unsigned long long ni;
+       unsigned int rate;
+       u8 regval;
+
+       cdata = &max98088->dai[0];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate_value(rate, &regval))
+               return -EINVAL;
+
+       snd_soc_update_bits(codec, M98088_REG_11_DAI1_CLKMODE,
+               M98088_CLKMODE_MASK, regval);
+       cdata->rate = rate;
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_14_DAI1_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0) {
+                       dev_err(codec->dev, "Invalid system clock frequency\n");
+                       return -EINVAL;
+               }
+               ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate;
+               do_div(ni, (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                       (ni >> 8) & 0x7F);
+               snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                       ni & 0xFF);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN,
+               M98088_SHDNRUN);
+
+       return 0;
+}
+
+static int max98088_dai2_hw_params(struct snd_pcm_substream *substream,
+                                  struct snd_pcm_hw_params *params,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       unsigned long long ni;
+       unsigned int rate;
+       u8 regval;
+
+       cdata = &max98088->dai[1];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate_value(rate, &regval))
+               return -EINVAL;
+
+       snd_soc_update_bits(codec, M98088_REG_19_DAI2_CLKMODE,
+               M98088_CLKMODE_MASK, regval);
+       cdata->rate = rate;
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_1C_DAI2_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0) {
+                       dev_err(codec->dev, "Invalid system clock frequency\n");
+                       return -EINVAL;
+               }
+               ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate;
+               do_div(ni, (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                       (ni >> 8) & 0x7F);
+               snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                       ni & 0xFF);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN,
+               M98088_SHDNRUN);
+
+       return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+                                  int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       /* Requested clock frequency is already setup */
+       if (freq == max98088->sysclk)
+               return 0;
+
+       max98088->sysclk = freq; /* remember current sysclk */
+
+       /* Setup clocks for slave mode, and using the PLL
+        * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+        *         0x02 (when master clk is 20MHz to 30MHz)..
+        */
+       if ((freq >= 10000000) && (freq < 20000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+       } else if ((freq >= 20000000) && (freq < 30000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+       } else {
+               dev_err(codec->dev, "Invalid master clock frequency\n");
+               return -EINVAL;
+       }
+
+       if (snd_soc_read(codec, M98088_REG_51_PWR_SYS)  & M98088_SHDNRUN) {
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN, 0);
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN, M98088_SHDNRUN);
+       }
+
+       dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+       max98088->sysclk = freq;
+       return 0;
+}
+
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       u8 reg15val;
+       u8 reg14val = 0;
+
+       cdata = &max98088->dai[0];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* Slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* Set to master mode */
+                       reg14val |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg14val |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg14val |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg14val |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg14val |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI |
+                       M98088_DAI_WCI, reg14val);
+
+               reg15val = M98088_DAI_BSEL64;
+               if (max98088->digmic)
+                       reg15val |= M98088_DAI_OSR64;
+               snd_soc_write(codec, M98088_REG_15_DAI1_CLOCK, reg15val);
+       }
+
+       return 0;
+}
+
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       u8 reg1Cval = 0;
+
+       cdata = &max98088->dai[1];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* Slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* Set to master mode */
+                       reg1Cval |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg1Cval |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg1Cval |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg1Cval |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg1Cval |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI |
+                       M98088_DAI_WCI, reg1Cval);
+
+               snd_soc_write(codec, M98088_REG_1D_DAI2_CLOCK,
+                       M98088_DAI_BSEL64);
+       }
+
+       return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       int i;
+
+       if (!codec->cache_sync)
+               return;
+
+       codec->cache_only = 0;
+
+       /* write back cached values if they're writeable and
+        * different from the hardware default.
+        */
+       for (i = 1; i < ARRAY_SIZE(max98088->reg_cache); i++) {
+               if (!max98088_access[i].writable)
+                       continue;
+
+               if (max98088->reg_cache[i] == max98088_reg[i])
+                       continue;
+
+               snd_soc_write(codec, i, max98088->reg_cache[i]);
+       }
+
+       codec->cache_sync = 0;
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+                                  enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               if (codec->bias_level == SND_SOC_BIAS_OFF)
+                       max98088_sync_cache(codec);
+
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, M98088_MBEN);
+               break;
+
+       case SND_SOC_BIAS_OFF:
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, 0);
+               codec->cache_sync = 1;
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai1_set_fmt,
+       .hw_params = max98088_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai2_set_fmt,
+       .hw_params = max98088_dai2_hw_params,
+};
+
+static struct snd_soc_dai_driver max98088_dai[] = {
+{
+       .name = "HiFi",
+       .playback = {
+               .stream_name = "HiFi Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .capture = {
+               .stream_name = "HiFi Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+        .ops = &max98088_dai1_ops,
+},
+{
+       .name = "Aux",
+       .playback = {
+               .stream_name = "Aux Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .ops = &max98088_dai2_ops,
+}
+};
+
+static int max98088_get_channel(const char *name)
+{
+       if (strcmp(name, "EQ1 Mode") == 0)
+               return 0;
+       if (strcmp(name, "EQ2 Mode") == 0)
+               return 1;
+       return -EINVAL;
+}
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !max98088->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+       sel = cdata->eq_sel;
+
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq_cfgcnt; i++) {
+               if (strcmp(pdata->eq_cfg[i].name, max98088->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq_cfg[best].name,
+               pdata->eq_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+       coef_set = &pdata->eq_cfg[sel];
+
+       m98088_eq_band(codec, 0, 0, coef_set->band1);
+       m98088_eq_band(codec, 0, 1, coef_set->band2);
+       m98088_eq_band(codec, 0, 2, coef_set->band3);
+       m98088_eq_band(codec, 0, 3, coef_set->band4);
+       m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+       /* Restore the original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !max98088->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->eq_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq_cfgcnt; i++) {
+               if (strcmp(pdata->eq_cfg[i].name, max98088->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq_cfg[best].name,
+               pdata->eq_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+       coef_set = &pdata->eq_cfg[sel];
+
+       m98088_eq_band(codec, 1, 0, coef_set->band1);
+       m98088_eq_band(codec, 1, 1, coef_set->band2);
+       m98088_eq_band(codec, 1, 2, coef_set->band3);
+       m98088_eq_band(codec, 1, 3, coef_set->band4);
+       m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+       /* Restore the original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+               save);
+}
+
+static int max98088_put_eq_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       int channel = max98088_get_channel(kcontrol->id.name);
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[channel];
+
+       if (sel >= pdata->eq_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+
+       switch (channel) {
+       case 0:
+               max98088_setup_eq1(codec);
+               break;
+       case 1:
+               max98088_setup_eq2(codec);
+               break;
+       }
+
+       return 0;
+}
+
+static int max98088_get_eq_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       int channel = max98088_get_channel(kcontrol->id.name);
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[channel];
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static void max98088_handle_eq_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *cfg;
+       unsigned int cfgcnt;
+       int i, j;
+       const char **t;
+       int ret;
+
+       struct snd_kcontrol_new controls[] = {
+               SOC_ENUM_EXT("EQ1 Mode",
+                       max98088->eq_enum,
+                       max98088_get_eq_enum,
+                       max98088_put_eq_enum),
+               SOC_ENUM_EXT("EQ2 Mode",
+                       max98088->eq_enum,
+                       max98088_get_eq_enum,
+                       max98088_put_eq_enum),
+       };
+
+       cfg = pdata->eq_cfg;
+       cfgcnt = pdata->eq_cfgcnt;
+
+       /* Setup an array of texts for the equalizer enum.
+        * This is based on Mark Brown's equalizer driver code.
+        */
+       max98088->eq_textcnt = 0;
+       max98088->eq_texts = NULL;
+       for (i = 0; i < cfgcnt; i++) {
+               for (j = 0; j < max98088->eq_textcnt; j++) {
+                       if (strcmp(cfg[i].name, max98088->eq_texts[j]) == 0)
+                               break;
+               }
+
+               if (j != max98088->eq_textcnt)
+                       continue;
+
+               /* Expand the array */
+               t = krealloc(max98088->eq_texts,
+                            sizeof(char *) * (max98088->eq_textcnt + 1),
+                            GFP_KERNEL);
+               if (t == NULL)
+                       continue;
+
+               /* Store the new entry */
+               t[max98088->eq_textcnt] = cfg[i].name;
+               max98088->eq_textcnt++;
+               max98088->eq_texts = t;
+       }
+
+       /* Now point the soc_enum to .texts array items */
+       max98088->eq_enum.texts = max98088->eq_texts;
+       max98088->eq_enum.max = max98088->eq_textcnt;
+
+       ret = snd_soc_add_controls(codec, controls, ARRAY_SIZE(controls));
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       u8 regval = 0;
+
+       if (!pdata) {
+               dev_dbg(codec->dev, "No platform data\n");
+               return;
+       }
+
+       /* Configure mic for analog/digital mic mode */
+       if (pdata->digmic_left_mode)
+               regval |= M98088_DIGMIC_L;
+
+       if (pdata->digmic_right_mode)
+               regval |= M98088_DIGMIC_R;
+
+       max98088->digmic = (regval ? 1 : 0);
+
+       snd_soc_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+       /* Configure receiver output */
+       regval = ((pdata->receiver_mode) ? M98088_REC_LINEMODE : 0);
+       snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+               M98088_REC_LINEMODE_MASK, regval);
+
+       /* Configure equalizers */
+       if (pdata->eq_cfgcnt)
+               max98088_handle_eq_pdata(codec);
+}
+
+#ifdef CONFIG_PM
+static int max98088_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static int max98088_resume(struct snd_soc_codec *codec)
+{
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+}
+#else
+#define max98088_suspend NULL
+#define max98088_resume NULL
+#endif
+
+static int max98088_probe(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       int ret = 0;
+
+       codec->cache_sync = 1;
+       memcpy(codec->reg_cache, max98088_reg, sizeof(max98088_reg));
+
+       ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+               return ret;
+       }
+
+       /* initalize private data */
+
+       max98088->sysclk = (unsigned)-1;
+       max98088->eq_textcnt = 0;
+
+       cdata = &max98088->dai[0];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_sel = 0;
+
+       cdata = &max98088->dai[1];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_sel = 0;
+
+       max98088->ina_state = 0;
+       max98088->inb_state = 0;
+       max98088->ex_mode = 0;
+       max98088->digmic = 0;
+       max98088->mic1pre = 0;
+       max98088->mic2pre = 0;
+
+       ret = snd_soc_read(codec, M98088_REG_FF_REV_ID);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to read device revision: %d\n",
+                       ret);
+               goto err_access;
+       }
+       dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+       snd_soc_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+
+       /* initialize registers cache to hardware default */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       snd_soc_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+       snd_soc_write(codec, M98088_REG_22_MIX_DAC,
+               M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+               M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+       snd_soc_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+       snd_soc_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+       snd_soc_write(codec, M98088_REG_16_DAI1_IOCFG,
+               M98088_S1NORMAL|M98088_SDATA);
+
+       snd_soc_write(codec, M98088_REG_1E_DAI2_IOCFG,
+               M98088_S2NORMAL|M98088_SDATA);
+
+       max98088_handle_pdata(codec);
+
+       max98088_add_widgets(codec);
+
+err_access:
+       return ret;
+}
+
+static int max98088_remove(struct snd_soc_codec *codec)
+{
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_max98088 = {
+       .probe   = max98088_probe,
+       .remove  = max98088_remove,
+       .suspend = max98088_suspend,
+       .resume  = max98088_resume,
+       .set_bias_level = max98088_set_bias_level,
+       .reg_cache_size = ARRAY_SIZE(max98088_reg),
+       .reg_word_size = sizeof(u8),
+       .reg_cache_default = max98088_reg,
+       .volatile_register = max98088_volatile_register,
+};
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+                            const struct i2c_device_id *id)
+{
+       struct max98088_priv *max98088;
+       int ret;
+
+       max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+       if (max98088 == NULL)
+               return -ENOMEM;
+
+       i2c_set_clientdata(i2c, max98088);
+       max98088->control_data = i2c;
+       max98088->pdata = i2c->dev.platform_data;
+
+       ret = snd_soc_register_codec(&i2c->dev,
+                       &soc_codec_dev_max98088, &max98088_dai[0], 2);
+       if (ret < 0)
+               kfree(max98088);
+       return ret;
+}
+
+static int max98088_i2c_remove(struct i2c_client *client)
+{
+       snd_soc_unregister_codec(&client->dev);
+       kfree(i2c_get_clientdata(client));
+       return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+       { "max98088", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+static struct i2c_driver max98088_i2c_driver = {
+       .driver = {
+               .name = "max98088",
+               .owner = THIS_MODULE,
+       },
+       .probe  = max98088_i2c_probe,
+       .remove = __devexit_p(max98088_i2c_remove),
+       .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&max98088_i2c_driver);
+       if (ret)
+               pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+       return ret;
+}
+module_init(max98088_init);
+
+static void __exit max98088_exit(void)
+{
+       i2c_del_driver(&max98088_i2c_driver);
+}
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang, Jesse Marroquin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h
new file mode 100644
index 0000000..e46b258
--- /dev/null
+++ b/sound/soc/codecs/max98088.h
@@ -0,0 +1,193 @@
+/*
+ * max98088.h -- MAX98088 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 _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS            0x00
+#define M98088_REG_01_MIC_STATUS            0x01
+#define M98088_REG_02_JACK_STAUS            0x02
+#define M98088_REG_03_BATTERY_VOLTAGE       0x03
+#define M98088_REG_0F_IRQ_ENABLE            0x0F
+#define M98088_REG_10_SYS_CLK               0x10
+#define M98088_REG_11_DAI1_CLKMODE          0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI        0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO        0x13
+#define M98088_REG_14_DAI1_FORMAT           0x14
+#define M98088_REG_15_DAI1_CLOCK            0x15
+#define M98088_REG_16_DAI1_IOCFG            0x16
+#define M98088_REG_17_DAI1_TDM              0x17
+#define M98088_REG_18_DAI1_FILTERS          0x18
+#define M98088_REG_19_DAI2_CLKMODE          0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI        0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO        0x1B
+#define M98088_REG_1C_DAI2_FORMAT           0x1C
+#define M98088_REG_1D_DAI2_CLOCK            0x1D
+#define M98088_REG_1E_DAI2_IOCFG            0x1E
+#define M98088_REG_1F_DAI2_TDM              0x1F
+#define M98088_REG_20_DAI2_FILTERS          0x20
+#define M98088_REG_21_SRC                   0x21
+#define M98088_REG_22_MIX_DAC               0x22
+#define M98088_REG_23_MIX_ADC_LEFT          0x23
+#define M98088_REG_24_MIX_ADC_RIGHT         0x24
+#define M98088_REG_25_MIX_HP_LEFT           0x25
+#define M98088_REG_26_MIX_HP_RIGHT          0x26
+#define M98088_REG_27_MIX_HP_CNTL           0x27
+#define M98088_REG_28_MIX_REC_LEFT          0x28
+#define M98088_REG_29_MIX_REC_RIGHT         0x29
+#define M98088_REG_2A_MIC_REC_CNTL          0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT          0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT         0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL          0x2D
+#define M98088_REG_2E_LVL_SIDETONE          0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY         0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ      0x30
+#define M98088_REG_31_LVL_DAI2_PLAY         0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ      0x32
+#define M98088_REG_33_LVL_ADC_L             0x33
+#define M98088_REG_34_LVL_ADC_R             0x34
+#define M98088_REG_35_LVL_MIC1              0x35
+#define M98088_REG_36_LVL_MIC2              0x36
+#define M98088_REG_37_LVL_INA               0x37
+#define M98088_REG_38_LVL_INB               0x38
+#define M98088_REG_39_LVL_HP_L              0x39
+#define M98088_REG_3A_LVL_HP_R              0x3A
+#define M98088_REG_3B_LVL_REC_L             0x3B
+#define M98088_REG_3C_LVL_REC_R             0x3C
+#define M98088_REG_3D_LVL_SPK_L             0x3D
+#define M98088_REG_3E_LVL_SPK_R             0x3E
+#define M98088_REG_3F_MICAGC_CFG            0x3F
+#define M98088_REG_40_MICAGC_THRESH         0x40
+#define M98088_REG_41_SPKDHP                0x41
+#define M98088_REG_42_SPKDHP_THRESH         0x42
+#define M98088_REG_43_SPKALC_COMP           0x43
+#define M98088_REG_44_PWRLMT_CFG            0x44
+#define M98088_REG_45_PWRLMT_TIME           0x45
+#define M98088_REG_46_THDLMT_CFG            0x46
+#define M98088_REG_47_CFG_AUDIO_IN          0x47
+#define M98088_REG_48_CFG_MIC               0x48
+#define M98088_REG_49_CFG_LEVEL             0x49
+#define M98088_REG_4A_CFG_BYPASS            0x4A
+#define M98088_REG_4B_CFG_JACKDET           0x4B
+#define M98088_REG_4C_PWR_EN_IN             0x4C
+#define M98088_REG_4D_PWR_EN_OUT            0x4D
+#define M98088_REG_4E_BIAS_CNTL             0x4E
+#define M98088_REG_4F_DAC_BIAS1             0x4F
+#define M98088_REG_50_DAC_BIAS2             0x50
+#define M98088_REG_51_PWR_SYS               0x51
+#define M98088_REG_52_DAI1_EQ_BASE          0x52
+#define M98088_REG_84_DAI2_EQ_BASE          0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE      0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE      0xC0
+#define M98088_REG_FF_REV_ID                0xFF
+
+#define M98088_REG_CNT                      (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_11_DAI1_CLKMODE, M98088_REG_19_DAI2_CLKMODE */
+       #define M98088_CLKMODE_MASK             0xFF
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+       #define M98088_DAI_MAS                  (1<<7)
+       #define M98088_DAI_WCI                  (1<<6)
+       #define M98088_DAI_BCI                  (1<<5)
+       #define M98088_DAI_DLY                  (1<<4)
+       #define M98088_DAI_TDM                  (1<<2)
+       #define M98088_DAI_FSW                  (1<<1)
+       #define M98088_DAI_WS                   (1<<0)
+
+/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */
+       #define M98088_DAI_BSEL64               (1<<0)
+       #define M98088_DAI_OSR64                (1<<6)
+
+/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */
+       #define M98088_S1NORMAL                 (1<<6)
+       #define M98088_S2NORMAL                 (2<<6)
+       #define M98088_SDATA                    (3<<0)
+
+/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */
+       #define M98088_DAI_DHF                  (1<<3)
+
+/* M98088_REG_22_MIX_DAC */
+       #define M98088_DAI1L_TO_DACL            (1<<7)
+       #define M98088_DAI1R_TO_DACL            (1<<6)
+       #define M98088_DAI2L_TO_DACL            (1<<5)
+       #define M98088_DAI2R_TO_DACL            (1<<4)
+       #define M98088_DAI1L_TO_DACR            (1<<3)
+       #define M98088_DAI1R_TO_DACR            (1<<2)
+       #define M98088_DAI2L_TO_DACR            (1<<1)
+       #define M98088_DAI2R_TO_DACR            (1<<0)
+
+/* M98088_REG_2A_MIC_REC_CNTL */
+       #define M98088_REC_LINEMODE             (1<<7)
+       #define M98088_REC_LINEMODE_MASK        (1<<7)
+
+/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */
+       #define M98088_MICPRE_MASK              (3<<5)
+       #define M98088_MICPRE_SHIFT             5
+
+/* M98088_REG_3A_LVL_HP_R */
+       #define M98088_HP_MUTE                  (1<<7)
+
+/* M98088_REG_3C_LVL_REC_R */
+       #define M98088_REC_MUTE                 (1<<7)
+
+/* M98088_REG_3E_LVL_SPK_R */
+       #define M98088_SP_MUTE                  (1<<7)
+
+/* M98088_REG_48_CFG_MIC */
+       #define M98088_EXTMIC_MASK              (3<<0)
+       #define M98088_DIGMIC_L                 (1<<5)
+       #define M98088_DIGMIC_R                 (1<<4)
+
+/* M98088_REG_49_CFG_LEVEL */
+       #define M98088_VSEN                     (1<<6)
+       #define M98088_ZDEN                     (1<<5)
+       #define M98088_EQ2EN                    (1<<1)
+       #define M98088_EQ1EN                    (1<<0)
+
+/* M98088_REG_4C_PWR_EN_IN */
+       #define M98088_INAEN                    (1<<7)
+       #define M98088_INBEN                    (1<<6)
+       #define M98088_MBEN                     (1<<3)
+       #define M98088_ADLEN                    (1<<1)
+       #define M98088_ADREN                    (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+       #define M98088_HPLEN                    (1<<7)
+       #define M98088_HPREN                    (1<<6)
+       #define M98088_HPEN                     ((1<<7)|(1<<6))
+       #define M98088_SPLEN                    (1<<5)
+       #define M98088_SPREN                    (1<<4)
+       #define M98088_RECEN                    (1<<3)
+       #define M98088_DALEN                    (1<<1)
+       #define M98088_DAREN                    (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+       #define M98088_SHDNRUN                  (1<<7)
+       #define M98088_PERFMODE                 (1<<3)
+       #define M98088_HPPLYBACK                (1<<2)
+       #define M98088_PWRSV8K                  (1<<1)
+       #define M98088_PWRSV                    (1<<0)
+
+/* Line inputs */
+#define LINE_INA  0
+#define LINE_INB  1
+
+#define M98088_COEFS_PER_BAND               5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+#endif
--
1.6.3.3


^ permalink raw reply related	[flat|nested] 46+ messages in thread

* RE: [PATCH] ASoC: Add max98088 CODEC driver
  2010-10-13 10:32     ` Mark Brown
@ 2010-10-14  3:18       ` Peter Hsiang
  -1 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-10-14  3:18 UTC (permalink / raw)
  To: Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Wed, Oct 13, 2010, Mark Brown wrote:
> On Tue, Oct 12, 2010 at 06:20:10PM -0700, Peter Hsiang wrote:
> 
> This looks pretty good, waiting for Liam's review but I think we can do
> further stuff incrementally.  Minor comments:
> 
> > +
> > +       /* Setup an array of texts for the equalizer enum.
> > +        * This is based on Mark Brown's equalizer driver code.
> > +        * It has been extended to support multiple equalizers.
> > +        */
> 
> The code that you're basing this on supports multiple equalizers too -
> things like the WM8994 have several.  Given that all those supported by
> the original code are identical there didn't seem to be any point in
> restricting which EQ a given setup can be deployed on - it's just one
> more thing that can go wrong.

Thanks - that is a very good idea.  I have implemented it and will
submit it shortly.


^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-10-14  3:18       ` Peter Hsiang
  0 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-10-14  3:18 UTC (permalink / raw)
  To: Mark Brown
  Cc: alsa-devel, Jesse, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Marroquin, Liam, Girdwood

On Wed, Oct 13, 2010, Mark Brown wrote:
> On Tue, Oct 12, 2010 at 06:20:10PM -0700, Peter Hsiang wrote:
> 
> This looks pretty good, waiting for Liam's review but I think we can do
> further stuff incrementally.  Minor comments:
> 
> > +
> > +       /* Setup an array of texts for the equalizer enum.
> > +        * This is based on Mark Brown's equalizer driver code.
> > +        * It has been extended to support multiple equalizers.
> > +        */
> 
> The code that you're basing this on supports multiple equalizers too -
> things like the WM8994 have several.  Given that all those supported by
> the original code are identical there didn't seem to be any point in
> restricting which EQ a given setup can be deployed on - it's just one
> more thing that can go wrong.

Thanks - that is a very good idea.  I have implemented it and will
submit it shortly.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-10-13  1:20 ` Peter Hsiang
@ 2010-10-13 10:32     ` Mark Brown
  2010-10-13 10:32     ` Mark Brown
  2010-10-14  3:30   ` Peter Hsiang
  2 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-10-13 10:32 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Tue, Oct 12, 2010 at 06:20:10PM -0700, Peter Hsiang wrote:

This looks pretty good, waiting for Liam's review but I think we can do
further stuff incrementally.  Minor comments:

> +
> +       /* Setup an array of texts for the equalizer enum.
> +        * This is based on Mark Brown's equalizer driver code.
> +        * It has been extended to support multiple equalizers.
> +        */

The code that you're basing this on supports multiple equalizers too -
things like the WM8994 have several.  Given that all those supported by
the original code are identical there didn't seem to be any point in
restricting which EQ a given setup can be deployed on - it's just one
more thing that can go wrong.

> +static int max98088_remove(struct snd_soc_codec *codec)
> +{
> +       if (codec->control_data)
> +               max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);

The control_data check should be redundant here.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-10-13 10:32     ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-10-13 10:32 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Jesse Marroquin, Liam Girdwood

On Tue, Oct 12, 2010 at 06:20:10PM -0700, Peter Hsiang wrote:

This looks pretty good, waiting for Liam's review but I think we can do
further stuff incrementally.  Minor comments:

> +
> +       /* Setup an array of texts for the equalizer enum.
> +        * This is based on Mark Brown's equalizer driver code.
> +        * It has been extended to support multiple equalizers.
> +        */

The code that you're basing this on supports multiple equalizers too -
things like the WM8994 have several.  Given that all those supported by
the original code are identical there didn't seem to be any point in
restricting which EQ a given setup can be deployed on - it's just one
more thing that can go wrong.

> +static int max98088_remove(struct snd_soc_codec *codec)
> +{
> +       if (codec->control_data)
> +               max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);

The control_data check should be redundant here.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-10-13  1:47   ` Joe Perches
@ 2010-10-13  8:24       ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-10-13  8:24 UTC (permalink / raw)
  To: Joe Perches
  Cc: Peter Hsiang, Jaroslav Kysela, Takashi Iwai, Liam Girdwood,
	Peter Ujfalusi, alsa-devel, linux-kernel, Jesse Marroquin

On Tue, Oct 12, 2010 at 06:47:52PM -0700, Joe Perches wrote:
> On Tue, 2010-10-12 at 18:20 -0700, Peter Hsiang wrote:

> > +static struct {
> > +       int readable;
> > +       int writable;
> > +       int vol;
> > +} max98088_access[M98088_REG_CNT] = {

> bool instead?

> static struct {
> 	bool readable;
> 	bool writable,
> 	bool vol;
> } etc...

The readable and writable fields are being used as bitmasks:

| +       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */

so this discards data which we may wish to use in future.

> readable isn't used anywhere, writeable is used as bool
> vol isn't a good name as it's easy to confuse with
> volume.  Maybe volatile_register?

vol is traditionally used for this throughout the subsystem.  It's
unfortuante that volatile is a keyword.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-10-13  8:24       ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-10-13  8:24 UTC (permalink / raw)
  To: Joe Perches
  Cc: alsa-devel, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Peter Hsiang, Jesse Marroquin, Liam Girdwood

On Tue, Oct 12, 2010 at 06:47:52PM -0700, Joe Perches wrote:
> On Tue, 2010-10-12 at 18:20 -0700, Peter Hsiang wrote:

> > +static struct {
> > +       int readable;
> > +       int writable;
> > +       int vol;
> > +} max98088_access[M98088_REG_CNT] = {

> bool instead?

> static struct {
> 	bool readable;
> 	bool writable,
> 	bool vol;
> } etc...

The readable and writable fields are being used as bitmasks:

| +       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */

so this discards data which we may wish to use in future.

> readable isn't used anywhere, writeable is used as bool
> vol isn't a good name as it's easy to confuse with
> volume.  Maybe volatile_register?

vol is traditionally used for this throughout the subsystem.  It's
unfortuante that volatile is a keyword.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-10-13  1:20 ` Peter Hsiang
@ 2010-10-13  1:47   ` Joe Perches
  2010-10-13  8:24       ` Mark Brown
  2010-10-13 10:32     ` Mark Brown
  2010-10-14  3:30   ` Peter Hsiang
  2 siblings, 1 reply; 46+ messages in thread
From: Joe Perches @ 2010-10-13  1:47 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown,
	Peter Ujfalusi, alsa-devel, linux-kernel, Jesse Marroquin

On Tue, 2010-10-12 at 18:20 -0700, Peter Hsiang wrote:
> This patch adds the MAX98088 CODEC driver.

Just trivial comments:

> +static struct {
> +       int readable;
> +       int writable;
> +       int vol;
> +} max98088_access[M98088_REG_CNT] = {

bool instead?

static struct {
	bool readable;
	bool writable,
	bool vol;
} etc...

readable isn't used anywhere, writeable is used as bool
vol isn't a good name as it's easy to confuse with
volume.  Maybe volatile_register?

> +static int max98088_volatile_register(unsigned int reg)
> +{
> +       return max98088_access[reg].vol;
> +}

No error checking if reg isn't limited to the array size.

> +static const char *max98088_exmode_texts[] = {
[]
> +static const char *max98088_ex_thresh[] = { /* volts PP */
[]
> +static const struct soc_enum max98088_ex_thresh_enum[] = {
[]
> +static const char *max98088_fltr_mode[] = {"Voice", "Music" };
[]
> +static const char *max98088_extmic_text[] = { "None", "MIC1", "MIC2" };
[]
> +static const char *max98088_dai1_fltr[] = {

static const char * const



^ permalink raw reply	[flat|nested] 46+ messages in thread

* [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-29  2:34 ` Peter Hsiang
  (?)
  (?)
@ 2010-10-13  1:20 ` Peter Hsiang
  2010-10-13  1:47   ` Joe Perches
                     ` (2 more replies)
  -1 siblings, 3 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-10-13  1:20 UTC (permalink / raw)
  To: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, Peter Ujfalusi
  Cc: alsa-devel, linux-kernel, Jesse Marroquin

This patch adds the MAX98088 CODEC driver.

Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
---
 include/sound/max98088.h    |   52 ++
 sound/soc/codecs/Kconfig    |    4 +
 sound/soc/codecs/Makefile   |    2 +
 sound/soc/codecs/max98088.c | 2141 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/max98088.h |  193 ++++
 5 files changed, 2392 insertions(+), 0 deletions(-)
 create mode 100644 include/sound/max98088.h
 create mode 100644 sound/soc/codecs/max98088.c
 create mode 100644 sound/soc/codecs/max98088.h

diff --git a/include/sound/max98088.h b/include/sound/max98088.h
new file mode 100644
index 0000000..0558ae0
--- /dev/null
+++ b/include/sound/max98088.h
@@ -0,0 +1,52 @@
+/*
+ * Platform data for MAX98088
+ *
+ * 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 as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __SOUND_MAX98088_PDATA_H__
+#define __SOUND_MAX98088_PDATA_H__
+
+/* Equalizer filter response configuration */
+struct max98088_eq_cfg {
+       const char *name;
+       unsigned int rate;
+       u16 band1[5];
+       u16 band2[5];
+       u16 band3[5];
+       u16 band4[5];
+       u16 band5[5];
+};
+
+/* codec platform data */
+struct max98088_pdata {
+
+       /* Equalizers for DAI1 and DAI2 */
+       struct max98088_eq_cfg *eq1_cfg;
+       struct max98088_eq_cfg *eq2_cfg;
+       unsigned int eq1_cfgcnt;
+       unsigned int eq2_cfgcnt;
+
+       /* Receiver output can be configured as power amplifier or LINE out */
+       /* Set receiver_mode to:
+        * 0 = amplifier output, or
+        * 1 = LINE level output
+        */
+       unsigned int receiver_mode:1;
+
+       /* Analog/digital microphone configuration:
+        * 0 = analog microphone input (normal setting)
+        * 1 = digital microphone input
+        */
+       unsigned int digmic_left_mode:1;
+       unsigned int digmic_right_mode:1;
+
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index ff7b922..53af7d2 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -27,6 +27,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_CS4270 if I2C
        select SND_SOC_DA7210 if I2C
        select SND_SOC_JZ4740 if SOC_JZ4740
+       select SND_SOC_MAX98088 if I2C
        select SND_SOC_MAX9877 if I2C
        select SND_SOC_PCM3008
        select SND_SOC_SPDIF
@@ -158,6 +159,9 @@ config SND_SOC_L3
 config SND_SOC_DA7210
         tristate

+config SND_SOC_MAX98088
+       tristate
+
 config SND_SOC_PCM3008
        tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index af4d4c4..2d4941d 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -15,6 +15,7 @@ snd-soc-cs4270-objs := cs4270.o
 snd-soc-cx20442-objs := cx20442.o
 snd-soc-da7210-objs := da7210.o
 snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
 snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-spdif-objs := spdif_transciever.o
 snd-soc-ssm2602-objs := ssm2602.o
@@ -89,6 +90,7 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
 obj-$(CONFIG_SND_SOC_DA7210)   += snd-soc-da7210.o
 obj-$(CONFIG_SND_SOC_L3)       += snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_JZ4740_CODEC)     += snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088)  += snd-soc-max98088.o
 obj-$(CONFIG_SND_SOC_PCM3008)  += snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_SPDIF)    += snd-soc-spdif.o
 obj-$(CONFIG_SND_SOC_SSM2602)  += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
new file mode 100644
index 0000000..8d56fb8
--- /dev/null
+++ b/sound/soc/codecs/max98088.c
@@ -0,0 +1,2141 @@
+/*
+ * max98088.c -- MAX98088 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 <linux/slab.h>
+#include <asm/div64.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+struct max98088_cdata {
+       unsigned int rate;
+       unsigned int fmt;
+       int eq_textcnt;
+       const char **eq_texts;
+       int eq_sel;
+       struct soc_enum eq_enum;
+};
+
+struct max98088_priv {
+       u8 reg_cache[M98088_REG_CNT];
+       void *control_data;
+       struct max98088_pdata *pdata;
+
+       unsigned int sysclk;
+       struct max98088_cdata dai[2];
+       u8 ina_state;
+       u8 inb_state;
+       unsigned int ex_mode;
+       unsigned int digmic;
+       unsigned int mic1pre;
+       unsigned int mic2pre;
+       unsigned int extmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+       0x00, /* 00 IRQ status */
+       0x00, /* 01 MIC status */
+       0x00, /* 02 jack status */
+       0x00, /* 03 battery voltage */
+       0x00, /* 04 */
+       0x00, /* 05 */
+       0x00, /* 06 */
+       0x00, /* 07 */
+       0x00, /* 08 */
+       0x00, /* 09 */
+       0x00, /* 0A */
+       0x00, /* 0B */
+       0x00, /* 0C */
+       0x00, /* 0D */
+       0x00, /* 0E */
+       0x00, /* 0F interrupt enable */
+
+       0x00, /* 10 master clock */
+       0x00, /* 11 DAI1 clock mode */
+       0x00, /* 12 DAI1 clock control */
+       0x00, /* 13 DAI1 clock control */
+       0x00, /* 14 DAI1 format */
+       0x00, /* 15 DAI1 clock */
+       0x00, /* 16 DAI1 config */
+       0x00, /* 17 DAI1 TDM */
+       0x00, /* 18 DAI1 filters */
+       0x00, /* 19 DAI2 clock mode */
+       0x00, /* 1A DAI2 clock control */
+       0x00, /* 1B DAI2 clock control */
+       0x00, /* 1C DAI2 format */
+       0x00, /* 1D DAI2 clock */
+       0x00, /* 1E DAI2 config */
+       0x00, /* 1F DAI2 TDM */
+
+       0x00, /* 20 DAI2 filters */
+       0x00, /* 21 data config */
+       0x00, /* 22 DAC mixer */
+       0x00, /* 23 left ADC mixer */
+       0x00, /* 24 right ADC mixer */
+       0x00, /* 25 left HP mixer */
+       0x00, /* 26 right HP mixer */
+       0x00, /* 27 HP control */
+       0x00, /* 28 left REC mixer */
+       0x00, /* 29 right REC mixer */
+       0x00, /* 2A REC control */
+       0x00, /* 2B left SPK mixer */
+       0x00, /* 2C right SPK mixer */
+       0x00, /* 2D SPK control */
+       0x00, /* 2E sidetone */
+       0x00, /* 2F DAI1 playback level */
+
+       0x00, /* 30 DAI1 playback level */
+       0x00, /* 31 DAI2 playback level */
+       0x00, /* 32 DAI2 playbakc level */
+       0x00, /* 33 left ADC level */
+       0x00, /* 34 right ADC level */
+       0x00, /* 35 MIC1 level */
+       0x00, /* 36 MIC2 level */
+       0x00, /* 37 INA level */
+       0x00, /* 38 INB level */
+       0x00, /* 39 left HP volume */
+       0x00, /* 3A right HP volume */
+       0x00, /* 3B left REC volume */
+       0x00, /* 3C right REC volume */
+       0x00, /* 3D left SPK volume */
+       0x00, /* 3E right SPK volume */
+       0x00, /* 3F MIC config */
+
+       0x00, /* 40 MIC threshold */
+       0x00, /* 41 excursion limiter filter */
+       0x00, /* 42 excursion limiter threshold */
+       0x00, /* 43 ALC */
+       0x00, /* 44 power limiter threshold */
+       0x00, /* 45 power limiter config */
+       0x00, /* 46 distortion limiter config */
+       0x00, /* 47 audio input */
+       0x00, /* 48 microphone */
+       0x00, /* 49 level control */
+       0x00, /* 4A bypass switches */
+       0x00, /* 4B jack detect */
+       0x00, /* 4C input enable */
+       0x00, /* 4D output enable */
+       0xF0, /* 4E bias control */
+       0x00, /* 4F DAC power */
+
+       0x0F, /* 50 DAC power */
+       0x00, /* 51 system */
+       0x00, /* 52 DAI1 EQ1 */
+       0x00, /* 53 DAI1 EQ1 */
+       0x00, /* 54 DAI1 EQ1 */
+       0x00, /* 55 DAI1 EQ1 */
+       0x00, /* 56 DAI1 EQ1 */
+       0x00, /* 57 DAI1 EQ1 */
+       0x00, /* 58 DAI1 EQ1 */
+       0x00, /* 59 DAI1 EQ1 */
+       0x00, /* 5A DAI1 EQ1 */
+       0x00, /* 5B DAI1 EQ1 */
+       0x00, /* 5C DAI1 EQ2 */
+       0x00, /* 5D DAI1 EQ2 */
+       0x00, /* 5E DAI1 EQ2 */
+       0x00, /* 5F DAI1 EQ2 */
+
+       0x00, /* 60 DAI1 EQ2 */
+       0x00, /* 61 DAI1 EQ2 */
+       0x00, /* 62 DAI1 EQ2 */
+       0x00, /* 63 DAI1 EQ2 */
+       0x00, /* 64 DAI1 EQ2 */
+       0x00, /* 65 DAI1 EQ2 */
+       0x00, /* 66 DAI1 EQ3 */
+       0x00, /* 67 DAI1 EQ3 */
+       0x00, /* 68 DAI1 EQ3 */
+       0x00, /* 69 DAI1 EQ3 */
+       0x00, /* 6A DAI1 EQ3 */
+       0x00, /* 6B DAI1 EQ3 */
+       0x00, /* 6C DAI1 EQ3 */
+       0x00, /* 6D DAI1 EQ3 */
+       0x00, /* 6E DAI1 EQ3 */
+       0x00, /* 6F DAI1 EQ3 */
+
+       0x00, /* 70 DAI1 EQ4 */
+       0x00, /* 71 DAI1 EQ4 */
+       0x00, /* 72 DAI1 EQ4 */
+       0x00, /* 73 DAI1 EQ4 */
+       0x00, /* 74 DAI1 EQ4 */
+       0x00, /* 75 DAI1 EQ4 */
+       0x00, /* 76 DAI1 EQ4 */
+       0x00, /* 77 DAI1 EQ4 */
+       0x00, /* 78 DAI1 EQ4 */
+       0x00, /* 79 DAI1 EQ4 */
+       0x00, /* 7A DAI1 EQ5 */
+       0x00, /* 7B DAI1 EQ5 */
+       0x00, /* 7C DAI1 EQ5 */
+       0x00, /* 7D DAI1 EQ5 */
+       0x00, /* 7E DAI1 EQ5 */
+       0x00, /* 7F DAI1 EQ5 */
+
+       0x00, /* 80 DAI1 EQ5 */
+       0x00, /* 81 DAI1 EQ5 */
+       0x00, /* 82 DAI1 EQ5 */
+       0x00, /* 83 DAI1 EQ5 */
+       0x00, /* 84 DAI2 EQ1 */
+       0x00, /* 85 DAI2 EQ1 */
+       0x00, /* 86 DAI2 EQ1 */
+       0x00, /* 87 DAI2 EQ1 */
+       0x00, /* 88 DAI2 EQ1 */
+       0x00, /* 89 DAI2 EQ1 */
+       0x00, /* 8A DAI2 EQ1 */
+       0x00, /* 8B DAI2 EQ1 */
+       0x00, /* 8C DAI2 EQ1 */
+       0x00, /* 8D DAI2 EQ1 */
+       0x00, /* 8E DAI2 EQ2 */
+       0x00, /* 8F DAI2 EQ2 */
+
+       0x00, /* 90 DAI2 EQ2 */
+       0x00, /* 91 DAI2 EQ2 */
+       0x00, /* 92 DAI2 EQ2 */
+       0x00, /* 93 DAI2 EQ2 */
+       0x00, /* 94 DAI2 EQ2 */
+       0x00, /* 95 DAI2 EQ2 */
+       0x00, /* 96 DAI2 EQ2 */
+       0x00, /* 97 DAI2 EQ2 */
+       0x00, /* 98 DAI2 EQ3 */
+       0x00, /* 99 DAI2 EQ3 */
+       0x00, /* 9A DAI2 EQ3 */
+       0x00, /* 9B DAI2 EQ3 */
+       0x00, /* 9C DAI2 EQ3 */
+       0x00, /* 9D DAI2 EQ3 */
+       0x00, /* 9E DAI2 EQ3 */
+       0x00, /* 9F DAI2 EQ3 */
+
+       0x00, /* A0 DAI2 EQ3 */
+       0x00, /* A1 DAI2 EQ3 */
+       0x00, /* A2 DAI2 EQ4 */
+       0x00, /* A3 DAI2 EQ4 */
+       0x00, /* A4 DAI2 EQ4 */
+       0x00, /* A5 DAI2 EQ4 */
+       0x00, /* A6 DAI2 EQ4 */
+       0x00, /* A7 DAI2 EQ4 */
+       0x00, /* A8 DAI2 EQ4 */
+       0x00, /* A9 DAI2 EQ4 */
+       0x00, /* AA DAI2 EQ4 */
+       0x00, /* AB DAI2 EQ4 */
+       0x00, /* AC DAI2 EQ5 */
+       0x00, /* AD DAI2 EQ5 */
+       0x00, /* AE DAI2 EQ5 */
+       0x00, /* AF DAI2 EQ5 */
+
+       0x00, /* B0 DAI2 EQ5 */
+       0x00, /* B1 DAI2 EQ5 */
+       0x00, /* B2 DAI2 EQ5 */
+       0x00, /* B3 DAI2 EQ5 */
+       0x00, /* B4 DAI2 EQ5 */
+       0x00, /* B5 DAI2 EQ5 */
+       0x00, /* B6 DAI1 biquad */
+       0x00, /* B7 DAI1 biquad */
+       0x00, /* B8 DAI1 biquad */
+       0x00, /* B9 DAI1 biquad */
+       0x00, /* BA DAI1 biquad */
+       0x00, /* BB DAI1 biquad */
+       0x00, /* BC DAI1 biquad */
+       0x00, /* BD DAI1 biquad */
+       0x00, /* BE DAI1 biquad */
+       0x00, /* BF DAI1 biquad */
+
+       0x00, /* C0 DAI2 biquad */
+       0x00, /* C1 DAI2 biquad */
+       0x00, /* C2 DAI2 biquad */
+       0x00, /* C3 DAI2 biquad */
+       0x00, /* C4 DAI2 biquad */
+       0x00, /* C5 DAI2 biquad */
+       0x00, /* C6 DAI2 biquad */
+       0x00, /* C7 DAI2 biquad */
+       0x00, /* C8 DAI2 biquad */
+       0x00, /* C9 DAI2 biquad */
+       0x00, /* CA */
+       0x00, /* CB */
+       0x00, /* CC */
+       0x00, /* CD */
+       0x00, /* CE */
+       0x00, /* CF */
+
+       0x00, /* D0 */
+       0x00, /* D1 */
+       0x00, /* D2 */
+       0x00, /* D3 */
+       0x00, /* D4 */
+       0x00, /* D5 */
+       0x00, /* D6 */
+       0x00, /* D7 */
+       0x00, /* D8 */
+       0x00, /* D9 */
+       0x00, /* DA */
+       0x70, /* DB */
+       0x00, /* DC */
+       0x00, /* DD */
+       0x00, /* DE */
+       0x00, /* DF */
+
+       0x00, /* E0 */
+       0x00, /* E1 */
+       0x00, /* E2 */
+       0x00, /* E3 */
+       0x00, /* E4 */
+       0x00, /* E5 */
+       0x00, /* E6 */
+       0x00, /* E7 */
+       0x00, /* E8 */
+       0x00, /* E9 */
+       0x00, /* EA */
+       0x00, /* EB */
+       0x00, /* EC */
+       0x00, /* ED */
+       0x00, /* EE */
+       0x00, /* EF */
+
+       0x00, /* F0 */
+       0x00, /* F1 */
+       0x00, /* F2 */
+       0x00, /* F3 */
+       0x00, /* F4 */
+       0x00, /* F5 */
+       0x00, /* F6 */
+       0x00, /* F7 */
+       0x00, /* F8 */
+       0x00, /* F9 */
+       0x00, /* FA */
+       0x00, /* FB */
+       0x00, /* FC */
+       0x00, /* FD */
+       0x00, /* FE */
+       0x00, /* FF */
+};
+
+static struct {
+       int readable;
+       int writable;
+       int vol;
+} max98088_access[M98088_REG_CNT] = {
+       { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+       { 0xFF, 0x00, 1 }, /* 01 MIC status */
+       { 0xFF, 0x00, 1 }, /* 02 jack status */
+       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+       { 0xFF, 0xFF, 0 }, /* 04 */
+       { 0xFF, 0xFF, 0 }, /* 05 */
+       { 0xFF, 0xFF, 0 }, /* 06 */
+       { 0xFF, 0xFF, 0 }, /* 07 */
+       { 0xFF, 0xFF, 0 }, /* 08 */
+       { 0xFF, 0xFF, 0 }, /* 09 */
+       { 0xFF, 0xFF, 0 }, /* 0A */
+       { 0xFF, 0xFF, 0 }, /* 0B */
+       { 0xFF, 0xFF, 0 }, /* 0C */
+       { 0xFF, 0xFF, 0 }, /* 0D */
+       { 0xFF, 0xFF, 0 }, /* 0E */
+       { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+       { 0xFF, 0xFF, 0 }, /* 10 master clock */
+       { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+       { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+       { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+       { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+       { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+       { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+       { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+       { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+       { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+       { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+       { 0xFF, 0xFF, 0 }, /* 21 data config */
+       { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+       { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 27 HP control */
+       { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 2A REC control */
+       { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+       { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+       { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+       { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+       { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+       { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+       { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+       { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+       { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+       { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+       { 0xFF, 0xFF, 0 }, /* 37 INA level */
+       { 0xFF, 0xFF, 0 }, /* 38 INB level */
+       { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+       { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+       { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+       { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 43 ALC */
+       { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+       { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+       { 0xFF, 0xFF, 0 }, /* 47 audio input */
+       { 0xFF, 0xFF, 0 }, /* 48 microphone */
+       { 0xFF, 0xFF, 0 }, /* 49 level control */
+       { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+       { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+       { 0xFF, 0xFF, 0 }, /* 4C input enable */
+       { 0xFF, 0xFF, 0 }, /* 4D output enable */
+       { 0xFF, 0xFF, 0 }, /* 4E bias control */
+       { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+       { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+       { 0xFF, 0xFF, 0 }, /* 51 system */
+       { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+       { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+       { 0x00, 0x00, 0 }, /* CA */
+       { 0x00, 0x00, 0 }, /* CB */
+       { 0x00, 0x00, 0 }, /* CC */
+       { 0x00, 0x00, 0 }, /* CD */
+       { 0x00, 0x00, 0 }, /* CE */
+       { 0x00, 0x00, 0 }, /* CF */
+
+       { 0x00, 0x00, 0 }, /* D0 */
+       { 0x00, 0x00, 0 }, /* D1 */
+       { 0x00, 0x00, 0 }, /* D2 */
+       { 0x00, 0x00, 0 }, /* D3 */
+       { 0x00, 0x00, 0 }, /* D4 */
+       { 0x00, 0x00, 0 }, /* D5 */
+       { 0x00, 0x00, 0 }, /* D6 */
+       { 0x00, 0x00, 0 }, /* D7 */
+       { 0x00, 0x00, 0 }, /* D8 */
+       { 0x00, 0x00, 0 }, /* D9 */
+       { 0x00, 0x00, 0 }, /* DA */
+       { 0x00, 0x00, 0 }, /* DB */
+       { 0x00, 0x00, 0 }, /* DC */
+       { 0x00, 0x00, 0 }, /* DD */
+       { 0x00, 0x00, 0 }, /* DE */
+       { 0x00, 0x00, 0 }, /* DF */
+
+       { 0x00, 0x00, 0 }, /* E0 */
+       { 0x00, 0x00, 0 }, /* E1 */
+       { 0x00, 0x00, 0 }, /* E2 */
+       { 0x00, 0x00, 0 }, /* E3 */
+       { 0x00, 0x00, 0 }, /* E4 */
+       { 0x00, 0x00, 0 }, /* E5 */
+       { 0x00, 0x00, 0 }, /* E6 */
+       { 0x00, 0x00, 0 }, /* E7 */
+       { 0x00, 0x00, 0 }, /* E8 */
+       { 0x00, 0x00, 0 }, /* E9 */
+       { 0x00, 0x00, 0 }, /* EA */
+       { 0x00, 0x00, 0 }, /* EB */
+       { 0x00, 0x00, 0 }, /* EC */
+       { 0x00, 0x00, 0 }, /* ED */
+       { 0x00, 0x00, 0 }, /* EE */
+       { 0x00, 0x00, 0 }, /* EF */
+
+       { 0x00, 0x00, 0 }, /* F0 */
+       { 0x00, 0x00, 0 }, /* F1 */
+       { 0x00, 0x00, 0 }, /* F2 */
+       { 0x00, 0x00, 0 }, /* F3 */
+       { 0x00, 0x00, 0 }, /* F4 */
+       { 0x00, 0x00, 0 }, /* F5 */
+       { 0x00, 0x00, 0 }, /* F6 */
+       { 0x00, 0x00, 0 }, /* F7 */
+       { 0x00, 0x00, 0 }, /* F8 */
+       { 0x00, 0x00, 0 }, /* F9 */
+       { 0x00, 0x00, 0 }, /* FA */
+       { 0x00, 0x00, 0 }, /* FB */
+       { 0x00, 0x00, 0 }, /* FC */
+       { 0x00, 0x00, 0 }, /* FD */
+       { 0x00, 0x00, 0 }, /* FE */
+       { 0xFF, 0x00, 1 }, /* FF */
+};
+
+static int max98088_volatile_register(unsigned int reg)
+{
+       return max98088_access[reg].vol;
+}
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+                   unsigned int band, u16 *coefs)
+{
+       unsigned int eq_reg;
+       unsigned int i;
+
+       BUG_ON(band > 4);
+       BUG_ON(dai > 1);
+
+       /* Load the base register address */
+       eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+       /* Add the band address offset, note adjustment for word address */
+       eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+       /* Step through the registers and coefs */
+       for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+               snd_soc_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+               snd_soc_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+       }
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_exmode_texts[] = {
+       "Off", "100Hz", "400Hz", "600Hz", "800Hz", "1000Hz", "200-400Hz",
+       "400-600Hz", "400-800Hz",
+};
+
+static const unsigned int max98088_exmode_values[] = {
+       0x00, 0x43, 0x10, 0x20, 0x30, 0x40, 0x11, 0x22, 0x32
+};
+
+static const struct soc_enum max98088_exmode_enum =
+       SOC_VALUE_ENUM_SINGLE(M98088_REG_41_SPKDHP, 0, 127,
+                             ARRAY_SIZE(max98088_exmode_texts),
+                             max98088_exmode_texts,
+                             max98088_exmode_values);
+static const struct snd_kcontrol_new max98088_exmode_controls =
+       SOC_DAPM_VALUE_ENUM("Route", max98088_exmode_enum);
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+       "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+               max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_extmic_text[] = { "None", "MIC1", "MIC2" };
+
+static const struct soc_enum max98088_extmic_enum =
+       SOC_ENUM_SINGLE(M98088_REG_48_CFG_MIC, 0, 3, max98088_extmic_text);
+
+static const struct snd_kcontrol_new max98088_extmic_mux =
+       SOC_DAPM_ENUM("External MIC Mux", max98088_extmic_enum);
+
+static const char *max98088_dai1_fltr[] = {
+       "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+       "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int sel = ucontrol->value.integer.value[0];
+
+       max98088->mic1pre = sel;
+       snd_soc_update_bits(codec, M98088_REG_35_LVL_MIC1, M98088_MICPRE_MASK,
+               (1+sel)<<M98088_MICPRE_SHIFT);
+
+       return 0;
+}
+
+static int max98088_mic1pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       ucontrol->value.integer.value[0] = max98088->mic1pre;
+       return 0;
+}
+
+static int max98088_mic2pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int sel = ucontrol->value.integer.value[0];
+
+       max98088->mic2pre = sel;
+       snd_soc_update_bits(codec, M98088_REG_36_LVL_MIC2, M98088_MICPRE_MASK,
+               (1+sel)<<M98088_MICPRE_SHIFT);
+
+       return 0;
+}
+
+static int max98088_mic2pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       ucontrol->value.integer.value[0] = max98088->mic2pre;
+       return 0;
+}
+
+static const unsigned int max98088_micboost_tlv[] = {
+       TLV_DB_RANGE_HEAD(2),
+       0, 1, TLV_DB_SCALE_ITEM(0, 2000, 0),
+       2, 2, TLV_DB_SCALE_ITEM(3000, 0, 0),
+};
+
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+       SOC_DOUBLE_R("Headphone Volume", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+       SOC_DOUBLE_R("Speaker Volume", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+       SOC_DOUBLE_R("Receiver Volume", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+       SOC_DOUBLE_R("Headphone Switch", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 7, 1, 1),
+       SOC_DOUBLE_R("Speaker Switch", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 7, 1, 1),
+       SOC_DOUBLE_R("Receiver Switch", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 7, 1, 1),
+
+       SOC_SINGLE("MIC1 Volume", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+       SOC_SINGLE("MIC2 Volume", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+       SOC_SINGLE_EXT_TLV("MIC1 Boost Volume",
+                       M98088_REG_35_LVL_MIC1, 5, 2, 0,
+                       max98088_mic1pre_get, max98088_mic1pre_set,
+                       max98088_micboost_tlv),
+       SOC_SINGLE_EXT_TLV("MIC2 Boost Volume",
+                       M98088_REG_36_LVL_MIC2, 5, 2, 0,
+                       max98088_mic2pre_get, max98088_mic2pre_set,
+                       max98088_micboost_tlv),
+
+       SOC_SINGLE("INA Volume", M98088_REG_37_LVL_INA, 0, 7, 1),
+       SOC_SINGLE("INB Volume", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+       SOC_SINGLE("ADCL Volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+       SOC_SINGLE("ADCR Volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+       SOC_SINGLE("ADCL Boost Volume", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+       SOC_SINGLE("ADCR Boost Volume", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+       SOC_SINGLE("EQ1 Switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+       SOC_SINGLE("EQ2 Switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+       SOC_ENUM("EX Limiter Threshold", max98088_ex_thresh_enum),
+
+       SOC_ENUM("DAI1 Filter Mode", max98088_filter_mode_enum),
+       SOC_ENUM("DAI1 DAC Filter", max98088_dai1_dac_filter_enum),
+       SOC_ENUM("DAI1 ADC Filter", max98088_dai1_adc_filter_enum),
+       SOC_SINGLE("DAI2 DC Block Switch", M98088_REG_20_DAI2_FILTERS,
+               0, 1, 0),
+
+       SOC_SINGLE("ALC Switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+       SOC_SINGLE("ALC Threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+       SOC_SINGLE("ALC Multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+       SOC_SINGLE("ALC Release Time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+       SOC_SINGLE("PWR Limiter Threshold", M98088_REG_44_PWRLMT_CFG,
+               4, 15, 0),
+       SOC_SINGLE("PWR Limiter Weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+       SOC_SINGLE("PWR Limiter Time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+       SOC_SINGLE("PWR Limiter Time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+       SOC_SINGLE("THD Limiter Threshold", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+       SOC_SINGLE("THD Limiter Time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_mic_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               if (w->reg == M98088_REG_35_LVL_MIC1) {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic1pre)<<M98088_MICPRE_SHIFT);
+               } else {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic2pre)<<M98088_MICPRE_SHIFT);
+               }
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK, 0);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * The line inputs are 2-channel stereo inputs with the left
+ * and right channels sharing a common PGA power control signal.
+ */
+static int max98088_line_pga(struct snd_soc_dapm_widget *w,
+                            int event, int line, u8 channel)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       u8 *state;
+
+       BUG_ON(!((channel == 1) || (channel == 2)));
+
+       switch (line) {
+       case LINE_INA:
+               state = &max98088->ina_state;
+               break;
+       case LINE_INB:
+               state = &max98088->inb_state;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               *state |= channel;
+               snd_soc_update_bits(codec, w->reg,
+                       (1 << w->shift), (1 << w->shift));
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               *state &= ~channel;
+               if (*state == 0) {
+                       snd_soc_update_bits(codec, w->reg,
+                               (1 << w->shift), 0);
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int max98088_pga_ina1_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INA, 1);
+}
+
+static int max98088_pga_ina2_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INA, 2);
+}
+
+static int max98088_pga_inb1_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INB, 1);
+}
+
+static int max98088_pga_inb2_event(struct snd_soc_dapm_widget *w,
+                                  struct snd_kcontrol *k, int event)
+{
+       return max98088_line_pga(w, event, LINE_INB, 2);
+}
+
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+       SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+       SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+       SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+       SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+       SND_SOC_DAPM_PGA("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+               7, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+               6, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+               5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+               4, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+               3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+               2, 0, NULL, 0),
+
+       SND_SOC_DAPM_MUX("External MIC", SND_SOC_NOPM, 0, 0,
+               &max98088_extmic_mux),
+
+       SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_left_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_right_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_PGA_E("MIC1 Input", M98088_REG_35_LVL_MIC1,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("MIC2 Input", M98088_REG_36_LVL_MIC2,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_ina1_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_ina2_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_inb1_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_inb2_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_MICBIAS("MICBIAS", M98088_REG_4C_PWR_EN_IN, 3, 0),
+
+       SND_SOC_DAPM_MUX("EX Limiter Mode", SND_SOC_NOPM, 0, 0,
+               &max98088_exmode_controls),
+
+       SND_SOC_DAPM_OUTPUT("HPL"),
+       SND_SOC_DAPM_OUTPUT("HPR"),
+       SND_SOC_DAPM_OUTPUT("SPKL"),
+       SND_SOC_DAPM_OUTPUT("SPKR"),
+       SND_SOC_DAPM_OUTPUT("RECL"),
+       SND_SOC_DAPM_OUTPUT("RECR"),
+
+       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 */
+       {"Left HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left HP Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right headphone output mixer */
+       {"Right HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right HP Mixer", "Left DAC2 Switch", "DACL2"  },
+       {"Right HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Left speaker output mixer */
+       {"Left SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right speaker output mixer */
+       {"Right SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Left REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Right REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       {"HP Left Out", NULL, "Left HP Mixer"},
+       {"HP Right Out", NULL, "Right HP Mixer"},
+       {"SPK Left Out", NULL, "Left SPK Mixer"},
+       {"SPK Right Out", NULL, "Right SPK Mixer"},
+       {"REC Left Out", NULL, "Left REC Mixer"},
+       {"REC Right Out", NULL, "Right REC Mixer"},
+
+       {"HPL", NULL, "HP Left Out"},
+       {"HPR", NULL, "HP Right Out"},
+       {"SPKL", NULL, "SPK Left Out"},
+       {"SPKR", NULL, "SPK Right Out"},
+       {"RECL", NULL, "REC Left Out"},
+       {"RECR", NULL, "REC Right Out"},
+
+       /* Left ADC input mixer */
+       {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right ADC input mixer */
+       {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right ADC Mixer", "INB2 Switch", "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"},
+       {"MIC1 Input", NULL, "MIC1"},
+       {"MIC2 Input", NULL, "MIC2"},
+};
+
+static int max98088_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, max98088_dapm_widgets,
+                                 ARRAY_SIZE(max98088_dapm_widgets));
+
+       snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+       snd_soc_add_controls(codec, max98088_snd_controls,
+                            ARRAY_SIZE(max98088_snd_controls));
+
+       snd_soc_dapm_new_widgets(codec);
+       return 0;
+}
+
+/* codec mclk clock divider coefficients */
+static const struct {
+       u32 rate;
+       u8  sr;
+} rate_table[] = {
+       {8000,  0x10},
+       {11025, 0x20},
+       {16000, 0x30},
+       {22050, 0x40},
+       {24000, 0x50},
+       {32000, 0x60},
+       {44100, 0x70},
+       {48000, 0x80},
+       {88200, 0x90},
+       {96000, 0xA0},
+};
+
+static inline int rate_value(int rate, u8 *value)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+               if (rate_table[i].rate >= rate) {
+                       *value = rate_table[i].sr;
+                       return 0;
+               }
+       }
+       *value = rate_table[0].sr;
+       return -EINVAL;
+}
+
+static int max98088_dai1_hw_params(struct snd_pcm_substream *substream,
+                                  struct snd_pcm_hw_params *params,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       unsigned long long ni;
+       unsigned int rate;
+       u8 regval;
+
+       cdata = &max98088->dai[0];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate_value(rate, &regval))
+               return -EINVAL;
+
+       snd_soc_update_bits(codec, M98088_REG_11_DAI1_CLKMODE,
+               M98088_CLKMODE_MASK, regval);
+       cdata->rate = rate;
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_14_DAI1_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0) {
+                       dev_err(codec->dev, "Invalid system clock frequency\n");
+                       return -EINVAL;
+               }
+               ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate;
+               do_div(ni, (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                       (ni >> 8) & 0x7F);
+               snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                       ni & 0xFF);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN,
+               M98088_SHDNRUN);
+
+       return 0;
+}
+
+static int max98088_dai2_hw_params(struct snd_pcm_substream *substream,
+                                  struct snd_pcm_hw_params *params,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       unsigned long long ni;
+       unsigned int rate;
+       u8 regval;
+
+       cdata = &max98088->dai[1];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate_value(rate, &regval))
+               return -EINVAL;
+
+       snd_soc_update_bits(codec, M98088_REG_19_DAI2_CLKMODE,
+               M98088_CLKMODE_MASK, regval);
+       cdata->rate = rate;
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_1C_DAI2_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0) {
+                       dev_err(codec->dev, "Invalid system clock frequency\n");
+                       return -EINVAL;
+               }
+               ni = 65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate;
+               do_div(ni, (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                       (ni >> 8) & 0x7F);
+               snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                       ni & 0xFF);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN,
+               M98088_SHDNRUN);
+
+       return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+                                  int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       /* Requested clock frequency is already setup */
+       if (freq == max98088->sysclk)
+               return 0;
+
+       max98088->sysclk = freq; /* remember current sysclk */
+
+       /* Setup clocks for slave mode, and using the PLL
+        * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+        *         0x02 (when master clk is 20MHz to 30MHz)..
+        */
+       if ((freq >= 10000000) && (freq < 20000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+       } else if ((freq >= 20000000) && (freq < 30000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+       } else {
+               dev_err(codec->dev, "Invalid master clock frequency\n");
+               return -EINVAL;
+       }
+
+       if (snd_soc_read(codec, M98088_REG_51_PWR_SYS)  & M98088_SHDNRUN) {
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN, 0);
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN, M98088_SHDNRUN);
+       }
+
+       dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+       max98088->sysclk = freq;
+       return 0;
+}
+
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       u8 reg15val;
+       u8 reg14val = 0;
+
+       cdata = &max98088->dai[0];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* Slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* Set to master mode */
+                       reg14val |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg14val |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg14val |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg14val |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg14val |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI |
+                       M98088_DAI_WCI, reg14val);
+
+               reg15val = M98088_DAI_BSEL64;
+               if (max98088->digmic)
+                       reg15val |= M98088_DAI_OSR64;
+               snd_soc_write(codec, M98088_REG_15_DAI1_CLOCK, reg15val);
+       }
+
+       return 0;
+}
+
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       u8 reg1Cval = 0;
+
+       cdata = &max98088->dai[1];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* Slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* Set to master mode */
+                       reg1Cval |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg1Cval |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg1Cval |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg1Cval |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg1Cval |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_MAS | M98088_DAI_DLY | M98088_DAI_BCI |
+                       M98088_DAI_WCI, reg1Cval);
+
+               snd_soc_write(codec, M98088_REG_1D_DAI2_CLOCK,
+                       M98088_DAI_BSEL64);
+       }
+
+       return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       int i;
+
+       if (!codec->cache_sync)
+               return;
+
+       codec->cache_only = 0;
+
+       /* write back cached values if they're writeable and
+        * different from the hardware default.
+        */
+       for (i = 1; i < ARRAY_SIZE(max98088->reg_cache); i++) {
+               if (!max98088_access[i].writable)
+                       continue;
+
+               if (max98088->reg_cache[i] == max98088_reg[i])
+                       continue;
+
+               snd_soc_write(codec, i, max98088->reg_cache[i]);
+       }
+
+       codec->cache_sync = 0;
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+                                  enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               if (codec->bias_level == SND_SOC_BIAS_OFF)
+                       max98088_sync_cache(codec);
+
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, M98088_MBEN);
+               break;
+
+       case SND_SOC_BIAS_OFF:
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, 0);
+               codec->cache_sync = 1;
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai1_set_fmt,
+       .hw_params = max98088_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai2_set_fmt,
+       .hw_params = max98088_dai2_hw_params,
+};
+
+static struct snd_soc_dai_driver max98088_dai[] = {
+{
+       .name = "HiFi",
+       .playback = {
+               .stream_name = "HiFi Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .capture = {
+               .stream_name = "HiFi Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+        .ops = &max98088_dai1_ops,
+},
+{
+       .name = "Aux",
+       .playback = {
+               .stream_name = "Aux Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .ops = &max98088_dai2_ops,
+}
+};
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+       sel = cdata->eq_sel;
+
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               if (strcmp(pdata->eq1_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq1_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq1_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq1_cfg[best].name,
+               pdata->eq1_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+       coef_set = &pdata->eq1_cfg[sel];
+
+       m98088_eq_band(codec, 0, 0, coef_set->band1);
+       m98088_eq_band(codec, 0, 1, coef_set->band2);
+       m98088_eq_band(codec, 0, 2, coef_set->band3);
+       m98088_eq_band(codec, 0, 3, coef_set->band4);
+       m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+       /* Restore the original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->eq_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               if (strcmp(pdata->eq2_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq2_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq2_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq2_cfg[best].name,
+               pdata->eq2_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+       coef_set = &pdata->eq2_cfg[sel];
+
+       m98088_eq_band(codec, 1, 0, coef_set->band1);
+       m98088_eq_band(codec, 1, 1, coef_set->band2);
+       m98088_eq_band(codec, 1, 2, coef_set->band3);
+       m98088_eq_band(codec, 1, 3, coef_set->band4);
+       m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+       /* Restore the original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+               save);
+}
+
+static int max98088_put_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[0];
+
+       if (sel >= pdata->eq1_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq1(codec);
+       return 0;
+}
+
+static int max98088_put_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[1];
+
+       if (sel >= pdata->eq2_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq2(codec);
+       return 0;
+}
+
+static int max98088_get_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static int max98088_get_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static void max98088_setup_eq_enum(struct max98088_eq_cfg *cfg,
+                                  unsigned int cfgcnt,
+                                  struct max98088_cdata *cdata)
+{
+       int i, j;
+       const char **t;
+
+       /* Setup an array of texts for the equalizer enum.
+        * This is based on Mark Brown's equalizer driver code.
+        * It has been extended to support multiple equalizers.
+        */
+       cdata->eq_textcnt = 0;
+       cdata->eq_texts = NULL;
+       for (i = 0; i < cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(cfg[i].name, cdata->eq_texts[j]) == 0)
+                               break;
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               /* Expand the array */
+               t = krealloc(cdata->eq_texts,
+                            sizeof(char *) * (cdata->eq_textcnt + 1),
+                            GFP_KERNEL);
+               if (t == NULL)
+                       continue;
+
+               /* Store the new entry */
+               t[cdata->eq_textcnt] = cfg[i].name;
+               cdata->eq_textcnt++;
+               cdata->eq_texts = t;
+       }
+
+       /* Now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+}
+
+static void max98088_handle_eq1_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata = &max98088->dai[0];
+       int ret;
+
+       struct snd_kcontrol_new eq1control =
+               SOC_ENUM_EXT("EQ1 Mode",
+                       max98088->dai[0].eq_enum,
+                       max98088_get_eq1_enum,
+                       max98088_put_eq1_enum);
+
+       max98088_setup_eq_enum(pdata->eq1_cfg, pdata->eq1_cfgcnt, cdata);
+
+       dev_dbg(codec->dev, "Installed %d EQ1 configurations\n",
+               cdata->eq_enum.max);
+
+       ret = snd_soc_add_controls(codec, &eq1control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_eq2_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata = &max98088->dai[1];
+       int ret;
+
+       struct snd_kcontrol_new eq2control =
+               SOC_ENUM_EXT("EQ2 Mode",
+                       max98088->dai[1].eq_enum,
+                       max98088_get_eq2_enum,
+                       max98088_put_eq2_enum);
+
+       max98088_setup_eq_enum(pdata->eq2_cfg, pdata->eq2_cfgcnt, cdata);
+
+       dev_dbg(codec->dev, "Installed %d EQ2 configurations\n",
+               cdata->eq_enum.max);
+
+       ret = snd_soc_add_controls(codec, &eq2control, 1);
+       if (ret != 0)
+               dev_dbg(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       u8 regval = 0;
+
+       if (!pdata) {
+               dev_dbg(codec->dev, "No platform data\n");
+               return;
+       }
+
+       /* Configure mic for analog/digital mic mode */
+       if (pdata->digmic_left_mode)
+               regval |= M98088_DIGMIC_L;
+
+       if (pdata->digmic_right_mode)
+               regval |= M98088_DIGMIC_R;
+
+       max98088->digmic = (regval ? 1 : 0);
+
+       snd_soc_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+       /* Configure receiver output */
+       regval = ((pdata->receiver_mode) ? M98088_REC_LINEMODE : 0);
+       snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+               M98088_REC_LINEMODE_MASK, regval);
+
+       /* Configure equalizers */
+       if (pdata->eq1_cfgcnt)
+               max98088_handle_eq1_pdata(codec);
+
+       if (pdata->eq2_cfgcnt)
+               max98088_handle_eq2_pdata(codec);
+}
+
+#ifdef CONFIG_PM
+static int max98088_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static int max98088_resume(struct snd_soc_codec *codec)
+{
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+}
+#else
+#define max98088_suspend NULL
+#define max98088_resume NULL
+#endif
+
+static int max98088_probe(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       int ret = 0;
+
+       codec->cache_sync = 1;
+       memcpy(codec->reg_cache, max98088_reg, sizeof(max98088_reg));
+
+       ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+               return ret;
+       }
+
+       /* initalize private data */
+
+       max98088->sysclk = (unsigned)-1;
+
+       cdata = &max98088->dai[0];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+
+       cdata = &max98088->dai[1];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+
+       max98088->ina_state = 0;
+       max98088->inb_state = 0;
+       max98088->ex_mode = 0;
+       max98088->digmic = 0;
+       max98088->mic1pre = 0;
+       max98088->mic2pre = 0;
+
+       ret = snd_soc_read(codec, M98088_REG_FF_REV_ID);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to read device revision: %d\n",
+                       ret);
+               goto err_access;
+       }
+       dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+       snd_soc_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+
+       /* initialize registers cache to hardware default */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       snd_soc_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+       snd_soc_write(codec, M98088_REG_22_MIX_DAC,
+               M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+               M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+       snd_soc_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+       snd_soc_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+       snd_soc_write(codec, M98088_REG_16_DAI1_IOCFG,
+               M98088_S1NORMAL|M98088_SDATA);
+
+       snd_soc_write(codec, M98088_REG_1E_DAI2_IOCFG,
+               M98088_S2NORMAL|M98088_SDATA);
+
+       max98088_handle_pdata(codec);
+
+       max98088_add_widgets(codec);
+
+err_access:
+       return ret;
+}
+
+static int max98088_remove(struct snd_soc_codec *codec)
+{
+       if (codec->control_data)
+               max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_max98088 = {
+       .probe   = max98088_probe,
+       .remove  = max98088_remove,
+       .suspend = max98088_suspend,
+       .resume  = max98088_resume,
+       .set_bias_level = max98088_set_bias_level,
+       .reg_cache_size = ARRAY_SIZE(max98088_reg),
+       .reg_word_size = sizeof(u8),
+       .reg_cache_default = max98088_reg,
+       .volatile_register = max98088_volatile_register,
+};
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+                            const struct i2c_device_id *id)
+{
+       struct max98088_priv *max98088;
+       int ret;
+
+       max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+       if (max98088 == NULL)
+               return -ENOMEM;
+
+       i2c_set_clientdata(i2c, max98088);
+       max98088->control_data = i2c;
+       max98088->pdata = i2c->dev.platform_data;
+
+       ret = snd_soc_register_codec(&i2c->dev,
+                       &soc_codec_dev_max98088, &max98088_dai[0], 2);
+       if (ret < 0)
+               kfree(max98088);
+       return ret;
+}
+
+static int max98088_i2c_remove(struct i2c_client *client)
+{
+       snd_soc_unregister_codec(&client->dev);
+       kfree(i2c_get_clientdata(client));
+       return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+       { "max98088", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+static struct i2c_driver max98088_i2c_driver = {
+       .driver = {
+               .name = "max98088",
+               .owner = THIS_MODULE,
+       },
+       .probe  = max98088_i2c_probe,
+       .remove = __devexit_p(max98088_i2c_remove),
+       .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&max98088_i2c_driver);
+       if (ret)
+               pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+       return ret;
+}
+module_init(max98088_init);
+
+static void __exit max98088_exit(void)
+{
+       i2c_del_driver(&max98088_i2c_driver);
+}
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang, Jesse Marroquin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h
new file mode 100644
index 0000000..e46b258
--- /dev/null
+++ b/sound/soc/codecs/max98088.h
@@ -0,0 +1,193 @@
+/*
+ * max98088.h -- MAX98088 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 _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS            0x00
+#define M98088_REG_01_MIC_STATUS            0x01
+#define M98088_REG_02_JACK_STAUS            0x02
+#define M98088_REG_03_BATTERY_VOLTAGE       0x03
+#define M98088_REG_0F_IRQ_ENABLE            0x0F
+#define M98088_REG_10_SYS_CLK               0x10
+#define M98088_REG_11_DAI1_CLKMODE          0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI        0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO        0x13
+#define M98088_REG_14_DAI1_FORMAT           0x14
+#define M98088_REG_15_DAI1_CLOCK            0x15
+#define M98088_REG_16_DAI1_IOCFG            0x16
+#define M98088_REG_17_DAI1_TDM              0x17
+#define M98088_REG_18_DAI1_FILTERS          0x18
+#define M98088_REG_19_DAI2_CLKMODE          0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI        0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO        0x1B
+#define M98088_REG_1C_DAI2_FORMAT           0x1C
+#define M98088_REG_1D_DAI2_CLOCK            0x1D
+#define M98088_REG_1E_DAI2_IOCFG            0x1E
+#define M98088_REG_1F_DAI2_TDM              0x1F
+#define M98088_REG_20_DAI2_FILTERS          0x20
+#define M98088_REG_21_SRC                   0x21
+#define M98088_REG_22_MIX_DAC               0x22
+#define M98088_REG_23_MIX_ADC_LEFT          0x23
+#define M98088_REG_24_MIX_ADC_RIGHT         0x24
+#define M98088_REG_25_MIX_HP_LEFT           0x25
+#define M98088_REG_26_MIX_HP_RIGHT          0x26
+#define M98088_REG_27_MIX_HP_CNTL           0x27
+#define M98088_REG_28_MIX_REC_LEFT          0x28
+#define M98088_REG_29_MIX_REC_RIGHT         0x29
+#define M98088_REG_2A_MIC_REC_CNTL          0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT          0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT         0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL          0x2D
+#define M98088_REG_2E_LVL_SIDETONE          0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY         0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ      0x30
+#define M98088_REG_31_LVL_DAI2_PLAY         0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ      0x32
+#define M98088_REG_33_LVL_ADC_L             0x33
+#define M98088_REG_34_LVL_ADC_R             0x34
+#define M98088_REG_35_LVL_MIC1              0x35
+#define M98088_REG_36_LVL_MIC2              0x36
+#define M98088_REG_37_LVL_INA               0x37
+#define M98088_REG_38_LVL_INB               0x38
+#define M98088_REG_39_LVL_HP_L              0x39
+#define M98088_REG_3A_LVL_HP_R              0x3A
+#define M98088_REG_3B_LVL_REC_L             0x3B
+#define M98088_REG_3C_LVL_REC_R             0x3C
+#define M98088_REG_3D_LVL_SPK_L             0x3D
+#define M98088_REG_3E_LVL_SPK_R             0x3E
+#define M98088_REG_3F_MICAGC_CFG            0x3F
+#define M98088_REG_40_MICAGC_THRESH         0x40
+#define M98088_REG_41_SPKDHP                0x41
+#define M98088_REG_42_SPKDHP_THRESH         0x42
+#define M98088_REG_43_SPKALC_COMP           0x43
+#define M98088_REG_44_PWRLMT_CFG            0x44
+#define M98088_REG_45_PWRLMT_TIME           0x45
+#define M98088_REG_46_THDLMT_CFG            0x46
+#define M98088_REG_47_CFG_AUDIO_IN          0x47
+#define M98088_REG_48_CFG_MIC               0x48
+#define M98088_REG_49_CFG_LEVEL             0x49
+#define M98088_REG_4A_CFG_BYPASS            0x4A
+#define M98088_REG_4B_CFG_JACKDET           0x4B
+#define M98088_REG_4C_PWR_EN_IN             0x4C
+#define M98088_REG_4D_PWR_EN_OUT            0x4D
+#define M98088_REG_4E_BIAS_CNTL             0x4E
+#define M98088_REG_4F_DAC_BIAS1             0x4F
+#define M98088_REG_50_DAC_BIAS2             0x50
+#define M98088_REG_51_PWR_SYS               0x51
+#define M98088_REG_52_DAI1_EQ_BASE          0x52
+#define M98088_REG_84_DAI2_EQ_BASE          0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE      0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE      0xC0
+#define M98088_REG_FF_REV_ID                0xFF
+
+#define M98088_REG_CNT                      (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_11_DAI1_CLKMODE, M98088_REG_19_DAI2_CLKMODE */
+       #define M98088_CLKMODE_MASK             0xFF
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+       #define M98088_DAI_MAS                  (1<<7)
+       #define M98088_DAI_WCI                  (1<<6)
+       #define M98088_DAI_BCI                  (1<<5)
+       #define M98088_DAI_DLY                  (1<<4)
+       #define M98088_DAI_TDM                  (1<<2)
+       #define M98088_DAI_FSW                  (1<<1)
+       #define M98088_DAI_WS                   (1<<0)
+
+/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */
+       #define M98088_DAI_BSEL64               (1<<0)
+       #define M98088_DAI_OSR64                (1<<6)
+
+/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */
+       #define M98088_S1NORMAL                 (1<<6)
+       #define M98088_S2NORMAL                 (2<<6)
+       #define M98088_SDATA                    (3<<0)
+
+/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */
+       #define M98088_DAI_DHF                  (1<<3)
+
+/* M98088_REG_22_MIX_DAC */
+       #define M98088_DAI1L_TO_DACL            (1<<7)
+       #define M98088_DAI1R_TO_DACL            (1<<6)
+       #define M98088_DAI2L_TO_DACL            (1<<5)
+       #define M98088_DAI2R_TO_DACL            (1<<4)
+       #define M98088_DAI1L_TO_DACR            (1<<3)
+       #define M98088_DAI1R_TO_DACR            (1<<2)
+       #define M98088_DAI2L_TO_DACR            (1<<1)
+       #define M98088_DAI2R_TO_DACR            (1<<0)
+
+/* M98088_REG_2A_MIC_REC_CNTL */
+       #define M98088_REC_LINEMODE             (1<<7)
+       #define M98088_REC_LINEMODE_MASK        (1<<7)
+
+/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */
+       #define M98088_MICPRE_MASK              (3<<5)
+       #define M98088_MICPRE_SHIFT             5
+
+/* M98088_REG_3A_LVL_HP_R */
+       #define M98088_HP_MUTE                  (1<<7)
+
+/* M98088_REG_3C_LVL_REC_R */
+       #define M98088_REC_MUTE                 (1<<7)
+
+/* M98088_REG_3E_LVL_SPK_R */
+       #define M98088_SP_MUTE                  (1<<7)
+
+/* M98088_REG_48_CFG_MIC */
+       #define M98088_EXTMIC_MASK              (3<<0)
+       #define M98088_DIGMIC_L                 (1<<5)
+       #define M98088_DIGMIC_R                 (1<<4)
+
+/* M98088_REG_49_CFG_LEVEL */
+       #define M98088_VSEN                     (1<<6)
+       #define M98088_ZDEN                     (1<<5)
+       #define M98088_EQ2EN                    (1<<1)
+       #define M98088_EQ1EN                    (1<<0)
+
+/* M98088_REG_4C_PWR_EN_IN */
+       #define M98088_INAEN                    (1<<7)
+       #define M98088_INBEN                    (1<<6)
+       #define M98088_MBEN                     (1<<3)
+       #define M98088_ADLEN                    (1<<1)
+       #define M98088_ADREN                    (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+       #define M98088_HPLEN                    (1<<7)
+       #define M98088_HPREN                    (1<<6)
+       #define M98088_HPEN                     ((1<<7)|(1<<6))
+       #define M98088_SPLEN                    (1<<5)
+       #define M98088_SPREN                    (1<<4)
+       #define M98088_RECEN                    (1<<3)
+       #define M98088_DALEN                    (1<<1)
+       #define M98088_DAREN                    (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+       #define M98088_SHDNRUN                  (1<<7)
+       #define M98088_PERFMODE                 (1<<3)
+       #define M98088_HPPLYBACK                (1<<2)
+       #define M98088_PWRSV8K                  (1<<1)
+       #define M98088_PWRSV                    (1<<0)
+
+/* Line inputs */
+#define LINE_INA  0
+#define LINE_INB  1
+
+#define M98088_COEFS_PER_BAND               5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+#endif
--
1.6.3.3


^ permalink raw reply related	[flat|nested] 46+ messages in thread

* RE: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-30  0:58           ` Mark Brown
  (?)
@ 2010-09-30  1:20           ` Peter Hsiang
  -1 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-30  1:20 UTC (permalink / raw)
  To: Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Wed, Sep 29, 2010, Mark Brown wrote:
> On Wed, Sep 29, 2010 at 05:52:08PM -0700, Peter Hsiang wrote:
> > On Wed, Sep 29, 2010, Mark Brown wrote:
> 
> > > You're looking for the non-DAPM equivalent of SND_SOC_DAPM_VALUE_MUX().
> 
> > This is a simple table lookup of a register value from the index
> > number given by SOC_ENUM, the same way it's been done in other drivers.
> 
> Yes, exactly.
> 
> > I found a "case snd_soc_dapm_value_mux:" in dapm_set_path_status()
> > Is this what you are referring to?
> > How is the code there relevant to this?
> 
> No, I'm referring to the fact that it provides an enum that allows
> arbatrary values to be set for each enum value rather than just allowing
> indexes as the standard SOC_ENUM does.  This is essentially what you've
> implemented, it would factor out the code so that others can use it too
> (and IIRC save you some code since IIRC you had more than one of these
> in the driver).

Thank you!


^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-30  0:52       ` Peter Hsiang
@ 2010-09-30  0:58           ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-30  0:58 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Wed, Sep 29, 2010 at 05:52:08PM -0700, Peter Hsiang wrote:
> On Wed, Sep 29, 2010, Mark Brown wrote:

> > You're looking for the non-DAPM equivalent of SND_SOC_DAPM_VALUE_MUX().

> This is a simple table lookup of a register value from the index
> number given by SOC_ENUM, the same way it's been done in other drivers.

Yes, exactly.

> I found a "case snd_soc_dapm_value_mux:" in dapm_set_path_status()
> Is this what you are referring to?
> How is the code there relevant to this?

No, I'm referring to the fact that it provides an enum that allows
arbatrary values to be set for each enum value rather than just allowing
indexes as the standard SOC_ENUM does.  This is essentially what you've
implemented, it would factor out the code so that others can use it too
(and IIRC save you some code since IIRC you had more than one of these
in the driver).

> > > > > +       /* powering down headphone gracefully */
> > > > > +       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
> > > > > +       if ((status & M98088_HPEN) == M98088_HPEN) {
> > > > > +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> > > > > +                       (status & ~M98088_HPEN));
> > > > > +       }
> > > > > +       schedule_timeout(msecs_to_jiffies(20));

> > > > This looks rather like it should just be a post event implementing a
> > > > timeout?

> > > This needs to work as a pre event.

> > Again, why is this?

> When powering down the headphone, the way that DAPM works is it
> likes to power off one item at a time, for example, the left channel,
> then right channel.  The headphone hardware likes to see the 

Have you tested with current Linux versions?  For quite a few kernel
releases now register writes are batched so that all the controls of a
given type in a single register will be set together.

> headphone bits L and R be powered down together, for optimum result.
> This works best with the pre method.  Powering up one channel at a 
> time later is fine, when DAPM resumes.

With current Linux versions you should see the left and right channels
powered down together (if they are both being powered down, of course).

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-30  0:58           ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-30  0:58 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Jesse Marroquin, Liam Girdwood

On Wed, Sep 29, 2010 at 05:52:08PM -0700, Peter Hsiang wrote:
> On Wed, Sep 29, 2010, Mark Brown wrote:

> > You're looking for the non-DAPM equivalent of SND_SOC_DAPM_VALUE_MUX().

> This is a simple table lookup of a register value from the index
> number given by SOC_ENUM, the same way it's been done in other drivers.

Yes, exactly.

> I found a "case snd_soc_dapm_value_mux:" in dapm_set_path_status()
> Is this what you are referring to?
> How is the code there relevant to this?

No, I'm referring to the fact that it provides an enum that allows
arbatrary values to be set for each enum value rather than just allowing
indexes as the standard SOC_ENUM does.  This is essentially what you've
implemented, it would factor out the code so that others can use it too
(and IIRC save you some code since IIRC you had more than one of these
in the driver).

> > > > > +       /* powering down headphone gracefully */
> > > > > +       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
> > > > > +       if ((status & M98088_HPEN) == M98088_HPEN) {
> > > > > +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> > > > > +                       (status & ~M98088_HPEN));
> > > > > +       }
> > > > > +       schedule_timeout(msecs_to_jiffies(20));

> > > > This looks rather like it should just be a post event implementing a
> > > > timeout?

> > > This needs to work as a pre event.

> > Again, why is this?

> When powering down the headphone, the way that DAPM works is it
> likes to power off one item at a time, for example, the left channel,
> then right channel.  The headphone hardware likes to see the 

Have you tested with current Linux versions?  For quite a few kernel
releases now register writes are batched so that all the controls of a
given type in a single register will be set together.

> headphone bits L and R be powered down together, for optimum result.
> This works best with the pre method.  Powering up one channel at a 
> time later is fine, when DAPM resumes.

With current Linux versions you should see the left and right channels
powered down together (if they are both being powered down, of course).

^ permalink raw reply	[flat|nested] 46+ messages in thread

* RE: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-29 22:18       ` Mark Brown
  (?)
@ 2010-09-30  0:52       ` Peter Hsiang
  2010-09-30  0:58           ` Mark Brown
  -1 siblings, 1 reply; 46+ messages in thread
From: Peter Hsiang @ 2010-09-30  0:52 UTC (permalink / raw)
  To: Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Wed, Sep 29, 2010, Mark Brown wrote:
> On Wed, Sep 29, 2010 at 02:42:32PM -0700, Peter Hsiang wrote:
> > On Wed, Sep 29, 2010, Mark Brown wrote:
> > > On Tue, Sep 28, 2010 at 07:34:34PM -0700, Peter Hsiang wrote:
> 
> > > You should add value muxes like we have for DAPM.
> 
> > Please clarify what you mean by referencing the specific
> > code usage case in the dapm source module.
> 
> You're looking for the non-DAPM equivalent of SND_SOC_DAPM_VALUE_MUX().

This is a simple table lookup of a register value from the index
number given by SOC_ENUM, the same way it's been done in other drivers.

I found a "case snd_soc_dapm_value_mux:" in dapm_set_path_status()
Is this what you are referring to?
How is the code there relevant to this?

> 
> > > > +       /* powering down headphone gracefully */
> > > > +       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
> > > > +       if ((status & M98088_HPEN) == M98088_HPEN) {
> > > > +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> > > > +                       (status & ~M98088_HPEN));
> > > > +       }
> > > > +       schedule_timeout(msecs_to_jiffies(20));
> 
> > > This looks rather like it should just be a post event implementing a
> > > timeout?
> 
> > This needs to work as a pre event.
> 
> Again, why is this?

When powering down the headphone, the way that DAPM works is it
likes to power off one item at a time, for example, the left channel,
then right channel.  The headphone hardware likes to see the 
headphone bits L and R be powered down together, for optimum result.
This works best with the pre method.  Powering up one channel at a 
time later is fine, when DAPM resumes.

> 
> > > > +       case SND_SOC_BIAS_STANDBY:
> > > > +               max98088_sync_cache(codec);
> > > > +               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
> > > > +                               M98088_MBEN, M98088_MBEN);
> > > > +               break;
> 
> > > Do you really want to sync the cache *every* time you go into standby?
> 
> > The sync_cache function itself will just return if the
> > codec->cache_sync flag is cleared from the first time it ran.
> > You do the exact same thing in your codec driver...
> > What is the change that you are suggesting?
> 
> The cache syncs should be part of some operation which would make it
> useful to sync the cache rather than just located at some point in the
> driver without any particular reason.  For example, with the drivers
> I've worked on the cache is synced after we enable the supplies for the
> device since the CODEC may have been powered off and therefore lost any
> register settings that might have been done.  If the cache sync is not
> associated with any such event then it's at best redundant and at worst
> the driver will loose some robustness since it becomes unclear if the
> events which cause a cache sync to be required are joined up with the
> triggering of a sync.

I see :)

> 
> > > > +module_init(max98088_init);
> 
> > > Normally this would be next to the function it references.
> 
> > Is this a new formatting style of the kernel now all across,
> > or is this a personal preference?
> 
> It's a global style for the kernel, though not enforced with 100%
> success.

Haha, got it. I will help you with this statistic from now on.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-29 21:42     ` Peter Hsiang
@ 2010-09-29 22:18       ` Mark Brown
  -1 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-29 22:18 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Wed, Sep 29, 2010 at 02:42:32PM -0700, Peter Hsiang wrote:
> On Wed, Sep 29, 2010, Mark Brown wrote:
> > On Tue, Sep 28, 2010 at 07:34:34PM -0700, Peter Hsiang wrote:

> > > +#define EQ_CFG_MAX 32

> > There doesn't seem to be any need to hard code a limit here?

> Are you requiring the implementation be exactly the same way you did?

It doesn't seem very good to hard code a limit when we already know how
to figure this out at runtime, especially given the amount of dynamic
allocation you already need for the strings themselves.

> > > +/*
> > > + * For kernels compiled without unsigned long long int division
> > > + */
> > > +unsigned long long int ulldiv(unsigned long long int dividend,
> > > +                             unsigned long long int divisor)

> > This is causing me some concern.  Even if it is required this does not
> > look like something that should be part of a specific driver - what
> > happens if some other driver needs the same thing?

> You are more than welcome to use this in your driver as well :)
> Just use it.  Sounds like you are suggesting to have it located in a
> central location outside of the codec driver?
> Would you like that in soc-core.c?  Let me know.

If it is needed it should be a core kernel feature as there is nothing
ASoC specific about doing a division, but you've not explained why it is
required in the first place - are you sure you're not looking for
do_div()?

> There are more than one way to solve a problem, and they all can be
> correct.
> Would additional comments to clarify that this is not register bits
> satisfy your requirement?

Like I say the issue is that the code is *very* opaque and non-idiomatic.

> > > +       if (event == SND_SOC_DAPM_POST_PMU) {
> > > +               /* ON */
> > > +               *state |= pga;
> > > +
> > > +               /* Turn on, avoiding unnecessary writes */
> > > +               val = snd_soc_read(codec, w->reg);
> > > +               if (!(val & (1 << w->shift))) {
> > > +                       val |= (1 << w->shift);
> > > +                       snd_soc_write(codec, w->reg, val);
> > > +               }

> > snd_soc_update_bits() will suppress unwanted register writes.

> From the comments sounds like it resemble snd_soc_update_bits.
> The code logic here is different from snd_soc_update_bits,
> and therefore snd_soc_update_bits can not be used for this
> like you suggested.

What makes you say this (and as with several other things here if things
aren't clear from the code then you should make it clear in the code)?

> > You should add value muxes like we have for DAPM.

> Please clarify what you mean by referencing the specific 
> code usage case in the dapm source module.

You're looking for the non-DAPM equivalent of SND_SOC_DAPM_VALUE_MUX().

> > > +static const char *max98088_extmic[] = {
> > > +       "Off",
> > > +       "MIC1",
> > > +       "MIC2",
> > > +};

> > > +static const struct soc_enum max98088_extmic_enum[] = {
> > > +       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
> > > +};

> > This looks like it should be in DAPM.

> This has nothing to do with power management.  
> I guess you were confused by the word "Off" in the options list.
> The "Off" case means the input for external mic is not used,
> i.e. disconnected.  It does not power up or down anything.

This is exactly my point - DAPM doesn't just manage power, it also
manages the audio routing (and the power as a function of that).
Remember that the DAPM map extends out of the CODEC so even if the CODEC
does not offer any meaningful power management within the device there
may be other external circuits which do (microphone biases being the
obvious example here).

> > > +       *mode = ucontrol->value.integer.value[0];
> > > +       return 0;
> > > +}
> > 

> > All this does is set a variable, changing the control will have no
> > effect.

> The register field works like this:
> 00 = preamp disabled
> 01 = 0dB
> 10 = 20dB
> 11 = 30dB
> So the gain and the power setting are in the same bit field.
> DAPM owns the power control.  Here the user settings are stored,
> and they get picked up by DAPM.

This does not address my comment at all.  Please address my comment - if
the user sets a value here one would expect that to have some immediate
effect on the operation of the device but all this does is store the
setting in a variable but there will be no immediate effect on the
operation of the device and there is nothing restricting when the value
can be set.  This means that the control state that the user sees can
vary from the actual device configuration.

> > > +       /* powering down headphone gracefully */
> > > +       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
> > > +       if ((status & M98088_HPEN) == M98088_HPEN) {
> > > +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> > > +                       (status & ~M98088_HPEN));
> > > +       }
> > > +       schedule_timeout(msecs_to_jiffies(20));

> > This looks rather like it should just be a post event implementing a
> > timeout?

> This needs to work as a pre event.

Again, why is this?

> > > +       if (rate != cdata->rate) {
> > > +               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
> > > +               if (rate_value(rate, &regval))
> > > +                       return -EINVAL;
> > > +
> > > +               snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
> > > +               cdata->rate = rate;
> > > +       }

> > Just use snd_soc_update_bits() to suppress unneeded writes.  Many of the
> > operaations have this issue.

> The full value for this register is accounted for here and so there
> is no need to use snd_soc_update_bits for this case.

The "to suppress unneeded writes" bit of my comment is important here -
removing the conditional statements would make the code easier to read.

> > > +       case SND_SOC_BIAS_STANDBY:
> > > +               max98088_sync_cache(codec);
> > > +               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
> > > +                               M98088_MBEN, M98088_MBEN);
> > > +               break;

> > Do you really want to sync the cache *every* time you go into standby?

> The sync_cache function itself will just return if the
> codec->cache_sync flag is cleared from the first time it ran.
> You do the exact same thing in your codec driver...
> What is the change that you are suggesting?

The cache syncs should be part of some operation which would make it
useful to sync the cache rather than just located at some point in the
driver without any particular reason.  For example, with the drivers
I've worked on the cache is synced after we enable the supplies for the
device since the CODEC may have been powered off and therefore lost any
register settings that might have been done.  If the cache sync is not
associated with any such event then it's at best redundant and at worst
the driver will loose some robustness since it becomes unclear if the
events which cause a cache sync to be required are joined up with the
triggering of a sync.

> > > +module_init(max98088_init);

> > Normally this would be next to the function it references.

> Is this a new formatting style of the kernel now all across,
> or is this a personal preference?

It's a global style for the kernel, though not enforced with 100%
success.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-29 22:18       ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-29 22:18 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Jesse Marroquin, Liam Girdwood

On Wed, Sep 29, 2010 at 02:42:32PM -0700, Peter Hsiang wrote:
> On Wed, Sep 29, 2010, Mark Brown wrote:
> > On Tue, Sep 28, 2010 at 07:34:34PM -0700, Peter Hsiang wrote:

> > > +#define EQ_CFG_MAX 32

> > There doesn't seem to be any need to hard code a limit here?

> Are you requiring the implementation be exactly the same way you did?

It doesn't seem very good to hard code a limit when we already know how
to figure this out at runtime, especially given the amount of dynamic
allocation you already need for the strings themselves.

> > > +/*
> > > + * For kernels compiled without unsigned long long int division
> > > + */
> > > +unsigned long long int ulldiv(unsigned long long int dividend,
> > > +                             unsigned long long int divisor)

> > This is causing me some concern.  Even if it is required this does not
> > look like something that should be part of a specific driver - what
> > happens if some other driver needs the same thing?

> You are more than welcome to use this in your driver as well :)
> Just use it.  Sounds like you are suggesting to have it located in a
> central location outside of the codec driver?
> Would you like that in soc-core.c?  Let me know.

If it is needed it should be a core kernel feature as there is nothing
ASoC specific about doing a division, but you've not explained why it is
required in the first place - are you sure you're not looking for
do_div()?

> There are more than one way to solve a problem, and they all can be
> correct.
> Would additional comments to clarify that this is not register bits
> satisfy your requirement?

Like I say the issue is that the code is *very* opaque and non-idiomatic.

> > > +       if (event == SND_SOC_DAPM_POST_PMU) {
> > > +               /* ON */
> > > +               *state |= pga;
> > > +
> > > +               /* Turn on, avoiding unnecessary writes */
> > > +               val = snd_soc_read(codec, w->reg);
> > > +               if (!(val & (1 << w->shift))) {
> > > +                       val |= (1 << w->shift);
> > > +                       snd_soc_write(codec, w->reg, val);
> > > +               }

> > snd_soc_update_bits() will suppress unwanted register writes.

> From the comments sounds like it resemble snd_soc_update_bits.
> The code logic here is different from snd_soc_update_bits,
> and therefore snd_soc_update_bits can not be used for this
> like you suggested.

What makes you say this (and as with several other things here if things
aren't clear from the code then you should make it clear in the code)?

> > You should add value muxes like we have for DAPM.

> Please clarify what you mean by referencing the specific 
> code usage case in the dapm source module.

You're looking for the non-DAPM equivalent of SND_SOC_DAPM_VALUE_MUX().

> > > +static const char *max98088_extmic[] = {
> > > +       "Off",
> > > +       "MIC1",
> > > +       "MIC2",
> > > +};

> > > +static const struct soc_enum max98088_extmic_enum[] = {
> > > +       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
> > > +};

> > This looks like it should be in DAPM.

> This has nothing to do with power management.  
> I guess you were confused by the word "Off" in the options list.
> The "Off" case means the input for external mic is not used,
> i.e. disconnected.  It does not power up or down anything.

This is exactly my point - DAPM doesn't just manage power, it also
manages the audio routing (and the power as a function of that).
Remember that the DAPM map extends out of the CODEC so even if the CODEC
does not offer any meaningful power management within the device there
may be other external circuits which do (microphone biases being the
obvious example here).

> > > +       *mode = ucontrol->value.integer.value[0];
> > > +       return 0;
> > > +}
> > 

> > All this does is set a variable, changing the control will have no
> > effect.

> The register field works like this:
> 00 = preamp disabled
> 01 = 0dB
> 10 = 20dB
> 11 = 30dB
> So the gain and the power setting are in the same bit field.
> DAPM owns the power control.  Here the user settings are stored,
> and they get picked up by DAPM.

This does not address my comment at all.  Please address my comment - if
the user sets a value here one would expect that to have some immediate
effect on the operation of the device but all this does is store the
setting in a variable but there will be no immediate effect on the
operation of the device and there is nothing restricting when the value
can be set.  This means that the control state that the user sees can
vary from the actual device configuration.

> > > +       /* powering down headphone gracefully */
> > > +       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
> > > +       if ((status & M98088_HPEN) == M98088_HPEN) {
> > > +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> > > +                       (status & ~M98088_HPEN));
> > > +       }
> > > +       schedule_timeout(msecs_to_jiffies(20));

> > This looks rather like it should just be a post event implementing a
> > timeout?

> This needs to work as a pre event.

Again, why is this?

> > > +       if (rate != cdata->rate) {
> > > +               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
> > > +               if (rate_value(rate, &regval))
> > > +                       return -EINVAL;
> > > +
> > > +               snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
> > > +               cdata->rate = rate;
> > > +       }

> > Just use snd_soc_update_bits() to suppress unneeded writes.  Many of the
> > operaations have this issue.

> The full value for this register is accounted for here and so there
> is no need to use snd_soc_update_bits for this case.

The "to suppress unneeded writes" bit of my comment is important here -
removing the conditional statements would make the code easier to read.

> > > +       case SND_SOC_BIAS_STANDBY:
> > > +               max98088_sync_cache(codec);
> > > +               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
> > > +                               M98088_MBEN, M98088_MBEN);
> > > +               break;

> > Do you really want to sync the cache *every* time you go into standby?

> The sync_cache function itself will just return if the
> codec->cache_sync flag is cleared from the first time it ran.
> You do the exact same thing in your codec driver...
> What is the change that you are suggesting?

The cache syncs should be part of some operation which would make it
useful to sync the cache rather than just located at some point in the
driver without any particular reason.  For example, with the drivers
I've worked on the cache is synced after we enable the supplies for the
device since the CODEC may have been powered off and therefore lost any
register settings that might have been done.  If the cache sync is not
associated with any such event then it's at best redundant and at worst
the driver will loose some robustness since it becomes unclear if the
events which cause a cache sync to be required are joined up with the
triggering of a sync.

> > > +module_init(max98088_init);

> > Normally this would be next to the function it references.

> Is this a new formatting style of the kernel now all across,
> or is this a personal preference?

It's a global style for the kernel, though not enforced with 100%
success.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* RE: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-29  3:37   ` Mark Brown
@ 2010-09-29 21:42     ` Peter Hsiang
  -1 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-29 21:42 UTC (permalink / raw)
  To: Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Wed, Sep 29, 2010, Mark Brown wrote:
> On Tue, Sep 28, 2010 at 07:34:34PM -0700, Peter Hsiang wrote:
> 
> > +#define EQ_CFG_MAX 32
> 
> There doesn't seem to be any need to hard code a limit here?

Are you requiring the implementation be exactly the same way you did?

> 
> > +/*
> > + * For kernels compiled without unsigned long long int division
> > + */
> > +unsigned long long int ulldiv(unsigned long long int dividend,
> > +                             unsigned long long int divisor)
> 
> This is causing me some concern.  Even if it is required this does not
> look like something that should be part of a specific driver - what
> happens if some other driver needs the same thing?

You are more than welcome to use this in your driver as well :)
Just use it.  Sounds like you are suggesting to have it located in a
central location outside of the codec driver?
Would you like that in soc-core.c?  Let me know.

> 
> > +/*
> > + * The INx1 and INx2 PGAs 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.
> > + */
> 
> This...
> 
> > +       if (strncmp(w->name, "INA1", 4) == 0) {
> > +               pga = INA1_PGA_BIT;
> > +               mask = INA1_PGA_BIT | INA2_PGA_BIT;
> 
> ...doesn't seem to correspond to this, at least with the normal way mask
> is used.  Apart from anything else it looks like you have individual
> mask bits for each of the INx1 and INx2 PGAs (since you can define mask
> bits for each of them).  It would be much clearer if the code made it
> obvious that these aren't register bits but rather that you're storing
> the data in a register-like bitfield in a variable.  I can't help but
> think that a reference count would be much clearer.

There are more than one way to solve a problem, and they all can be
correct.
Would additional comments to clarify that this is not register bits
satisfy your requirement?

> 
> > +       if (event == SND_SOC_DAPM_POST_PMU) {
> > +               /* ON */
> > +               *state |= pga;
> > +
> > +               /* Turn on, avoiding unnecessary writes */
> > +               val = snd_soc_read(codec, w->reg);
> > +               if (!(val & (1 << w->shift))) {
> > +                       val |= (1 << w->shift);
> > +                       snd_soc_write(codec, w->reg, val);
> > +               }
> 
> snd_soc_update_bits() will suppress unwanted register writes.

>From the comments sounds like it resemble snd_soc_update_bits.
The code logic here is different from snd_soc_update_bits,
and therefore snd_soc_update_bits can not be used for this
like you suggested.

> > +
> > +static const unsigned int ex_mode_table[] = {
> > +       0x00,           /* disabled */
> > +       (0<<4)|3,       /* 100Hz */
> > +       (1<<4)|0,       /* 400Hz */
> > +       (2<<4)|0,       /* 600Hz */
> > +       (3<<4)|0,       /* 800Hz */
> > +       (4<<4)|0,       /* 1000Hz */
> > +       (1<<4)|1,       /* 200-400Hz */
> > +       (2<<4)|2,       /* 400-600Hz */
> > +       (3<<4)|2,       /* 400-800Hz */
> > +};
> 
> You should add value muxes like we have for DAPM.

Please clarify what you mean by referencing the specific 
code usage case in the dapm source module.

> 
> > +static const char *max98088_extmic[] = {
> > +       "Off",
> > +       "MIC1",
> > +       "MIC2",
> > +};
> 
> > +static const struct soc_enum max98088_extmic_enum[] = {
> > +       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
> > +};
> 
> This looks like it should be in DAPM.

This has nothing to do with power management.  
I guess you were confused by the word "Off" in the options list.
The "Off" case means the input for external mic is not used,
i.e. disconnected.  It does not power up or down anything.

> 
> > +static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
> > +                               struct snd_ctl_elem_value *ucontrol)
> > +{
> > +       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
> > +       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
> > +       unsigned int *mode = &max98088->mic1pre;
> > +       int sel = ucontrol->value.integer.value[0];
> > +
> > +       if (sel >= ARRAY_SIZE(max98088_micpre))
> > +               return -EINVAL;
> > +
> > +       *mode = ucontrol->value.integer.value[0];
> > +       return 0;
> > +}
> 
> I'd expect that some action would be taken when the value is set here.
> All this does is set a variable, changing the control will have no
> effect.

The register field works like this:
00 = preamp disabled
01 = 0dB
10 = 20dB
11 = 30dB
So the gain and the power setting are in the same bit field.
DAPM owns the power control.  Here the user settings are stored,
and they get picked up by DAPM.

> 
> > +static int max98088_hp_event(struct snd_soc_dapm_widget *w,
> > +                            struct snd_kcontrol *kcontrol, int event)
> > +{
> > +       struct snd_soc_codec *codec = w->codec;
> > +       u16 status;
> > +
> > +       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
> > +
> > +       /* powering down headphone gracefully */
> > +       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
> > +       if ((status & M98088_HPEN) == M98088_HPEN) {
> > +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> > +                       (status & ~M98088_HPEN));
> > +       }
> > +       schedule_timeout(msecs_to_jiffies(20));
> 
> This looks rather like it should just be a post event implementing a
> timeout?

This needs to work as a pre event.

> 
> > +       if (rate != cdata->rate) {
> > +               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
> > +               if (rate_value(rate, &regval))
> > +                       return -EINVAL;
> > +
> > +               snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
> > +               cdata->rate = rate;
> > +       }
> 
> Just use snd_soc_update_bits() to suppress unneeded writes.  Many of the
> operaations have this issue.

The full value for this register is accounted for here and so there
is no need to use snd_soc_update_bits for this case.

> 
> > +               & M98088_DAI_MAS) {
> > +               if (max98088->sysclk == 0)
> > +                       return -EINVAL;
> 
> You should print an error here so users can tell what went wrong.

Yes thank you.

> 
> > +       case SND_SOC_BIAS_STANDBY:
> > +               max98088_sync_cache(codec);
> > +               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
> > +                               M98088_MBEN, M98088_MBEN);
> > +               break;
> 
> Do you really want to sync the cache *every* time you go into standby?

The sync_cache function itself will just return if the
codec->cache_sync flag is cleared from the first time it ran.
You do the exact same thing in your codec driver...
What is the change that you are suggesting?

> 
> > +module_init(max98088_init);
> 
> Normally this would be next to the function it references.

Is this a new formatting style of the kernel now all across,
or is this a personal preference?
Not a problem, I can change it.  Just that I find a huge number 
of drivers in the kernel having the module_init and module_exit 
together at the very end.


^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-29 21:42     ` Peter Hsiang
  0 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-29 21:42 UTC (permalink / raw)
  To: Mark Brown
  Cc: alsa-devel, Jesse, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Marroquin, Liam, Girdwood

On Wed, Sep 29, 2010, Mark Brown wrote:
> On Tue, Sep 28, 2010 at 07:34:34PM -0700, Peter Hsiang wrote:
> 
> > +#define EQ_CFG_MAX 32
> 
> There doesn't seem to be any need to hard code a limit here?

Are you requiring the implementation be exactly the same way you did?

> 
> > +/*
> > + * For kernels compiled without unsigned long long int division
> > + */
> > +unsigned long long int ulldiv(unsigned long long int dividend,
> > +                             unsigned long long int divisor)
> 
> This is causing me some concern.  Even if it is required this does not
> look like something that should be part of a specific driver - what
> happens if some other driver needs the same thing?

You are more than welcome to use this in your driver as well :)
Just use it.  Sounds like you are suggesting to have it located in a
central location outside of the codec driver?
Would you like that in soc-core.c?  Let me know.

> 
> > +/*
> > + * The INx1 and INx2 PGAs 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.
> > + */
> 
> This...
> 
> > +       if (strncmp(w->name, "INA1", 4) == 0) {
> > +               pga = INA1_PGA_BIT;
> > +               mask = INA1_PGA_BIT | INA2_PGA_BIT;
> 
> ...doesn't seem to correspond to this, at least with the normal way mask
> is used.  Apart from anything else it looks like you have individual
> mask bits for each of the INx1 and INx2 PGAs (since you can define mask
> bits for each of them).  It would be much clearer if the code made it
> obvious that these aren't register bits but rather that you're storing
> the data in a register-like bitfield in a variable.  I can't help but
> think that a reference count would be much clearer.

There are more than one way to solve a problem, and they all can be
correct.
Would additional comments to clarify that this is not register bits
satisfy your requirement?

> 
> > +       if (event == SND_SOC_DAPM_POST_PMU) {
> > +               /* ON */
> > +               *state |= pga;
> > +
> > +               /* Turn on, avoiding unnecessary writes */
> > +               val = snd_soc_read(codec, w->reg);
> > +               if (!(val & (1 << w->shift))) {
> > +                       val |= (1 << w->shift);
> > +                       snd_soc_write(codec, w->reg, val);
> > +               }
> 
> snd_soc_update_bits() will suppress unwanted register writes.

>From the comments sounds like it resemble snd_soc_update_bits.
The code logic here is different from snd_soc_update_bits,
and therefore snd_soc_update_bits can not be used for this
like you suggested.

> > +
> > +static const unsigned int ex_mode_table[] = {
> > +       0x00,           /* disabled */
> > +       (0<<4)|3,       /* 100Hz */
> > +       (1<<4)|0,       /* 400Hz */
> > +       (2<<4)|0,       /* 600Hz */
> > +       (3<<4)|0,       /* 800Hz */
> > +       (4<<4)|0,       /* 1000Hz */
> > +       (1<<4)|1,       /* 200-400Hz */
> > +       (2<<4)|2,       /* 400-600Hz */
> > +       (3<<4)|2,       /* 400-800Hz */
> > +};
> 
> You should add value muxes like we have for DAPM.

Please clarify what you mean by referencing the specific 
code usage case in the dapm source module.

> 
> > +static const char *max98088_extmic[] = {
> > +       "Off",
> > +       "MIC1",
> > +       "MIC2",
> > +};
> 
> > +static const struct soc_enum max98088_extmic_enum[] = {
> > +       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
> > +};
> 
> This looks like it should be in DAPM.

This has nothing to do with power management.  
I guess you were confused by the word "Off" in the options list.
The "Off" case means the input for external mic is not used,
i.e. disconnected.  It does not power up or down anything.

> 
> > +static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
> > +                               struct snd_ctl_elem_value *ucontrol)
> > +{
> > +       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
> > +       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
> > +       unsigned int *mode = &max98088->mic1pre;
> > +       int sel = ucontrol->value.integer.value[0];
> > +
> > +       if (sel >= ARRAY_SIZE(max98088_micpre))
> > +               return -EINVAL;
> > +
> > +       *mode = ucontrol->value.integer.value[0];
> > +       return 0;
> > +}
> 
> I'd expect that some action would be taken when the value is set here.
> All this does is set a variable, changing the control will have no
> effect.

The register field works like this:
00 = preamp disabled
01 = 0dB
10 = 20dB
11 = 30dB
So the gain and the power setting are in the same bit field.
DAPM owns the power control.  Here the user settings are stored,
and they get picked up by DAPM.

> 
> > +static int max98088_hp_event(struct snd_soc_dapm_widget *w,
> > +                            struct snd_kcontrol *kcontrol, int event)
> > +{
> > +       struct snd_soc_codec *codec = w->codec;
> > +       u16 status;
> > +
> > +       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
> > +
> > +       /* powering down headphone gracefully */
> > +       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
> > +       if ((status & M98088_HPEN) == M98088_HPEN) {
> > +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> > +                       (status & ~M98088_HPEN));
> > +       }
> > +       schedule_timeout(msecs_to_jiffies(20));
> 
> This looks rather like it should just be a post event implementing a
> timeout?

This needs to work as a pre event.

> 
> > +       if (rate != cdata->rate) {
> > +               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
> > +               if (rate_value(rate, &regval))
> > +                       return -EINVAL;
> > +
> > +               snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
> > +               cdata->rate = rate;
> > +       }
> 
> Just use snd_soc_update_bits() to suppress unneeded writes.  Many of the
> operaations have this issue.

The full value for this register is accounted for here and so there
is no need to use snd_soc_update_bits for this case.

> 
> > +               & M98088_DAI_MAS) {
> > +               if (max98088->sysclk == 0)
> > +                       return -EINVAL;
> 
> You should print an error here so users can tell what went wrong.

Yes thank you.

> 
> > +       case SND_SOC_BIAS_STANDBY:
> > +               max98088_sync_cache(codec);
> > +               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
> > +                               M98088_MBEN, M98088_MBEN);
> > +               break;
> 
> Do you really want to sync the cache *every* time you go into standby?

The sync_cache function itself will just return if the
codec->cache_sync flag is cleared from the first time it ran.
You do the exact same thing in your codec driver...
What is the change that you are suggesting?

> 
> > +module_init(max98088_init);
> 
> Normally this would be next to the function it references.

Is this a new formatting style of the kernel now all across,
or is this a personal preference?
Not a problem, I can change it.  Just that I find a huge number 
of drivers in the kernel having the module_init and module_exit 
together at the very end.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-29  2:34 ` Peter Hsiang
@ 2010-09-29  3:37   ` Mark Brown
  -1 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-29  3:37 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Tue, Sep 28, 2010 at 07:34:34PM -0700, Peter Hsiang wrote:

> +#define EQ_CFG_MAX 32

There doesn't seem to be any need to hard code a limit here?

> +static int max98088_hw_write(struct snd_soc_codec *codec, unsigned int reg,
> +                            unsigned int value)
> +{
> +       u8 data[2];
> +
> +       data[0] = reg;
> +       data[1] = value;
> +       if (codec->hw_write(codec->control_data, data, 2) == 2)
> +               return 0;
> +       else
> +               return -EIO;
> +}

As previously discussed you should use the soc-cache code here.

> +/*
> + * For kernels compiled without unsigned long long int division
> + */
> +unsigned long long int ulldiv(unsigned long long int dividend,
> +                             unsigned long long int divisor)

This is causing me some concern.  Even if it is required this does not
look like something that should be part of a specific driver - what
happens if some other driver needs the same thing?

> +/*
> + * The INx1 and INx2 PGAs 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.
> + */

This...

> +       if (strncmp(w->name, "INA1", 4) == 0) {
> +               pga = INA1_PGA_BIT;
> +               mask = INA1_PGA_BIT | INA2_PGA_BIT;

...doesn't seem to correspond to this, at least with the normal way mask
is used.  Apart from anything else it looks like you have individual
mask bits for each of the INx1 and INx2 PGAs (since you can define mask
bits for each of them).  It would be much clearer if the code made it
obvious that these aren't register bits but rather that you're storing
the data in a register-like bitfield in a variable.  I can't help but
think that a reference count would be much clearer.

> +       if (event == SND_SOC_DAPM_POST_PMU) {
> +               /* ON */
> +               *state |= pga;
> +
> +               /* Turn on, avoiding unnecessary writes */
> +               val = snd_soc_read(codec, w->reg);
> +               if (!(val & (1 << w->shift))) {
> +                       val |= (1 << w->shift);
> +                       snd_soc_write(codec, w->reg, val);
> +               }

snd_soc_update_bits() will suppress unwanted register writes.

> +       }
> +
> +       return;
> +}

No need for return statements at the end of void functions.

> +
> +static const unsigned int ex_mode_table[] = {
> +       0x00,           /* disabled */
> +       (0<<4)|3,       /* 100Hz */
> +       (1<<4)|0,       /* 400Hz */
> +       (2<<4)|0,       /* 600Hz */
> +       (3<<4)|0,       /* 800Hz */
> +       (4<<4)|0,       /* 1000Hz */
> +       (1<<4)|1,       /* 200-400Hz */
> +       (2<<4)|2,       /* 400-600Hz */
> +       (3<<4)|2,       /* 400-800Hz */
> +};

You should add value muxes like we have for DAPM.

> +static const char *max98088_micpre[] = {
> +       "0dB",
> +       "20dB",
> +       "30dB",
> +};

> +static const struct soc_enum max98088_micpre_enum[] = {
> +       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_micpre), max98088_micpre),
> +};

This should be a TLV control, not an enum.

> +static const char *max98088_extmic[] = {
> +       "Off",
> +       "MIC1",
> +       "MIC2",
> +};

> +static const struct soc_enum max98088_extmic_enum[] = {
> +       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
> +};

This looks like it should be in DAPM.

> +static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
> +                               struct snd_ctl_elem_value *ucontrol)
> +{
> +       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
> +       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
> +       unsigned int *mode = &max98088->mic1pre;
> +       int sel = ucontrol->value.integer.value[0];
> +
> +       if (sel >= ARRAY_SIZE(max98088_micpre))
> +               return -EINVAL;
> +
> +       *mode = ucontrol->value.integer.value[0];
> +       return 0;
> +}

I'd expect that some action would be taken when the value is set here.
All this does is set a variable, changing the control will have no
effect.

> +static int max98088_hp_event(struct snd_soc_dapm_widget *w,
> +                            struct snd_kcontrol *kcontrol, int event)
> +{
> +       struct snd_soc_codec *codec = w->codec;
> +       u16 status;
> +
> +       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
> +
> +       /* powering down headphone gracefully */
> +       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
> +       if ((status & M98088_HPEN) == M98088_HPEN) {
> +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> +                       (status & ~M98088_HPEN));
> +       }
> +       schedule_timeout(msecs_to_jiffies(20));

This looks rather like it should just be a post event implementing a
timeout?

> +       if (rate != cdata->rate) {
> +               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
> +               if (rate_value(rate, &regval))
> +                       return -EINVAL;
> +
> +               snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
> +               cdata->rate = rate;
> +       }

Just use snd_soc_update_bits() to suppress unneeded writes.  Many of the
operaations have this issue.

> +               & M98088_DAI_MAS) {
> +               if (max98088->sysclk == 0)
> +                       return -EINVAL;

You should print an error here so users can tell what went wrong.

> +               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
> +               case SND_SOC_DAIFMT_I2S:
> +                       reg14val |= M98088_DAI_DLY;
> +                       break;
> +               case SND_SOC_DAIFMT_LEFT_J:
> +                       reg14msk |= M98088_DAI_DLY;
> +                       break;

This looks fishy - in one case you set the value, in another format you
set the mask, both to the same bitfield.   I'd expect the mask of bits
being updated to stay constant, but you seem to be treating the mask as
meaning bits to be cleared.  I rather suspect you'll run into problems
if you test this.

> +       case SND_SOC_BIAS_STANDBY:
> +               max98088_sync_cache(codec);
> +               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
> +                               M98088_MBEN, M98088_MBEN);
> +               break;

Do you really want to sync the cache *every* time you go into standby?

> +
> +       case SND_SOC_BIAS_OFF:
> +               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
> +                               M98088_MBEN, 0);
> +#ifdef CONFIG_REGULATOR
> +               codec->cache_sync = 1;
> +#endif

Why the ifdef?

> +
> +       /* Build an array of texts for the enum API. The number
> +        * of texts is likely fewer than the number of configurations
> +        * due to multiple sample rates for the same text name. */
> +       cdata->eq_textcnt = 0;
> +       for (i = 0; i < pdata->eq1_cfgcnt; i++) {

It might be nice to give credit to the drivers you've based your work on :)

> +       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
> +
> +       /* Sync reg_cache with the hardware */
> +       for (i = 0; i < M98088_REG_CNT; i++) {
> +               if (i == M98088_REG_51_PWR_SYS)
> +                       continue;
> +
> +               if (!max98088_access[i].writable)
> +                       continue;
> +
> +               max98088_hw_write(codec, i, cache[i]);
> +       }

This appears to duplicate the register sync in your bias management.

> +static struct i2c_driver max98088_i2c_driver = {
> +       .driver = {
> +               .name = "max98088-codec",

Drop the -codec from the name.

> +module_init(max98088_init);

Normally this would be next to the function it references.

> +struct max98088_setup_data {
> +       unsigned short i2c_address;
> +};

This should be removed.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-29  3:37   ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-29  3:37 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Jesse Marroquin, Liam Girdwood

On Tue, Sep 28, 2010 at 07:34:34PM -0700, Peter Hsiang wrote:

> +#define EQ_CFG_MAX 32

There doesn't seem to be any need to hard code a limit here?

> +static int max98088_hw_write(struct snd_soc_codec *codec, unsigned int reg,
> +                            unsigned int value)
> +{
> +       u8 data[2];
> +
> +       data[0] = reg;
> +       data[1] = value;
> +       if (codec->hw_write(codec->control_data, data, 2) == 2)
> +               return 0;
> +       else
> +               return -EIO;
> +}

As previously discussed you should use the soc-cache code here.

> +/*
> + * For kernels compiled without unsigned long long int division
> + */
> +unsigned long long int ulldiv(unsigned long long int dividend,
> +                             unsigned long long int divisor)

This is causing me some concern.  Even if it is required this does not
look like something that should be part of a specific driver - what
happens if some other driver needs the same thing?

> +/*
> + * The INx1 and INx2 PGAs 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.
> + */

This...

> +       if (strncmp(w->name, "INA1", 4) == 0) {
> +               pga = INA1_PGA_BIT;
> +               mask = INA1_PGA_BIT | INA2_PGA_BIT;

...doesn't seem to correspond to this, at least with the normal way mask
is used.  Apart from anything else it looks like you have individual
mask bits for each of the INx1 and INx2 PGAs (since you can define mask
bits for each of them).  It would be much clearer if the code made it
obvious that these aren't register bits but rather that you're storing
the data in a register-like bitfield in a variable.  I can't help but
think that a reference count would be much clearer.

> +       if (event == SND_SOC_DAPM_POST_PMU) {
> +               /* ON */
> +               *state |= pga;
> +
> +               /* Turn on, avoiding unnecessary writes */
> +               val = snd_soc_read(codec, w->reg);
> +               if (!(val & (1 << w->shift))) {
> +                       val |= (1 << w->shift);
> +                       snd_soc_write(codec, w->reg, val);
> +               }

snd_soc_update_bits() will suppress unwanted register writes.

> +       }
> +
> +       return;
> +}

No need for return statements at the end of void functions.

> +
> +static const unsigned int ex_mode_table[] = {
> +       0x00,           /* disabled */
> +       (0<<4)|3,       /* 100Hz */
> +       (1<<4)|0,       /* 400Hz */
> +       (2<<4)|0,       /* 600Hz */
> +       (3<<4)|0,       /* 800Hz */
> +       (4<<4)|0,       /* 1000Hz */
> +       (1<<4)|1,       /* 200-400Hz */
> +       (2<<4)|2,       /* 400-600Hz */
> +       (3<<4)|2,       /* 400-800Hz */
> +};

You should add value muxes like we have for DAPM.

> +static const char *max98088_micpre[] = {
> +       "0dB",
> +       "20dB",
> +       "30dB",
> +};

> +static const struct soc_enum max98088_micpre_enum[] = {
> +       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_micpre), max98088_micpre),
> +};

This should be a TLV control, not an enum.

> +static const char *max98088_extmic[] = {
> +       "Off",
> +       "MIC1",
> +       "MIC2",
> +};

> +static const struct soc_enum max98088_extmic_enum[] = {
> +       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
> +};

This looks like it should be in DAPM.

> +static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
> +                               struct snd_ctl_elem_value *ucontrol)
> +{
> +       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
> +       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
> +       unsigned int *mode = &max98088->mic1pre;
> +       int sel = ucontrol->value.integer.value[0];
> +
> +       if (sel >= ARRAY_SIZE(max98088_micpre))
> +               return -EINVAL;
> +
> +       *mode = ucontrol->value.integer.value[0];
> +       return 0;
> +}

I'd expect that some action would be taken when the value is set here.
All this does is set a variable, changing the control will have no
effect.

> +static int max98088_hp_event(struct snd_soc_dapm_widget *w,
> +                            struct snd_kcontrol *kcontrol, int event)
> +{
> +       struct snd_soc_codec *codec = w->codec;
> +       u16 status;
> +
> +       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
> +
> +       /* powering down headphone gracefully */
> +       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
> +       if ((status & M98088_HPEN) == M98088_HPEN) {
> +               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
> +                       (status & ~M98088_HPEN));
> +       }
> +       schedule_timeout(msecs_to_jiffies(20));

This looks rather like it should just be a post event implementing a
timeout?

> +       if (rate != cdata->rate) {
> +               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
> +               if (rate_value(rate, &regval))
> +                       return -EINVAL;
> +
> +               snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
> +               cdata->rate = rate;
> +       }

Just use snd_soc_update_bits() to suppress unneeded writes.  Many of the
operaations have this issue.

> +               & M98088_DAI_MAS) {
> +               if (max98088->sysclk == 0)
> +                       return -EINVAL;

You should print an error here so users can tell what went wrong.

> +               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
> +               case SND_SOC_DAIFMT_I2S:
> +                       reg14val |= M98088_DAI_DLY;
> +                       break;
> +               case SND_SOC_DAIFMT_LEFT_J:
> +                       reg14msk |= M98088_DAI_DLY;
> +                       break;

This looks fishy - in one case you set the value, in another format you
set the mask, both to the same bitfield.   I'd expect the mask of bits
being updated to stay constant, but you seem to be treating the mask as
meaning bits to be cleared.  I rather suspect you'll run into problems
if you test this.

> +       case SND_SOC_BIAS_STANDBY:
> +               max98088_sync_cache(codec);
> +               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
> +                               M98088_MBEN, M98088_MBEN);
> +               break;

Do you really want to sync the cache *every* time you go into standby?

> +
> +       case SND_SOC_BIAS_OFF:
> +               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
> +                               M98088_MBEN, 0);
> +#ifdef CONFIG_REGULATOR
> +               codec->cache_sync = 1;
> +#endif

Why the ifdef?

> +
> +       /* Build an array of texts for the enum API. The number
> +        * of texts is likely fewer than the number of configurations
> +        * due to multiple sample rates for the same text name. */
> +       cdata->eq_textcnt = 0;
> +       for (i = 0; i < pdata->eq1_cfgcnt; i++) {

It might be nice to give credit to the drivers you've based your work on :)

> +       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
> +
> +       /* Sync reg_cache with the hardware */
> +       for (i = 0; i < M98088_REG_CNT; i++) {
> +               if (i == M98088_REG_51_PWR_SYS)
> +                       continue;
> +
> +               if (!max98088_access[i].writable)
> +                       continue;
> +
> +               max98088_hw_write(codec, i, cache[i]);
> +       }

This appears to duplicate the register sync in your bias management.

> +static struct i2c_driver max98088_i2c_driver = {
> +       .driver = {
> +               .name = "max98088-codec",

Drop the -codec from the name.

> +module_init(max98088_init);

Normally this would be next to the function it references.

> +struct max98088_setup_data {
> +       unsigned short i2c_address;
> +};

This should be removed.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-29  2:34 ` Peter Hsiang
  0 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-29  2:34 UTC (permalink / raw)
  To: Jaroslav Kysela, Takashi Iwai, Peter Hsiang, Liam Girdwood,
	Mark Brown, Peter Ujfalusi
  Cc: alsa-devel, linux-kernel, Jesse Marroquin

This patch adds the MAX98088 CODEC driver.

Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
---
 include/sound/max98088.h    |   54 +
 sound/soc/codecs/Kconfig    |    4 +
 sound/soc/codecs/Makefile   |    2 +
 sound/soc/codecs/max98088.c | 2355 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/max98088.h |  190 ++++
 5 files changed, 2605 insertions(+), 0 deletions(-)
 create mode 100644 include/sound/max98088.h
 create mode 100644 sound/soc/codecs/max98088.c
 create mode 100644 sound/soc/codecs/max98088.h

diff --git a/include/sound/max98088.h b/include/sound/max98088.h
new file mode 100644
index 0000000..30652be
--- /dev/null
+++ b/include/sound/max98088.h
@@ -0,0 +1,54 @@
+/*
+ * Platform data for MAX98088
+ *
+ * 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 as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __SOUND_MAX98088_PDATA_H__
+#define __SOUND_MAX98088_PDATA_H__
+
+#define EQ_CFG_MAX 32
+
+/* Equalizer filter response configuration */
+struct max98088_eq_cfg {
+       const char *name;
+       unsigned int rate;
+       u16 band1[5];
+       u16 band2[5];
+       u16 band3[5];
+       u16 band4[5];
+       u16 band5[5];
+};
+
+/* codec platform data */
+struct max98088_pdata {
+
+       /* Equalizers for DAI1 and DAI2 */
+       struct max98088_eq_cfg *eq1_cfg;
+       struct max98088_eq_cfg *eq2_cfg;
+       unsigned int eq1_cfgcnt;
+       unsigned int eq2_cfgcnt;
+
+       /* Receiver output can be configured as power amplifier or LINE out */
+       /* Set receiver_mode to:
+        * 0 = amplifier output, or
+        * 1 = LINE level output
+        */
+       unsigned int receiver_mode:1;
+
+       /* Analog/digital microphone configuration:
+        * 0 = analog microphone input (normal setting)
+        * 1 = digital microphone input
+        */
+       unsigned int digmic_left_mode:1;
+       unsigned int digmic_right_mode:1;
+
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4ccc2b7..4e6713c 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -27,6 +27,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_CS4270 if I2C
        select SND_SOC_DA7210 if I2C
        select SND_SOC_JZ4740 if SOC_JZ4740
+       select SND_SOC_MAX98088 if I2C
        select SND_SOC_MAX9877 if I2C
        select SND_SOC_PCM3008
        select SND_SOC_SPDIF
@@ -157,6 +158,9 @@ config SND_SOC_L3
 config SND_SOC_DA7210
         tristate

+config SND_SOC_MAX98088
+       tristate
+
 config SND_SOC_PCM3008
        tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 23e7e2c..7184611 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -15,6 +15,7 @@ snd-soc-cs4270-objs := cs4270.o
 snd-soc-cx20442-objs := cx20442.o
 snd-soc-da7210-objs := da7210.o
 snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
 snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-spdif-objs := spdif_transciever.o
 snd-soc-ssm2602-objs := ssm2602.o
@@ -88,6 +89,7 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
 obj-$(CONFIG_SND_SOC_DA7210)   += snd-soc-da7210.o
 obj-$(CONFIG_SND_SOC_L3)       += snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_JZ4740_CODEC)     += snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088)  += snd-soc-max98088.o
 obj-$(CONFIG_SND_SOC_PCM3008)  += snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_SPDIF)    += snd-soc-spdif.o
 obj-$(CONFIG_SND_SOC_SSM2602)  += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
new file mode 100644
index 0000000..52f57d5
--- /dev/null
+++ b/sound/soc/codecs/max98088.c
@@ -0,0 +1,2355 @@
+/*
+ * max98088.c -- MAX98088 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 <linux/slab.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+/* Configurations associated with each channel of DAI stream */
+struct max98088_cdata {
+       unsigned int rate;
+       unsigned int fmt;
+       int eq_textcnt;
+       const char *eq_texts[EQ_CFG_MAX];
+       int eq_sel;
+       struct soc_enum eq_enum;
+};
+
+/* Codec private data */
+struct max98088_priv {
+       u8 reg_cache[M98088_REG_CNT];
+       void *control_data;
+       struct max98088_pdata *pdata;
+
+       unsigned int sysclk;
+       struct max98088_cdata dai[2];
+       u8 power_state;
+       unsigned int ex_mode;
+       unsigned int digmic;
+       unsigned int mic1pre;
+       unsigned int mic2pre;
+       unsigned int extmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+       0x00, /* 00 IRQ status */
+       0x00, /* 01 MIC status */
+       0x00, /* 02 jack status */
+       0x00, /* 03 battery voltage */
+       0x00, /* 04 */
+       0x00, /* 05 */
+       0x00, /* 06 */
+       0x00, /* 07 */
+       0x00, /* 08 */
+       0x00, /* 09 */
+       0x00, /* 0A */
+       0x00, /* 0B */
+       0x00, /* 0C */
+       0x00, /* 0D */
+       0x00, /* 0E */
+       0x00, /* 0F interrupt enable */
+
+       0x00, /* 10 master clock */
+       0x00, /* 11 DAI1 clock mode */
+       0x00, /* 12 DAI1 clock control */
+       0x00, /* 13 DAI1 clock control */
+       0x00, /* 14 DAI1 format */
+       0x00, /* 15 DAI1 clock */
+       0x00, /* 16 DAI1 config */
+       0x00, /* 17 DAI1 TDM */
+       0x00, /* 18 DAI1 filters */
+       0x00, /* 19 DAI2 clock mode */
+       0x00, /* 1A DAI2 clock control */
+       0x00, /* 1B DAI2 clock control */
+       0x00, /* 1C DAI2 format */
+       0x00, /* 1D DAI2 clock */
+       0x00, /* 1E DAI2 config */
+       0x00, /* 1F DAI2 TDM */
+
+       0x00, /* 20 DAI2 filters */
+       0x00, /* 21 data config */
+       0x00, /* 22 DAC mixer */
+       0x00, /* 23 left ADC mixer */
+       0x00, /* 24 right ADC mixer */
+       0x00, /* 25 left HP mixer */
+       0x00, /* 26 right HP mixer */
+       0x00, /* 27 HP control */
+       0x00, /* 28 left REC mixer */
+       0x00, /* 29 right REC mixer */
+       0x00, /* 2A REC control */
+       0x00, /* 2B left SPK mixer */
+       0x00, /* 2C right SPK mixer */
+       0x00, /* 2D SPK control */
+       0x00, /* 2E sidetone */
+       0x00, /* 2F DAI1 playback level */
+
+       0x00, /* 30 DAI1 playback level */
+       0x00, /* 31 DAI2 playback level */
+       0x00, /* 32 DAI2 playbakc level */
+       0x00, /* 33 left ADC level */
+       0x00, /* 34 right ADC level */
+       0x00, /* 35 MIC1 level */
+       0x00, /* 36 MIC2 level */
+       0x00, /* 37 INA level */
+       0x00, /* 38 INB level */
+       0x00, /* 39 left HP volume */
+       0x00, /* 3A right HP volume */
+       0x00, /* 3B left REC volume */
+       0x00, /* 3C right REC volume */
+       0x00, /* 3D left SPK volume */
+       0x00, /* 3E right SPK volume */
+       0x00, /* 3F MIC config */
+
+       0x00, /* 40 MIC threshold */
+       0x00, /* 41 excursion limiter filter */
+       0x00, /* 42 excursion limiter threshold */
+       0x00, /* 43 ALC */
+       0x00, /* 44 power limiter threshold */
+       0x00, /* 45 power limiter config */
+       0x00, /* 46 distortion limiter config */
+       0x00, /* 47 audio input */
+       0x00, /* 48 microphone */
+       0x00, /* 49 level control */
+       0x00, /* 4A bypass switches */
+       0x00, /* 4B jack detect */
+       0x00, /* 4C input enable */
+       0x00, /* 4D output enable */
+       0xF0, /* 4E bias control */
+       0x00, /* 4F DAC power */
+
+       0x0F, /* 50 DAC power */
+       0x00, /* 51 system */
+       0x00, /* 52 DAI1 EQ1 */
+       0x00, /* 53 DAI1 EQ1 */
+       0x00, /* 54 DAI1 EQ1 */
+       0x00, /* 55 DAI1 EQ1 */
+       0x00, /* 56 DAI1 EQ1 */
+       0x00, /* 57 DAI1 EQ1 */
+       0x00, /* 58 DAI1 EQ1 */
+       0x00, /* 59 DAI1 EQ1 */
+       0x00, /* 5A DAI1 EQ1 */
+       0x00, /* 5B DAI1 EQ1 */
+       0x00, /* 5C DAI1 EQ2 */
+       0x00, /* 5D DAI1 EQ2 */
+       0x00, /* 5E DAI1 EQ2 */
+       0x00, /* 5F DAI1 EQ2 */
+
+       0x00, /* 60 DAI1 EQ2 */
+       0x00, /* 61 DAI1 EQ2 */
+       0x00, /* 62 DAI1 EQ2 */
+       0x00, /* 63 DAI1 EQ2 */
+       0x00, /* 64 DAI1 EQ2 */
+       0x00, /* 65 DAI1 EQ2 */
+       0x00, /* 66 DAI1 EQ3 */
+       0x00, /* 67 DAI1 EQ3 */
+       0x00, /* 68 DAI1 EQ3 */
+       0x00, /* 69 DAI1 EQ3 */
+       0x00, /* 6A DAI1 EQ3 */
+       0x00, /* 6B DAI1 EQ3 */
+       0x00, /* 6C DAI1 EQ3 */
+       0x00, /* 6D DAI1 EQ3 */
+       0x00, /* 6E DAI1 EQ3 */
+       0x00, /* 6F DAI1 EQ3 */
+
+       0x00, /* 70 DAI1 EQ4 */
+       0x00, /* 71 DAI1 EQ4 */
+       0x00, /* 72 DAI1 EQ4 */
+       0x00, /* 73 DAI1 EQ4 */
+       0x00, /* 74 DAI1 EQ4 */
+       0x00, /* 75 DAI1 EQ4 */
+       0x00, /* 76 DAI1 EQ4 */
+       0x00, /* 77 DAI1 EQ4 */
+       0x00, /* 78 DAI1 EQ4 */
+       0x00, /* 79 DAI1 EQ4 */
+       0x00, /* 7A DAI1 EQ5 */
+       0x00, /* 7B DAI1 EQ5 */
+       0x00, /* 7C DAI1 EQ5 */
+       0x00, /* 7D DAI1 EQ5 */
+       0x00, /* 7E DAI1 EQ5 */
+       0x00, /* 7F DAI1 EQ5 */
+
+       0x00, /* 80 DAI1 EQ5 */
+       0x00, /* 81 DAI1 EQ5 */
+       0x00, /* 82 DAI1 EQ5 */
+       0x00, /* 83 DAI1 EQ5 */
+       0x00, /* 84 DAI2 EQ1 */
+       0x00, /* 85 DAI2 EQ1 */
+       0x00, /* 86 DAI2 EQ1 */
+       0x00, /* 87 DAI2 EQ1 */
+       0x00, /* 88 DAI2 EQ1 */
+       0x00, /* 89 DAI2 EQ1 */
+       0x00, /* 8A DAI2 EQ1 */
+       0x00, /* 8B DAI2 EQ1 */
+       0x00, /* 8C DAI2 EQ1 */
+       0x00, /* 8D DAI2 EQ1 */
+       0x00, /* 8E DAI2 EQ2 */
+       0x00, /* 8F DAI2 EQ2 */
+
+       0x00, /* 90 DAI2 EQ2 */
+       0x00, /* 91 DAI2 EQ2 */
+       0x00, /* 92 DAI2 EQ2 */
+       0x00, /* 93 DAI2 EQ2 */
+       0x00, /* 94 DAI2 EQ2 */
+       0x00, /* 95 DAI2 EQ2 */
+       0x00, /* 96 DAI2 EQ2 */
+       0x00, /* 97 DAI2 EQ2 */
+       0x00, /* 98 DAI2 EQ3 */
+       0x00, /* 99 DAI2 EQ3 */
+       0x00, /* 9A DAI2 EQ3 */
+       0x00, /* 9B DAI2 EQ3 */
+       0x00, /* 9C DAI2 EQ3 */
+       0x00, /* 9D DAI2 EQ3 */
+       0x00, /* 9E DAI2 EQ3 */
+       0x00, /* 9F DAI2 EQ3 */
+
+       0x00, /* A0 DAI2 EQ3 */
+       0x00, /* A1 DAI2 EQ3 */
+       0x00, /* A2 DAI2 EQ4 */
+       0x00, /* A3 DAI2 EQ4 */
+       0x00, /* A4 DAI2 EQ4 */
+       0x00, /* A5 DAI2 EQ4 */
+       0x00, /* A6 DAI2 EQ4 */
+       0x00, /* A7 DAI2 EQ4 */
+       0x00, /* A8 DAI2 EQ4 */
+       0x00, /* A9 DAI2 EQ4 */
+       0x00, /* AA DAI2 EQ4 */
+       0x00, /* AB DAI2 EQ4 */
+       0x00, /* AC DAI2 EQ5 */
+       0x00, /* AD DAI2 EQ5 */
+       0x00, /* AE DAI2 EQ5 */
+       0x00, /* AF DAI2 EQ5 */
+
+       0x00, /* B0 DAI2 EQ5 */
+       0x00, /* B1 DAI2 EQ5 */
+       0x00, /* B2 DAI2 EQ5 */
+       0x00, /* B3 DAI2 EQ5 */
+       0x00, /* B4 DAI2 EQ5 */
+       0x00, /* B5 DAI2 EQ5 */
+       0x00, /* B6 DAI1 biquad */
+       0x00, /* B7 DAI1 biquad */
+       0x00, /* B8 DAI1 biquad */
+       0x00, /* B9 DAI1 biquad */
+       0x00, /* BA DAI1 biquad */
+       0x00, /* BB DAI1 biquad */
+       0x00, /* BC DAI1 biquad */
+       0x00, /* BD DAI1 biquad */
+       0x00, /* BE DAI1 biquad */
+       0x00, /* BF DAI1 biquad */
+
+       0x00, /* C0 DAI2 biquad */
+       0x00, /* C1 DAI2 biquad */
+       0x00, /* C2 DAI2 biquad */
+       0x00, /* C3 DAI2 biquad */
+       0x00, /* C4 DAI2 biquad */
+       0x00, /* C5 DAI2 biquad */
+       0x00, /* C6 DAI2 biquad */
+       0x00, /* C7 DAI2 biquad */
+       0x00, /* C8 DAI2 biquad */
+       0x00, /* C9 DAI2 biquad */
+       0x00, /* CA */
+       0x00, /* CB */
+       0x00, /* CC */
+       0x00, /* CD */
+       0x00, /* CE */
+       0x00, /* CF */
+
+       0x00, /* D0 */
+       0x00, /* D1 */
+       0x00, /* D2 */
+       0x00, /* D3 */
+       0x00, /* D4 */
+       0x00, /* D5 */
+       0x00, /* D6 */
+       0x00, /* D7 */
+       0x00, /* D8 */
+       0x00, /* D9 */
+       0x00, /* DA */
+       0x70, /* DB */
+       0x00, /* DC */
+       0x00, /* DD */
+       0x00, /* DE */
+       0x00, /* DF */
+
+       0x00, /* E0 */
+       0x00, /* E1 */
+       0x00, /* E2 */
+       0x00, /* E3 */
+       0x00, /* E4 */
+       0x00, /* E5 */
+       0x00, /* E6 */
+       0x00, /* E7 */
+       0x00, /* E8 */
+       0x00, /* E9 */
+       0x00, /* EA */
+       0x00, /* EB */
+       0x00, /* EC */
+       0x00, /* ED */
+       0x00, /* EE */
+       0x00, /* EF */
+
+       0x00, /* F0 */
+       0x00, /* F1 */
+       0x00, /* F2 */
+       0x00, /* F3 */
+       0x00, /* F4 */
+       0x00, /* F5 */
+       0x00, /* F6 */
+       0x00, /* F7 */
+       0x00, /* F8 */
+       0x00, /* F9 */
+       0x00, /* FA */
+       0x00, /* FB */
+       0x00, /* FC */
+       0x00, /* FD */
+       0x00, /* FE */
+       0x00, /* FF */
+};
+
+static struct {
+       int readable;
+       int writable;
+       int vol;
+} max98088_access[M98088_REG_CNT] = {
+       { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+       { 0xFF, 0x00, 1 }, /* 01 MIC status */
+       { 0xFF, 0x00, 1 }, /* 02 jack status */
+       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+       { 0xFF, 0xFF, 0 }, /* 04 */
+       { 0xFF, 0xFF, 0 }, /* 05 */
+       { 0xFF, 0xFF, 0 }, /* 06 */
+       { 0xFF, 0xFF, 0 }, /* 07 */
+       { 0xFF, 0xFF, 0 }, /* 08 */
+       { 0xFF, 0xFF, 0 }, /* 09 */
+       { 0xFF, 0xFF, 0 }, /* 0A */
+       { 0xFF, 0xFF, 0 }, /* 0B */
+       { 0xFF, 0xFF, 0 }, /* 0C */
+       { 0xFF, 0xFF, 0 }, /* 0D */
+       { 0xFF, 0xFF, 0 }, /* 0E */
+       { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+       { 0xFF, 0xFF, 0 }, /* 10 master clock */
+       { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+       { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+       { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+       { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+       { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+       { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+       { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+       { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+       { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+       { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+       { 0xFF, 0xFF, 0 }, /* 21 data config */
+       { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+       { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 27 HP control */
+       { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 2A REC control */
+       { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+       { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+       { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+       { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+       { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+       { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+       { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+       { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+       { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+       { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+       { 0xFF, 0xFF, 0 }, /* 37 INA level */
+       { 0xFF, 0xFF, 0 }, /* 38 INB level */
+       { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+       { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+       { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+       { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 43 ALC */
+       { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+       { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+       { 0xFF, 0xFF, 0 }, /* 47 audio input */
+       { 0xFF, 0xFF, 0 }, /* 48 microphone */
+       { 0xFF, 0xFF, 0 }, /* 49 level control */
+       { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+       { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+       { 0xFF, 0xFF, 0 }, /* 4C input enable */
+       { 0xFF, 0xFF, 0 }, /* 4D output enable */
+       { 0xFF, 0xFF, 0 }, /* 4E bias control */
+       { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+       { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+       { 0xFF, 0xFF, 0 }, /* 51 system */
+       { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+       { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+       { 0x00, 0x00, 0 }, /* CA */
+       { 0x00, 0x00, 0 }, /* CB */
+       { 0x00, 0x00, 0 }, /* CC */
+       { 0x00, 0x00, 0 }, /* CD */
+       { 0x00, 0x00, 0 }, /* CE */
+       { 0x00, 0x00, 0 }, /* CF */
+
+       { 0x00, 0x00, 0 }, /* D0 */
+       { 0x00, 0x00, 0 }, /* D1 */
+       { 0x00, 0x00, 0 }, /* D2 */
+       { 0x00, 0x00, 0 }, /* D3 */
+       { 0x00, 0x00, 0 }, /* D4 */
+       { 0x00, 0x00, 0 }, /* D5 */
+       { 0x00, 0x00, 0 }, /* D6 */
+       { 0x00, 0x00, 0 }, /* D7 */
+       { 0x00, 0x00, 0 }, /* D8 */
+       { 0x00, 0x00, 0 }, /* D9 */
+       { 0x00, 0x00, 0 }, /* DA */
+       { 0x00, 0x00, 0 }, /* DB */
+       { 0x00, 0x00, 0 }, /* DC */
+       { 0x00, 0x00, 0 }, /* DD */
+       { 0x00, 0x00, 0 }, /* DE */
+       { 0x00, 0x00, 0 }, /* DF */
+
+       { 0x00, 0x00, 0 }, /* E0 */
+       { 0x00, 0x00, 0 }, /* E1 */
+       { 0x00, 0x00, 0 }, /* E2 */
+       { 0x00, 0x00, 0 }, /* E3 */
+       { 0x00, 0x00, 0 }, /* E4 */
+       { 0x00, 0x00, 0 }, /* E5 */
+       { 0x00, 0x00, 0 }, /* E6 */
+       { 0x00, 0x00, 0 }, /* E7 */
+       { 0x00, 0x00, 0 }, /* E8 */
+       { 0x00, 0x00, 0 }, /* E9 */
+       { 0x00, 0x00, 0 }, /* EA */
+       { 0x00, 0x00, 0 }, /* EB */
+       { 0x00, 0x00, 0 }, /* EC */
+       { 0x00, 0x00, 0 }, /* ED */
+       { 0x00, 0x00, 0 }, /* EE */
+       { 0x00, 0x00, 0 }, /* EF */
+
+       { 0x00, 0x00, 0 }, /* F0 */
+       { 0x00, 0x00, 0 }, /* F1 */
+       { 0x00, 0x00, 0 }, /* F2 */
+       { 0x00, 0x00, 0 }, /* F3 */
+       { 0x00, 0x00, 0 }, /* F4 */
+       { 0x00, 0x00, 0 }, /* F5 */
+       { 0x00, 0x00, 0 }, /* F6 */
+       { 0x00, 0x00, 0 }, /* F7 */
+       { 0x00, 0x00, 0 }, /* F8 */
+       { 0x00, 0x00, 0 }, /* F9 */
+       { 0x00, 0x00, 0 }, /* FA */
+       { 0x00, 0x00, 0 }, /* FB */
+       { 0x00, 0x00, 0 }, /* FC */
+       { 0x00, 0x00, 0 }, /* FD */
+       { 0x00, 0x00, 0 }, /* FE */
+       { 0xFF, 0x00, 1 }, /* FF */
+};
+
+static int max98088_volatile_register(unsigned int reg)
+{
+       return max98088_access[reg].vol;
+}
+
+static int max98088_hw_write(struct snd_soc_codec *codec, unsigned int reg,
+                            unsigned int value)
+{
+       u8 data[2];
+
+       data[0] = reg;
+       data[1] = value;
+       if (codec->hw_write(codec->control_data, data, 2) == 2)
+               return 0;
+       else
+               return -EIO;
+}
+
+/*
+ * For kernels compiled without unsigned long long int division
+ */
+unsigned long long int ulldiv(unsigned long long int dividend,
+                             unsigned long long int divisor)
+{
+       unsigned long long int quotient = 0;
+       int shift = 1;
+
+       BUG_ON(divisor == 0);
+
+       /* Result is 1.0 if divisor and dividend are equal */
+       if (divisor == dividend)
+               return 1;
+
+       /* Normalize divisor */
+       while (!(divisor & 0x8000000000000000ULL)) {
+               divisor <<= 1;
+               ++shift;
+       }
+
+       /* Shift and subtract */
+       while (shift--) {
+               quotient <<= 1;
+
+               if (divisor <= dividend) {
+                       dividend -= divisor;
+                       ++quotient;
+               }
+               divisor >>= 1;
+       }
+
+       /* Round up */
+       if (dividend > divisor)
+               ++quotient;
+
+       return quotient;
+}
+
+#define INA1_PGA_BIT 0x01
+#define INA2_PGA_BIT 0x02
+#define INB1_PGA_BIT 0x04
+#define INB2_PGA_BIT 0x08
+/*
+ * The INx1 and INx2 PGAs 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.
+ */
+static int max98088_pga_event(struct snd_soc_dapm_widget *w,
+                             struct snd_kcontrol *k, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       u8 *state = &max98088->power_state;
+       unsigned int val;
+       unsigned int pga;
+       unsigned int mask;
+
+       BUG_ON(w->reg != M98088_REG_4C_PWR_EN_IN);
+
+       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;
+       }
+
+       if (event == SND_SOC_DAPM_POST_PMU) {
+               /* ON */
+               *state |= pga;
+
+               /* Turn on, avoiding unnecessary writes */
+               val = snd_soc_read(codec, w->reg);
+               if (!(val & (1 << w->shift))) {
+                       val |= (1 << w->shift);
+                       snd_soc_write(codec, w->reg, val);
+               }
+       } else if (event == SND_SOC_DAPM_POST_PMD) {
+               /* OFF */
+               *state &= ~pga;
+
+               /* Turn off if both are off, avoiding unnecessary writes */
+               if (!(*state & mask)) {
+                       val = snd_soc_read(codec, w->reg);
+                       if (val & (1 << w->shift)) {
+                               val &= ~(1 << w->shift);
+                               snd_soc_write(codec, w->reg, val);
+                       }
+               }
+       } else {
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+                   unsigned int band, u16 *coefs)
+{
+       unsigned int eq_reg;
+       unsigned int i;
+
+       BUG_ON(band > 4);
+       BUG_ON(dai > 1);
+
+       /* Load the base register address */
+       eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+       /* Add the band address offset, note adjustment for word address */
+       eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+       /* Step through the registers and coefs */
+       for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+               snd_soc_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+               snd_soc_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+       }
+
+       return;
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_ex_mode[] = {
+       "Off",
+       "100Hz",
+       "400Hz",
+       "600Hz",
+       "800Hz",
+       "1000Hz",
+       "200-400Hz",
+       "400-600Hz",
+       "400-800Hz",
+};
+
+static const unsigned int ex_mode_table[] = {
+       0x00,           /* disabled */
+       (0<<4)|3,       /* 100Hz */
+       (1<<4)|0,       /* 400Hz */
+       (2<<4)|0,       /* 600Hz */
+       (3<<4)|0,       /* 800Hz */
+       (4<<4)|0,       /* 1000Hz */
+       (1<<4)|1,       /* 200-400Hz */
+       (2<<4)|2,       /* 400-600Hz */
+       (3<<4)|2,       /* 400-800Hz */
+};
+
+static const struct soc_enum max98088_ex_mode_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_ex_mode), max98088_ex_mode),
+};
+
+static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->ex_mode;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(ex_mode_table))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       snd_soc_write(codec, M98088_REG_41_SPKDHP,
+               ex_mode_table[*mode]);
+
+       return 0;
+}
+
+static int max98088_ex_mode_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->ex_mode;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+       "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+               max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_dai1_fltr[] = {
+       "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+       "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static const char *max98088_micpre[] = {
+       "0dB",
+       "20dB",
+       "30dB",
+};
+
+static const struct soc_enum max98088_micpre_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_micpre), max98088_micpre),
+};
+
+static const char *max98088_extmic[] = {
+       "Off",
+       "MIC1",
+       "MIC2",
+};
+
+static const struct soc_enum max98088_extmic_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
+};
+
+static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->mic1pre;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_micpre))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       return 0;
+}
+
+static int max98088_mic1pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->mic1pre;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static int max98088_mic2pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->mic2pre;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_micpre))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       return 0;
+}
+
+static int max98088_mic2pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->mic2pre;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static int max98088_extmic_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->extmic_mode;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_extmic))
+               return -EINVAL;
+
+       *mode = sel;
+       snd_soc_update_bits(codec, M98088_REG_48_CFG_MIC,
+               M98088_EXTMIC_MASK, sel);
+
+       return 0;
+}
+
+static int max98088_extmic_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->extmic_mode;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+       /* Analog outputs */
+
+       SOC_DOUBLE_R("Headphone Volume", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+       SOC_DOUBLE_R("Speaker Volume", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+       SOC_DOUBLE_R("Receiver Volume", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+       SOC_DOUBLE_R("Headphone Switch", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 7, 1, 1),
+       SOC_DOUBLE_R("Speaker Switch", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 7, 1, 1),
+       SOC_DOUBLE_R("Receiver Switch", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 7, 1, 1),
+
+       /* Analog inputs */
+
+       SOC_SINGLE("MIC1 Volume", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+       SOC_SINGLE("MIC2 Volume", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+       SOC_ENUM_EXT("MIC1 Boost Volume", max98088_micpre_enum,
+               max98088_mic1pre_get, max98088_mic1pre_set),
+
+       SOC_ENUM_EXT("MIC2 Boost Volume", max98088_micpre_enum,
+               max98088_mic2pre_get, max98088_mic2pre_set),
+
+       SOC_ENUM_EXT("Ext MIC Switch", max98088_extmic_enum,
+               max98088_extmic_get, max98088_extmic_set),
+
+       SOC_SINGLE("INA Volume", M98088_REG_37_LVL_INA, 0, 7, 1),
+       SOC_SINGLE("INB Volume", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+       /* ADC input digital gains and volume controls */
+
+       SOC_SINGLE("ADCL Volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+       SOC_SINGLE("ADCR Volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+       SOC_SINGLE("ADCL Boost Volume", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+       SOC_SINGLE("ADCR Boost Volume", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+       /* Equalizer */
+
+       SOC_SINGLE("EQ1 Switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+       SOC_SINGLE("EQ2 Switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+       /* Excursion limiter */
+
+       SOC_ENUM_EXT("EX Limiter Mode", max98088_ex_mode_enum,
+               max98088_ex_mode_get, max98088_ex_mode_set),
+       SOC_ENUM("EX Limiter Threshold", max98088_ex_thresh_enum),
+
+       /* Voice/music filters */
+
+       SOC_ENUM("DAI1 Filter Mode", max98088_filter_mode_enum),
+       SOC_ENUM("DAI1 DAC Filter", max98088_dai1_dac_filter_enum),
+       SOC_ENUM("DAI1 ADC Filter", max98088_dai1_adc_filter_enum),
+       SOC_SINGLE("DAI2 DC Block Switch", M98088_REG_20_DAI2_FILTERS,
+               0, 1, 0),
+
+       /* Automatic level control (for both DAI1/DAI2) */
+
+       SOC_SINGLE("ALC Switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+       SOC_SINGLE("ALC Threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+       SOC_SINGLE("ALC Multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+       SOC_SINGLE("ALC Release Time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+       /* Power limiter */
+
+       SOC_SINGLE("PWR Limiter Threshold", M98088_REG_44_PWRLMT_CFG,
+               4, 15, 0),
+       SOC_SINGLE("PWR Limiter Weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+       SOC_SINGLE("PWR Limiter Time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+       SOC_SINGLE("PWR Limiter Time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+       /* THD distortion limiter */
+
+       SOC_SINGLE("THD Limiter Threshold", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+       SOC_SINGLE("THD Limiter Time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_hp_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       u16 status;
+
+       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
+
+       /* powering down headphone gracefully */
+       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
+       if ((status & M98088_HPEN) == M98088_HPEN) {
+               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
+                       (status & ~M98088_HPEN));
+       }
+       schedule_timeout(msecs_to_jiffies(20));
+
+       return 0;
+}
+
+static int max98088_mic_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               if (w->reg == M98088_REG_35_LVL_MIC1) {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic1pre)<<M98088_MICPRE_SHIFT);
+               } else {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic2pre)<<M98088_MICPRE_SHIFT);
+               }
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK, 0);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* DAPM widgets top level */
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+       SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+       SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+       SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+       SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+       SND_SOC_DAPM_PGA_E("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+               7, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+       SND_SOC_DAPM_PGA_E("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+               6, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+
+       SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+               5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+               4, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+               3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+               2, 0, NULL, 0),
+
+       SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_left_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_right_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_PGA_E("MIC1 Input", M98088_REG_35_LVL_MIC1,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("MIC2 Input", M98088_REG_36_LVL_MIC2,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_MICBIAS("MICBIAS", M98088_REG_4C_PWR_EN_IN, 3, 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("RECL"),
+       SND_SOC_DAPM_OUTPUT("RECR"),
+
+       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"),
+};
+
+/* DAPM AUDIO_MAP: */
+static const struct snd_soc_dapm_route audio_map[] = {
+       /* Left headphone output mixer */
+       {"Left HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left HP Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right headphone output mixer */
+       {"Right HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right HP Mixer", "Left DAC2 Switch", "DACL2"  },
+       {"Right HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Left speaker output mixer */
+       {"Left SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right speaker output mixer */
+       {"Right SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Left REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Right REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       {"HP Left Out", NULL, "Left HP Mixer"},
+       {"HP Right Out", NULL, "Right HP Mixer"},
+       {"SPK Left Out", NULL, "Left SPK Mixer"},
+       {"SPK Right Out", NULL, "Right SPK Mixer"},
+       {"REC Left Out", NULL, "Left REC Mixer"},
+       {"REC Right Out", NULL, "Right REC Mixer"},
+
+       {"HPL", NULL, "HP Left Out"},
+       {"HPR", NULL, "HP Right Out"},
+       {"SPKL", NULL, "SPK Left Out"},
+       {"SPKR", NULL, "SPK Right Out"},
+       {"RECL", NULL, "REC Left Out"},
+       {"RECR", NULL, "REC Right Out"},
+
+       /* Left ADC input mixer */
+       {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right ADC input mixer */
+       {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right ADC Mixer", "INB2 Switch", "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"},
+       {"MIC1 Input", NULL, "MIC1"},
+       {"MIC2 Input", NULL, "MIC2"},
+};
+
+static int max98088_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, max98088_dapm_widgets,
+                                 ARRAY_SIZE(max98088_dapm_widgets));
+
+       snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+       snd_soc_add_controls(codec, max98088_snd_controls,
+                            ARRAY_SIZE(max98088_snd_controls));
+
+       snd_soc_dapm_new_widgets(codec);
+       return 0;
+}
+
+/* codec mclk clock divider coefficients */
+static const struct {
+       u32 rate;
+       u8  sr;
+} rate_table[] = {
+       {8000,  0x10},
+       {11025, 0x20},
+       {16000, 0x30},
+       {22050, 0x40},
+       {24000, 0x50},
+       {32000, 0x60},
+       {44100, 0x70},
+       {48000, 0x80},
+       {88200, 0x90},
+       {96000, 0xA0},
+};
+
+static inline int rate_value(int rate, u8 *value)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+               if (rate_table[i].rate >= rate) {
+                       *value = rate_table[i].sr;
+                       return 0;
+               }
+       }
+       *value = rate_table[0].sr;
+       return -EINVAL;
+}
+
+static int max98088_dai1_hw_params(struct snd_pcm_substream *substream,
+                                  struct snd_pcm_hw_params *params,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 regval;
+       u16 ni;
+
+       cdata = &max98088->dai[0];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
+               if (rate_value(rate, &regval))
+                       return -EINVAL;
+
+               snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_14_DAI1_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0)
+                       return -EINVAL;
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+static int max98088_dai2_hw_params(struct snd_pcm_substream *substream,
+                                  struct snd_pcm_hw_params *params,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 regval;
+       u16 ni;
+
+       cdata = &max98088->dai[1];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI2 SR2 value for the DSP */
+               if (rate_value(rate, &regval))
+                       return -EINVAL;
+
+               snd_soc_write(codec, M98088_REG_19_DAI2_CLKMODE, regval);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_1C_DAI2_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0)
+                       return -EINVAL;
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+                                  int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       /* requested clock frequency is already setup */
+       if (freq == max98088->sysclk)
+               return 0;
+
+       max98088->sysclk = freq; /* remember current sysclk */
+
+       /* setup clocks for slave mode, and using the PLL
+        * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+        *         0x02 (when master clk is 20MHz to 30MHz)..
+        */
+       if ((freq >= 10000000) && (freq < 20000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+       } else if ((freq >= 20000000) && (freq < 30000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+       } else {
+               dev_err(codec->dev, "Invalid master clock frequency\n");
+               return -EINVAL;
+       }
+
+       if (snd_soc_read(codec, M98088_REG_51_PWR_SYS)  & M98088_SHDNRUN) {
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN, 0);
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       0, M98088_SHDNRUN);
+       }
+
+       dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+       max98088->sysclk = freq;
+       return 0;
+}
+
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       u8 reg15val;
+       u8 reg14msk = 0;
+       u8 reg14val = 0;
+
+       cdata = &max98088->dai[0];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* mask MAS to select slave mode */
+                       reg14msk |= M98088_DAI_MAS;
+                       /* slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* set to master mode */
+                       reg14val |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg14val |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       reg14msk |= M98088_DAI_DLY;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       reg14msk |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg14msk |= M98088_DAI_BCI;
+                       reg14val |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg14msk |= M98088_DAI_WCI;
+                       reg14val |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg14val |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       reg14msk, reg14val);
+
+               reg15val = M98088_DAI_BSEL64;
+               if (max98088->digmic)
+                       reg15val |= M98088_DAI_OSR64;
+               snd_soc_write(codec, M98088_REG_15_DAI1_CLOCK, reg15val);
+       }
+
+       return 0;
+}
+
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       u8 reg1Cmsk = 0;
+       u8 reg1Cval = 0;
+
+       cdata = &max98088->dai[1];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* mask MAS to select slave mode */
+                       reg1Cmsk |= M98088_DAI_MAS;
+                       /* slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* set to master mode */
+                       reg1Cval |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg1Cval |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       reg1Cmsk |= M98088_DAI_DLY;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       reg1Cmsk |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg1Cmsk |= M98088_DAI_BCI;
+                       reg1Cval |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg1Cmsk |= M98088_DAI_WCI;
+                       reg1Cval |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg1Cval |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       reg1Cmsk, reg1Cval);
+
+               snd_soc_write(codec, M98088_REG_1D_DAI2_CLOCK,
+                       M98088_DAI_BSEL64);
+       }
+
+       return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       int i;
+
+       if (!codec->cache_sync)
+               return;
+
+       codec->cache_only = 0;
+
+       /* write back cached values if they're writeable and
+        * different from the hardware default.
+        */
+       for (i = 1; i < ARRAY_SIZE(max98088->reg_cache); i++) {
+               if (!max98088_access[i].writable)
+                       continue;
+
+               if (max98088->reg_cache[i] == max98088_reg[i])
+                       continue;
+
+               snd_soc_write(codec, i, max98088->reg_cache[i]);
+       }
+
+       codec->cache_sync = 0;
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+                                 enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               max98088_sync_cache(codec);
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, M98088_MBEN);
+               break;
+
+       case SND_SOC_BIAS_OFF:
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, 0);
+#ifdef CONFIG_REGULATOR
+               codec->cache_sync = 1;
+#endif
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai1_set_fmt,
+       .hw_params = max98088_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai2_set_fmt,
+       .hw_params = max98088_dai2_hw_params,
+};
+
+static struct snd_soc_dai_driver max98088_dai[] = {
+{
+       .name = "HiFi",
+       .playback = {
+               .stream_name = "HiFi Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .capture = {
+               .stream_name = "HiFi Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+        .ops = &max98088_dai1_ops,
+},
+{
+       .name = "Aux",
+       .playback = {
+               .stream_name = "Aux Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .ops = &max98088_dai2_ops,
+}
+};
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+       sel = cdata->eq_sel;
+
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               if (strcmp(pdata->eq1_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq1_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq1_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq1_cfg[best].name,
+               pdata->eq1_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+       coef_set = &pdata->eq1_cfg[sel];
+
+       m98088_eq_band(codec, 0, 0, coef_set->band1);
+       m98088_eq_band(codec, 0, 1, coef_set->band2);
+       m98088_eq_band(codec, 0, 2, coef_set->band3);
+       m98088_eq_band(codec, 0, 3, coef_set->band4);
+       m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->eq_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               if (strcmp(pdata->eq2_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq2_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq2_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq2_cfg[best].name,
+               pdata->eq2_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+       coef_set = &pdata->eq2_cfg[sel];
+
+       m98088_eq_band(codec, 1, 0, coef_set->band1);
+       m98088_eq_band(codec, 1, 1, coef_set->band2);
+       m98088_eq_band(codec, 1, 2, coef_set->band3);
+       m98088_eq_band(codec, 1, 3, coef_set->band4);
+       m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+               save);
+}
+
+
+static int max98088_put_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[0];
+
+       if (sel >= pdata->eq1_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq1(codec);
+       return 0;
+}
+
+static int max98088_put_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[1];
+
+       if (sel >= pdata->eq2_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq2(codec);
+       return 0;
+}
+
+static int max98088_get_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static int max98088_get_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static void max98088_handle_eq1_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq1control =
+               SOC_ENUM_EXT("EQ1 Mode",
+                       max98088->dai[0].eq_enum,
+                       max98088_get_eq1_enum,
+                       max98088_put_eq1_enum);
+
+       cdata = &max98088->dai[0];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq1_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               /* ...and remember the new version. */
+               cdata->eq_texts[i] = pdata->eq1_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ1 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       ret = snd_soc_add_controls(codec, &eq1control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_eq2_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq2control =
+               SOC_ENUM_EXT("EQ2 Mode",
+                       max98088->dai[1].eq_enum,
+                       max98088_get_eq2_enum,
+                       max98088_put_eq2_enum);
+
+       cdata = &max98088->dai[1];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq2_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               cdata->eq_texts[i] = pdata->eq2_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ2 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       ret = snd_soc_add_controls(codec, &eq2control, 1);
+       if (ret != 0)
+               printk(KERN_ERR "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       u8 regval = 0;
+
+       if (!pdata) {
+               dev_dbg(codec->dev, "No platform data\n");
+               return;
+       }
+
+       /* configure mic for analog/digital mic mode */
+       if (pdata->digmic_left_mode)
+               regval |= M98088_DIGMIC_L;
+
+       if (pdata->digmic_right_mode)
+               regval |= M98088_DIGMIC_R;
+
+       max98088->digmic = (regval ? 1 : 0);
+
+       snd_soc_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+       /* configure receiver output */
+       regval = ((pdata->receiver_mode) ? M98088_REC_LINEMODE : 0);
+       snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+               M98088_REC_LINEMODE_MASK, regval);
+
+       /* configure equalizers */
+       if (pdata->eq1_cfgcnt)
+               max98088_handle_eq1_pdata(codec);
+
+       if (pdata->eq2_cfgcnt)
+               max98088_handle_eq2_pdata(codec);
+}
+
+#ifdef CONFIG_PM
+static int max98088_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static int max98088_resume(struct snd_soc_codec *codec)
+{
+       int i;
+       u8 *cache = codec->reg_cache;
+
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       /* Sync reg_cache with the hardware */
+       for (i = 0; i < M98088_REG_CNT; i++) {
+               if (i == M98088_REG_51_PWR_SYS)
+                       continue;
+
+               if (!max98088_access[i].writable)
+                       continue;
+
+               max98088_hw_write(codec, i, cache[i]);
+       }
+
+       /* now enter into the resume mode bias level */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+}
+#else
+#define max98088_suspend NULL
+#define max98088_resume NULL
+#endif
+
+static int max98088_probe(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       int ret = 0;
+
+       codec->cache_sync = 1;
+       memcpy(codec->reg_cache, max98088_reg, sizeof(max98088_reg));
+
+       ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+               return ret;
+       }
+
+       /* initalize private data */
+
+       max98088->sysclk = (unsigned)-1;
+
+       cdata = &max98088->dai[0];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+
+       cdata = &max98088->dai[1];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+
+       max98088->power_state = 0; /* INA INB power enable state */
+       max98088->ex_mode = 0; /* excursion limiter mode */
+       max98088->digmic = 0; /* 0=analog, 1=digital */
+       max98088->mic1pre = 0;
+       max98088->mic2pre = 0;
+
+       ret = snd_soc_read(codec, M98088_REG_FF_REV_ID);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to read device revision: %d\n",
+                       ret);
+               goto err_access;
+       }
+       dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+       snd_soc_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+
+       /* initialize registers cache to hardware default */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       snd_soc_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+       snd_soc_write(codec, M98088_REG_22_MIX_DAC,
+               M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+               M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+       snd_soc_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+       snd_soc_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+       snd_soc_write(codec, M98088_REG_16_DAI1_IOCFG,
+               M98088_S1NORMAL|M98088_SDATA);
+
+       snd_soc_write(codec, M98088_REG_1E_DAI2_IOCFG,
+               M98088_S2NORMAL|M98088_SDATA);
+
+       max98088_handle_pdata(codec);
+
+       max98088_add_widgets(codec);
+
+err_access:
+       return ret;
+}
+
+static int max98088_remove(struct snd_soc_codec *codec)
+{
+       if (codec->control_data)
+               max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_max98088 = {
+       .probe   = max98088_probe,
+       .remove  = max98088_remove,
+       .suspend = max98088_suspend,
+       .resume  = max98088_resume,
+       .set_bias_level = max98088_set_bias_level,
+       .reg_cache_size = ARRAY_SIZE(max98088_reg),
+       .reg_word_size = sizeof(u8),
+       .reg_cache_default = max98088_reg,
+       .volatile_register = max98088_volatile_register,
+};
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+                            const struct i2c_device_id *id)
+{
+       struct max98088_priv *max98088;
+       int ret;
+
+       max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+       if (max98088 == NULL)
+               return -ENOMEM;
+
+       i2c_set_clientdata(i2c, max98088);
+       max98088->control_data = i2c;
+       max98088->pdata = i2c->dev.platform_data;
+
+       ret = snd_soc_register_codec(&i2c->dev,
+                       &soc_codec_dev_max98088, &max98088_dai[0], 2);
+       if (ret < 0)
+               kfree(max98088);
+       return ret;
+}
+
+static int max98088_i2c_remove(struct i2c_client *client)
+{
+       snd_soc_unregister_codec(&client->dev);
+       kfree(i2c_get_clientdata(client));
+       return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+       { "max98088", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+static struct i2c_driver max98088_i2c_driver = {
+       .driver = {
+               .name = "max98088-codec",
+               .owner = THIS_MODULE,
+       },
+       .probe  = max98088_i2c_probe,
+       .remove = __devexit_p(max98088_i2c_remove),
+       .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&max98088_i2c_driver);
+       if (ret)
+               pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+       return ret;
+}
+
+static void __exit max98088_exit(void)
+{
+       i2c_del_driver(&max98088_i2c_driver);
+}
+
+module_init(max98088_init);
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang, Jesse Marroquin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h
new file mode 100644
index 0000000..9e9d8da
--- /dev/null
+++ b/sound/soc/codecs/max98088.h
@@ -0,0 +1,190 @@
+/*
+ * max98088.h -- MAX98088 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 _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS            0x00
+#define M98088_REG_01_MIC_STATUS            0x01
+#define M98088_REG_02_JACK_STAUS            0x02
+#define M98088_REG_03_BATTERY_VOLTAGE       0x03
+#define M98088_REG_0F_IRQ_ENABLE            0x0F
+#define M98088_REG_10_SYS_CLK               0x10
+#define M98088_REG_11_DAI1_CLKMODE          0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI        0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO        0x13
+#define M98088_REG_14_DAI1_FORMAT           0x14
+#define M98088_REG_15_DAI1_CLOCK            0x15
+#define M98088_REG_16_DAI1_IOCFG            0x16
+#define M98088_REG_17_DAI1_TDM              0x17
+#define M98088_REG_18_DAI1_FILTERS          0x18
+#define M98088_REG_19_DAI2_CLKMODE          0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI        0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO        0x1B
+#define M98088_REG_1C_DAI2_FORMAT           0x1C
+#define M98088_REG_1D_DAI2_CLOCK            0x1D
+#define M98088_REG_1E_DAI2_IOCFG            0x1E
+#define M98088_REG_1F_DAI2_TDM              0x1F
+#define M98088_REG_20_DAI2_FILTERS          0x20
+#define M98088_REG_21_SRC                   0x21
+#define M98088_REG_22_MIX_DAC               0x22
+#define M98088_REG_23_MIX_ADC_LEFT          0x23
+#define M98088_REG_24_MIX_ADC_RIGHT         0x24
+#define M98088_REG_25_MIX_HP_LEFT           0x25
+#define M98088_REG_26_MIX_HP_RIGHT          0x26
+#define M98088_REG_27_MIX_HP_CNTL           0x27
+#define M98088_REG_28_MIX_REC_LEFT          0x28
+#define M98088_REG_29_MIX_REC_RIGHT         0x29
+#define M98088_REG_2A_MIC_REC_CNTL          0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT          0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT         0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL          0x2D
+#define M98088_REG_2E_LVL_SIDETONE          0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY         0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ      0x30
+#define M98088_REG_31_LVL_DAI2_PLAY         0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ      0x32
+#define M98088_REG_33_LVL_ADC_L             0x33
+#define M98088_REG_34_LVL_ADC_R             0x34
+#define M98088_REG_35_LVL_MIC1              0x35
+#define M98088_REG_36_LVL_MIC2              0x36
+#define M98088_REG_37_LVL_INA               0x37
+#define M98088_REG_38_LVL_INB               0x38
+#define M98088_REG_39_LVL_HP_L              0x39
+#define M98088_REG_3A_LVL_HP_R              0x3A
+#define M98088_REG_3B_LVL_REC_L             0x3B
+#define M98088_REG_3C_LVL_REC_R             0x3C
+#define M98088_REG_3D_LVL_SPK_L             0x3D
+#define M98088_REG_3E_LVL_SPK_R             0x3E
+#define M98088_REG_3F_MICAGC_CFG            0x3F
+#define M98088_REG_40_MICAGC_THRESH         0x40
+#define M98088_REG_41_SPKDHP                0x41
+#define M98088_REG_42_SPKDHP_THRESH         0x42
+#define M98088_REG_43_SPKALC_COMP           0x43
+#define M98088_REG_44_PWRLMT_CFG            0x44
+#define M98088_REG_45_PWRLMT_TIME           0x45
+#define M98088_REG_46_THDLMT_CFG            0x46
+#define M98088_REG_47_CFG_AUDIO_IN          0x47
+#define M98088_REG_48_CFG_MIC               0x48
+#define M98088_REG_49_CFG_LEVEL             0x49
+#define M98088_REG_4A_CFG_BYPASS            0x4A
+#define M98088_REG_4B_CFG_JACKDET           0x4B
+#define M98088_REG_4C_PWR_EN_IN             0x4C
+#define M98088_REG_4D_PWR_EN_OUT            0x4D
+#define M98088_REG_4E_BIAS_CNTL             0x4E
+#define M98088_REG_4F_DAC_BIAS1             0x4F
+#define M98088_REG_50_DAC_BIAS2             0x50
+#define M98088_REG_51_PWR_SYS               0x51
+#define M98088_REG_52_DAI1_EQ_BASE          0x52
+#define M98088_REG_84_DAI2_EQ_BASE          0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE      0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE      0xC0
+#define M98088_REG_FF_REV_ID                0xFF
+
+#define M98088_REG_CNT                      (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+       #define M98088_DAI_MAS                  (1<<7)
+       #define M98088_DAI_WCI                  (1<<6)
+       #define M98088_DAI_BCI                  (1<<5)
+       #define M98088_DAI_DLY                  (1<<4)
+       #define M98088_DAI_TDM                  (1<<2)
+       #define M98088_DAI_FSW                  (1<<1)
+       #define M98088_DAI_WS                   (1<<0)
+
+/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */
+       #define M98088_DAI_BSEL64               (1<<0)
+       #define M98088_DAI_OSR64                (1<<6)
+
+/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */
+       #define M98088_S1NORMAL                 (1<<6)
+       #define M98088_S2NORMAL                 (2<<6)
+       #define M98088_SDATA                    (3<<0)
+
+/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */
+       #define M98088_DAI_DHF                  (1<<3)
+
+/* M98088_REG_22_MIX_DAC */
+       #define M98088_DAI1L_TO_DACL            (1<<7)
+       #define M98088_DAI1R_TO_DACL            (1<<6)
+       #define M98088_DAI2L_TO_DACL            (1<<5)
+       #define M98088_DAI2R_TO_DACL            (1<<4)
+       #define M98088_DAI1L_TO_DACR            (1<<3)
+       #define M98088_DAI1R_TO_DACR            (1<<2)
+       #define M98088_DAI2L_TO_DACR            (1<<1)
+       #define M98088_DAI2R_TO_DACR            (1<<0)
+
+/* M98088_REG_2A_MIC_REC_CNTL */
+       #define M98088_REC_LINEMODE             (1<<7)
+       #define M98088_REC_LINEMODE_MASK        (1<<7)
+
+/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */
+       #define M98088_MICPRE_MASK              (3<<5)
+       #define M98088_MICPRE_SHIFT             5
+
+/* M98088_REG_3A_LVL_HP_R */
+       #define M98088_HP_MUTE                  (1<<7)
+
+/* M98088_REG_3C_LVL_REC_R */
+       #define M98088_REC_MUTE                 (1<<7)
+
+/* M98088_REG_3E_LVL_SPK_R */
+       #define M98088_SP_MUTE                  (1<<7)
+
+/* M98088_REG_48_CFG_MIC */
+       #define M98088_EXTMIC_MASK              (3<<0)
+       #define M98088_DIGMIC_L                 (1<<5)
+       #define M98088_DIGMIC_R                 (1<<4)
+
+/* M98088_REG_49_CFG_LEVEL */
+       #define M98088_VSEN                     (1<<6)
+       #define M98088_ZDEN                     (1<<5)
+       #define M98088_EQ2EN                    (1<<1)
+       #define M98088_EQ1EN                    (1<<0)
+
+/* M98088_REG_4C_PWR_EN_IN */
+       #define M98088_INAEN                    (1<<7)
+       #define M98088_INBEN                    (1<<6)
+       #define M98088_MBEN                     (1<<3)
+       #define M98088_ADLEN                    (1<<1)
+       #define M98088_ADREN                    (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+       #define M98088_HPLEN                    (1<<7)
+       #define M98088_HPREN                    (1<<6)
+       #define M98088_HPEN                     ((1<<7)|(1<<6))
+       #define M98088_SPLEN                    (1<<5)
+       #define M98088_SPREN                    (1<<4)
+       #define M98088_RECEN                    (1<<3)
+       #define M98088_DALEN                    (1<<1)
+       #define M98088_DAREN                    (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+       #define M98088_SHDNRUN                  (1<<7)
+       #define M98088_PERFMODE                 (1<<3)
+       #define M98088_HPPLYBACK                (1<<2)
+       #define M98088_PWRSV8K                  (1<<1)
+       #define M98088_PWRSV                    (1<<0)
+
+#define M98088_COEFS_PER_BAND               5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+struct max98088_setup_data {
+       unsigned short i2c_address;
+};
+
+#endif
--
1.6.3.3


^ permalink raw reply related	[flat|nested] 46+ messages in thread

* [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-29  2:34 ` Peter Hsiang
  0 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-29  2:34 UTC (permalink / raw)
  To: Jaroslav Kysela, Takashi Iwai, Peter Hsiang, Liam Girdwood, Mark Brown
  Cc: alsa-devel, linux-kernel, Jesse Marroquin

This patch adds the MAX98088 CODEC driver.

Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
---
 include/sound/max98088.h    |   54 +
 sound/soc/codecs/Kconfig    |    4 +
 sound/soc/codecs/Makefile   |    2 +
 sound/soc/codecs/max98088.c | 2355 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/max98088.h |  190 ++++
 5 files changed, 2605 insertions(+), 0 deletions(-)
 create mode 100644 include/sound/max98088.h
 create mode 100644 sound/soc/codecs/max98088.c
 create mode 100644 sound/soc/codecs/max98088.h

diff --git a/include/sound/max98088.h b/include/sound/max98088.h
new file mode 100644
index 0000000..30652be
--- /dev/null
+++ b/include/sound/max98088.h
@@ -0,0 +1,54 @@
+/*
+ * Platform data for MAX98088
+ *
+ * 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 as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __SOUND_MAX98088_PDATA_H__
+#define __SOUND_MAX98088_PDATA_H__
+
+#define EQ_CFG_MAX 32
+
+/* Equalizer filter response configuration */
+struct max98088_eq_cfg {
+       const char *name;
+       unsigned int rate;
+       u16 band1[5];
+       u16 band2[5];
+       u16 band3[5];
+       u16 band4[5];
+       u16 band5[5];
+};
+
+/* codec platform data */
+struct max98088_pdata {
+
+       /* Equalizers for DAI1 and DAI2 */
+       struct max98088_eq_cfg *eq1_cfg;
+       struct max98088_eq_cfg *eq2_cfg;
+       unsigned int eq1_cfgcnt;
+       unsigned int eq2_cfgcnt;
+
+       /* Receiver output can be configured as power amplifier or LINE out */
+       /* Set receiver_mode to:
+        * 0 = amplifier output, or
+        * 1 = LINE level output
+        */
+       unsigned int receiver_mode:1;
+
+       /* Analog/digital microphone configuration:
+        * 0 = analog microphone input (normal setting)
+        * 1 = digital microphone input
+        */
+       unsigned int digmic_left_mode:1;
+       unsigned int digmic_right_mode:1;
+
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4ccc2b7..4e6713c 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -27,6 +27,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_CS4270 if I2C
        select SND_SOC_DA7210 if I2C
        select SND_SOC_JZ4740 if SOC_JZ4740
+       select SND_SOC_MAX98088 if I2C
        select SND_SOC_MAX9877 if I2C
        select SND_SOC_PCM3008
        select SND_SOC_SPDIF
@@ -157,6 +158,9 @@ config SND_SOC_L3
 config SND_SOC_DA7210
         tristate

+config SND_SOC_MAX98088
+       tristate
+
 config SND_SOC_PCM3008
        tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 23e7e2c..7184611 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -15,6 +15,7 @@ snd-soc-cs4270-objs := cs4270.o
 snd-soc-cx20442-objs := cx20442.o
 snd-soc-da7210-objs := da7210.o
 snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
 snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-spdif-objs := spdif_transciever.o
 snd-soc-ssm2602-objs := ssm2602.o
@@ -88,6 +89,7 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
 obj-$(CONFIG_SND_SOC_DA7210)   += snd-soc-da7210.o
 obj-$(CONFIG_SND_SOC_L3)       += snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_JZ4740_CODEC)     += snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088)  += snd-soc-max98088.o
 obj-$(CONFIG_SND_SOC_PCM3008)  += snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_SPDIF)    += snd-soc-spdif.o
 obj-$(CONFIG_SND_SOC_SSM2602)  += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
new file mode 100644
index 0000000..52f57d5
--- /dev/null
+++ b/sound/soc/codecs/max98088.c
@@ -0,0 +1,2355 @@
+/*
+ * max98088.c -- MAX98088 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 <linux/slab.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+/* Configurations associated with each channel of DAI stream */
+struct max98088_cdata {
+       unsigned int rate;
+       unsigned int fmt;
+       int eq_textcnt;
+       const char *eq_texts[EQ_CFG_MAX];
+       int eq_sel;
+       struct soc_enum eq_enum;
+};
+
+/* Codec private data */
+struct max98088_priv {
+       u8 reg_cache[M98088_REG_CNT];
+       void *control_data;
+       struct max98088_pdata *pdata;
+
+       unsigned int sysclk;
+       struct max98088_cdata dai[2];
+       u8 power_state;
+       unsigned int ex_mode;
+       unsigned int digmic;
+       unsigned int mic1pre;
+       unsigned int mic2pre;
+       unsigned int extmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+       0x00, /* 00 IRQ status */
+       0x00, /* 01 MIC status */
+       0x00, /* 02 jack status */
+       0x00, /* 03 battery voltage */
+       0x00, /* 04 */
+       0x00, /* 05 */
+       0x00, /* 06 */
+       0x00, /* 07 */
+       0x00, /* 08 */
+       0x00, /* 09 */
+       0x00, /* 0A */
+       0x00, /* 0B */
+       0x00, /* 0C */
+       0x00, /* 0D */
+       0x00, /* 0E */
+       0x00, /* 0F interrupt enable */
+
+       0x00, /* 10 master clock */
+       0x00, /* 11 DAI1 clock mode */
+       0x00, /* 12 DAI1 clock control */
+       0x00, /* 13 DAI1 clock control */
+       0x00, /* 14 DAI1 format */
+       0x00, /* 15 DAI1 clock */
+       0x00, /* 16 DAI1 config */
+       0x00, /* 17 DAI1 TDM */
+       0x00, /* 18 DAI1 filters */
+       0x00, /* 19 DAI2 clock mode */
+       0x00, /* 1A DAI2 clock control */
+       0x00, /* 1B DAI2 clock control */
+       0x00, /* 1C DAI2 format */
+       0x00, /* 1D DAI2 clock */
+       0x00, /* 1E DAI2 config */
+       0x00, /* 1F DAI2 TDM */
+
+       0x00, /* 20 DAI2 filters */
+       0x00, /* 21 data config */
+       0x00, /* 22 DAC mixer */
+       0x00, /* 23 left ADC mixer */
+       0x00, /* 24 right ADC mixer */
+       0x00, /* 25 left HP mixer */
+       0x00, /* 26 right HP mixer */
+       0x00, /* 27 HP control */
+       0x00, /* 28 left REC mixer */
+       0x00, /* 29 right REC mixer */
+       0x00, /* 2A REC control */
+       0x00, /* 2B left SPK mixer */
+       0x00, /* 2C right SPK mixer */
+       0x00, /* 2D SPK control */
+       0x00, /* 2E sidetone */
+       0x00, /* 2F DAI1 playback level */
+
+       0x00, /* 30 DAI1 playback level */
+       0x00, /* 31 DAI2 playback level */
+       0x00, /* 32 DAI2 playbakc level */
+       0x00, /* 33 left ADC level */
+       0x00, /* 34 right ADC level */
+       0x00, /* 35 MIC1 level */
+       0x00, /* 36 MIC2 level */
+       0x00, /* 37 INA level */
+       0x00, /* 38 INB level */
+       0x00, /* 39 left HP volume */
+       0x00, /* 3A right HP volume */
+       0x00, /* 3B left REC volume */
+       0x00, /* 3C right REC volume */
+       0x00, /* 3D left SPK volume */
+       0x00, /* 3E right SPK volume */
+       0x00, /* 3F MIC config */
+
+       0x00, /* 40 MIC threshold */
+       0x00, /* 41 excursion limiter filter */
+       0x00, /* 42 excursion limiter threshold */
+       0x00, /* 43 ALC */
+       0x00, /* 44 power limiter threshold */
+       0x00, /* 45 power limiter config */
+       0x00, /* 46 distortion limiter config */
+       0x00, /* 47 audio input */
+       0x00, /* 48 microphone */
+       0x00, /* 49 level control */
+       0x00, /* 4A bypass switches */
+       0x00, /* 4B jack detect */
+       0x00, /* 4C input enable */
+       0x00, /* 4D output enable */
+       0xF0, /* 4E bias control */
+       0x00, /* 4F DAC power */
+
+       0x0F, /* 50 DAC power */
+       0x00, /* 51 system */
+       0x00, /* 52 DAI1 EQ1 */
+       0x00, /* 53 DAI1 EQ1 */
+       0x00, /* 54 DAI1 EQ1 */
+       0x00, /* 55 DAI1 EQ1 */
+       0x00, /* 56 DAI1 EQ1 */
+       0x00, /* 57 DAI1 EQ1 */
+       0x00, /* 58 DAI1 EQ1 */
+       0x00, /* 59 DAI1 EQ1 */
+       0x00, /* 5A DAI1 EQ1 */
+       0x00, /* 5B DAI1 EQ1 */
+       0x00, /* 5C DAI1 EQ2 */
+       0x00, /* 5D DAI1 EQ2 */
+       0x00, /* 5E DAI1 EQ2 */
+       0x00, /* 5F DAI1 EQ2 */
+
+       0x00, /* 60 DAI1 EQ2 */
+       0x00, /* 61 DAI1 EQ2 */
+       0x00, /* 62 DAI1 EQ2 */
+       0x00, /* 63 DAI1 EQ2 */
+       0x00, /* 64 DAI1 EQ2 */
+       0x00, /* 65 DAI1 EQ2 */
+       0x00, /* 66 DAI1 EQ3 */
+       0x00, /* 67 DAI1 EQ3 */
+       0x00, /* 68 DAI1 EQ3 */
+       0x00, /* 69 DAI1 EQ3 */
+       0x00, /* 6A DAI1 EQ3 */
+       0x00, /* 6B DAI1 EQ3 */
+       0x00, /* 6C DAI1 EQ3 */
+       0x00, /* 6D DAI1 EQ3 */
+       0x00, /* 6E DAI1 EQ3 */
+       0x00, /* 6F DAI1 EQ3 */
+
+       0x00, /* 70 DAI1 EQ4 */
+       0x00, /* 71 DAI1 EQ4 */
+       0x00, /* 72 DAI1 EQ4 */
+       0x00, /* 73 DAI1 EQ4 */
+       0x00, /* 74 DAI1 EQ4 */
+       0x00, /* 75 DAI1 EQ4 */
+       0x00, /* 76 DAI1 EQ4 */
+       0x00, /* 77 DAI1 EQ4 */
+       0x00, /* 78 DAI1 EQ4 */
+       0x00, /* 79 DAI1 EQ4 */
+       0x00, /* 7A DAI1 EQ5 */
+       0x00, /* 7B DAI1 EQ5 */
+       0x00, /* 7C DAI1 EQ5 */
+       0x00, /* 7D DAI1 EQ5 */
+       0x00, /* 7E DAI1 EQ5 */
+       0x00, /* 7F DAI1 EQ5 */
+
+       0x00, /* 80 DAI1 EQ5 */
+       0x00, /* 81 DAI1 EQ5 */
+       0x00, /* 82 DAI1 EQ5 */
+       0x00, /* 83 DAI1 EQ5 */
+       0x00, /* 84 DAI2 EQ1 */
+       0x00, /* 85 DAI2 EQ1 */
+       0x00, /* 86 DAI2 EQ1 */
+       0x00, /* 87 DAI2 EQ1 */
+       0x00, /* 88 DAI2 EQ1 */
+       0x00, /* 89 DAI2 EQ1 */
+       0x00, /* 8A DAI2 EQ1 */
+       0x00, /* 8B DAI2 EQ1 */
+       0x00, /* 8C DAI2 EQ1 */
+       0x00, /* 8D DAI2 EQ1 */
+       0x00, /* 8E DAI2 EQ2 */
+       0x00, /* 8F DAI2 EQ2 */
+
+       0x00, /* 90 DAI2 EQ2 */
+       0x00, /* 91 DAI2 EQ2 */
+       0x00, /* 92 DAI2 EQ2 */
+       0x00, /* 93 DAI2 EQ2 */
+       0x00, /* 94 DAI2 EQ2 */
+       0x00, /* 95 DAI2 EQ2 */
+       0x00, /* 96 DAI2 EQ2 */
+       0x00, /* 97 DAI2 EQ2 */
+       0x00, /* 98 DAI2 EQ3 */
+       0x00, /* 99 DAI2 EQ3 */
+       0x00, /* 9A DAI2 EQ3 */
+       0x00, /* 9B DAI2 EQ3 */
+       0x00, /* 9C DAI2 EQ3 */
+       0x00, /* 9D DAI2 EQ3 */
+       0x00, /* 9E DAI2 EQ3 */
+       0x00, /* 9F DAI2 EQ3 */
+
+       0x00, /* A0 DAI2 EQ3 */
+       0x00, /* A1 DAI2 EQ3 */
+       0x00, /* A2 DAI2 EQ4 */
+       0x00, /* A3 DAI2 EQ4 */
+       0x00, /* A4 DAI2 EQ4 */
+       0x00, /* A5 DAI2 EQ4 */
+       0x00, /* A6 DAI2 EQ4 */
+       0x00, /* A7 DAI2 EQ4 */
+       0x00, /* A8 DAI2 EQ4 */
+       0x00, /* A9 DAI2 EQ4 */
+       0x00, /* AA DAI2 EQ4 */
+       0x00, /* AB DAI2 EQ4 */
+       0x00, /* AC DAI2 EQ5 */
+       0x00, /* AD DAI2 EQ5 */
+       0x00, /* AE DAI2 EQ5 */
+       0x00, /* AF DAI2 EQ5 */
+
+       0x00, /* B0 DAI2 EQ5 */
+       0x00, /* B1 DAI2 EQ5 */
+       0x00, /* B2 DAI2 EQ5 */
+       0x00, /* B3 DAI2 EQ5 */
+       0x00, /* B4 DAI2 EQ5 */
+       0x00, /* B5 DAI2 EQ5 */
+       0x00, /* B6 DAI1 biquad */
+       0x00, /* B7 DAI1 biquad */
+       0x00, /* B8 DAI1 biquad */
+       0x00, /* B9 DAI1 biquad */
+       0x00, /* BA DAI1 biquad */
+       0x00, /* BB DAI1 biquad */
+       0x00, /* BC DAI1 biquad */
+       0x00, /* BD DAI1 biquad */
+       0x00, /* BE DAI1 biquad */
+       0x00, /* BF DAI1 biquad */
+
+       0x00, /* C0 DAI2 biquad */
+       0x00, /* C1 DAI2 biquad */
+       0x00, /* C2 DAI2 biquad */
+       0x00, /* C3 DAI2 biquad */
+       0x00, /* C4 DAI2 biquad */
+       0x00, /* C5 DAI2 biquad */
+       0x00, /* C6 DAI2 biquad */
+       0x00, /* C7 DAI2 biquad */
+       0x00, /* C8 DAI2 biquad */
+       0x00, /* C9 DAI2 biquad */
+       0x00, /* CA */
+       0x00, /* CB */
+       0x00, /* CC */
+       0x00, /* CD */
+       0x00, /* CE */
+       0x00, /* CF */
+
+       0x00, /* D0 */
+       0x00, /* D1 */
+       0x00, /* D2 */
+       0x00, /* D3 */
+       0x00, /* D4 */
+       0x00, /* D5 */
+       0x00, /* D6 */
+       0x00, /* D7 */
+       0x00, /* D8 */
+       0x00, /* D9 */
+       0x00, /* DA */
+       0x70, /* DB */
+       0x00, /* DC */
+       0x00, /* DD */
+       0x00, /* DE */
+       0x00, /* DF */
+
+       0x00, /* E0 */
+       0x00, /* E1 */
+       0x00, /* E2 */
+       0x00, /* E3 */
+       0x00, /* E4 */
+       0x00, /* E5 */
+       0x00, /* E6 */
+       0x00, /* E7 */
+       0x00, /* E8 */
+       0x00, /* E9 */
+       0x00, /* EA */
+       0x00, /* EB */
+       0x00, /* EC */
+       0x00, /* ED */
+       0x00, /* EE */
+       0x00, /* EF */
+
+       0x00, /* F0 */
+       0x00, /* F1 */
+       0x00, /* F2 */
+       0x00, /* F3 */
+       0x00, /* F4 */
+       0x00, /* F5 */
+       0x00, /* F6 */
+       0x00, /* F7 */
+       0x00, /* F8 */
+       0x00, /* F9 */
+       0x00, /* FA */
+       0x00, /* FB */
+       0x00, /* FC */
+       0x00, /* FD */
+       0x00, /* FE */
+       0x00, /* FF */
+};
+
+static struct {
+       int readable;
+       int writable;
+       int vol;
+} max98088_access[M98088_REG_CNT] = {
+       { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+       { 0xFF, 0x00, 1 }, /* 01 MIC status */
+       { 0xFF, 0x00, 1 }, /* 02 jack status */
+       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+       { 0xFF, 0xFF, 0 }, /* 04 */
+       { 0xFF, 0xFF, 0 }, /* 05 */
+       { 0xFF, 0xFF, 0 }, /* 06 */
+       { 0xFF, 0xFF, 0 }, /* 07 */
+       { 0xFF, 0xFF, 0 }, /* 08 */
+       { 0xFF, 0xFF, 0 }, /* 09 */
+       { 0xFF, 0xFF, 0 }, /* 0A */
+       { 0xFF, 0xFF, 0 }, /* 0B */
+       { 0xFF, 0xFF, 0 }, /* 0C */
+       { 0xFF, 0xFF, 0 }, /* 0D */
+       { 0xFF, 0xFF, 0 }, /* 0E */
+       { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+       { 0xFF, 0xFF, 0 }, /* 10 master clock */
+       { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+       { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+       { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+       { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+       { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+       { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+       { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+       { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+       { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+       { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+       { 0xFF, 0xFF, 0 }, /* 21 data config */
+       { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+       { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 27 HP control */
+       { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 2A REC control */
+       { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+       { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+       { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+       { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+       { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+       { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+       { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+       { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+       { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+       { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+       { 0xFF, 0xFF, 0 }, /* 37 INA level */
+       { 0xFF, 0xFF, 0 }, /* 38 INB level */
+       { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+       { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+       { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+       { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 43 ALC */
+       { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+       { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+       { 0xFF, 0xFF, 0 }, /* 47 audio input */
+       { 0xFF, 0xFF, 0 }, /* 48 microphone */
+       { 0xFF, 0xFF, 0 }, /* 49 level control */
+       { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+       { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+       { 0xFF, 0xFF, 0 }, /* 4C input enable */
+       { 0xFF, 0xFF, 0 }, /* 4D output enable */
+       { 0xFF, 0xFF, 0 }, /* 4E bias control */
+       { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+       { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+       { 0xFF, 0xFF, 0 }, /* 51 system */
+       { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+       { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+       { 0x00, 0x00, 0 }, /* CA */
+       { 0x00, 0x00, 0 }, /* CB */
+       { 0x00, 0x00, 0 }, /* CC */
+       { 0x00, 0x00, 0 }, /* CD */
+       { 0x00, 0x00, 0 }, /* CE */
+       { 0x00, 0x00, 0 }, /* CF */
+
+       { 0x00, 0x00, 0 }, /* D0 */
+       { 0x00, 0x00, 0 }, /* D1 */
+       { 0x00, 0x00, 0 }, /* D2 */
+       { 0x00, 0x00, 0 }, /* D3 */
+       { 0x00, 0x00, 0 }, /* D4 */
+       { 0x00, 0x00, 0 }, /* D5 */
+       { 0x00, 0x00, 0 }, /* D6 */
+       { 0x00, 0x00, 0 }, /* D7 */
+       { 0x00, 0x00, 0 }, /* D8 */
+       { 0x00, 0x00, 0 }, /* D9 */
+       { 0x00, 0x00, 0 }, /* DA */
+       { 0x00, 0x00, 0 }, /* DB */
+       { 0x00, 0x00, 0 }, /* DC */
+       { 0x00, 0x00, 0 }, /* DD */
+       { 0x00, 0x00, 0 }, /* DE */
+       { 0x00, 0x00, 0 }, /* DF */
+
+       { 0x00, 0x00, 0 }, /* E0 */
+       { 0x00, 0x00, 0 }, /* E1 */
+       { 0x00, 0x00, 0 }, /* E2 */
+       { 0x00, 0x00, 0 }, /* E3 */
+       { 0x00, 0x00, 0 }, /* E4 */
+       { 0x00, 0x00, 0 }, /* E5 */
+       { 0x00, 0x00, 0 }, /* E6 */
+       { 0x00, 0x00, 0 }, /* E7 */
+       { 0x00, 0x00, 0 }, /* E8 */
+       { 0x00, 0x00, 0 }, /* E9 */
+       { 0x00, 0x00, 0 }, /* EA */
+       { 0x00, 0x00, 0 }, /* EB */
+       { 0x00, 0x00, 0 }, /* EC */
+       { 0x00, 0x00, 0 }, /* ED */
+       { 0x00, 0x00, 0 }, /* EE */
+       { 0x00, 0x00, 0 }, /* EF */
+
+       { 0x00, 0x00, 0 }, /* F0 */
+       { 0x00, 0x00, 0 }, /* F1 */
+       { 0x00, 0x00, 0 }, /* F2 */
+       { 0x00, 0x00, 0 }, /* F3 */
+       { 0x00, 0x00, 0 }, /* F4 */
+       { 0x00, 0x00, 0 }, /* F5 */
+       { 0x00, 0x00, 0 }, /* F6 */
+       { 0x00, 0x00, 0 }, /* F7 */
+       { 0x00, 0x00, 0 }, /* F8 */
+       { 0x00, 0x00, 0 }, /* F9 */
+       { 0x00, 0x00, 0 }, /* FA */
+       { 0x00, 0x00, 0 }, /* FB */
+       { 0x00, 0x00, 0 }, /* FC */
+       { 0x00, 0x00, 0 }, /* FD */
+       { 0x00, 0x00, 0 }, /* FE */
+       { 0xFF, 0x00, 1 }, /* FF */
+};
+
+static int max98088_volatile_register(unsigned int reg)
+{
+       return max98088_access[reg].vol;
+}
+
+static int max98088_hw_write(struct snd_soc_codec *codec, unsigned int reg,
+                            unsigned int value)
+{
+       u8 data[2];
+
+       data[0] = reg;
+       data[1] = value;
+       if (codec->hw_write(codec->control_data, data, 2) == 2)
+               return 0;
+       else
+               return -EIO;
+}
+
+/*
+ * For kernels compiled without unsigned long long int division
+ */
+unsigned long long int ulldiv(unsigned long long int dividend,
+                             unsigned long long int divisor)
+{
+       unsigned long long int quotient = 0;
+       int shift = 1;
+
+       BUG_ON(divisor == 0);
+
+       /* Result is 1.0 if divisor and dividend are equal */
+       if (divisor == dividend)
+               return 1;
+
+       /* Normalize divisor */
+       while (!(divisor & 0x8000000000000000ULL)) {
+               divisor <<= 1;
+               ++shift;
+       }
+
+       /* Shift and subtract */
+       while (shift--) {
+               quotient <<= 1;
+
+               if (divisor <= dividend) {
+                       dividend -= divisor;
+                       ++quotient;
+               }
+               divisor >>= 1;
+       }
+
+       /* Round up */
+       if (dividend > divisor)
+               ++quotient;
+
+       return quotient;
+}
+
+#define INA1_PGA_BIT 0x01
+#define INA2_PGA_BIT 0x02
+#define INB1_PGA_BIT 0x04
+#define INB2_PGA_BIT 0x08
+/*
+ * The INx1 and INx2 PGAs 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.
+ */
+static int max98088_pga_event(struct snd_soc_dapm_widget *w,
+                             struct snd_kcontrol *k, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       u8 *state = &max98088->power_state;
+       unsigned int val;
+       unsigned int pga;
+       unsigned int mask;
+
+       BUG_ON(w->reg != M98088_REG_4C_PWR_EN_IN);
+
+       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;
+       }
+
+       if (event == SND_SOC_DAPM_POST_PMU) {
+               /* ON */
+               *state |= pga;
+
+               /* Turn on, avoiding unnecessary writes */
+               val = snd_soc_read(codec, w->reg);
+               if (!(val & (1 << w->shift))) {
+                       val |= (1 << w->shift);
+                       snd_soc_write(codec, w->reg, val);
+               }
+       } else if (event == SND_SOC_DAPM_POST_PMD) {
+               /* OFF */
+               *state &= ~pga;
+
+               /* Turn off if both are off, avoiding unnecessary writes */
+               if (!(*state & mask)) {
+                       val = snd_soc_read(codec, w->reg);
+                       if (val & (1 << w->shift)) {
+                               val &= ~(1 << w->shift);
+                               snd_soc_write(codec, w->reg, val);
+                       }
+               }
+       } else {
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+                   unsigned int band, u16 *coefs)
+{
+       unsigned int eq_reg;
+       unsigned int i;
+
+       BUG_ON(band > 4);
+       BUG_ON(dai > 1);
+
+       /* Load the base register address */
+       eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+       /* Add the band address offset, note adjustment for word address */
+       eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+       /* Step through the registers and coefs */
+       for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+               snd_soc_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+               snd_soc_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+       }
+
+       return;
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_ex_mode[] = {
+       "Off",
+       "100Hz",
+       "400Hz",
+       "600Hz",
+       "800Hz",
+       "1000Hz",
+       "200-400Hz",
+       "400-600Hz",
+       "400-800Hz",
+};
+
+static const unsigned int ex_mode_table[] = {
+       0x00,           /* disabled */
+       (0<<4)|3,       /* 100Hz */
+       (1<<4)|0,       /* 400Hz */
+       (2<<4)|0,       /* 600Hz */
+       (3<<4)|0,       /* 800Hz */
+       (4<<4)|0,       /* 1000Hz */
+       (1<<4)|1,       /* 200-400Hz */
+       (2<<4)|2,       /* 400-600Hz */
+       (3<<4)|2,       /* 400-800Hz */
+};
+
+static const struct soc_enum max98088_ex_mode_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_ex_mode), max98088_ex_mode),
+};
+
+static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->ex_mode;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(ex_mode_table))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       snd_soc_write(codec, M98088_REG_41_SPKDHP,
+               ex_mode_table[*mode]);
+
+       return 0;
+}
+
+static int max98088_ex_mode_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->ex_mode;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+       "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+               max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_dai1_fltr[] = {
+       "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+       "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static const char *max98088_micpre[] = {
+       "0dB",
+       "20dB",
+       "30dB",
+};
+
+static const struct soc_enum max98088_micpre_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_micpre), max98088_micpre),
+};
+
+static const char *max98088_extmic[] = {
+       "Off",
+       "MIC1",
+       "MIC2",
+};
+
+static const struct soc_enum max98088_extmic_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
+};
+
+static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->mic1pre;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_micpre))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       return 0;
+}
+
+static int max98088_mic1pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->mic1pre;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static int max98088_mic2pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->mic2pre;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_micpre))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       return 0;
+}
+
+static int max98088_mic2pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->mic2pre;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static int max98088_extmic_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->extmic_mode;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_extmic))
+               return -EINVAL;
+
+       *mode = sel;
+       snd_soc_update_bits(codec, M98088_REG_48_CFG_MIC,
+               M98088_EXTMIC_MASK, sel);
+
+       return 0;
+}
+
+static int max98088_extmic_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       unsigned int *mode = &max98088->extmic_mode;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+       /* Analog outputs */
+
+       SOC_DOUBLE_R("Headphone Volume", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+       SOC_DOUBLE_R("Speaker Volume", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+       SOC_DOUBLE_R("Receiver Volume", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+       SOC_DOUBLE_R("Headphone Switch", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 7, 1, 1),
+       SOC_DOUBLE_R("Speaker Switch", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 7, 1, 1),
+       SOC_DOUBLE_R("Receiver Switch", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 7, 1, 1),
+
+       /* Analog inputs */
+
+       SOC_SINGLE("MIC1 Volume", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+       SOC_SINGLE("MIC2 Volume", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+       SOC_ENUM_EXT("MIC1 Boost Volume", max98088_micpre_enum,
+               max98088_mic1pre_get, max98088_mic1pre_set),
+
+       SOC_ENUM_EXT("MIC2 Boost Volume", max98088_micpre_enum,
+               max98088_mic2pre_get, max98088_mic2pre_set),
+
+       SOC_ENUM_EXT("Ext MIC Switch", max98088_extmic_enum,
+               max98088_extmic_get, max98088_extmic_set),
+
+       SOC_SINGLE("INA Volume", M98088_REG_37_LVL_INA, 0, 7, 1),
+       SOC_SINGLE("INB Volume", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+       /* ADC input digital gains and volume controls */
+
+       SOC_SINGLE("ADCL Volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+       SOC_SINGLE("ADCR Volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+       SOC_SINGLE("ADCL Boost Volume", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+       SOC_SINGLE("ADCR Boost Volume", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+       /* Equalizer */
+
+       SOC_SINGLE("EQ1 Switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+       SOC_SINGLE("EQ2 Switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+       /* Excursion limiter */
+
+       SOC_ENUM_EXT("EX Limiter Mode", max98088_ex_mode_enum,
+               max98088_ex_mode_get, max98088_ex_mode_set),
+       SOC_ENUM("EX Limiter Threshold", max98088_ex_thresh_enum),
+
+       /* Voice/music filters */
+
+       SOC_ENUM("DAI1 Filter Mode", max98088_filter_mode_enum),
+       SOC_ENUM("DAI1 DAC Filter", max98088_dai1_dac_filter_enum),
+       SOC_ENUM("DAI1 ADC Filter", max98088_dai1_adc_filter_enum),
+       SOC_SINGLE("DAI2 DC Block Switch", M98088_REG_20_DAI2_FILTERS,
+               0, 1, 0),
+
+       /* Automatic level control (for both DAI1/DAI2) */
+
+       SOC_SINGLE("ALC Switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+       SOC_SINGLE("ALC Threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+       SOC_SINGLE("ALC Multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+       SOC_SINGLE("ALC Release Time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+       /* Power limiter */
+
+       SOC_SINGLE("PWR Limiter Threshold", M98088_REG_44_PWRLMT_CFG,
+               4, 15, 0),
+       SOC_SINGLE("PWR Limiter Weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+       SOC_SINGLE("PWR Limiter Time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+       SOC_SINGLE("PWR Limiter Time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+       /* THD distortion limiter */
+
+       SOC_SINGLE("THD Limiter Threshold", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+       SOC_SINGLE("THD Limiter Time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_hp_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       u16 status;
+
+       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
+
+       /* powering down headphone gracefully */
+       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
+       if ((status & M98088_HPEN) == M98088_HPEN) {
+               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
+                       (status & ~M98088_HPEN));
+       }
+       schedule_timeout(msecs_to_jiffies(20));
+
+       return 0;
+}
+
+static int max98088_mic_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               if (w->reg == M98088_REG_35_LVL_MIC1) {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic1pre)<<M98088_MICPRE_SHIFT);
+               } else {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic2pre)<<M98088_MICPRE_SHIFT);
+               }
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK, 0);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* DAPM widgets top level */
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+       SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+       SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+       SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+       SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+       SND_SOC_DAPM_PGA_E("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+               7, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+       SND_SOC_DAPM_PGA_E("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+               6, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+
+       SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+               5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+               4, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+               3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+               2, 0, NULL, 0),
+
+       SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_left_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_right_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_PGA_E("MIC1 Input", M98088_REG_35_LVL_MIC1,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("MIC2 Input", M98088_REG_36_LVL_MIC2,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_MICBIAS("MICBIAS", M98088_REG_4C_PWR_EN_IN, 3, 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("RECL"),
+       SND_SOC_DAPM_OUTPUT("RECR"),
+
+       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"),
+};
+
+/* DAPM AUDIO_MAP: */
+static const struct snd_soc_dapm_route audio_map[] = {
+       /* Left headphone output mixer */
+       {"Left HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left HP Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right headphone output mixer */
+       {"Right HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right HP Mixer", "Left DAC2 Switch", "DACL2"  },
+       {"Right HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Left speaker output mixer */
+       {"Left SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right speaker output mixer */
+       {"Right SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Left REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Right REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       {"HP Left Out", NULL, "Left HP Mixer"},
+       {"HP Right Out", NULL, "Right HP Mixer"},
+       {"SPK Left Out", NULL, "Left SPK Mixer"},
+       {"SPK Right Out", NULL, "Right SPK Mixer"},
+       {"REC Left Out", NULL, "Left REC Mixer"},
+       {"REC Right Out", NULL, "Right REC Mixer"},
+
+       {"HPL", NULL, "HP Left Out"},
+       {"HPR", NULL, "HP Right Out"},
+       {"SPKL", NULL, "SPK Left Out"},
+       {"SPKR", NULL, "SPK Right Out"},
+       {"RECL", NULL, "REC Left Out"},
+       {"RECR", NULL, "REC Right Out"},
+
+       /* Left ADC input mixer */
+       {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right ADC input mixer */
+       {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right ADC Mixer", "INB2 Switch", "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"},
+       {"MIC1 Input", NULL, "MIC1"},
+       {"MIC2 Input", NULL, "MIC2"},
+};
+
+static int max98088_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, max98088_dapm_widgets,
+                                 ARRAY_SIZE(max98088_dapm_widgets));
+
+       snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+       snd_soc_add_controls(codec, max98088_snd_controls,
+                            ARRAY_SIZE(max98088_snd_controls));
+
+       snd_soc_dapm_new_widgets(codec);
+       return 0;
+}
+
+/* codec mclk clock divider coefficients */
+static const struct {
+       u32 rate;
+       u8  sr;
+} rate_table[] = {
+       {8000,  0x10},
+       {11025, 0x20},
+       {16000, 0x30},
+       {22050, 0x40},
+       {24000, 0x50},
+       {32000, 0x60},
+       {44100, 0x70},
+       {48000, 0x80},
+       {88200, 0x90},
+       {96000, 0xA0},
+};
+
+static inline int rate_value(int rate, u8 *value)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+               if (rate_table[i].rate >= rate) {
+                       *value = rate_table[i].sr;
+                       return 0;
+               }
+       }
+       *value = rate_table[0].sr;
+       return -EINVAL;
+}
+
+static int max98088_dai1_hw_params(struct snd_pcm_substream *substream,
+                                  struct snd_pcm_hw_params *params,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 regval;
+       u16 ni;
+
+       cdata = &max98088->dai[0];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
+               if (rate_value(rate, &regval))
+                       return -EINVAL;
+
+               snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_14_DAI1_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0)
+                       return -EINVAL;
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+static int max98088_dai2_hw_params(struct snd_pcm_substream *substream,
+                                  struct snd_pcm_hw_params *params,
+                                  struct snd_soc_dai *dai)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 regval;
+       u16 ni;
+
+       cdata = &max98088->dai[1];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI2 SR2 value for the DSP */
+               if (rate_value(rate, &regval))
+                       return -EINVAL;
+
+               snd_soc_write(codec, M98088_REG_19_DAI2_CLKMODE, regval);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_1C_DAI2_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0)
+                       return -EINVAL;
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+                                  int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+
+       /* requested clock frequency is already setup */
+       if (freq == max98088->sysclk)
+               return 0;
+
+       max98088->sysclk = freq; /* remember current sysclk */
+
+       /* setup clocks for slave mode, and using the PLL
+        * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+        *         0x02 (when master clk is 20MHz to 30MHz)..
+        */
+       if ((freq >= 10000000) && (freq < 20000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+       } else if ((freq >= 20000000) && (freq < 30000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+       } else {
+               dev_err(codec->dev, "Invalid master clock frequency\n");
+               return -EINVAL;
+       }
+
+       if (snd_soc_read(codec, M98088_REG_51_PWR_SYS)  & M98088_SHDNRUN) {
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN, 0);
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       0, M98088_SHDNRUN);
+       }
+
+       dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+       max98088->sysclk = freq;
+       return 0;
+}
+
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       u8 reg15val;
+       u8 reg14msk = 0;
+       u8 reg14val = 0;
+
+       cdata = &max98088->dai[0];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* mask MAS to select slave mode */
+                       reg14msk |= M98088_DAI_MAS;
+                       /* slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* set to master mode */
+                       reg14val |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg14val |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       reg14msk |= M98088_DAI_DLY;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       reg14msk |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg14msk |= M98088_DAI_BCI;
+                       reg14val |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg14msk |= M98088_DAI_WCI;
+                       reg14val |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg14val |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       reg14msk, reg14val);
+
+               reg15val = M98088_DAI_BSEL64;
+               if (max98088->digmic)
+                       reg15val |= M98088_DAI_OSR64;
+               snd_soc_write(codec, M98088_REG_15_DAI1_CLOCK, reg15val);
+       }
+
+       return 0;
+}
+
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       u8 reg1Cmsk = 0;
+       u8 reg1Cval = 0;
+
+       cdata = &max98088->dai[1];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* mask MAS to select slave mode */
+                       reg1Cmsk |= M98088_DAI_MAS;
+                       /* slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* set to master mode */
+                       reg1Cval |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg1Cval |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       reg1Cmsk |= M98088_DAI_DLY;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       reg1Cmsk |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg1Cmsk |= M98088_DAI_BCI;
+                       reg1Cval |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg1Cmsk |= M98088_DAI_WCI;
+                       reg1Cval |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg1Cval |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       reg1Cmsk, reg1Cval);
+
+               snd_soc_write(codec, M98088_REG_1D_DAI2_CLOCK,
+                       M98088_DAI_BSEL64);
+       }
+
+       return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       int i;
+
+       if (!codec->cache_sync)
+               return;
+
+       codec->cache_only = 0;
+
+       /* write back cached values if they're writeable and
+        * different from the hardware default.
+        */
+       for (i = 1; i < ARRAY_SIZE(max98088->reg_cache); i++) {
+               if (!max98088_access[i].writable)
+                       continue;
+
+               if (max98088->reg_cache[i] == max98088_reg[i])
+                       continue;
+
+               snd_soc_write(codec, i, max98088->reg_cache[i]);
+       }
+
+       codec->cache_sync = 0;
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+                                 enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               max98088_sync_cache(codec);
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, M98088_MBEN);
+               break;
+
+       case SND_SOC_BIAS_OFF:
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, 0);
+#ifdef CONFIG_REGULATOR
+               codec->cache_sync = 1;
+#endif
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai1_set_fmt,
+       .hw_params = max98088_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai2_set_fmt,
+       .hw_params = max98088_dai2_hw_params,
+};
+
+static struct snd_soc_dai_driver max98088_dai[] = {
+{
+       .name = "HiFi",
+       .playback = {
+               .stream_name = "HiFi Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .capture = {
+               .stream_name = "HiFi Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+        .ops = &max98088_dai1_ops,
+},
+{
+       .name = "Aux",
+       .playback = {
+               .stream_name = "Aux Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .ops = &max98088_dai2_ops,
+}
+};
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+       sel = cdata->eq_sel;
+
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               if (strcmp(pdata->eq1_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq1_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq1_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq1_cfg[best].name,
+               pdata->eq1_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+       coef_set = &pdata->eq1_cfg[sel];
+
+       m98088_eq_band(codec, 0, 0, coef_set->band1);
+       m98088_eq_band(codec, 0, 1, coef_set->band2);
+       m98088_eq_band(codec, 0, 2, coef_set->band3);
+       m98088_eq_band(codec, 0, 3, coef_set->band4);
+       m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->eq_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               if (strcmp(pdata->eq2_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq2_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq2_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq2_cfg[best].name,
+               pdata->eq2_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+       coef_set = &pdata->eq2_cfg[sel];
+
+       m98088_eq_band(codec, 1, 0, coef_set->band1);
+       m98088_eq_band(codec, 1, 1, coef_set->band2);
+       m98088_eq_band(codec, 1, 2, coef_set->band3);
+       m98088_eq_band(codec, 1, 3, coef_set->band4);
+       m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+               save);
+}
+
+
+static int max98088_put_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[0];
+
+       if (sel >= pdata->eq1_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq1(codec);
+       return 0;
+}
+
+static int max98088_put_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[1];
+
+       if (sel >= pdata->eq2_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq2(codec);
+       return 0;
+}
+
+static int max98088_get_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static int max98088_get_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static void max98088_handle_eq1_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq1control =
+               SOC_ENUM_EXT("EQ1 Mode",
+                       max98088->dai[0].eq_enum,
+                       max98088_get_eq1_enum,
+                       max98088_put_eq1_enum);
+
+       cdata = &max98088->dai[0];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq1_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               /* ...and remember the new version. */
+               cdata->eq_texts[i] = pdata->eq1_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ1 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       ret = snd_soc_add_controls(codec, &eq1control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_eq2_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq2control =
+               SOC_ENUM_EXT("EQ2 Mode",
+                       max98088->dai[1].eq_enum,
+                       max98088_get_eq2_enum,
+                       max98088_put_eq2_enum);
+
+       cdata = &max98088->dai[1];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq2_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               cdata->eq_texts[i] = pdata->eq2_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ2 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       ret = snd_soc_add_controls(codec, &eq2control, 1);
+       if (ret != 0)
+               printk(KERN_ERR "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_pdata(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_pdata *pdata = max98088->pdata;
+       u8 regval = 0;
+
+       if (!pdata) {
+               dev_dbg(codec->dev, "No platform data\n");
+               return;
+       }
+
+       /* configure mic for analog/digital mic mode */
+       if (pdata->digmic_left_mode)
+               regval |= M98088_DIGMIC_L;
+
+       if (pdata->digmic_right_mode)
+               regval |= M98088_DIGMIC_R;
+
+       max98088->digmic = (regval ? 1 : 0);
+
+       snd_soc_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+       /* configure receiver output */
+       regval = ((pdata->receiver_mode) ? M98088_REC_LINEMODE : 0);
+       snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+               M98088_REC_LINEMODE_MASK, regval);
+
+       /* configure equalizers */
+       if (pdata->eq1_cfgcnt)
+               max98088_handle_eq1_pdata(codec);
+
+       if (pdata->eq2_cfgcnt)
+               max98088_handle_eq2_pdata(codec);
+}
+
+#ifdef CONFIG_PM
+static int max98088_suspend(struct snd_soc_codec *codec, pm_message_t state)
+{
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static int max98088_resume(struct snd_soc_codec *codec)
+{
+       int i;
+       u8 *cache = codec->reg_cache;
+
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       /* Sync reg_cache with the hardware */
+       for (i = 0; i < M98088_REG_CNT; i++) {
+               if (i == M98088_REG_51_PWR_SYS)
+                       continue;
+
+               if (!max98088_access[i].writable)
+                       continue;
+
+               max98088_hw_write(codec, i, cache[i]);
+       }
+
+       /* now enter into the resume mode bias level */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+}
+#else
+#define max98088_suspend NULL
+#define max98088_resume NULL
+#endif
+
+static int max98088_probe(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = snd_soc_codec_get_drvdata(codec);
+       struct max98088_cdata *cdata;
+       int ret = 0;
+
+       codec->cache_sync = 1;
+       memcpy(codec->reg_cache, max98088_reg, sizeof(max98088_reg));
+
+       ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+               return ret;
+       }
+
+       /* initalize private data */
+
+       max98088->sysclk = (unsigned)-1;
+
+       cdata = &max98088->dai[0];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+
+       cdata = &max98088->dai[1];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+
+       max98088->power_state = 0; /* INA INB power enable state */
+       max98088->ex_mode = 0; /* excursion limiter mode */
+       max98088->digmic = 0; /* 0=analog, 1=digital */
+       max98088->mic1pre = 0;
+       max98088->mic2pre = 0;
+
+       ret = snd_soc_read(codec, M98088_REG_FF_REV_ID);
+       if (ret < 0) {
+               dev_err(codec->dev, "Failed to read device revision: %d\n",
+                       ret);
+               goto err_access;
+       }
+       dev_info(codec->dev, "revision %c\n", ret + 'A');
+
+       snd_soc_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+
+       /* initialize registers cache to hardware default */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       snd_soc_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+       snd_soc_write(codec, M98088_REG_22_MIX_DAC,
+               M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+               M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+       snd_soc_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+       snd_soc_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+       snd_soc_write(codec, M98088_REG_16_DAI1_IOCFG,
+               M98088_S1NORMAL|M98088_SDATA);
+
+       snd_soc_write(codec, M98088_REG_1E_DAI2_IOCFG,
+               M98088_S2NORMAL|M98088_SDATA);
+
+       max98088_handle_pdata(codec);
+
+       max98088_add_widgets(codec);
+
+err_access:
+       return ret;
+}
+
+static int max98088_remove(struct snd_soc_codec *codec)
+{
+       if (codec->control_data)
+               max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_max98088 = {
+       .probe   = max98088_probe,
+       .remove  = max98088_remove,
+       .suspend = max98088_suspend,
+       .resume  = max98088_resume,
+       .set_bias_level = max98088_set_bias_level,
+       .reg_cache_size = ARRAY_SIZE(max98088_reg),
+       .reg_word_size = sizeof(u8),
+       .reg_cache_default = max98088_reg,
+       .volatile_register = max98088_volatile_register,
+};
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+                            const struct i2c_device_id *id)
+{
+       struct max98088_priv *max98088;
+       int ret;
+
+       max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+       if (max98088 == NULL)
+               return -ENOMEM;
+
+       i2c_set_clientdata(i2c, max98088);
+       max98088->control_data = i2c;
+       max98088->pdata = i2c->dev.platform_data;
+
+       ret = snd_soc_register_codec(&i2c->dev,
+                       &soc_codec_dev_max98088, &max98088_dai[0], 2);
+       if (ret < 0)
+               kfree(max98088);
+       return ret;
+}
+
+static int max98088_i2c_remove(struct i2c_client *client)
+{
+       snd_soc_unregister_codec(&client->dev);
+       kfree(i2c_get_clientdata(client));
+       return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+       { "max98088", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+static struct i2c_driver max98088_i2c_driver = {
+       .driver = {
+               .name = "max98088-codec",
+               .owner = THIS_MODULE,
+       },
+       .probe  = max98088_i2c_probe,
+       .remove = __devexit_p(max98088_i2c_remove),
+       .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&max98088_i2c_driver);
+       if (ret)
+               pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+       return ret;
+}
+
+static void __exit max98088_exit(void)
+{
+       i2c_del_driver(&max98088_i2c_driver);
+}
+
+module_init(max98088_init);
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang, Jesse Marroquin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h
new file mode 100644
index 0000000..9e9d8da
--- /dev/null
+++ b/sound/soc/codecs/max98088.h
@@ -0,0 +1,190 @@
+/*
+ * max98088.h -- MAX98088 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 _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS            0x00
+#define M98088_REG_01_MIC_STATUS            0x01
+#define M98088_REG_02_JACK_STAUS            0x02
+#define M98088_REG_03_BATTERY_VOLTAGE       0x03
+#define M98088_REG_0F_IRQ_ENABLE            0x0F
+#define M98088_REG_10_SYS_CLK               0x10
+#define M98088_REG_11_DAI1_CLKMODE          0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI        0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO        0x13
+#define M98088_REG_14_DAI1_FORMAT           0x14
+#define M98088_REG_15_DAI1_CLOCK            0x15
+#define M98088_REG_16_DAI1_IOCFG            0x16
+#define M98088_REG_17_DAI1_TDM              0x17
+#define M98088_REG_18_DAI1_FILTERS          0x18
+#define M98088_REG_19_DAI2_CLKMODE          0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI        0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO        0x1B
+#define M98088_REG_1C_DAI2_FORMAT           0x1C
+#define M98088_REG_1D_DAI2_CLOCK            0x1D
+#define M98088_REG_1E_DAI2_IOCFG            0x1E
+#define M98088_REG_1F_DAI2_TDM              0x1F
+#define M98088_REG_20_DAI2_FILTERS          0x20
+#define M98088_REG_21_SRC                   0x21
+#define M98088_REG_22_MIX_DAC               0x22
+#define M98088_REG_23_MIX_ADC_LEFT          0x23
+#define M98088_REG_24_MIX_ADC_RIGHT         0x24
+#define M98088_REG_25_MIX_HP_LEFT           0x25
+#define M98088_REG_26_MIX_HP_RIGHT          0x26
+#define M98088_REG_27_MIX_HP_CNTL           0x27
+#define M98088_REG_28_MIX_REC_LEFT          0x28
+#define M98088_REG_29_MIX_REC_RIGHT         0x29
+#define M98088_REG_2A_MIC_REC_CNTL          0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT          0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT         0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL          0x2D
+#define M98088_REG_2E_LVL_SIDETONE          0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY         0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ      0x30
+#define M98088_REG_31_LVL_DAI2_PLAY         0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ      0x32
+#define M98088_REG_33_LVL_ADC_L             0x33
+#define M98088_REG_34_LVL_ADC_R             0x34
+#define M98088_REG_35_LVL_MIC1              0x35
+#define M98088_REG_36_LVL_MIC2              0x36
+#define M98088_REG_37_LVL_INA               0x37
+#define M98088_REG_38_LVL_INB               0x38
+#define M98088_REG_39_LVL_HP_L              0x39
+#define M98088_REG_3A_LVL_HP_R              0x3A
+#define M98088_REG_3B_LVL_REC_L             0x3B
+#define M98088_REG_3C_LVL_REC_R             0x3C
+#define M98088_REG_3D_LVL_SPK_L             0x3D
+#define M98088_REG_3E_LVL_SPK_R             0x3E
+#define M98088_REG_3F_MICAGC_CFG            0x3F
+#define M98088_REG_40_MICAGC_THRESH         0x40
+#define M98088_REG_41_SPKDHP                0x41
+#define M98088_REG_42_SPKDHP_THRESH         0x42
+#define M98088_REG_43_SPKALC_COMP           0x43
+#define M98088_REG_44_PWRLMT_CFG            0x44
+#define M98088_REG_45_PWRLMT_TIME           0x45
+#define M98088_REG_46_THDLMT_CFG            0x46
+#define M98088_REG_47_CFG_AUDIO_IN          0x47
+#define M98088_REG_48_CFG_MIC               0x48
+#define M98088_REG_49_CFG_LEVEL             0x49
+#define M98088_REG_4A_CFG_BYPASS            0x4A
+#define M98088_REG_4B_CFG_JACKDET           0x4B
+#define M98088_REG_4C_PWR_EN_IN             0x4C
+#define M98088_REG_4D_PWR_EN_OUT            0x4D
+#define M98088_REG_4E_BIAS_CNTL             0x4E
+#define M98088_REG_4F_DAC_BIAS1             0x4F
+#define M98088_REG_50_DAC_BIAS2             0x50
+#define M98088_REG_51_PWR_SYS               0x51
+#define M98088_REG_52_DAI1_EQ_BASE          0x52
+#define M98088_REG_84_DAI2_EQ_BASE          0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE      0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE      0xC0
+#define M98088_REG_FF_REV_ID                0xFF
+
+#define M98088_REG_CNT                      (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+       #define M98088_DAI_MAS                  (1<<7)
+       #define M98088_DAI_WCI                  (1<<6)
+       #define M98088_DAI_BCI                  (1<<5)
+       #define M98088_DAI_DLY                  (1<<4)
+       #define M98088_DAI_TDM                  (1<<2)
+       #define M98088_DAI_FSW                  (1<<1)
+       #define M98088_DAI_WS                   (1<<0)
+
+/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */
+       #define M98088_DAI_BSEL64               (1<<0)
+       #define M98088_DAI_OSR64                (1<<6)
+
+/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */
+       #define M98088_S1NORMAL                 (1<<6)
+       #define M98088_S2NORMAL                 (2<<6)
+       #define M98088_SDATA                    (3<<0)
+
+/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */
+       #define M98088_DAI_DHF                  (1<<3)
+
+/* M98088_REG_22_MIX_DAC */
+       #define M98088_DAI1L_TO_DACL            (1<<7)
+       #define M98088_DAI1R_TO_DACL            (1<<6)
+       #define M98088_DAI2L_TO_DACL            (1<<5)
+       #define M98088_DAI2R_TO_DACL            (1<<4)
+       #define M98088_DAI1L_TO_DACR            (1<<3)
+       #define M98088_DAI1R_TO_DACR            (1<<2)
+       #define M98088_DAI2L_TO_DACR            (1<<1)
+       #define M98088_DAI2R_TO_DACR            (1<<0)
+
+/* M98088_REG_2A_MIC_REC_CNTL */
+       #define M98088_REC_LINEMODE             (1<<7)
+       #define M98088_REC_LINEMODE_MASK        (1<<7)
+
+/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */
+       #define M98088_MICPRE_MASK              (3<<5)
+       #define M98088_MICPRE_SHIFT             5
+
+/* M98088_REG_3A_LVL_HP_R */
+       #define M98088_HP_MUTE                  (1<<7)
+
+/* M98088_REG_3C_LVL_REC_R */
+       #define M98088_REC_MUTE                 (1<<7)
+
+/* M98088_REG_3E_LVL_SPK_R */
+       #define M98088_SP_MUTE                  (1<<7)
+
+/* M98088_REG_48_CFG_MIC */
+       #define M98088_EXTMIC_MASK              (3<<0)
+       #define M98088_DIGMIC_L                 (1<<5)
+       #define M98088_DIGMIC_R                 (1<<4)
+
+/* M98088_REG_49_CFG_LEVEL */
+       #define M98088_VSEN                     (1<<6)
+       #define M98088_ZDEN                     (1<<5)
+       #define M98088_EQ2EN                    (1<<1)
+       #define M98088_EQ1EN                    (1<<0)
+
+/* M98088_REG_4C_PWR_EN_IN */
+       #define M98088_INAEN                    (1<<7)
+       #define M98088_INBEN                    (1<<6)
+       #define M98088_MBEN                     (1<<3)
+       #define M98088_ADLEN                    (1<<1)
+       #define M98088_ADREN                    (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+       #define M98088_HPLEN                    (1<<7)
+       #define M98088_HPREN                    (1<<6)
+       #define M98088_HPEN                     ((1<<7)|(1<<6))
+       #define M98088_SPLEN                    (1<<5)
+       #define M98088_SPREN                    (1<<4)
+       #define M98088_RECEN                    (1<<3)
+       #define M98088_DALEN                    (1<<1)
+       #define M98088_DAREN                    (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+       #define M98088_SHDNRUN                  (1<<7)
+       #define M98088_PERFMODE                 (1<<3)
+       #define M98088_HPPLYBACK                (1<<2)
+       #define M98088_PWRSV8K                  (1<<1)
+       #define M98088_PWRSV                    (1<<0)
+
+#define M98088_COEFS_PER_BAND               5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+struct max98088_setup_data {
+       unsigned short i2c_address;
+};
+
+#endif
--
1.6.3.3

^ permalink raw reply related	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-23 17:56     ` Peter Hsiang
@ 2010-09-23 18:38       ` Mark Brown
  -1 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-23 18:38 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Thu, Sep 23, 2010 at 10:56:38AM -0700, Peter Hsiang wrote:
> On Thu, Sep 23, 2010, Mark Brown wrote:

> > |   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git

> > Currently you should be looking at for-next or for-2.6.37, though the
> > branch names will change as kernel development moves forward.

> What is the git command line path/parameter for for-next or for-2.6.37?
> Something equivalent to...
> ....pub/scm/linux/kernel/git/next/linux-next.git 

That is the git path to the repository.  for-next and for-2.6.37 are
branches in that repository.

> Why do the files in git/broonie/sound-2.6.git look older than -next?
> For example, it's missing soc-cache.c, and the codec drivers there
> use the old I/O method.  Same drivers in -next are newer and use the soc-cache.

You're looking at the wrong branch, you need to look at one of the
branches mentioned above.  git branch -r to list remote branches.  I
can't remember what the default branch is but since it changes with
every release it's not always up to date.

Developing against -next is also OK, it contains everything in the above
tree (and then some).

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-23 18:38       ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-23 18:38 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Jesse Marroquin, Liam Girdwood

On Thu, Sep 23, 2010 at 10:56:38AM -0700, Peter Hsiang wrote:
> On Thu, Sep 23, 2010, Mark Brown wrote:

> > |   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git

> > Currently you should be looking at for-next or for-2.6.37, though the
> > branch names will change as kernel development moves forward.

> What is the git command line path/parameter for for-next or for-2.6.37?
> Something equivalent to...
> ....pub/scm/linux/kernel/git/next/linux-next.git 

That is the git path to the repository.  for-next and for-2.6.37 are
branches in that repository.

> Why do the files in git/broonie/sound-2.6.git look older than -next?
> For example, it's missing soc-cache.c, and the codec drivers there
> use the old I/O method.  Same drivers in -next are newer and use the soc-cache.

You're looking at the wrong branch, you need to look at one of the
branches mentioned above.  git branch -r to list remote branches.  I
can't remember what the default branch is but since it changes with
every release it's not always up to date.

Developing against -next is also OK, it contains everything in the above
tree (and then some).

^ permalink raw reply	[flat|nested] 46+ messages in thread

* RE: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-23 12:04   ` Mark Brown
@ 2010-09-23 17:56     ` Peter Hsiang
  -1 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-23 17:56 UTC (permalink / raw)
  To: Mark Brown
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Thu, Sep 23, 2010, Mark Brown wrote:
> On Wed, Sep 22, 2010 at 07:58:48PM -0700, Peter Hsiang wrote:
> 
> > This patch adds the MAX98088 CODEC driver.
> 
> > Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
> 
> When you submitted the driver on the 31st of August my first comment in
> the review was:
> 
> | The major issue with this is that the driver needs to be updated to
> | current ASoC versions - you should always be working against the trees
> | that are in linux-next for new drivers.  For ASoC you should be looking
> | at:
> 
> |   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
> 
> Currently you should be looking at for-next or for-2.6.37, though the
> branch names will change as kernel development moves forward.

What is the git command line path/parameter for for-next or for-2.6.37?
Something equivalent to...
....pub/scm/linux/kernel/git/next/linux-next.git 

Why do the files in git/broonie/sound-2.6.git look older than -next?
For example, it's missing soc-cache.c, and the codec drivers there
use the old I/O method.  Same drivers in -next are newer and use the soc-cache.
 
> 
> This must be rectified before the driver can be considered for inclusion
> in the kernel.  As things stand the code will not compile.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-23 17:56     ` Peter Hsiang
  0 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-23 17:56 UTC (permalink / raw)
  To: Mark Brown
  Cc: alsa-devel, Jesse, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Marroquin, Liam, Girdwood

On Thu, Sep 23, 2010, Mark Brown wrote:
> On Wed, Sep 22, 2010 at 07:58:48PM -0700, Peter Hsiang wrote:
> 
> > This patch adds the MAX98088 CODEC driver.
> 
> > Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
> 
> When you submitted the driver on the 31st of August my first comment in
> the review was:
> 
> | The major issue with this is that the driver needs to be updated to
> | current ASoC versions - you should always be working against the trees
> | that are in linux-next for new drivers.  For ASoC you should be looking
> | at:
> 
> |   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git
> 
> Currently you should be looking at for-next or for-2.6.37, though the
> branch names will change as kernel development moves forward.

What is the git command line path/parameter for for-next or for-2.6.37?
Something equivalent to...
....pub/scm/linux/kernel/git/next/linux-next.git 

Why do the files in git/broonie/sound-2.6.git look older than -next?
For example, it's missing soc-cache.c, and the codec drivers there
use the old I/O method.  Same drivers in -next are newer and use the soc-cache.
 
> 
> This must be rectified before the driver can be considered for inclusion
> in the kernel.  As things stand the code will not compile.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
  2010-09-23  2:58 ` Peter Hsiang
@ 2010-09-23 12:04   ` Mark Brown
  -1 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-23 12:04 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Peter Ujfalusi,
	alsa-devel, linux-kernel, Jesse Marroquin

On Wed, Sep 22, 2010 at 07:58:48PM -0700, Peter Hsiang wrote:

> This patch adds the MAX98088 CODEC driver.

> Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>

When you submitted the driver on the 31st of August my first comment in
the review was:

| The major issue with this is that the driver needs to be updated to
| current ASoC versions - you should always be working against the trees
| that are in linux-next for new drivers.  For ASoC you should be looking
| at:

|   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git

Currently you should be looking at for-next or for-2.6.37, though the
branch names will change as kernel development moves forward.

This must be rectified before the driver can be considered for inclusion
in the kernel.  As things stand the code will not compile.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* Re: [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-23 12:04   ` Mark Brown
  0 siblings, 0 replies; 46+ messages in thread
From: Mark Brown @ 2010-09-23 12:04 UTC (permalink / raw)
  To: Peter Hsiang
  Cc: alsa-devel, Takashi Iwai, Peter Ujfalusi, linux-kernel,
	Jesse Marroquin, Liam Girdwood

On Wed, Sep 22, 2010 at 07:58:48PM -0700, Peter Hsiang wrote:

> This patch adds the MAX98088 CODEC driver.

> Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>

When you submitted the driver on the 31st of August my first comment in
the review was:

| The major issue with this is that the driver needs to be updated to
| current ASoC versions - you should always be working against the trees
| that are in linux-next for new drivers.  For ASoC you should be looking
| at:

|   git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound-2.6.git

Currently you should be looking at for-next or for-2.6.37, though the
branch names will change as kernel development moves forward.

This must be rectified before the driver can be considered for inclusion
in the kernel.  As things stand the code will not compile.

^ permalink raw reply	[flat|nested] 46+ messages in thread

* [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-23  2:58 ` Peter Hsiang
  0 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-23  2:58 UTC (permalink / raw)
  To: Jaroslav Kysela, Takashi Iwai, Peter Hsiang, Liam Girdwood,
	Mark Brown, Peter Ujfalusi
  Cc: alsa-devel, linux-kernel, Jesse Marroquin

This patch adds the MAX98088 CODEC driver.

Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
---
 include/sound/max98088.h    |   54 +
 sound/soc/codecs/Kconfig    |    4 +
 sound/soc/codecs/Makefile   |    2 +
 sound/soc/codecs/max98088.c | 2477 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/max98088.h |  193 ++++
 5 files changed, 2730 insertions(+), 0 deletions(-)
 create mode 100644 include/sound/max98088.h
 create mode 100644 sound/soc/codecs/max98088.c
 create mode 100644 sound/soc/codecs/max98088.h

diff --git a/include/sound/max98088.h b/include/sound/max98088.h
new file mode 100644
index 0000000..30652be
--- /dev/null
+++ b/include/sound/max98088.h
@@ -0,0 +1,54 @@
+/*
+ * Platform data for MAX98088
+ *
+ * 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 as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __SOUND_MAX98088_PDATA_H__
+#define __SOUND_MAX98088_PDATA_H__
+
+#define EQ_CFG_MAX 32
+
+/* Equalizer filter response configuration */
+struct max98088_eq_cfg {
+       const char *name;
+       unsigned int rate;
+       u16 band1[5];
+       u16 band2[5];
+       u16 band3[5];
+       u16 band4[5];
+       u16 band5[5];
+};
+
+/* codec platform data */
+struct max98088_pdata {
+
+       /* Equalizers for DAI1 and DAI2 */
+       struct max98088_eq_cfg *eq1_cfg;
+       struct max98088_eq_cfg *eq2_cfg;
+       unsigned int eq1_cfgcnt;
+       unsigned int eq2_cfgcnt;
+
+       /* Receiver output can be configured as power amplifier or LINE out */
+       /* Set receiver_mode to:
+        * 0 = amplifier output, or
+        * 1 = LINE level output
+        */
+       unsigned int receiver_mode:1;
+
+       /* Analog/digital microphone configuration:
+        * 0 = analog microphone input (normal setting)
+        * 1 = digital microphone input
+        */
+       unsigned int digmic_left_mode:1;
+       unsigned int digmic_right_mode:1;
+
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4ccc2b7..4e6713c 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -27,6 +27,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_CS4270 if I2C
        select SND_SOC_DA7210 if I2C
        select SND_SOC_JZ4740 if SOC_JZ4740
+       select SND_SOC_MAX98088 if I2C
        select SND_SOC_MAX9877 if I2C
        select SND_SOC_PCM3008
        select SND_SOC_SPDIF
@@ -157,6 +158,9 @@ config SND_SOC_L3
 config SND_SOC_DA7210
         tristate

+config SND_SOC_MAX98088
+       tristate
+
 config SND_SOC_PCM3008
        tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 23e7e2c..7184611 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -15,6 +15,7 @@ snd-soc-cs4270-objs := cs4270.o
 snd-soc-cx20442-objs := cx20442.o
 snd-soc-da7210-objs := da7210.o
 snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
 snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-spdif-objs := spdif_transciever.o
 snd-soc-ssm2602-objs := ssm2602.o
@@ -88,6 +89,7 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
 obj-$(CONFIG_SND_SOC_DA7210)   += snd-soc-da7210.o
 obj-$(CONFIG_SND_SOC_L3)       += snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_JZ4740_CODEC)     += snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088)  += snd-soc-max98088.o
 obj-$(CONFIG_SND_SOC_PCM3008)  += snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_SPDIF)    += snd-soc-spdif.o
 obj-$(CONFIG_SND_SOC_SSM2602)  += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
new file mode 100644
index 0000000..00319d6
--- /dev/null
+++ b/sound/soc/codecs/max98088.c
@@ -0,0 +1,2477 @@
+/*
+ * max98088.c -- MAX98088 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 <linux/slab.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+static struct snd_soc_codec *max98088_codec;
+struct snd_soc_codec_device soc_codec_dev_max98088;
+
+/* Configurations associated with each channel of DAI stream */
+struct max98088_cdata {
+       unsigned int rate;
+       unsigned int fmt;
+       int eq_textcnt;
+       const char *eq_texts[EQ_CFG_MAX];
+       int eq_sel;
+       struct soc_enum eq_enum;
+};
+
+/* Codec private data */
+struct max98088_priv {
+       struct snd_soc_codec codec;
+       struct max98088_pdata *pdata;
+       u8 reg_cache[M98088_REG_CNT];
+       unsigned int sysclk;
+       struct max98088_cdata dai[2];
+       u8 power_state;
+       unsigned int ex_mode;
+       unsigned int digmic;
+       unsigned int mic1pre;
+       unsigned int mic2pre;
+       unsigned int extmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+       0x00, /* 00 IRQ status */
+       0x00, /* 01 MIC status */
+       0x00, /* 02 jack status */
+       0x00, /* 03 battery voltage */
+       0x00, /* 04 */
+       0x00, /* 05 */
+       0x00, /* 06 */
+       0x00, /* 07 */
+       0x00, /* 08 */
+       0x00, /* 09 */
+       0x00, /* 0A */
+       0x00, /* 0B */
+       0x00, /* 0C */
+       0x00, /* 0D */
+       0x00, /* 0E */
+       0x00, /* 0F interrupt enable */
+
+       0x00, /* 10 master clock */
+       0x00, /* 11 DAI1 clock mode */
+       0x00, /* 12 DAI1 clock control */
+       0x00, /* 13 DAI1 clock control */
+       0x00, /* 14 DAI1 format */
+       0x00, /* 15 DAI1 clock */
+       0x00, /* 16 DAI1 config */
+       0x00, /* 17 DAI1 TDM */
+       0x00, /* 18 DAI1 filters */
+       0x00, /* 19 DAI2 clock mode */
+       0x00, /* 1A DAI2 clock control */
+       0x00, /* 1B DAI2 clock control */
+       0x00, /* 1C DAI2 format */
+       0x00, /* 1D DAI2 clock */
+       0x00, /* 1E DAI2 config */
+       0x00, /* 1F DAI2 TDM */
+
+       0x00, /* 20 DAI2 filters */
+       0x00, /* 21 data config */
+       0x00, /* 22 DAC mixer */
+       0x00, /* 23 left ADC mixer */
+       0x00, /* 24 right ADC mixer */
+       0x00, /* 25 left HP mixer */
+       0x00, /* 26 right HP mixer */
+       0x00, /* 27 HP control */
+       0x00, /* 28 left REC mixer */
+       0x00, /* 29 right REC mixer */
+       0x00, /* 2A REC control */
+       0x00, /* 2B left SPK mixer */
+       0x00, /* 2C right SPK mixer */
+       0x00, /* 2D SPK control */
+       0x00, /* 2E sidetone */
+       0x00, /* 2F DAI1 playback level */
+
+       0x00, /* 30 DAI1 playback level */
+       0x00, /* 31 DAI2 playback level */
+       0x00, /* 32 DAI2 playbakc level */
+       0x00, /* 33 left ADC level */
+       0x00, /* 34 right ADC level */
+       0x00, /* 35 MIC1 level */
+       0x00, /* 36 MIC2 level */
+       0x00, /* 37 INA level */
+       0x00, /* 38 INB level */
+       0x00, /* 39 left HP volume */
+       0x00, /* 3A right HP volume */
+       0x00, /* 3B left REC volume */
+       0x00, /* 3C right REC volume */
+       0x00, /* 3D left SPK volume */
+       0x00, /* 3E right SPK volume */
+       0x00, /* 3F MIC config */
+
+       0x00, /* 40 MIC threshold */
+       0x00, /* 41 excursion limiter filter */
+       0x00, /* 42 excursion limiter threshold */
+       0x00, /* 43 ALC */
+       0x00, /* 44 power limiter threshold */
+       0x00, /* 45 power limiter config */
+       0x00, /* 46 distortion limiter config */
+       0x00, /* 47 audio input */
+       0x00, /* 48 microphone */
+       0x00, /* 49 level control */
+       0x00, /* 4A bypass switches */
+       0x00, /* 4B jack detect */
+       0x00, /* 4C input enable */
+       0x00, /* 4D output enable */
+       0xF0, /* 4E bias control */
+       0x00, /* 4F DAC power */
+
+       0x0F, /* 50 DAC power */
+       0x00, /* 51 system */
+       0x00, /* 52 DAI1 EQ1 */
+       0x00, /* 53 DAI1 EQ1 */
+       0x00, /* 54 DAI1 EQ1 */
+       0x00, /* 55 DAI1 EQ1 */
+       0x00, /* 56 DAI1 EQ1 */
+       0x00, /* 57 DAI1 EQ1 */
+       0x00, /* 58 DAI1 EQ1 */
+       0x00, /* 59 DAI1 EQ1 */
+       0x00, /* 5A DAI1 EQ1 */
+       0x00, /* 5B DAI1 EQ1 */
+       0x00, /* 5C DAI1 EQ2 */
+       0x00, /* 5D DAI1 EQ2 */
+       0x00, /* 5E DAI1 EQ2 */
+       0x00, /* 5F DAI1 EQ2 */
+
+       0x00, /* 60 DAI1 EQ2 */
+       0x00, /* 61 DAI1 EQ2 */
+       0x00, /* 62 DAI1 EQ2 */
+       0x00, /* 63 DAI1 EQ2 */
+       0x00, /* 64 DAI1 EQ2 */
+       0x00, /* 65 DAI1 EQ2 */
+       0x00, /* 66 DAI1 EQ3 */
+       0x00, /* 67 DAI1 EQ3 */
+       0x00, /* 68 DAI1 EQ3 */
+       0x00, /* 69 DAI1 EQ3 */
+       0x00, /* 6A DAI1 EQ3 */
+       0x00, /* 6B DAI1 EQ3 */
+       0x00, /* 6C DAI1 EQ3 */
+       0x00, /* 6D DAI1 EQ3 */
+       0x00, /* 6E DAI1 EQ3 */
+       0x00, /* 6F DAI1 EQ3 */
+
+       0x00, /* 70 DAI1 EQ4 */
+       0x00, /* 71 DAI1 EQ4 */
+       0x00, /* 72 DAI1 EQ4 */
+       0x00, /* 73 DAI1 EQ4 */
+       0x00, /* 74 DAI1 EQ4 */
+       0x00, /* 75 DAI1 EQ4 */
+       0x00, /* 76 DAI1 EQ4 */
+       0x00, /* 77 DAI1 EQ4 */
+       0x00, /* 78 DAI1 EQ4 */
+       0x00, /* 79 DAI1 EQ4 */
+       0x00, /* 7A DAI1 EQ5 */
+       0x00, /* 7B DAI1 EQ5 */
+       0x00, /* 7C DAI1 EQ5 */
+       0x00, /* 7D DAI1 EQ5 */
+       0x00, /* 7E DAI1 EQ5 */
+       0x00, /* 7F DAI1 EQ5 */
+
+       0x00, /* 80 DAI1 EQ5 */
+       0x00, /* 81 DAI1 EQ5 */
+       0x00, /* 82 DAI1 EQ5 */
+       0x00, /* 83 DAI1 EQ5 */
+       0x00, /* 84 DAI2 EQ1 */
+       0x00, /* 85 DAI2 EQ1 */
+       0x00, /* 86 DAI2 EQ1 */
+       0x00, /* 87 DAI2 EQ1 */
+       0x00, /* 88 DAI2 EQ1 */
+       0x00, /* 89 DAI2 EQ1 */
+       0x00, /* 8A DAI2 EQ1 */
+       0x00, /* 8B DAI2 EQ1 */
+       0x00, /* 8C DAI2 EQ1 */
+       0x00, /* 8D DAI2 EQ1 */
+       0x00, /* 8E DAI2 EQ2 */
+       0x00, /* 8F DAI2 EQ2 */
+
+       0x00, /* 90 DAI2 EQ2 */
+       0x00, /* 91 DAI2 EQ2 */
+       0x00, /* 92 DAI2 EQ2 */
+       0x00, /* 93 DAI2 EQ2 */
+       0x00, /* 94 DAI2 EQ2 */
+       0x00, /* 95 DAI2 EQ2 */
+       0x00, /* 96 DAI2 EQ2 */
+       0x00, /* 97 DAI2 EQ2 */
+       0x00, /* 98 DAI2 EQ3 */
+       0x00, /* 99 DAI2 EQ3 */
+       0x00, /* 9A DAI2 EQ3 */
+       0x00, /* 9B DAI2 EQ3 */
+       0x00, /* 9C DAI2 EQ3 */
+       0x00, /* 9D DAI2 EQ3 */
+       0x00, /* 9E DAI2 EQ3 */
+       0x00, /* 9F DAI2 EQ3 */
+
+       0x00, /* A0 DAI2 EQ3 */
+       0x00, /* A1 DAI2 EQ3 */
+       0x00, /* A2 DAI2 EQ4 */
+       0x00, /* A3 DAI2 EQ4 */
+       0x00, /* A4 DAI2 EQ4 */
+       0x00, /* A5 DAI2 EQ4 */
+       0x00, /* A6 DAI2 EQ4 */
+       0x00, /* A7 DAI2 EQ4 */
+       0x00, /* A8 DAI2 EQ4 */
+       0x00, /* A9 DAI2 EQ4 */
+       0x00, /* AA DAI2 EQ4 */
+       0x00, /* AB DAI2 EQ4 */
+       0x00, /* AC DAI2 EQ5 */
+       0x00, /* AD DAI2 EQ5 */
+       0x00, /* AE DAI2 EQ5 */
+       0x00, /* AF DAI2 EQ5 */
+
+       0x00, /* B0 DAI2 EQ5 */
+       0x00, /* B1 DAI2 EQ5 */
+       0x00, /* B2 DAI2 EQ5 */
+       0x00, /* B3 DAI2 EQ5 */
+       0x00, /* B4 DAI2 EQ5 */
+       0x00, /* B5 DAI2 EQ5 */
+       0x00, /* B6 DAI1 biquad */
+       0x00, /* B7 DAI1 biquad */
+       0x00, /* B8 DAI1 biquad */
+       0x00, /* B9 DAI1 biquad */
+       0x00, /* BA DAI1 biquad */
+       0x00, /* BB DAI1 biquad */
+       0x00, /* BC DAI1 biquad */
+       0x00, /* BD DAI1 biquad */
+       0x00, /* BE DAI1 biquad */
+       0x00, /* BF DAI1 biquad */
+
+       0x00, /* C0 DAI2 biquad */
+       0x00, /* C1 DAI2 biquad */
+       0x00, /* C2 DAI2 biquad */
+       0x00, /* C3 DAI2 biquad */
+       0x00, /* C4 DAI2 biquad */
+       0x00, /* C5 DAI2 biquad */
+       0x00, /* C6 DAI2 biquad */
+       0x00, /* C7 DAI2 biquad */
+       0x00, /* C8 DAI2 biquad */
+       0x00, /* C9 DAI2 biquad */
+       0x00, /* CA */
+       0x00, /* CB */
+       0x00, /* CC */
+       0x00, /* CD */
+       0x00, /* CE */
+       0x00, /* CF */
+
+       0x00, /* D0 */
+       0x00, /* D1 */
+       0x00, /* D2 */
+       0x00, /* D3 */
+       0x00, /* D4 */
+       0x00, /* D5 */
+       0x00, /* D6 */
+       0x00, /* D7 */
+       0x00, /* D8 */
+       0x00, /* D9 */
+       0x00, /* DA */
+       0x70, /* DB */
+       0x00, /* DC */
+       0x00, /* DD */
+       0x00, /* DE */
+       0x00, /* DF */
+
+       0x00, /* E0 */
+       0x00, /* E1 */
+       0x00, /* E2 */
+       0x00, /* E3 */
+       0x00, /* E4 */
+       0x00, /* E5 */
+       0x00, /* E6 */
+       0x00, /* E7 */
+       0x00, /* E8 */
+       0x00, /* E9 */
+       0x00, /* EA */
+       0x00, /* EB */
+       0x00, /* EC */
+       0x00, /* ED */
+       0x00, /* EE */
+       0x00, /* EF */
+
+       0x00, /* F0 */
+       0x00, /* F1 */
+       0x00, /* F2 */
+       0x00, /* F3 */
+       0x00, /* F4 */
+       0x00, /* F5 */
+       0x00, /* F6 */
+       0x00, /* F7 */
+       0x00, /* F8 */
+       0x00, /* F9 */
+       0x00, /* FA */
+       0x00, /* FB */
+       0x00, /* FC */
+       0x00, /* FD */
+       0x00, /* FE */
+       0x00, /* FF */
+};
+
+static struct {
+       int readable;
+       int writable;
+       int vol;
+} max98088_access[M98088_REG_CNT] = {
+       { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+       { 0xFF, 0x00, 1 }, /* 01 MIC status */
+       { 0xFF, 0x00, 1 }, /* 02 jack status */
+       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+       { 0xFF, 0xFF, 0 }, /* 04 */
+       { 0xFF, 0xFF, 0 }, /* 05 */
+       { 0xFF, 0xFF, 0 }, /* 06 */
+       { 0xFF, 0xFF, 0 }, /* 07 */
+       { 0xFF, 0xFF, 0 }, /* 08 */
+       { 0xFF, 0xFF, 0 }, /* 09 */
+       { 0xFF, 0xFF, 0 }, /* 0A */
+       { 0xFF, 0xFF, 0 }, /* 0B */
+       { 0xFF, 0xFF, 0 }, /* 0C */
+       { 0xFF, 0xFF, 0 }, /* 0D */
+       { 0xFF, 0xFF, 0 }, /* 0E */
+       { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+       { 0xFF, 0xFF, 0 }, /* 10 master clock */
+       { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+       { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+       { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+       { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+       { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+       { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+       { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+       { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+       { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+       { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+       { 0xFF, 0xFF, 0 }, /* 21 data config */
+       { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+       { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 27 HP control */
+       { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 2A REC control */
+       { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+       { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+       { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+       { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+       { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+       { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+       { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+       { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+       { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+       { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+       { 0xFF, 0xFF, 0 }, /* 37 INA level */
+       { 0xFF, 0xFF, 0 }, /* 38 INB level */
+       { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+       { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+       { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+       { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 43 ALC */
+       { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+       { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+       { 0xFF, 0xFF, 0 }, /* 47 audio input */
+       { 0xFF, 0xFF, 0 }, /* 48 microphone */
+       { 0xFF, 0xFF, 0 }, /* 49 level control */
+       { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+       { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+       { 0xFF, 0xFF, 0 }, /* 4C input enable */
+       { 0xFF, 0xFF, 0 }, /* 4D output enable */
+       { 0xFF, 0xFF, 0 }, /* 4E bias control */
+       { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+       { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+       { 0xFF, 0xFF, 0 }, /* 51 system */
+       { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+       { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+       { 0x00, 0x00, 0 }, /* CA */
+       { 0x00, 0x00, 0 }, /* CB */
+       { 0x00, 0x00, 0 }, /* CC */
+       { 0x00, 0x00, 0 }, /* CD */
+       { 0x00, 0x00, 0 }, /* CE */
+       { 0x00, 0x00, 0 }, /* CF */
+
+       { 0x00, 0x00, 0 }, /* D0 */
+       { 0x00, 0x00, 0 }, /* D1 */
+       { 0x00, 0x00, 0 }, /* D2 */
+       { 0x00, 0x00, 0 }, /* D3 */
+       { 0x00, 0x00, 0 }, /* D4 */
+       { 0x00, 0x00, 0 }, /* D5 */
+       { 0x00, 0x00, 0 }, /* D6 */
+       { 0x00, 0x00, 0 }, /* D7 */
+       { 0x00, 0x00, 0 }, /* D8 */
+       { 0x00, 0x00, 0 }, /* D9 */
+       { 0x00, 0x00, 0 }, /* DA */
+       { 0x00, 0x00, 0 }, /* DB */
+       { 0x00, 0x00, 0 }, /* DC */
+       { 0x00, 0x00, 0 }, /* DD */
+       { 0x00, 0x00, 0 }, /* DE */
+       { 0x00, 0x00, 0 }, /* DF */
+
+       { 0x00, 0x00, 0 }, /* E0 */
+       { 0x00, 0x00, 0 }, /* E1 */
+       { 0x00, 0x00, 0 }, /* E2 */
+       { 0x00, 0x00, 0 }, /* E3 */
+       { 0x00, 0x00, 0 }, /* E4 */
+       { 0x00, 0x00, 0 }, /* E5 */
+       { 0x00, 0x00, 0 }, /* E6 */
+       { 0x00, 0x00, 0 }, /* E7 */
+       { 0x00, 0x00, 0 }, /* E8 */
+       { 0x00, 0x00, 0 }, /* E9 */
+       { 0x00, 0x00, 0 }, /* EA */
+       { 0x00, 0x00, 0 }, /* EB */
+       { 0x00, 0x00, 0 }, /* EC */
+       { 0x00, 0x00, 0 }, /* ED */
+       { 0x00, 0x00, 0 }, /* EE */
+       { 0x00, 0x00, 0 }, /* EF */
+
+       { 0x00, 0x00, 0 }, /* F0 */
+       { 0x00, 0x00, 0 }, /* F1 */
+       { 0x00, 0x00, 0 }, /* F2 */
+       { 0x00, 0x00, 0 }, /* F3 */
+       { 0x00, 0x00, 0 }, /* F4 */
+       { 0x00, 0x00, 0 }, /* F5 */
+       { 0x00, 0x00, 0 }, /* F6 */
+       { 0x00, 0x00, 0 }, /* F7 */
+       { 0x00, 0x00, 0 }, /* F8 */
+       { 0x00, 0x00, 0 }, /* F9 */
+       { 0x00, 0x00, 0 }, /* FA */
+       { 0x00, 0x00, 0 }, /* FB */
+       { 0x00, 0x00, 0 }, /* FC */
+       { 0x00, 0x00, 0 }, /* FD */
+       { 0x00, 0x00, 0 }, /* FE */
+       { 0xFF, 0x00, 1 }, /* FF */
+};
+
+static int max98088_volatile_register(unsigned int reg)
+{
+       return max98088_access[reg].vol;
+}
+
+static int max98088_hw_write(struct snd_soc_codec *codec, unsigned int reg,
+                            unsigned int value)
+{
+       u8 data[2];
+
+       data[0] = reg;
+       data[1] = value;
+       if (codec->hw_write(codec->control_data, data, 2) == 2)
+               return 0;
+       else
+               return -EIO;
+}
+
+/*
+ * For kernels compiled without unsigned long long int division
+ */
+unsigned long long int ulldiv(unsigned long long int dividend,
+                             unsigned long long int divisor)
+{
+       unsigned long long int quotient = 0;
+       int shift = 1;
+
+       BUG_ON(divisor == 0);
+
+       /* Result is 1.0 if divisor and dividend are equal */
+       if (divisor == dividend)
+               return 1;
+
+       /* Normalize divisor */
+       while (!(divisor & 0x8000000000000000ULL)) {
+               divisor <<= 1;
+               ++shift;
+       }
+
+       /* Shift and subtract */
+       while (shift--) {
+               quotient <<= 1;
+
+               if (divisor <= dividend) {
+                       dividend -= divisor;
+                       ++quotient;
+               }
+               divisor >>= 1;
+       }
+
+       /* Round up */
+       if (dividend > divisor)
+               ++quotient;
+
+       return quotient;
+}
+
+#define INA1_PGA_BIT 0x01
+#define INA2_PGA_BIT 0x02
+#define INB1_PGA_BIT 0x04
+#define INB2_PGA_BIT 0x08
+/*
+ * The INx1 and INx2 PGAs 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.
+ */
+static int max98088_pga_event(struct snd_soc_dapm_widget *w,
+                             struct snd_kcontrol *k, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       u8 *state = &max98088->power_state;
+       unsigned int val;
+       unsigned int pga;
+       unsigned int mask;
+
+       BUG_ON(w->reg != M98088_REG_4C_PWR_EN_IN);
+
+       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;
+       }
+
+       if (event == SND_SOC_DAPM_POST_PMU) {
+               /* ON */
+               *state |= pga;
+
+               /* Turn on, avoiding unnecessary writes */
+               val = snd_soc_read(codec, w->reg);
+               if (!(val & (1 << w->shift))) {
+                       val |= (1 << w->shift);
+                       snd_soc_write(codec, w->reg, val);
+               }
+       } else if (event == SND_SOC_DAPM_POST_PMD) {
+               /* OFF */
+               *state &= ~pga;
+
+               /* Turn off if both are off, avoiding unnecessary writes */
+               if (!(*state & mask)) {
+                       val = snd_soc_read(codec, w->reg);
+                       if (val & (1 << w->shift)) {
+                               val &= ~(1 << w->shift);
+                               snd_soc_write(codec, w->reg, val);
+                       }
+               }
+       } else {
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+                   unsigned int band, u16 *coefs)
+{
+       unsigned int eq_reg;
+       unsigned int i;
+
+       BUG_ON(band > 4);
+       BUG_ON(dai > 1);
+
+       /* Load the base register address */
+       eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+       /* Add the band address offset, note adjustment for word address */
+       eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+       /* Step through the registers and coefs */
+       for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+               snd_soc_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+               snd_soc_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+       }
+
+       return;
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_ex_mode[] = {
+       "Off",
+       "100Hz",
+       "400Hz",
+       "600Hz",
+       "800Hz",
+       "1000Hz",
+       "200-400Hz",
+       "400-600Hz",
+       "400-800Hz",
+};
+
+static const unsigned int ex_mode_table[] = {
+       0x00,           /* disabled */
+       (0<<4)|3,       /* 100Hz */
+       (1<<4)|0,       /* 400Hz */
+       (2<<4)|0,       /* 600Hz */
+       (3<<4)|0,       /* 800Hz */
+       (4<<4)|0,       /* 1000Hz */
+       (1<<4)|1,       /* 200-400Hz */
+       (2<<4)|2,       /* 400-600Hz */
+       (3<<4)|2,       /* 400-800Hz */
+};
+
+static const struct soc_enum max98088_ex_mode_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_ex_mode), max98088_ex_mode),
+};
+
+static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->ex_mode;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(ex_mode_table))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       snd_soc_write(codec, M98088_REG_41_SPKDHP,
+               ex_mode_table[*mode]);
+
+       return 0;
+}
+
+static int max98088_ex_mode_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->ex_mode;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+       "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+               max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_dai1_fltr[] = {
+       "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+       "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static const char *max98088_micpre[] = {
+       "0dB",
+       "20dB",
+       "30dB",
+};
+
+static const struct soc_enum max98088_micpre_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_micpre), max98088_micpre),
+};
+
+static const char *max98088_extmic[] = {
+       "Off",
+       "MIC1",
+       "MIC2",
+};
+
+static const struct soc_enum max98088_extmic_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
+};
+
+static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->mic1pre;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_micpre))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       return 0;
+}
+
+static int max98088_mic1pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->mic1pre;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static int max98088_mic2pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->mic2pre;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_micpre))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       return 0;
+}
+
+static int max98088_mic2pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->mic2pre;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static int max98088_extmic_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->extmic_mode;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_extmic))
+               return -EINVAL;
+
+       *mode = sel;
+       snd_soc_update_bits(codec, M98088_REG_48_CFG_MIC,
+               M98088_EXTMIC_MASK, sel);
+
+       return 0;
+}
+
+static int max98088_extmic_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->extmic_mode;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+       /* Analog outputs */
+
+       SOC_DOUBLE_R("Headphone Volume", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+       SOC_DOUBLE_R("Speaker Volume", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+       SOC_DOUBLE_R("Receiver Volume", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+       SOC_DOUBLE_R("Headphone Switch", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 7, 1, 1),
+       SOC_DOUBLE_R("Speaker Switch", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 7, 1, 1),
+       SOC_DOUBLE_R("Receiver Switch", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 7, 1, 1),
+
+       /* Analog inputs */
+
+       SOC_SINGLE("MIC1 Volume", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+       SOC_SINGLE("MIC2 Volume", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+       SOC_ENUM_EXT("MIC1 Boost Volume", max98088_micpre_enum,
+               max98088_mic1pre_get, max98088_mic1pre_set),
+
+       SOC_ENUM_EXT("MIC2 Boost Volume", max98088_micpre_enum,
+               max98088_mic2pre_get, max98088_mic2pre_set),
+
+       SOC_ENUM_EXT("Ext MIC Switch", max98088_extmic_enum,
+               max98088_extmic_get, max98088_extmic_set),
+
+       SOC_SINGLE("INA Volume", M98088_REG_37_LVL_INA, 0, 7, 1),
+       SOC_SINGLE("INB Volume", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+       /* ADC input digital gains and volume controls */
+
+       SOC_SINGLE("ADCL Volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+       SOC_SINGLE("ADCR Volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+       SOC_SINGLE("ADCL Boost Volume", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+       SOC_SINGLE("ADCR Boost Volume", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+       /* Equalizer */
+
+       SOC_SINGLE("EQ1 Switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+       SOC_SINGLE("EQ2 Switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+       /* Excursion limiter */
+
+       SOC_ENUM_EXT("EX Limiter Mode", max98088_ex_mode_enum,
+               max98088_ex_mode_get, max98088_ex_mode_set),
+       SOC_ENUM("EX Limiter Threshold", max98088_ex_thresh_enum),
+
+       /* Voice/music filters */
+
+       SOC_ENUM("DAI1 Filter Mode", max98088_filter_mode_enum),
+       SOC_ENUM("DAI1 DAC Filter", max98088_dai1_dac_filter_enum),
+       SOC_ENUM("DAI1 ADC Filter", max98088_dai1_adc_filter_enum),
+       SOC_SINGLE("DAI2 DC Block Switch", M98088_REG_20_DAI2_FILTERS,
+               0, 1, 0),
+
+       /* Automatic level control (for both DAI1/DAI2) */
+
+       SOC_SINGLE("ALC Switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+       SOC_SINGLE("ALC Threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+       SOC_SINGLE("ALC Multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+       SOC_SINGLE("ALC Release Time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+       /* Power limiter */
+
+       SOC_SINGLE("PWR Limiter Threshold", M98088_REG_44_PWRLMT_CFG,
+               4, 15, 0),
+       SOC_SINGLE("PWR Limiter Weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+       SOC_SINGLE("PWR Limiter Time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+       SOC_SINGLE("PWR Limiter Time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+       /* THD distortion limiter */
+
+       SOC_SINGLE("THD Limiter Threshold", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+       SOC_SINGLE("THD Limiter Time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_hp_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       u16 status;
+
+       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
+
+       /* powering down headphone gracefully */
+       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
+       if ((status & M98088_HPEN) == M98088_HPEN) {
+               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
+                       (status & ~M98088_HPEN));
+       }
+       schedule_timeout(msecs_to_jiffies(20));
+
+       return 0;
+}
+
+static int max98088_mic_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               if (w->reg == M98088_REG_35_LVL_MIC1) {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic1pre)<<M98088_MICPRE_SHIFT);
+               } else {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic2pre)<<M98088_MICPRE_SHIFT);
+               }
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK, 0);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* DAPM widgets top level */
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+       SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+       SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+       SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+       SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+       SND_SOC_DAPM_PGA_E("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+               7, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+       SND_SOC_DAPM_PGA_E("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+               6, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+
+       SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+               5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+               4, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+               3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+               2, 0, NULL, 0),
+
+       SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_left_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_right_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_PGA_E("MIC1 Input", M98088_REG_35_LVL_MIC1,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("MIC2 Input", M98088_REG_36_LVL_MIC2,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_MICBIAS("MICBIAS", M98088_REG_4C_PWR_EN_IN, 3, 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("RECL"),
+       SND_SOC_DAPM_OUTPUT("RECR"),
+
+       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"),
+};
+
+/* DAPM AUDIO_MAP: */
+static const struct snd_soc_dapm_route audio_map[] = {
+       /* Left headphone output mixer */
+       {"Left HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left HP Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right headphone output mixer */
+       {"Right HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right HP Mixer", "Left DAC2 Switch", "DACL2"  },
+       {"Right HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Left speaker output mixer */
+       {"Left SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right speaker output mixer */
+       {"Right SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Left REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Right REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       {"HP Left Out", NULL, "Left HP Mixer"},
+       {"HP Right Out", NULL, "Right HP Mixer"},
+       {"SPK Left Out", NULL, "Left SPK Mixer"},
+       {"SPK Right Out", NULL, "Right SPK Mixer"},
+       {"REC Left Out", NULL, "Left REC Mixer"},
+       {"REC Right Out", NULL, "Right REC Mixer"},
+
+       {"HPL", NULL, "HP Left Out"},
+       {"HPR", NULL, "HP Right Out"},
+       {"SPKL", NULL, "SPK Left Out"},
+       {"SPKR", NULL, "SPK Right Out"},
+       {"RECL", NULL, "REC Left Out"},
+       {"RECR", NULL, "REC Right Out"},
+
+       /* Left ADC input mixer */
+       {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right ADC input mixer */
+       {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right ADC Mixer", "INB2 Switch", "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"},
+       {"MIC1 Input", NULL, "MIC1"},
+       {"MIC2 Input", NULL, "MIC2"},
+};
+
+static int max98088_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, max98088_dapm_widgets,
+                                 ARRAY_SIZE(max98088_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;
+}
+
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       u8 reg15val;
+       u8 reg14msk = 0;
+       u8 reg14val = 0;
+
+       cdata = &max98088->dai[0];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* mask MAS to select slave mode */
+                       reg14msk |= M98088_DAI_MAS;
+                       /* slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* set to master mode */
+                       reg14val |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg14val |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       reg14msk |= M98088_DAI_DLY;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       reg14msk |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg14msk |= M98088_DAI_BCI;
+                       reg14val |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg14msk |= M98088_DAI_WCI;
+                       reg14val |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg14val |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       reg14msk, reg14val);
+
+               reg15val = M98088_DAI_BSEL64;
+               if (max98088->digmic)
+                       reg15val |= M98088_DAI_OSR64;
+               snd_soc_write(codec, M98088_REG_15_DAI1_CLOCK, reg15val);
+       }
+
+       return 0;
+}
+
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       u8 reg1Cmsk = 0;
+       u8 reg1Cval = 0;
+
+       cdata = &max98088->dai[1];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* mask MAS to select slave mode */
+                       reg1Cmsk |= M98088_DAI_MAS;
+                       /* slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* set to master mode */
+                       reg1Cval |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg1Cval |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       reg1Cmsk |= M98088_DAI_DLY;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       reg1Cmsk |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg1Cmsk |= M98088_DAI_BCI;
+                       reg1Cval |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg1Cmsk |= M98088_DAI_WCI;
+                       reg1Cval |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg1Cval |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       reg1Cmsk, reg1Cval);
+
+               snd_soc_write(codec, M98088_REG_1D_DAI2_CLOCK,
+                       M98088_DAI_BSEL64);
+       }
+
+       return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+                                  int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+
+       /* requested clock frequency is already setup */
+       if (freq == max98088->sysclk)
+               return 0;
+
+       max98088->sysclk = freq; /* remember current sysclk */
+
+       /* setup clocks for slave mode, and using the PLL
+        * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+        *         0x02 (when master clk is 20MHz to 30MHz)..
+        */
+       if ((freq >= 10000000) && (freq < 20000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+       } else if ((freq >= 20000000) && (freq < 30000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+       } else {
+               dev_err(codec->dev, "Invalid master clock frequency\n");
+               return -EINVAL;
+       }
+
+       if (snd_soc_read(codec, M98088_REG_51_PWR_SYS)  & M98088_SHDNRUN) {
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN, 0);
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       0, M98088_SHDNRUN);
+       }
+
+       dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+       max98088->sysclk = freq;
+       return 0;
+}
+
+/* codec mclk clock divider coefficients */
+static const struct {
+       u32 rate;
+       u8  sr;
+} rate_table[] = {
+       {8000,  0x10},
+       {11025, 0x20},
+       {16000, 0x30},
+       {22050, 0x40},
+       {24000, 0x50},
+       {32000, 0x60},
+       {44100, 0x70},
+       {48000, 0x80},
+       {88200, 0x90},
+       {96000, 0xA0},
+};
+
+static inline int rate_value(int rate, u8 *value)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+               if (rate_table[i].rate >= rate) {
+                       *value = rate_table[i].sr;
+                       return 0;
+               }
+       }
+       *value = rate_table[0].sr;
+       return -EINVAL;
+}
+
+/* Setup hw params and sample rate */
+static int max98088_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 max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 regval;
+       u16 ni;
+
+       cdata = &max98088->dai[0];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
+               if (rate_value(rate, &regval))
+                       return -EINVAL;
+
+               snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_14_DAI1_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0)
+                       return -EINVAL;
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+/* Setup hw params and sample rate */
+static int max98088_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 max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 regval;
+       u16 ni;
+
+       cdata = &max98088->dai[1];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI2 SR2 value for the DSP */
+               if (rate_value(rate, &regval))
+                       return -EINVAL;
+
+               snd_soc_write(codec, M98088_REG_19_DAI2_CLKMODE, regval);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_1C_DAI2_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0)
+                       return -EINVAL;
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       int i;
+
+       if (!codec->cache_sync)
+               return;
+
+       codec->cache_only = 0;
+
+       /* write back cached values if they're writeable and
+        * different from the hardware default.
+        */
+       for (i = 1; i < ARRAY_SIZE(max98088->reg_cache); i++) {
+               if (!max98088_access[i].writable)
+                       continue;
+
+               if (max98088->reg_cache[i] == max98088_reg[i])
+                       continue;
+
+               snd_soc_write(codec, i, max98088->reg_cache[i]);
+       }
+
+       codec->cache_sync = 0;
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+                                 enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               max98088_sync_cache(codec);
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, M98088_MBEN);
+               break;
+
+       case SND_SOC_BIAS_OFF:
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, 0);
+#ifdef CONFIG_REGULATOR
+               codec->cache_sync = 1;
+#endif
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai1_set_fmt,
+       .hw_params = max98088_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai2_set_fmt,
+       .hw_params = max98088_dai2_hw_params,
+};
+
+struct snd_soc_dai max98088_dai[] = {
+{
+       .name = "HiFi",
+       .playback = {
+               .stream_name = "HiFi Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .capture = {
+               .stream_name = "HiFi Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+        .ops = &max98088_dai1_ops,
+},
+{
+       .name = "Aux",
+       .playback = {
+               .stream_name = "Aux Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .ops = &max98088_dai2_ops,
+}
+};
+EXPORT_SYMBOL_GPL(max98088_dai);
+
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+       sel = cdata->eq_sel;
+
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               if (strcmp(pdata->eq1_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq1_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq1_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq1_cfg[best].name,
+               pdata->eq1_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+       coef_set = &pdata->eq1_cfg[sel];
+
+       m98088_eq_band(codec, 0, 0, coef_set->band1);
+       m98088_eq_band(codec, 0, 1, coef_set->band2);
+       m98088_eq_band(codec, 0, 2, coef_set->band3);
+       m98088_eq_band(codec, 0, 3, coef_set->band4);
+       m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->eq_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               if (strcmp(pdata->eq2_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq2_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq2_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq2_cfg[best].name,
+               pdata->eq2_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+       coef_set = &pdata->eq2_cfg[sel];
+
+       m98088_eq_band(codec, 1, 0, coef_set->band1);
+       m98088_eq_band(codec, 1, 1, coef_set->band2);
+       m98088_eq_band(codec, 1, 2, coef_set->band3);
+       m98088_eq_band(codec, 1, 3, coef_set->band4);
+       m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+               save);
+}
+
+
+static int max98088_put_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[0];
+
+       if (sel >= pdata->eq1_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq1(codec);
+       return 0;
+}
+
+static int max98088_put_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[1];
+
+       if (sel >= pdata->eq2_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq2(codec);
+       return 0;
+}
+
+static int max98088_get_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static int max98088_get_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static void max98088_handle_eq1_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq1control =
+               SOC_ENUM_EXT("EQ1 Mode",
+                       max98088->dai[0].eq_enum,
+                       max98088_get_eq1_enum,
+                       max98088_put_eq1_enum);
+
+       cdata = &max98088->dai[0];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq1_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               /* ...and remember the new version. */
+               cdata->eq_texts[i] = pdata->eq1_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ1 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       BUG_ON(&max98088->codec == 0);
+       BUG_ON((max98088->codec).card == 0);
+
+       ret = snd_soc_add_controls(&max98088->codec, &eq1control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_eq2_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq2control =
+               SOC_ENUM_EXT("EQ2 Mode",
+                       max98088->dai[1].eq_enum,
+                       max98088_get_eq2_enum,
+                       max98088_put_eq2_enum);
+
+       cdata = &max98088->dai[1];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq2_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               cdata->eq_texts[i] = pdata->eq2_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ2 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       ret = snd_soc_add_controls(&max98088->codec, &eq2control, 1);
+       if (ret != 0)
+               printk(KERN_ERR "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       u8 regval = 0;
+
+       if (!pdata)
+               return;
+
+       /* configure mic for analog/digital mic mode */
+       if (pdata->digmic_left_mode)
+               regval |= M98088_DIGMIC_L;
+
+       if (pdata->digmic_right_mode)
+               regval |= M98088_DIGMIC_R;
+
+       max98088->digmic = (regval ? 1 : 0);
+
+       snd_soc_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+       /* configure receiver output */
+       regval = ((pdata->receiver_mode) ? M98088_REC_LINEMODE : 0);
+       snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+               M98088_REC_LINEMODE_MASK, regval);
+
+       /* configure equalizers */
+       if (pdata->eq1_cfgcnt)
+               max98088_handle_eq1_pdata(max98088);
+
+       if (pdata->eq2_cfgcnt)
+               max98088_handle_eq2_pdata(max98088);
+}
+
+static int max98088_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;
+
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static int max98088_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+       int i;
+       u8 *cache = codec->reg_cache;
+
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       /* Sync reg_cache with the hardware */
+       for (i = 0; i < M98088_REG_CNT; i++) {
+               if (i == M98088_REG_51_PWR_SYS)
+                       continue;
+
+               if (!max98088_access[i].writable)
+                       continue;
+
+               max98088_hw_write(codec, i, cache[i]);
+       }
+
+       /* now enter into the resume mode bias level */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+}
+
+/*
+ * Make sure that a max98088 is attached to the I2C bus.
+ */
+static int max98088_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+       struct max98088_priv *max98088;
+       int ret = 0;
+
+       dev_printk(KERN_INFO, &pdev->dev, "MAX98088 Audio CODEC\n");
+
+       if (!max98088_codec) {
+               dev_err(&pdev->dev, "Codec device is not registered\n");
+               return -EINVAL;
+       }
+
+       socdev->card->codec = max98088_codec;
+       codec = max98088_codec;
+       max98088 = codec->private_data;
+
+       /* register pcms */
+       ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to create pcms: %d\n", ret);
+               goto pcm_err;
+       }
+
+       snd_soc_add_controls(codec, max98088_snd_controls,
+                            ARRAY_SIZE(max98088_snd_controls));
+
+       /* install additional amixer controls: EQ and excursion limiter */
+       if (max98088->pdata)
+               max98088_handle_pdata(max98088);
+       else
+               dev_err(&pdev->dev, "No platform data\n");
+
+       max98088_add_widgets(codec);
+
+       ret = snd_soc_init_card(socdev);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "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;
+}
+
+static int max98088_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)
+               max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+
+       return 0;
+}
+
+/* expose to machine driver */
+struct snd_soc_codec_device soc_codec_dev_max98088 = {
+       .probe   = max98088_probe,
+       .remove  = max98088_remove,
+       .suspend = max98088_suspend,
+       .resume  = max98088_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_max98088);
+
+
+static int max98088_register(struct max98088_priv *max98088,
+                            enum snd_soc_control_type control)
+
+{
+       int ret, i, version;
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_cdata *cdata;
+
+       if (max98088_codec) {
+               dev_err(codec->dev, "Another MAX98088 is registered\n");
+               ret = -EINVAL;
+               goto err;
+       }
+
+       mutex_init(&codec->mutex);
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       codec->name = "MAX98088";
+       codec->owner = THIS_MODULE;
+
+       /* setup DAPM event bias level and handler function */
+       codec->bias_level = SND_SOC_BIAS_STANDBY;
+       codec->set_bias_level = max98088_set_bias_level;
+
+       codec->dai = max98088_dai;
+       codec->num_dai = ARRAY_SIZE(max98088_dai);
+       codec->private_data = max98088;
+
+       codec->reg_cache_size = M98088_REG_CNT;
+       codec->reg_cache = &max98088->reg_cache;
+       codec->volatile_register = max98088_volatile_register;
+       codec->cache_sync = 1;
+       memcpy(codec->reg_cache, max98088_reg, sizeof(max98088_reg));
+
+       ret = snd_soc_codec_set_cache_io(codec, 8, 8, control);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+               goto err;
+       }
+
+       version = snd_soc_read(codec, M98088_REG_FF_REV_ID);
+       dev_dbg(codec->dev, "MAX98088 revision 0x%02X\n", version);
+
+       /* initalize private data */
+
+       max98088->sysclk = (unsigned)-1;
+
+       cdata = &max98088->dai[0];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+
+       cdata = &max98088->dai[1];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+
+       max98088->power_state = 0; /* INA INB power enable state */
+       max98088->ex_mode = 0; /* excursion limiter mode */
+       max98088->digmic = 0; /* 0=analog, 1=digital */
+       max98088->mic1pre = 0;
+       max98088->mic2pre = 0;
+
+       snd_soc_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+
+       /* initialize registers cache to hardware default */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       snd_soc_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+       snd_soc_write(codec, M98088_REG_22_MIX_DAC,
+               M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+               M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+       snd_soc_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+       snd_soc_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+       snd_soc_write(codec, M98088_REG_16_DAI1_IOCFG,
+               M98088_S1NORMAL|M98088_SDATA);
+
+       snd_soc_write(codec, M98088_REG_1E_DAI2_IOCFG,
+               M98088_S2NORMAL|M98088_SDATA);
+
+       /* power on device */
+       max98088_codec = codec;
+       for (i = 0; i < codec->num_dai; i++)
+               max98088_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(&max98088_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(max98088);
+
+       return ret;
+}
+
+static void max98088_unregister(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       snd_soc_unregister_dais(max98088_dai, codec->num_dai);
+       snd_soc_unregister_codec(codec);
+       kfree(max98088);
+       max98088_codec = NULL;
+}
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+                            const struct i2c_device_id *id)
+{
+       struct snd_soc_codec *codec;
+       struct max98088_priv *max98088;
+
+       max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+       if (max98088 == NULL)
+               return -ENOMEM;
+
+       /* codec structure is inside the private data */
+       codec = &(max98088->codec);
+       codec->hw_write = (hw_write_t) i2c_master_send;
+
+       codec->private_data = max98088;
+       i2c_set_clientdata(i2c, max98088);
+       codec->control_data = i2c;
+
+       max98088->pdata = i2c->dev.platform_data;
+
+       codec->dev = &i2c->dev;
+
+       return max98088_register(max98088, SND_SOC_I2C);
+}
+
+static int max98088_i2c_remove(struct i2c_client *client)
+{
+       struct max98088_priv *max98088 = i2c_get_clientdata(client);
+       max98088_unregister(max98088);
+       return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+       { "max98088", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+
+static struct i2c_driver max98088_i2c_driver = {
+       .driver = {
+               .name = "MAX98088 I2C Codec",
+               .owner = THIS_MODULE,
+       },
+       .probe  = max98088_i2c_probe,
+       .remove = __devexit_p(max98088_i2c_remove),
+       .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&max98088_i2c_driver);
+       if (ret)
+               pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+       return ret;
+}
+
+static void __exit max98088_exit(void)
+{
+       i2c_del_driver(&max98088_i2c_driver);
+}
+
+module_init(max98088_init);
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang, Jesse Marroquin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h
new file mode 100644
index 0000000..bf6fae9
--- /dev/null
+++ b/sound/soc/codecs/max98088.h
@@ -0,0 +1,193 @@
+/*
+ * max98088.h -- MAX98088 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 _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS            0x00
+#define M98088_REG_01_MIC_STATUS            0x01
+#define M98088_REG_02_JACK_STAUS            0x02
+#define M98088_REG_03_BATTERY_VOLTAGE       0x03
+#define M98088_REG_0F_IRQ_ENABLE            0x0F
+#define M98088_REG_10_SYS_CLK               0x10
+#define M98088_REG_11_DAI1_CLKMODE          0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI        0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO        0x13
+#define M98088_REG_14_DAI1_FORMAT           0x14
+#define M98088_REG_15_DAI1_CLOCK            0x15
+#define M98088_REG_16_DAI1_IOCFG            0x16
+#define M98088_REG_17_DAI1_TDM              0x17
+#define M98088_REG_18_DAI1_FILTERS          0x18
+#define M98088_REG_19_DAI2_CLKMODE          0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI        0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO        0x1B
+#define M98088_REG_1C_DAI2_FORMAT           0x1C
+#define M98088_REG_1D_DAI2_CLOCK            0x1D
+#define M98088_REG_1E_DAI2_IOCFG            0x1E
+#define M98088_REG_1F_DAI2_TDM              0x1F
+#define M98088_REG_20_DAI2_FILTERS          0x20
+#define M98088_REG_21_SRC                   0x21
+#define M98088_REG_22_MIX_DAC               0x22
+#define M98088_REG_23_MIX_ADC_LEFT          0x23
+#define M98088_REG_24_MIX_ADC_RIGHT         0x24
+#define M98088_REG_25_MIX_HP_LEFT           0x25
+#define M98088_REG_26_MIX_HP_RIGHT          0x26
+#define M98088_REG_27_MIX_HP_CNTL           0x27
+#define M98088_REG_28_MIX_REC_LEFT          0x28
+#define M98088_REG_29_MIX_REC_RIGHT         0x29
+#define M98088_REG_2A_MIC_REC_CNTL          0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT          0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT         0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL          0x2D
+#define M98088_REG_2E_LVL_SIDETONE          0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY         0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ      0x30
+#define M98088_REG_31_LVL_DAI2_PLAY         0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ      0x32
+#define M98088_REG_33_LVL_ADC_L             0x33
+#define M98088_REG_34_LVL_ADC_R             0x34
+#define M98088_REG_35_LVL_MIC1              0x35
+#define M98088_REG_36_LVL_MIC2              0x36
+#define M98088_REG_37_LVL_INA               0x37
+#define M98088_REG_38_LVL_INB               0x38
+#define M98088_REG_39_LVL_HP_L              0x39
+#define M98088_REG_3A_LVL_HP_R              0x3A
+#define M98088_REG_3B_LVL_REC_L             0x3B
+#define M98088_REG_3C_LVL_REC_R             0x3C
+#define M98088_REG_3D_LVL_SPK_L             0x3D
+#define M98088_REG_3E_LVL_SPK_R             0x3E
+#define M98088_REG_3F_MICAGC_CFG            0x3F
+#define M98088_REG_40_MICAGC_THRESH         0x40
+#define M98088_REG_41_SPKDHP                0x41
+#define M98088_REG_42_SPKDHP_THRESH         0x42
+#define M98088_REG_43_SPKALC_COMP           0x43
+#define M98088_REG_44_PWRLMT_CFG            0x44
+#define M98088_REG_45_PWRLMT_TIME           0x45
+#define M98088_REG_46_THDLMT_CFG            0x46
+#define M98088_REG_47_CFG_AUDIO_IN          0x47
+#define M98088_REG_48_CFG_MIC               0x48
+#define M98088_REG_49_CFG_LEVEL             0x49
+#define M98088_REG_4A_CFG_BYPASS            0x4A
+#define M98088_REG_4B_CFG_JACKDET           0x4B
+#define M98088_REG_4C_PWR_EN_IN             0x4C
+#define M98088_REG_4D_PWR_EN_OUT            0x4D
+#define M98088_REG_4E_BIAS_CNTL             0x4E
+#define M98088_REG_4F_DAC_BIAS1             0x4F
+#define M98088_REG_50_DAC_BIAS2             0x50
+#define M98088_REG_51_PWR_SYS               0x51
+#define M98088_REG_52_DAI1_EQ_BASE          0x52
+#define M98088_REG_84_DAI2_EQ_BASE          0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE      0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE      0xC0
+#define M98088_REG_FF_REV_ID                0xFF
+
+#define M98088_REG_CNT                      (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+       #define M98088_DAI_MAS                  (1<<7)
+       #define M98088_DAI_WCI                  (1<<6)
+       #define M98088_DAI_BCI                  (1<<5)
+       #define M98088_DAI_DLY                  (1<<4)
+       #define M98088_DAI_TDM                  (1<<2)
+       #define M98088_DAI_FSW                  (1<<1)
+       #define M98088_DAI_WS                   (1<<0)
+
+/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */
+       #define M98088_DAI_BSEL64               (1<<0)
+       #define M98088_DAI_OSR64                (1<<6)
+
+/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */
+       #define M98088_S1NORMAL                 (1<<6)
+       #define M98088_S2NORMAL                 (2<<6)
+       #define M98088_SDATA                    (3<<0)
+
+/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */
+       #define M98088_DAI_DHF                  (1<<3)
+
+/* M98088_REG_22_MIX_DAC */
+       #define M98088_DAI1L_TO_DACL            (1<<7)
+       #define M98088_DAI1R_TO_DACL            (1<<6)
+       #define M98088_DAI2L_TO_DACL            (1<<5)
+       #define M98088_DAI2R_TO_DACL            (1<<4)
+       #define M98088_DAI1L_TO_DACR            (1<<3)
+       #define M98088_DAI1R_TO_DACR            (1<<2)
+       #define M98088_DAI2L_TO_DACR            (1<<1)
+       #define M98088_DAI2R_TO_DACR            (1<<0)
+
+/* M98088_REG_2A_MIC_REC_CNTL */
+       #define M98088_REC_LINEMODE             (1<<7)
+       #define M98088_REC_LINEMODE_MASK        (1<<7)
+
+/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */
+       #define M98088_MICPRE_MASK              (3<<5)
+       #define M98088_MICPRE_SHIFT             5
+
+/* M98088_REG_3A_LVL_HP_R */
+       #define M98088_HP_MUTE                  (1<<7)
+
+/* M98088_REG_3C_LVL_REC_R */
+       #define M98088_REC_MUTE                 (1<<7)
+
+/* M98088_REG_3E_LVL_SPK_R */
+       #define M98088_SP_MUTE                  (1<<7)
+
+/* M98088_REG_48_CFG_MIC */
+       #define M98088_EXTMIC_MASK              (3<<0)
+       #define M98088_DIGMIC_L                 (1<<5)
+       #define M98088_DIGMIC_R                 (1<<4)
+
+/* M98088_REG_49_CFG_LEVEL */
+       #define M98088_VSEN                     (1<<6)
+       #define M98088_ZDEN                     (1<<5)
+       #define M98088_EQ2EN                    (1<<1)
+       #define M98088_EQ1EN                    (1<<0)
+
+/* M98088_REG_4C_PWR_EN_IN */
+       #define M98088_INAEN                    (1<<7)
+       #define M98088_INBEN                    (1<<6)
+       #define M98088_MBEN                     (1<<3)
+       #define M98088_ADLEN                    (1<<1)
+       #define M98088_ADREN                    (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+       #define M98088_HPLEN                    (1<<7)
+       #define M98088_HPREN                    (1<<6)
+       #define M98088_HPEN                     ((1<<7)|(1<<6))
+       #define M98088_SPLEN                    (1<<5)
+       #define M98088_SPREN                    (1<<4)
+       #define M98088_RECEN                    (1<<3)
+       #define M98088_DALEN                    (1<<1)
+       #define M98088_DAREN                    (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+       #define M98088_SHDNRUN                  (1<<7)
+       #define M98088_PERFMODE                 (1<<3)
+       #define M98088_HPPLYBACK                (1<<2)
+       #define M98088_PWRSV8K                  (1<<1)
+       #define M98088_PWRSV                    (1<<0)
+
+#define M98088_COEFS_PER_BAND               5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+struct max98088_setup_data {
+       unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai max98088_dai[];
+extern struct snd_soc_codec_device soc_codec_dev_max98088;
+
+#endif
--
1.6.3.3


^ permalink raw reply related	[flat|nested] 46+ messages in thread

* [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-09-23  2:58 ` Peter Hsiang
  0 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-09-23  2:58 UTC (permalink / raw)
  To: Jaroslav Kysela, Takashi Iwai, Peter Hsiang, Liam Girdwood, Mark Brown
  Cc: alsa-devel, linux-kernel, Jesse Marroquin

This patch adds the MAX98088 CODEC driver.

Signed-off-by: Peter Hsiang <peter.hsiang@maxim-ic.com>
---
 include/sound/max98088.h    |   54 +
 sound/soc/codecs/Kconfig    |    4 +
 sound/soc/codecs/Makefile   |    2 +
 sound/soc/codecs/max98088.c | 2477 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/max98088.h |  193 ++++
 5 files changed, 2730 insertions(+), 0 deletions(-)
 create mode 100644 include/sound/max98088.h
 create mode 100644 sound/soc/codecs/max98088.c
 create mode 100644 sound/soc/codecs/max98088.h

diff --git a/include/sound/max98088.h b/include/sound/max98088.h
new file mode 100644
index 0000000..30652be
--- /dev/null
+++ b/include/sound/max98088.h
@@ -0,0 +1,54 @@
+/*
+ * Platform data for MAX98088
+ *
+ * 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 as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __SOUND_MAX98088_PDATA_H__
+#define __SOUND_MAX98088_PDATA_H__
+
+#define EQ_CFG_MAX 32
+
+/* Equalizer filter response configuration */
+struct max98088_eq_cfg {
+       const char *name;
+       unsigned int rate;
+       u16 band1[5];
+       u16 band2[5];
+       u16 band3[5];
+       u16 band4[5];
+       u16 band5[5];
+};
+
+/* codec platform data */
+struct max98088_pdata {
+
+       /* Equalizers for DAI1 and DAI2 */
+       struct max98088_eq_cfg *eq1_cfg;
+       struct max98088_eq_cfg *eq2_cfg;
+       unsigned int eq1_cfgcnt;
+       unsigned int eq2_cfgcnt;
+
+       /* Receiver output can be configured as power amplifier or LINE out */
+       /* Set receiver_mode to:
+        * 0 = amplifier output, or
+        * 1 = LINE level output
+        */
+       unsigned int receiver_mode:1;
+
+       /* Analog/digital microphone configuration:
+        * 0 = analog microphone input (normal setting)
+        * 1 = digital microphone input
+        */
+       unsigned int digmic_left_mode:1;
+       unsigned int digmic_right_mode:1;
+
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4ccc2b7..4e6713c 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -27,6 +27,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_CS4270 if I2C
        select SND_SOC_DA7210 if I2C
        select SND_SOC_JZ4740 if SOC_JZ4740
+       select SND_SOC_MAX98088 if I2C
        select SND_SOC_MAX9877 if I2C
        select SND_SOC_PCM3008
        select SND_SOC_SPDIF
@@ -157,6 +158,9 @@ config SND_SOC_L3
 config SND_SOC_DA7210
         tristate

+config SND_SOC_MAX98088
+       tristate
+
 config SND_SOC_PCM3008
        tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 23e7e2c..7184611 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -15,6 +15,7 @@ snd-soc-cs4270-objs := cs4270.o
 snd-soc-cx20442-objs := cx20442.o
 snd-soc-da7210-objs := da7210.o
 snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
 snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-spdif-objs := spdif_transciever.o
 snd-soc-ssm2602-objs := ssm2602.o
@@ -88,6 +89,7 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
 obj-$(CONFIG_SND_SOC_DA7210)   += snd-soc-da7210.o
 obj-$(CONFIG_SND_SOC_L3)       += snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_JZ4740_CODEC)     += snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088)  += snd-soc-max98088.o
 obj-$(CONFIG_SND_SOC_PCM3008)  += snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_SPDIF)    += snd-soc-spdif.o
 obj-$(CONFIG_SND_SOC_SSM2602)  += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
new file mode 100644
index 0000000..00319d6
--- /dev/null
+++ b/sound/soc/codecs/max98088.c
@@ -0,0 +1,2477 @@
+/*
+ * max98088.c -- MAX98088 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 <linux/slab.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+static struct snd_soc_codec *max98088_codec;
+struct snd_soc_codec_device soc_codec_dev_max98088;
+
+/* Configurations associated with each channel of DAI stream */
+struct max98088_cdata {
+       unsigned int rate;
+       unsigned int fmt;
+       int eq_textcnt;
+       const char *eq_texts[EQ_CFG_MAX];
+       int eq_sel;
+       struct soc_enum eq_enum;
+};
+
+/* Codec private data */
+struct max98088_priv {
+       struct snd_soc_codec codec;
+       struct max98088_pdata *pdata;
+       u8 reg_cache[M98088_REG_CNT];
+       unsigned int sysclk;
+       struct max98088_cdata dai[2];
+       u8 power_state;
+       unsigned int ex_mode;
+       unsigned int digmic;
+       unsigned int mic1pre;
+       unsigned int mic2pre;
+       unsigned int extmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+       0x00, /* 00 IRQ status */
+       0x00, /* 01 MIC status */
+       0x00, /* 02 jack status */
+       0x00, /* 03 battery voltage */
+       0x00, /* 04 */
+       0x00, /* 05 */
+       0x00, /* 06 */
+       0x00, /* 07 */
+       0x00, /* 08 */
+       0x00, /* 09 */
+       0x00, /* 0A */
+       0x00, /* 0B */
+       0x00, /* 0C */
+       0x00, /* 0D */
+       0x00, /* 0E */
+       0x00, /* 0F interrupt enable */
+
+       0x00, /* 10 master clock */
+       0x00, /* 11 DAI1 clock mode */
+       0x00, /* 12 DAI1 clock control */
+       0x00, /* 13 DAI1 clock control */
+       0x00, /* 14 DAI1 format */
+       0x00, /* 15 DAI1 clock */
+       0x00, /* 16 DAI1 config */
+       0x00, /* 17 DAI1 TDM */
+       0x00, /* 18 DAI1 filters */
+       0x00, /* 19 DAI2 clock mode */
+       0x00, /* 1A DAI2 clock control */
+       0x00, /* 1B DAI2 clock control */
+       0x00, /* 1C DAI2 format */
+       0x00, /* 1D DAI2 clock */
+       0x00, /* 1E DAI2 config */
+       0x00, /* 1F DAI2 TDM */
+
+       0x00, /* 20 DAI2 filters */
+       0x00, /* 21 data config */
+       0x00, /* 22 DAC mixer */
+       0x00, /* 23 left ADC mixer */
+       0x00, /* 24 right ADC mixer */
+       0x00, /* 25 left HP mixer */
+       0x00, /* 26 right HP mixer */
+       0x00, /* 27 HP control */
+       0x00, /* 28 left REC mixer */
+       0x00, /* 29 right REC mixer */
+       0x00, /* 2A REC control */
+       0x00, /* 2B left SPK mixer */
+       0x00, /* 2C right SPK mixer */
+       0x00, /* 2D SPK control */
+       0x00, /* 2E sidetone */
+       0x00, /* 2F DAI1 playback level */
+
+       0x00, /* 30 DAI1 playback level */
+       0x00, /* 31 DAI2 playback level */
+       0x00, /* 32 DAI2 playbakc level */
+       0x00, /* 33 left ADC level */
+       0x00, /* 34 right ADC level */
+       0x00, /* 35 MIC1 level */
+       0x00, /* 36 MIC2 level */
+       0x00, /* 37 INA level */
+       0x00, /* 38 INB level */
+       0x00, /* 39 left HP volume */
+       0x00, /* 3A right HP volume */
+       0x00, /* 3B left REC volume */
+       0x00, /* 3C right REC volume */
+       0x00, /* 3D left SPK volume */
+       0x00, /* 3E right SPK volume */
+       0x00, /* 3F MIC config */
+
+       0x00, /* 40 MIC threshold */
+       0x00, /* 41 excursion limiter filter */
+       0x00, /* 42 excursion limiter threshold */
+       0x00, /* 43 ALC */
+       0x00, /* 44 power limiter threshold */
+       0x00, /* 45 power limiter config */
+       0x00, /* 46 distortion limiter config */
+       0x00, /* 47 audio input */
+       0x00, /* 48 microphone */
+       0x00, /* 49 level control */
+       0x00, /* 4A bypass switches */
+       0x00, /* 4B jack detect */
+       0x00, /* 4C input enable */
+       0x00, /* 4D output enable */
+       0xF0, /* 4E bias control */
+       0x00, /* 4F DAC power */
+
+       0x0F, /* 50 DAC power */
+       0x00, /* 51 system */
+       0x00, /* 52 DAI1 EQ1 */
+       0x00, /* 53 DAI1 EQ1 */
+       0x00, /* 54 DAI1 EQ1 */
+       0x00, /* 55 DAI1 EQ1 */
+       0x00, /* 56 DAI1 EQ1 */
+       0x00, /* 57 DAI1 EQ1 */
+       0x00, /* 58 DAI1 EQ1 */
+       0x00, /* 59 DAI1 EQ1 */
+       0x00, /* 5A DAI1 EQ1 */
+       0x00, /* 5B DAI1 EQ1 */
+       0x00, /* 5C DAI1 EQ2 */
+       0x00, /* 5D DAI1 EQ2 */
+       0x00, /* 5E DAI1 EQ2 */
+       0x00, /* 5F DAI1 EQ2 */
+
+       0x00, /* 60 DAI1 EQ2 */
+       0x00, /* 61 DAI1 EQ2 */
+       0x00, /* 62 DAI1 EQ2 */
+       0x00, /* 63 DAI1 EQ2 */
+       0x00, /* 64 DAI1 EQ2 */
+       0x00, /* 65 DAI1 EQ2 */
+       0x00, /* 66 DAI1 EQ3 */
+       0x00, /* 67 DAI1 EQ3 */
+       0x00, /* 68 DAI1 EQ3 */
+       0x00, /* 69 DAI1 EQ3 */
+       0x00, /* 6A DAI1 EQ3 */
+       0x00, /* 6B DAI1 EQ3 */
+       0x00, /* 6C DAI1 EQ3 */
+       0x00, /* 6D DAI1 EQ3 */
+       0x00, /* 6E DAI1 EQ3 */
+       0x00, /* 6F DAI1 EQ3 */
+
+       0x00, /* 70 DAI1 EQ4 */
+       0x00, /* 71 DAI1 EQ4 */
+       0x00, /* 72 DAI1 EQ4 */
+       0x00, /* 73 DAI1 EQ4 */
+       0x00, /* 74 DAI1 EQ4 */
+       0x00, /* 75 DAI1 EQ4 */
+       0x00, /* 76 DAI1 EQ4 */
+       0x00, /* 77 DAI1 EQ4 */
+       0x00, /* 78 DAI1 EQ4 */
+       0x00, /* 79 DAI1 EQ4 */
+       0x00, /* 7A DAI1 EQ5 */
+       0x00, /* 7B DAI1 EQ5 */
+       0x00, /* 7C DAI1 EQ5 */
+       0x00, /* 7D DAI1 EQ5 */
+       0x00, /* 7E DAI1 EQ5 */
+       0x00, /* 7F DAI1 EQ5 */
+
+       0x00, /* 80 DAI1 EQ5 */
+       0x00, /* 81 DAI1 EQ5 */
+       0x00, /* 82 DAI1 EQ5 */
+       0x00, /* 83 DAI1 EQ5 */
+       0x00, /* 84 DAI2 EQ1 */
+       0x00, /* 85 DAI2 EQ1 */
+       0x00, /* 86 DAI2 EQ1 */
+       0x00, /* 87 DAI2 EQ1 */
+       0x00, /* 88 DAI2 EQ1 */
+       0x00, /* 89 DAI2 EQ1 */
+       0x00, /* 8A DAI2 EQ1 */
+       0x00, /* 8B DAI2 EQ1 */
+       0x00, /* 8C DAI2 EQ1 */
+       0x00, /* 8D DAI2 EQ1 */
+       0x00, /* 8E DAI2 EQ2 */
+       0x00, /* 8F DAI2 EQ2 */
+
+       0x00, /* 90 DAI2 EQ2 */
+       0x00, /* 91 DAI2 EQ2 */
+       0x00, /* 92 DAI2 EQ2 */
+       0x00, /* 93 DAI2 EQ2 */
+       0x00, /* 94 DAI2 EQ2 */
+       0x00, /* 95 DAI2 EQ2 */
+       0x00, /* 96 DAI2 EQ2 */
+       0x00, /* 97 DAI2 EQ2 */
+       0x00, /* 98 DAI2 EQ3 */
+       0x00, /* 99 DAI2 EQ3 */
+       0x00, /* 9A DAI2 EQ3 */
+       0x00, /* 9B DAI2 EQ3 */
+       0x00, /* 9C DAI2 EQ3 */
+       0x00, /* 9D DAI2 EQ3 */
+       0x00, /* 9E DAI2 EQ3 */
+       0x00, /* 9F DAI2 EQ3 */
+
+       0x00, /* A0 DAI2 EQ3 */
+       0x00, /* A1 DAI2 EQ3 */
+       0x00, /* A2 DAI2 EQ4 */
+       0x00, /* A3 DAI2 EQ4 */
+       0x00, /* A4 DAI2 EQ4 */
+       0x00, /* A5 DAI2 EQ4 */
+       0x00, /* A6 DAI2 EQ4 */
+       0x00, /* A7 DAI2 EQ4 */
+       0x00, /* A8 DAI2 EQ4 */
+       0x00, /* A9 DAI2 EQ4 */
+       0x00, /* AA DAI2 EQ4 */
+       0x00, /* AB DAI2 EQ4 */
+       0x00, /* AC DAI2 EQ5 */
+       0x00, /* AD DAI2 EQ5 */
+       0x00, /* AE DAI2 EQ5 */
+       0x00, /* AF DAI2 EQ5 */
+
+       0x00, /* B0 DAI2 EQ5 */
+       0x00, /* B1 DAI2 EQ5 */
+       0x00, /* B2 DAI2 EQ5 */
+       0x00, /* B3 DAI2 EQ5 */
+       0x00, /* B4 DAI2 EQ5 */
+       0x00, /* B5 DAI2 EQ5 */
+       0x00, /* B6 DAI1 biquad */
+       0x00, /* B7 DAI1 biquad */
+       0x00, /* B8 DAI1 biquad */
+       0x00, /* B9 DAI1 biquad */
+       0x00, /* BA DAI1 biquad */
+       0x00, /* BB DAI1 biquad */
+       0x00, /* BC DAI1 biquad */
+       0x00, /* BD DAI1 biquad */
+       0x00, /* BE DAI1 biquad */
+       0x00, /* BF DAI1 biquad */
+
+       0x00, /* C0 DAI2 biquad */
+       0x00, /* C1 DAI2 biquad */
+       0x00, /* C2 DAI2 biquad */
+       0x00, /* C3 DAI2 biquad */
+       0x00, /* C4 DAI2 biquad */
+       0x00, /* C5 DAI2 biquad */
+       0x00, /* C6 DAI2 biquad */
+       0x00, /* C7 DAI2 biquad */
+       0x00, /* C8 DAI2 biquad */
+       0x00, /* C9 DAI2 biquad */
+       0x00, /* CA */
+       0x00, /* CB */
+       0x00, /* CC */
+       0x00, /* CD */
+       0x00, /* CE */
+       0x00, /* CF */
+
+       0x00, /* D0 */
+       0x00, /* D1 */
+       0x00, /* D2 */
+       0x00, /* D3 */
+       0x00, /* D4 */
+       0x00, /* D5 */
+       0x00, /* D6 */
+       0x00, /* D7 */
+       0x00, /* D8 */
+       0x00, /* D9 */
+       0x00, /* DA */
+       0x70, /* DB */
+       0x00, /* DC */
+       0x00, /* DD */
+       0x00, /* DE */
+       0x00, /* DF */
+
+       0x00, /* E0 */
+       0x00, /* E1 */
+       0x00, /* E2 */
+       0x00, /* E3 */
+       0x00, /* E4 */
+       0x00, /* E5 */
+       0x00, /* E6 */
+       0x00, /* E7 */
+       0x00, /* E8 */
+       0x00, /* E9 */
+       0x00, /* EA */
+       0x00, /* EB */
+       0x00, /* EC */
+       0x00, /* ED */
+       0x00, /* EE */
+       0x00, /* EF */
+
+       0x00, /* F0 */
+       0x00, /* F1 */
+       0x00, /* F2 */
+       0x00, /* F3 */
+       0x00, /* F4 */
+       0x00, /* F5 */
+       0x00, /* F6 */
+       0x00, /* F7 */
+       0x00, /* F8 */
+       0x00, /* F9 */
+       0x00, /* FA */
+       0x00, /* FB */
+       0x00, /* FC */
+       0x00, /* FD */
+       0x00, /* FE */
+       0x00, /* FF */
+};
+
+static struct {
+       int readable;
+       int writable;
+       int vol;
+} max98088_access[M98088_REG_CNT] = {
+       { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+       { 0xFF, 0x00, 1 }, /* 01 MIC status */
+       { 0xFF, 0x00, 1 }, /* 02 jack status */
+       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+       { 0xFF, 0xFF, 0 }, /* 04 */
+       { 0xFF, 0xFF, 0 }, /* 05 */
+       { 0xFF, 0xFF, 0 }, /* 06 */
+       { 0xFF, 0xFF, 0 }, /* 07 */
+       { 0xFF, 0xFF, 0 }, /* 08 */
+       { 0xFF, 0xFF, 0 }, /* 09 */
+       { 0xFF, 0xFF, 0 }, /* 0A */
+       { 0xFF, 0xFF, 0 }, /* 0B */
+       { 0xFF, 0xFF, 0 }, /* 0C */
+       { 0xFF, 0xFF, 0 }, /* 0D */
+       { 0xFF, 0xFF, 0 }, /* 0E */
+       { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+       { 0xFF, 0xFF, 0 }, /* 10 master clock */
+       { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+       { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+       { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+       { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+       { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+       { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+       { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+       { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+       { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+       { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+       { 0xFF, 0xFF, 0 }, /* 21 data config */
+       { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+       { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 27 HP control */
+       { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 2A REC control */
+       { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+       { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+       { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+       { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+       { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+       { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+       { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+       { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+       { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+       { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+       { 0xFF, 0xFF, 0 }, /* 37 INA level */
+       { 0xFF, 0xFF, 0 }, /* 38 INB level */
+       { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+       { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+       { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+       { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 43 ALC */
+       { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+       { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+       { 0xFF, 0xFF, 0 }, /* 47 audio input */
+       { 0xFF, 0xFF, 0 }, /* 48 microphone */
+       { 0xFF, 0xFF, 0 }, /* 49 level control */
+       { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+       { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+       { 0xFF, 0xFF, 0 }, /* 4C input enable */
+       { 0xFF, 0xFF, 0 }, /* 4D output enable */
+       { 0xFF, 0xFF, 0 }, /* 4E bias control */
+       { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+       { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+       { 0xFF, 0xFF, 0 }, /* 51 system */
+       { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+       { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+       { 0x00, 0x00, 0 }, /* CA */
+       { 0x00, 0x00, 0 }, /* CB */
+       { 0x00, 0x00, 0 }, /* CC */
+       { 0x00, 0x00, 0 }, /* CD */
+       { 0x00, 0x00, 0 }, /* CE */
+       { 0x00, 0x00, 0 }, /* CF */
+
+       { 0x00, 0x00, 0 }, /* D0 */
+       { 0x00, 0x00, 0 }, /* D1 */
+       { 0x00, 0x00, 0 }, /* D2 */
+       { 0x00, 0x00, 0 }, /* D3 */
+       { 0x00, 0x00, 0 }, /* D4 */
+       { 0x00, 0x00, 0 }, /* D5 */
+       { 0x00, 0x00, 0 }, /* D6 */
+       { 0x00, 0x00, 0 }, /* D7 */
+       { 0x00, 0x00, 0 }, /* D8 */
+       { 0x00, 0x00, 0 }, /* D9 */
+       { 0x00, 0x00, 0 }, /* DA */
+       { 0x00, 0x00, 0 }, /* DB */
+       { 0x00, 0x00, 0 }, /* DC */
+       { 0x00, 0x00, 0 }, /* DD */
+       { 0x00, 0x00, 0 }, /* DE */
+       { 0x00, 0x00, 0 }, /* DF */
+
+       { 0x00, 0x00, 0 }, /* E0 */
+       { 0x00, 0x00, 0 }, /* E1 */
+       { 0x00, 0x00, 0 }, /* E2 */
+       { 0x00, 0x00, 0 }, /* E3 */
+       { 0x00, 0x00, 0 }, /* E4 */
+       { 0x00, 0x00, 0 }, /* E5 */
+       { 0x00, 0x00, 0 }, /* E6 */
+       { 0x00, 0x00, 0 }, /* E7 */
+       { 0x00, 0x00, 0 }, /* E8 */
+       { 0x00, 0x00, 0 }, /* E9 */
+       { 0x00, 0x00, 0 }, /* EA */
+       { 0x00, 0x00, 0 }, /* EB */
+       { 0x00, 0x00, 0 }, /* EC */
+       { 0x00, 0x00, 0 }, /* ED */
+       { 0x00, 0x00, 0 }, /* EE */
+       { 0x00, 0x00, 0 }, /* EF */
+
+       { 0x00, 0x00, 0 }, /* F0 */
+       { 0x00, 0x00, 0 }, /* F1 */
+       { 0x00, 0x00, 0 }, /* F2 */
+       { 0x00, 0x00, 0 }, /* F3 */
+       { 0x00, 0x00, 0 }, /* F4 */
+       { 0x00, 0x00, 0 }, /* F5 */
+       { 0x00, 0x00, 0 }, /* F6 */
+       { 0x00, 0x00, 0 }, /* F7 */
+       { 0x00, 0x00, 0 }, /* F8 */
+       { 0x00, 0x00, 0 }, /* F9 */
+       { 0x00, 0x00, 0 }, /* FA */
+       { 0x00, 0x00, 0 }, /* FB */
+       { 0x00, 0x00, 0 }, /* FC */
+       { 0x00, 0x00, 0 }, /* FD */
+       { 0x00, 0x00, 0 }, /* FE */
+       { 0xFF, 0x00, 1 }, /* FF */
+};
+
+static int max98088_volatile_register(unsigned int reg)
+{
+       return max98088_access[reg].vol;
+}
+
+static int max98088_hw_write(struct snd_soc_codec *codec, unsigned int reg,
+                            unsigned int value)
+{
+       u8 data[2];
+
+       data[0] = reg;
+       data[1] = value;
+       if (codec->hw_write(codec->control_data, data, 2) == 2)
+               return 0;
+       else
+               return -EIO;
+}
+
+/*
+ * For kernels compiled without unsigned long long int division
+ */
+unsigned long long int ulldiv(unsigned long long int dividend,
+                             unsigned long long int divisor)
+{
+       unsigned long long int quotient = 0;
+       int shift = 1;
+
+       BUG_ON(divisor == 0);
+
+       /* Result is 1.0 if divisor and dividend are equal */
+       if (divisor == dividend)
+               return 1;
+
+       /* Normalize divisor */
+       while (!(divisor & 0x8000000000000000ULL)) {
+               divisor <<= 1;
+               ++shift;
+       }
+
+       /* Shift and subtract */
+       while (shift--) {
+               quotient <<= 1;
+
+               if (divisor <= dividend) {
+                       dividend -= divisor;
+                       ++quotient;
+               }
+               divisor >>= 1;
+       }
+
+       /* Round up */
+       if (dividend > divisor)
+               ++quotient;
+
+       return quotient;
+}
+
+#define INA1_PGA_BIT 0x01
+#define INA2_PGA_BIT 0x02
+#define INB1_PGA_BIT 0x04
+#define INB2_PGA_BIT 0x08
+/*
+ * The INx1 and INx2 PGAs 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.
+ */
+static int max98088_pga_event(struct snd_soc_dapm_widget *w,
+                             struct snd_kcontrol *k, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       u8 *state = &max98088->power_state;
+       unsigned int val;
+       unsigned int pga;
+       unsigned int mask;
+
+       BUG_ON(w->reg != M98088_REG_4C_PWR_EN_IN);
+
+       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;
+       }
+
+       if (event == SND_SOC_DAPM_POST_PMU) {
+               /* ON */
+               *state |= pga;
+
+               /* Turn on, avoiding unnecessary writes */
+               val = snd_soc_read(codec, w->reg);
+               if (!(val & (1 << w->shift))) {
+                       val |= (1 << w->shift);
+                       snd_soc_write(codec, w->reg, val);
+               }
+       } else if (event == SND_SOC_DAPM_POST_PMD) {
+               /* OFF */
+               *state &= ~pga;
+
+               /* Turn off if both are off, avoiding unnecessary writes */
+               if (!(*state & mask)) {
+                       val = snd_soc_read(codec, w->reg);
+                       if (val & (1 << w->shift)) {
+                               val &= ~(1 << w->shift);
+                               snd_soc_write(codec, w->reg, val);
+                       }
+               }
+       } else {
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+                   unsigned int band, u16 *coefs)
+{
+       unsigned int eq_reg;
+       unsigned int i;
+
+       BUG_ON(band > 4);
+       BUG_ON(dai > 1);
+
+       /* Load the base register address */
+       eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+       /* Add the band address offset, note adjustment for word address */
+       eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+       /* Step through the registers and coefs */
+       for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+               snd_soc_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+               snd_soc_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+       }
+
+       return;
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_ex_mode[] = {
+       "Off",
+       "100Hz",
+       "400Hz",
+       "600Hz",
+       "800Hz",
+       "1000Hz",
+       "200-400Hz",
+       "400-600Hz",
+       "400-800Hz",
+};
+
+static const unsigned int ex_mode_table[] = {
+       0x00,           /* disabled */
+       (0<<4)|3,       /* 100Hz */
+       (1<<4)|0,       /* 400Hz */
+       (2<<4)|0,       /* 600Hz */
+       (3<<4)|0,       /* 800Hz */
+       (4<<4)|0,       /* 1000Hz */
+       (1<<4)|1,       /* 200-400Hz */
+       (2<<4)|2,       /* 400-600Hz */
+       (3<<4)|2,       /* 400-800Hz */
+};
+
+static const struct soc_enum max98088_ex_mode_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_ex_mode), max98088_ex_mode),
+};
+
+static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->ex_mode;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(ex_mode_table))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       snd_soc_write(codec, M98088_REG_41_SPKDHP,
+               ex_mode_table[*mode]);
+
+       return 0;
+}
+
+static int max98088_ex_mode_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->ex_mode;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+       "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+               max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_dai1_fltr[] = {
+       "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+       "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static const char *max98088_micpre[] = {
+       "0dB",
+       "20dB",
+       "30dB",
+};
+
+static const struct soc_enum max98088_micpre_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_micpre), max98088_micpre),
+};
+
+static const char *max98088_extmic[] = {
+       "Off",
+       "MIC1",
+       "MIC2",
+};
+
+static const struct soc_enum max98088_extmic_enum[] = {
+       SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max98088_extmic), max98088_extmic),
+};
+
+static int max98088_mic1pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->mic1pre;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_micpre))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       return 0;
+}
+
+static int max98088_mic1pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->mic1pre;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static int max98088_mic2pre_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->mic2pre;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_micpre))
+               return -EINVAL;
+
+       *mode = ucontrol->value.integer.value[0];
+       return 0;
+}
+
+static int max98088_mic2pre_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->mic2pre;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static int max98088_extmic_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->extmic_mode;
+       int sel = ucontrol->value.integer.value[0];
+
+       if (sel >= ARRAY_SIZE(max98088_extmic))
+               return -EINVAL;
+
+       *mode = sel;
+       snd_soc_update_bits(codec, M98088_REG_48_CFG_MIC,
+               M98088_EXTMIC_MASK, sel);
+
+       return 0;
+}
+
+static int max98088_extmic_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->extmic_mode;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+       /* Analog outputs */
+
+       SOC_DOUBLE_R("Headphone Volume", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+       SOC_DOUBLE_R("Speaker Volume", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+       SOC_DOUBLE_R("Receiver Volume", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+       SOC_DOUBLE_R("Headphone Switch", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 7, 1, 1),
+       SOC_DOUBLE_R("Speaker Switch", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 7, 1, 1),
+       SOC_DOUBLE_R("Receiver Switch", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 7, 1, 1),
+
+       /* Analog inputs */
+
+       SOC_SINGLE("MIC1 Volume", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+       SOC_SINGLE("MIC2 Volume", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+       SOC_ENUM_EXT("MIC1 Boost Volume", max98088_micpre_enum,
+               max98088_mic1pre_get, max98088_mic1pre_set),
+
+       SOC_ENUM_EXT("MIC2 Boost Volume", max98088_micpre_enum,
+               max98088_mic2pre_get, max98088_mic2pre_set),
+
+       SOC_ENUM_EXT("Ext MIC Switch", max98088_extmic_enum,
+               max98088_extmic_get, max98088_extmic_set),
+
+       SOC_SINGLE("INA Volume", M98088_REG_37_LVL_INA, 0, 7, 1),
+       SOC_SINGLE("INB Volume", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+       /* ADC input digital gains and volume controls */
+
+       SOC_SINGLE("ADCL Volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+       SOC_SINGLE("ADCR Volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+       SOC_SINGLE("ADCL Boost Volume", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+       SOC_SINGLE("ADCR Boost Volume", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+       /* Equalizer */
+
+       SOC_SINGLE("EQ1 Switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+       SOC_SINGLE("EQ2 Switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+       /* Excursion limiter */
+
+       SOC_ENUM_EXT("EX Limiter Mode", max98088_ex_mode_enum,
+               max98088_ex_mode_get, max98088_ex_mode_set),
+       SOC_ENUM("EX Limiter Threshold", max98088_ex_thresh_enum),
+
+       /* Voice/music filters */
+
+       SOC_ENUM("DAI1 Filter Mode", max98088_filter_mode_enum),
+       SOC_ENUM("DAI1 DAC Filter", max98088_dai1_dac_filter_enum),
+       SOC_ENUM("DAI1 ADC Filter", max98088_dai1_adc_filter_enum),
+       SOC_SINGLE("DAI2 DC Block Switch", M98088_REG_20_DAI2_FILTERS,
+               0, 1, 0),
+
+       /* Automatic level control (for both DAI1/DAI2) */
+
+       SOC_SINGLE("ALC Switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+       SOC_SINGLE("ALC Threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+       SOC_SINGLE("ALC Multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+       SOC_SINGLE("ALC Release Time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+       /* Power limiter */
+
+       SOC_SINGLE("PWR Limiter Threshold", M98088_REG_44_PWRLMT_CFG,
+               4, 15, 0),
+       SOC_SINGLE("PWR Limiter Weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+       SOC_SINGLE("PWR Limiter Time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+       SOC_SINGLE("PWR Limiter Time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+       /* THD distortion limiter */
+
+       SOC_SINGLE("THD Limiter Threshold", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+       SOC_SINGLE("THD Limiter Time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1 Switch", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2 Switch", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_hp_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       u16 status;
+
+       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
+
+       /* powering down headphone gracefully */
+       status = snd_soc_read(codec, M98088_REG_4D_PWR_EN_OUT);
+       if ((status & M98088_HPEN) == M98088_HPEN) {
+               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
+                       (status & ~M98088_HPEN));
+       }
+       schedule_timeout(msecs_to_jiffies(20));
+
+       return 0;
+}
+
+static int max98088_mic_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+
+       switch (event) {
+       case SND_SOC_DAPM_POST_PMU:
+               if (w->reg == M98088_REG_35_LVL_MIC1) {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic1pre)<<M98088_MICPRE_SHIFT);
+               } else {
+                       snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK,
+                               (1+max98088->mic2pre)<<M98088_MICPRE_SHIFT);
+               }
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               snd_soc_update_bits(codec, w->reg, M98088_MICPRE_MASK, 0);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/* DAPM widgets top level */
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+       SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+       SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+       SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+       SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+       SND_SOC_DAPM_PGA_E("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+               7, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+       SND_SOC_DAPM_PGA_E("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+               6, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+
+       SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+               5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+               4, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+               3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+               2, 0, NULL, 0),
+
+       SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_left_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_right_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_PGA_E("MIC1 Input", M98088_REG_35_LVL_MIC1,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("MIC2 Input", M98088_REG_36_LVL_MIC2,
+               5, 0, NULL, 0, max98088_mic_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_MICBIAS("MICBIAS", M98088_REG_4C_PWR_EN_IN, 3, 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("RECL"),
+       SND_SOC_DAPM_OUTPUT("RECR"),
+
+       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"),
+};
+
+/* DAPM AUDIO_MAP: */
+static const struct snd_soc_dapm_route audio_map[] = {
+       /* Left headphone output mixer */
+       {"Left HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left HP Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right headphone output mixer */
+       {"Right HP Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right HP Mixer", "Left DAC2 Switch", "DACL2"  },
+       {"Right HP Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right HP Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right HP Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right HP Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right HP Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right HP Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right HP Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right HP Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Left speaker output mixer */
+       {"Left SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right speaker output mixer */
+       {"Right SPK Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right SPK Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right SPK Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right SPK Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right SPK Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right SPK Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right SPK Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right SPK Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right SPK Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right SPK Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Left REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Left REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Left REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Left REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Left REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Right REC Mixer", "Left DAC1 Switch", "DACL1"},
+       {"Right REC Mixer", "Left DAC2 Switch", "DACL2"},
+       {"Right REC Mixer", "Right DAC1 Switch", "DACR1"},
+       {"Right REC Mixer", "Right DAC2 Switch", "DACR2"},
+       {"Right REC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right REC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right REC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right REC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right REC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right REC Mixer", "INB2 Switch", "INB2 Input"},
+
+       {"HP Left Out", NULL, "Left HP Mixer"},
+       {"HP Right Out", NULL, "Right HP Mixer"},
+       {"SPK Left Out", NULL, "Left SPK Mixer"},
+       {"SPK Right Out", NULL, "Right SPK Mixer"},
+       {"REC Left Out", NULL, "Left REC Mixer"},
+       {"REC Right Out", NULL, "Right REC Mixer"},
+
+       {"HPL", NULL, "HP Left Out"},
+       {"HPR", NULL, "HP Right Out"},
+       {"SPKL", NULL, "SPK Left Out"},
+       {"SPKR", NULL, "SPK Right Out"},
+       {"RECL", NULL, "REC Left Out"},
+       {"RECR", NULL, "REC Right Out"},
+
+       /* Left ADC input mixer */
+       {"Left ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Left ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Left ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Left ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Left ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Left ADC Mixer", "INB2 Switch", "INB2 Input"},
+
+       /* Right ADC input mixer */
+       {"Right ADC Mixer", "MIC1 Switch", "MIC1 Input"},
+       {"Right ADC Mixer", "MIC2 Switch", "MIC2 Input"},
+       {"Right ADC Mixer", "INA1 Switch", "INA1 Input"},
+       {"Right ADC Mixer", "INA2 Switch", "INA2 Input"},
+       {"Right ADC Mixer", "INB1 Switch", "INB1 Input"},
+       {"Right ADC Mixer", "INB2 Switch", "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"},
+       {"MIC1 Input", NULL, "MIC1"},
+       {"MIC2 Input", NULL, "MIC2"},
+};
+
+static int max98088_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, max98088_dapm_widgets,
+                                 ARRAY_SIZE(max98088_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;
+}
+
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       u8 reg15val;
+       u8 reg14msk = 0;
+       u8 reg14val = 0;
+
+       cdata = &max98088->dai[0];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* mask MAS to select slave mode */
+                       reg14msk |= M98088_DAI_MAS;
+                       /* slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* set to master mode */
+                       reg14val |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg14val |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       reg14msk |= M98088_DAI_DLY;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       reg14msk |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg14msk |= M98088_DAI_BCI;
+                       reg14val |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg14msk |= M98088_DAI_WCI;
+                       reg14val |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg14val |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       reg14msk, reg14val);
+
+               reg15val = M98088_DAI_BSEL64;
+               if (max98088->digmic)
+                       reg15val |= M98088_DAI_OSR64;
+               snd_soc_write(codec, M98088_REG_15_DAI1_CLOCK, reg15val);
+       }
+
+       return 0;
+}
+
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       u8 reg1Cmsk = 0;
+       u8 reg1Cval = 0;
+
+       cdata = &max98088->dai[1];
+
+       if (fmt != cdata->fmt) {
+               cdata->fmt = fmt;
+
+               switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+               case SND_SOC_DAIFMT_CBS_CFS:
+                       /* mask MAS to select slave mode */
+                       reg1Cmsk |= M98088_DAI_MAS;
+                       /* slave mode PLL */
+                       snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                               0x80);
+                       snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM:
+                       /* set to master mode */
+                       reg1Cval |= M98088_DAI_MAS;
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM:
+               case SND_SOC_DAIFMT_CBM_CFS:
+               default:
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+               case SND_SOC_DAIFMT_I2S:
+                       reg1Cval |= M98088_DAI_DLY;
+                       break;
+               case SND_SOC_DAIFMT_LEFT_J:
+                       reg1Cmsk |= M98088_DAI_DLY;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       reg1Cmsk |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       reg1Cmsk |= M98088_DAI_BCI;
+                       reg1Cval |= M98088_DAI_WCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       reg1Cmsk |= M98088_DAI_WCI;
+                       reg1Cval |= M98088_DAI_BCI;
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       reg1Cval |= M98088_DAI_BCI|M98088_DAI_WCI;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       reg1Cmsk, reg1Cval);
+
+               snd_soc_write(codec, M98088_REG_1D_DAI2_CLOCK,
+                       M98088_DAI_BSEL64);
+       }
+
+       return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+                                  int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+
+       /* requested clock frequency is already setup */
+       if (freq == max98088->sysclk)
+               return 0;
+
+       max98088->sysclk = freq; /* remember current sysclk */
+
+       /* setup clocks for slave mode, and using the PLL
+        * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+        *         0x02 (when master clk is 20MHz to 30MHz)..
+        */
+       if ((freq >= 10000000) && (freq < 20000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+       } else if ((freq >= 20000000) && (freq < 30000000)) {
+               snd_soc_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+       } else {
+               dev_err(codec->dev, "Invalid master clock frequency\n");
+               return -EINVAL;
+       }
+
+       if (snd_soc_read(codec, M98088_REG_51_PWR_SYS)  & M98088_SHDNRUN) {
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN, 0);
+               snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                       0, M98088_SHDNRUN);
+       }
+
+       dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+       max98088->sysclk = freq;
+       return 0;
+}
+
+/* codec mclk clock divider coefficients */
+static const struct {
+       u32 rate;
+       u8  sr;
+} rate_table[] = {
+       {8000,  0x10},
+       {11025, 0x20},
+       {16000, 0x30},
+       {22050, 0x40},
+       {24000, 0x50},
+       {32000, 0x60},
+       {44100, 0x70},
+       {48000, 0x80},
+       {88200, 0x90},
+       {96000, 0xA0},
+};
+
+static inline int rate_value(int rate, u8 *value)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+               if (rate_table[i].rate >= rate) {
+                       *value = rate_table[i].sr;
+                       return 0;
+               }
+       }
+       *value = rate_table[0].sr;
+       return -EINVAL;
+}
+
+/* Setup hw params and sample rate */
+static int max98088_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 max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 regval;
+       u16 ni;
+
+       cdata = &max98088->dai[0];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
+               if (rate_value(rate, &regval))
+                       return -EINVAL;
+
+               snd_soc_write(codec, M98088_REG_11_DAI1_CLKMODE, regval);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_14_DAI1_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0)
+                       return -EINVAL;
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               snd_soc_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+/* Setup hw params and sample rate */
+static int max98088_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 max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 regval;
+       u16 ni;
+
+       cdata = &max98088->dai[1];
+
+       rate = params_rate(params);
+
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, M98088_DAI_WS);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI2 SR2 value for the DSP */
+               if (rate_value(rate, &regval))
+                       return -EINVAL;
+
+               snd_soc_write(codec, M98088_REG_19_DAI2_CLKMODE, regval);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (snd_soc_read(codec, M98088_REG_1C_DAI2_FORMAT)
+               & M98088_DAI_MAS) {
+               if (max98088->sysclk == 0)
+                       return -EINVAL;
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               snd_soc_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               snd_soc_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       M98088_DAI_DHF, M98088_DAI_DHF);
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       int i;
+
+       if (!codec->cache_sync)
+               return;
+
+       codec->cache_only = 0;
+
+       /* write back cached values if they're writeable and
+        * different from the hardware default.
+        */
+       for (i = 1; i < ARRAY_SIZE(max98088->reg_cache); i++) {
+               if (!max98088_access[i].writable)
+                       continue;
+
+               if (max98088->reg_cache[i] == max98088_reg[i])
+                       continue;
+
+               snd_soc_write(codec, i, max98088->reg_cache[i]);
+       }
+
+       codec->cache_sync = 0;
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+                                 enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               max98088_sync_cache(codec);
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, M98088_MBEN);
+               break;
+
+       case SND_SOC_BIAS_OFF:
+               snd_soc_update_bits(codec, M98088_REG_4C_PWR_EN_IN,
+                               M98088_MBEN, 0);
+#ifdef CONFIG_REGULATOR
+               codec->cache_sync = 1;
+#endif
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai1_set_fmt,
+       .hw_params = max98088_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai2_set_fmt,
+       .hw_params = max98088_dai2_hw_params,
+};
+
+struct snd_soc_dai max98088_dai[] = {
+{
+       .name = "HiFi",
+       .playback = {
+               .stream_name = "HiFi Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .capture = {
+               .stream_name = "HiFi Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+        .ops = &max98088_dai1_ops,
+},
+{
+       .name = "Aux",
+       .playback = {
+               .stream_name = "Aux Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .ops = &max98088_dai2_ops,
+}
+};
+EXPORT_SYMBOL_GPL(max98088_dai);
+
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+       sel = cdata->eq_sel;
+
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               if (strcmp(pdata->eq1_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq1_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq1_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq1_cfg[best].name,
+               pdata->eq1_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+       coef_set = &pdata->eq1_cfg[sel];
+
+       m98088_eq_band(codec, 0, 0, coef_set->band1);
+       m98088_eq_band(codec, 0, 1, coef_set->band2);
+       m98088_eq_band(codec, 0, 2, coef_set->band3);
+       m98088_eq_band(codec, 0, 3, coef_set->band4);
+       m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->eq_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               if (strcmp(pdata->eq2_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq2_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq2_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq2_cfg[best].name,
+               pdata->eq2_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+       coef_set = &pdata->eq2_cfg[sel];
+
+       m98088_eq_band(codec, 1, 0, coef_set->band1);
+       m98088_eq_band(codec, 1, 1, coef_set->band2);
+       m98088_eq_band(codec, 1, 2, coef_set->band3);
+       m98088_eq_band(codec, 1, 3, coef_set->band4);
+       m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+               save);
+}
+
+
+static int max98088_put_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[0];
+
+       if (sel >= pdata->eq1_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq1(codec);
+       return 0;
+}
+
+static int max98088_put_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[1];
+
+       if (sel >= pdata->eq2_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq2(codec);
+       return 0;
+}
+
+static int max98088_get_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static int max98088_get_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static void max98088_handle_eq1_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq1control =
+               SOC_ENUM_EXT("EQ1 Mode",
+                       max98088->dai[0].eq_enum,
+                       max98088_get_eq1_enum,
+                       max98088_put_eq1_enum);
+
+       cdata = &max98088->dai[0];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq1_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               /* ...and remember the new version. */
+               cdata->eq_texts[i] = pdata->eq1_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ1 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       BUG_ON(&max98088->codec == 0);
+       BUG_ON((max98088->codec).card == 0);
+
+       ret = snd_soc_add_controls(&max98088->codec, &eq1control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_eq2_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq2control =
+               SOC_ENUM_EXT("EQ2 Mode",
+                       max98088->dai[1].eq_enum,
+                       max98088_get_eq2_enum,
+                       max98088_put_eq2_enum);
+
+       cdata = &max98088->dai[1];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq2_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               cdata->eq_texts[i] = pdata->eq2_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ2 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       ret = snd_soc_add_controls(&max98088->codec, &eq2control, 1);
+       if (ret != 0)
+               printk(KERN_ERR "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       u8 regval = 0;
+
+       if (!pdata)
+               return;
+
+       /* configure mic for analog/digital mic mode */
+       if (pdata->digmic_left_mode)
+               regval |= M98088_DIGMIC_L;
+
+       if (pdata->digmic_right_mode)
+               regval |= M98088_DIGMIC_R;
+
+       max98088->digmic = (regval ? 1 : 0);
+
+       snd_soc_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+       /* configure receiver output */
+       regval = ((pdata->receiver_mode) ? M98088_REC_LINEMODE : 0);
+       snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+               M98088_REC_LINEMODE_MASK, regval);
+
+       /* configure equalizers */
+       if (pdata->eq1_cfgcnt)
+               max98088_handle_eq1_pdata(max98088);
+
+       if (pdata->eq2_cfgcnt)
+               max98088_handle_eq2_pdata(max98088);
+}
+
+static int max98088_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;
+
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static int max98088_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+       int i;
+       u8 *cache = codec->reg_cache;
+
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       /* Sync reg_cache with the hardware */
+       for (i = 0; i < M98088_REG_CNT; i++) {
+               if (i == M98088_REG_51_PWR_SYS)
+                       continue;
+
+               if (!max98088_access[i].writable)
+                       continue;
+
+               max98088_hw_write(codec, i, cache[i]);
+       }
+
+       /* now enter into the resume mode bias level */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+}
+
+/*
+ * Make sure that a max98088 is attached to the I2C bus.
+ */
+static int max98088_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+       struct max98088_priv *max98088;
+       int ret = 0;
+
+       dev_printk(KERN_INFO, &pdev->dev, "MAX98088 Audio CODEC\n");
+
+       if (!max98088_codec) {
+               dev_err(&pdev->dev, "Codec device is not registered\n");
+               return -EINVAL;
+       }
+
+       socdev->card->codec = max98088_codec;
+       codec = max98088_codec;
+       max98088 = codec->private_data;
+
+       /* register pcms */
+       ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to create pcms: %d\n", ret);
+               goto pcm_err;
+       }
+
+       snd_soc_add_controls(codec, max98088_snd_controls,
+                            ARRAY_SIZE(max98088_snd_controls));
+
+       /* install additional amixer controls: EQ and excursion limiter */
+       if (max98088->pdata)
+               max98088_handle_pdata(max98088);
+       else
+               dev_err(&pdev->dev, "No platform data\n");
+
+       max98088_add_widgets(codec);
+
+       ret = snd_soc_init_card(socdev);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "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;
+}
+
+static int max98088_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)
+               max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+
+       return 0;
+}
+
+/* expose to machine driver */
+struct snd_soc_codec_device soc_codec_dev_max98088 = {
+       .probe   = max98088_probe,
+       .remove  = max98088_remove,
+       .suspend = max98088_suspend,
+       .resume  = max98088_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_max98088);
+
+
+static int max98088_register(struct max98088_priv *max98088,
+                            enum snd_soc_control_type control)
+
+{
+       int ret, i, version;
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_cdata *cdata;
+
+       if (max98088_codec) {
+               dev_err(codec->dev, "Another MAX98088 is registered\n");
+               ret = -EINVAL;
+               goto err;
+       }
+
+       mutex_init(&codec->mutex);
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       codec->name = "MAX98088";
+       codec->owner = THIS_MODULE;
+
+       /* setup DAPM event bias level and handler function */
+       codec->bias_level = SND_SOC_BIAS_STANDBY;
+       codec->set_bias_level = max98088_set_bias_level;
+
+       codec->dai = max98088_dai;
+       codec->num_dai = ARRAY_SIZE(max98088_dai);
+       codec->private_data = max98088;
+
+       codec->reg_cache_size = M98088_REG_CNT;
+       codec->reg_cache = &max98088->reg_cache;
+       codec->volatile_register = max98088_volatile_register;
+       codec->cache_sync = 1;
+       memcpy(codec->reg_cache, max98088_reg, sizeof(max98088_reg));
+
+       ret = snd_soc_codec_set_cache_io(codec, 8, 8, control);
+       if (ret != 0) {
+               dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
+               goto err;
+       }
+
+       version = snd_soc_read(codec, M98088_REG_FF_REV_ID);
+       dev_dbg(codec->dev, "MAX98088 revision 0x%02X\n", version);
+
+       /* initalize private data */
+
+       max98088->sysclk = (unsigned)-1;
+
+       cdata = &max98088->dai[0];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+
+       cdata = &max98088->dai[1];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+
+       max98088->power_state = 0; /* INA INB power enable state */
+       max98088->ex_mode = 0; /* excursion limiter mode */
+       max98088->digmic = 0; /* 0=analog, 1=digital */
+       max98088->mic1pre = 0;
+       max98088->mic2pre = 0;
+
+       snd_soc_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+
+       /* initialize registers cache to hardware default */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       snd_soc_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+       snd_soc_write(codec, M98088_REG_22_MIX_DAC,
+               M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+               M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+       snd_soc_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+       snd_soc_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+       snd_soc_write(codec, M98088_REG_16_DAI1_IOCFG,
+               M98088_S1NORMAL|M98088_SDATA);
+
+       snd_soc_write(codec, M98088_REG_1E_DAI2_IOCFG,
+               M98088_S2NORMAL|M98088_SDATA);
+
+       /* power on device */
+       max98088_codec = codec;
+       for (i = 0; i < codec->num_dai; i++)
+               max98088_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(&max98088_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(max98088);
+
+       return ret;
+}
+
+static void max98088_unregister(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       snd_soc_unregister_dais(max98088_dai, codec->num_dai);
+       snd_soc_unregister_codec(codec);
+       kfree(max98088);
+       max98088_codec = NULL;
+}
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+                            const struct i2c_device_id *id)
+{
+       struct snd_soc_codec *codec;
+       struct max98088_priv *max98088;
+
+       max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+       if (max98088 == NULL)
+               return -ENOMEM;
+
+       /* codec structure is inside the private data */
+       codec = &(max98088->codec);
+       codec->hw_write = (hw_write_t) i2c_master_send;
+
+       codec->private_data = max98088;
+       i2c_set_clientdata(i2c, max98088);
+       codec->control_data = i2c;
+
+       max98088->pdata = i2c->dev.platform_data;
+
+       codec->dev = &i2c->dev;
+
+       return max98088_register(max98088, SND_SOC_I2C);
+}
+
+static int max98088_i2c_remove(struct i2c_client *client)
+{
+       struct max98088_priv *max98088 = i2c_get_clientdata(client);
+       max98088_unregister(max98088);
+       return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+       { "max98088", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+
+static struct i2c_driver max98088_i2c_driver = {
+       .driver = {
+               .name = "MAX98088 I2C Codec",
+               .owner = THIS_MODULE,
+       },
+       .probe  = max98088_i2c_probe,
+       .remove = __devexit_p(max98088_i2c_remove),
+       .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&max98088_i2c_driver);
+       if (ret)
+               pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+       return ret;
+}
+
+static void __exit max98088_exit(void)
+{
+       i2c_del_driver(&max98088_i2c_driver);
+}
+
+module_init(max98088_init);
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang, Jesse Marroquin");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h
new file mode 100644
index 0000000..bf6fae9
--- /dev/null
+++ b/sound/soc/codecs/max98088.h
@@ -0,0 +1,193 @@
+/*
+ * max98088.h -- MAX98088 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 _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS            0x00
+#define M98088_REG_01_MIC_STATUS            0x01
+#define M98088_REG_02_JACK_STAUS            0x02
+#define M98088_REG_03_BATTERY_VOLTAGE       0x03
+#define M98088_REG_0F_IRQ_ENABLE            0x0F
+#define M98088_REG_10_SYS_CLK               0x10
+#define M98088_REG_11_DAI1_CLKMODE          0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI        0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO        0x13
+#define M98088_REG_14_DAI1_FORMAT           0x14
+#define M98088_REG_15_DAI1_CLOCK            0x15
+#define M98088_REG_16_DAI1_IOCFG            0x16
+#define M98088_REG_17_DAI1_TDM              0x17
+#define M98088_REG_18_DAI1_FILTERS          0x18
+#define M98088_REG_19_DAI2_CLKMODE          0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI        0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO        0x1B
+#define M98088_REG_1C_DAI2_FORMAT           0x1C
+#define M98088_REG_1D_DAI2_CLOCK            0x1D
+#define M98088_REG_1E_DAI2_IOCFG            0x1E
+#define M98088_REG_1F_DAI2_TDM              0x1F
+#define M98088_REG_20_DAI2_FILTERS          0x20
+#define M98088_REG_21_SRC                   0x21
+#define M98088_REG_22_MIX_DAC               0x22
+#define M98088_REG_23_MIX_ADC_LEFT          0x23
+#define M98088_REG_24_MIX_ADC_RIGHT         0x24
+#define M98088_REG_25_MIX_HP_LEFT           0x25
+#define M98088_REG_26_MIX_HP_RIGHT          0x26
+#define M98088_REG_27_MIX_HP_CNTL           0x27
+#define M98088_REG_28_MIX_REC_LEFT          0x28
+#define M98088_REG_29_MIX_REC_RIGHT         0x29
+#define M98088_REG_2A_MIC_REC_CNTL          0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT          0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT         0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL          0x2D
+#define M98088_REG_2E_LVL_SIDETONE          0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY         0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ      0x30
+#define M98088_REG_31_LVL_DAI2_PLAY         0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ      0x32
+#define M98088_REG_33_LVL_ADC_L             0x33
+#define M98088_REG_34_LVL_ADC_R             0x34
+#define M98088_REG_35_LVL_MIC1              0x35
+#define M98088_REG_36_LVL_MIC2              0x36
+#define M98088_REG_37_LVL_INA               0x37
+#define M98088_REG_38_LVL_INB               0x38
+#define M98088_REG_39_LVL_HP_L              0x39
+#define M98088_REG_3A_LVL_HP_R              0x3A
+#define M98088_REG_3B_LVL_REC_L             0x3B
+#define M98088_REG_3C_LVL_REC_R             0x3C
+#define M98088_REG_3D_LVL_SPK_L             0x3D
+#define M98088_REG_3E_LVL_SPK_R             0x3E
+#define M98088_REG_3F_MICAGC_CFG            0x3F
+#define M98088_REG_40_MICAGC_THRESH         0x40
+#define M98088_REG_41_SPKDHP                0x41
+#define M98088_REG_42_SPKDHP_THRESH         0x42
+#define M98088_REG_43_SPKALC_COMP           0x43
+#define M98088_REG_44_PWRLMT_CFG            0x44
+#define M98088_REG_45_PWRLMT_TIME           0x45
+#define M98088_REG_46_THDLMT_CFG            0x46
+#define M98088_REG_47_CFG_AUDIO_IN          0x47
+#define M98088_REG_48_CFG_MIC               0x48
+#define M98088_REG_49_CFG_LEVEL             0x49
+#define M98088_REG_4A_CFG_BYPASS            0x4A
+#define M98088_REG_4B_CFG_JACKDET           0x4B
+#define M98088_REG_4C_PWR_EN_IN             0x4C
+#define M98088_REG_4D_PWR_EN_OUT            0x4D
+#define M98088_REG_4E_BIAS_CNTL             0x4E
+#define M98088_REG_4F_DAC_BIAS1             0x4F
+#define M98088_REG_50_DAC_BIAS2             0x50
+#define M98088_REG_51_PWR_SYS               0x51
+#define M98088_REG_52_DAI1_EQ_BASE          0x52
+#define M98088_REG_84_DAI2_EQ_BASE          0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE      0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE      0xC0
+#define M98088_REG_FF_REV_ID                0xFF
+
+#define M98088_REG_CNT                      (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+       #define M98088_DAI_MAS                  (1<<7)
+       #define M98088_DAI_WCI                  (1<<6)
+       #define M98088_DAI_BCI                  (1<<5)
+       #define M98088_DAI_DLY                  (1<<4)
+       #define M98088_DAI_TDM                  (1<<2)
+       #define M98088_DAI_FSW                  (1<<1)
+       #define M98088_DAI_WS                   (1<<0)
+
+/* M98088_REG_15_DAI1_CLOCK, M98088_REG_1D_DAI2_CLOCK */
+       #define M98088_DAI_BSEL64               (1<<0)
+       #define M98088_DAI_OSR64                (1<<6)
+
+/* M98088_REG_16_DAI1_IOCFG, M98088_REG_1E_DAI2_IOCFG */
+       #define M98088_S1NORMAL                 (1<<6)
+       #define M98088_S2NORMAL                 (2<<6)
+       #define M98088_SDATA                    (3<<0)
+
+/* M98088_REG_18_DAI1_FILTERS, M98088_REG_20_DAI2_FILTERS */
+       #define M98088_DAI_DHF                  (1<<3)
+
+/* M98088_REG_22_MIX_DAC */
+       #define M98088_DAI1L_TO_DACL            (1<<7)
+       #define M98088_DAI1R_TO_DACL            (1<<6)
+       #define M98088_DAI2L_TO_DACL            (1<<5)
+       #define M98088_DAI2R_TO_DACL            (1<<4)
+       #define M98088_DAI1L_TO_DACR            (1<<3)
+       #define M98088_DAI1R_TO_DACR            (1<<2)
+       #define M98088_DAI2L_TO_DACR            (1<<1)
+       #define M98088_DAI2R_TO_DACR            (1<<0)
+
+/* M98088_REG_2A_MIC_REC_CNTL */
+       #define M98088_REC_LINEMODE             (1<<7)
+       #define M98088_REC_LINEMODE_MASK        (1<<7)
+
+/* M98088_REG_35_LVL_MIC1, M98088_REG_36_LVL_MIC2 */
+       #define M98088_MICPRE_MASK              (3<<5)
+       #define M98088_MICPRE_SHIFT             5
+
+/* M98088_REG_3A_LVL_HP_R */
+       #define M98088_HP_MUTE                  (1<<7)
+
+/* M98088_REG_3C_LVL_REC_R */
+       #define M98088_REC_MUTE                 (1<<7)
+
+/* M98088_REG_3E_LVL_SPK_R */
+       #define M98088_SP_MUTE                  (1<<7)
+
+/* M98088_REG_48_CFG_MIC */
+       #define M98088_EXTMIC_MASK              (3<<0)
+       #define M98088_DIGMIC_L                 (1<<5)
+       #define M98088_DIGMIC_R                 (1<<4)
+
+/* M98088_REG_49_CFG_LEVEL */
+       #define M98088_VSEN                     (1<<6)
+       #define M98088_ZDEN                     (1<<5)
+       #define M98088_EQ2EN                    (1<<1)
+       #define M98088_EQ1EN                    (1<<0)
+
+/* M98088_REG_4C_PWR_EN_IN */
+       #define M98088_INAEN                    (1<<7)
+       #define M98088_INBEN                    (1<<6)
+       #define M98088_MBEN                     (1<<3)
+       #define M98088_ADLEN                    (1<<1)
+       #define M98088_ADREN                    (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+       #define M98088_HPLEN                    (1<<7)
+       #define M98088_HPREN                    (1<<6)
+       #define M98088_HPEN                     ((1<<7)|(1<<6))
+       #define M98088_SPLEN                    (1<<5)
+       #define M98088_SPREN                    (1<<4)
+       #define M98088_RECEN                    (1<<3)
+       #define M98088_DALEN                    (1<<1)
+       #define M98088_DAREN                    (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+       #define M98088_SHDNRUN                  (1<<7)
+       #define M98088_PERFMODE                 (1<<3)
+       #define M98088_HPPLYBACK                (1<<2)
+       #define M98088_PWRSV8K                  (1<<1)
+       #define M98088_PWRSV                    (1<<0)
+
+#define M98088_COEFS_PER_BAND               5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+struct max98088_setup_data {
+       unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai max98088_dai[];
+extern struct snd_soc_codec_device soc_codec_dev_max98088;
+
+#endif
--
1.6.3.3

^ permalink raw reply related	[flat|nested] 46+ messages in thread

* [PATCH] ASoC: Add max98088 CODEC driver
@ 2010-08-31 21:08 Peter Hsiang
  0 siblings, 0 replies; 46+ messages in thread
From: Peter Hsiang @ 2010-08-31 21:08 UTC (permalink / raw)
  To: Jaroslav Kysela, Takashi Iwai, Peter Hsiang, Jesse Marroquin,
	Liam Girdwood

This patch adds the MAX98088 CODEC driver.

Signed-off-by: Peter Hsiang <peter.hsiang at maxim-ic.com>
---
 include/sound/max98088.h    |   87 ++
 sound/soc/codecs/Kconfig    |    4 +
 sound/soc/codecs/Makefile   |    2 +
 sound/soc/codecs/max98088.c | 2871 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/max98088.h |  161 +++
 5 files changed, 3125 insertions(+), 0 deletions(-)
 create mode 100644 include/sound/max98088.h
 create mode 100644 sound/soc/codecs/max98088.c
 create mode 100644 sound/soc/codecs/max98088.h

diff --git a/include/sound/max98088.h b/include/sound/max98088.h
new file mode 100644
index 0000000..7a6c53c
--- /dev/null
+++ b/include/sound/max98088.h
@@ -0,0 +1,87 @@
+/*
+ * Platform data for MAX98088
+ *
+ * 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 as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ */
+
+#ifndef __SOUND_MAX98088_PDATA_H__
+#define __SOUND_MAX98088_PDATA_H__
+
+#define EQ_CFG_MAX 32
+
+/* Equalizer filter response configuration */
+struct max98088_eq_cfg {
+       const char *name;
+       unsigned int rate;
+       u16 band1[5];
+       u16 band2[5];
+       u16 band3[5];
+       u16 band4[5];
+       u16 band5[5];
+};
+
+/* Speaker excursion limiter filter response configurations */
+#define EX_CFG_MAX 32
+
+struct max98088_excursion_cfg {
+       const char *name;
+       unsigned int rate;
+       u16 resp[5];
+};
+
+/* codec platform data */
+struct max98088_pdata {
+
+       /* Equalizers for DAI1 and DAI2 */
+       struct max98088_eq_cfg *eq1_cfg;
+       struct max98088_eq_cfg *eq2_cfg;
+       unsigned int eq1_cfgcnt;
+       unsigned int eq2_cfgcnt;
+
+       /* Excursion limiters for DAI1 and DAI2 */
+       struct max98088_excursion_cfg *ex1_cfg;
+       struct max98088_excursion_cfg *ex2_cfg;
+       unsigned int ex1_cfgcnt;
+       unsigned int ex2_cfgcnt;
+
+       /* Receiver output can be configured as power amplifier or LINE out */
+       /* Set receiver_mode to:
+        * 0 = amplifier output, or
+        * 1 = LINE level output
+        */
+       unsigned int receiver_mode;
+
+       /* Analog/digital microphone configuration:
+        * 0 = analog microphone input (normal setting)
+        * 1 = digital microhpone input
+        */
+       unsigned int digmic_left_enable;
+       unsigned int digmic_right_enable;
+
+       /* Normal microphone connection, or external connection through INA
+        * 0 = normal connection
+        * 1 = INA1 input
+        * 2 = INA2 input
+        */
+       unsigned int extmic_mode;
+
+       /* Bypass option for INA to MIC1 connection
+        * 0 = normal setting
+        * 1 = bypass enabled
+        */
+       unsigned int ina_to_mic1_bypass;
+
+       /* Bypass option for MIC1 to MIC2 connection
+        * 0 = normal setting
+        * 1 = bypass enabled
+        */
+       unsigned int mic1_to_mic2_bypass;
+};
+
+#endif
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 155c127..e9a3c74 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -28,6 +28,7 @@ config SND_SOC_ALL_CODECS
        select SND_SOC_DA7210 if I2C
        select SND_SOC_JZ4740 if SOC_JZ4740
        select SND_SOC_MAX9877 if I2C
+       select SND_SOC_MAX98088 if I2C
        select SND_SOC_PCM3008
        select SND_SOC_SPDIF
        select SND_SOC_SSM2602 if I2C
@@ -156,6 +157,9 @@ config SND_SOC_L3
 config SND_SOC_DA7210
         tristate

+config SND_SOC_MAX98088
+       tristate
+
 config SND_SOC_PCM3008
        tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 10d468e..fe9d104 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -15,6 +15,7 @@ snd-soc-cs4270-objs := cs4270.o
 snd-soc-cx20442-objs := cx20442.o
 snd-soc-da7210-objs := da7210.o
 snd-soc-l3-objs := l3.o
+snd-soc-max98088-objs := max98088.o
 snd-soc-pcm3008-objs := pcm3008.o
 snd-soc-spdif-objs := spdif_transciever.o
 snd-soc-ssm2602-objs := ssm2602.o
@@ -87,6 +88,7 @@ obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
 obj-$(CONFIG_SND_SOC_DA7210)   += snd-soc-da7210.o
 obj-$(CONFIG_SND_SOC_L3)       += snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_JZ4740_CODEC)     += snd-soc-jz4740-codec.o
+obj-$(CONFIG_SND_SOC_MAX98088)  += snd-soc-max98088.o
 obj-$(CONFIG_SND_SOC_PCM3008)  += snd-soc-pcm3008.o
 obj-$(CONFIG_SND_SOC_SPDIF)    += snd-soc-spdif.o
 obj-$(CONFIG_SND_SOC_SSM2602)  += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c
new file mode 100644
index 0000000..72f599f
--- /dev/null
+++ b/sound/soc/codecs/max98088.c
@@ -0,0 +1,2871 @@
+/*
+ * max98088.c -- MAX98088 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 <linux/slab.h>
+#include <sound/max98088.h>
+#include "max98088.h"
+
+static struct snd_soc_codec *max98088_codec;
+struct snd_soc_codec_device soc_codec_dev_max98088;
+
+/* configurations associated with each channel of DAI stream */
+struct max98088_cdata {
+       unsigned int rate;
+       unsigned int fmt;
+
+       /* Equalizer parameters */
+       int eq_textcnt;
+       const char *eq_texts[EQ_CFG_MAX];
+       int eq_sel;
+       struct soc_enum eq_enum;
+
+       /* Excursion limiter parameters */
+       int ex_textcnt;
+       const char *ex_texts[EX_CFG_MAX];
+       int ex_sel;
+       struct soc_enum ex_enum;
+};
+
+/* codec private data */
+struct max98088_priv {
+       struct snd_soc_codec codec;
+       struct max98088_pdata *pdata;
+       u8 reg_cache[M98088_REG_CNT];
+       unsigned int sysclk;
+       struct max98088_cdata dai[2];
+       u8 power_state;
+       unsigned int ex_mode;
+       unsigned int digmic_mode;
+};
+
+static const u8 max98088_reg[M98088_REG_CNT] = {
+       0x00, /* 00 IRQ status */
+       0x00, /* 01 MIC status */
+       0x00, /* 02 jack status */
+       0x00, /* 03 battery voltage */
+       0x00, /* 04 */
+       0x00, /* 05 */
+       0x00, /* 06 */
+       0x00, /* 07 */
+       0x00, /* 08 */
+       0x00, /* 09 */
+       0x00, /* 0A */
+       0x00, /* 0B */
+       0x00, /* 0C */
+       0x00, /* 0D */
+       0x00, /* 0E */
+       0x00, /* 0F interrupt enable */
+
+       0x00, /* 10 master clock */
+       0x00, /* 11 DAI1 clock mode */
+       0x00, /* 12 DAI1 clock control */
+       0x00, /* 13 DAI1 clock control */
+       0x00, /* 14 DAI1 format */
+       0x00, /* 15 DAI1 clock */
+       0x00, /* 16 DAI1 config */
+       0x00, /* 17 DAI1 TDM */
+       0x00, /* 18 DAI1 filters */
+       0x00, /* 19 DAI2 clock mode */
+       0x00, /* 1A DAI2 clock control */
+       0x00, /* 1B DAI2 clock control */
+       0x00, /* 1C DAI2 format */
+       0x00, /* 1D DAI2 clock */
+       0x00, /* 1E DAI2 config */
+       0x00, /* 1F DAI2 TDM */
+
+       0x00, /* 20 DAI2 filters */
+       0x00, /* 21 data config */
+       0x00, /* 22 DAC mixer */
+       0x00, /* 23 left ADC mixer */
+       0x00, /* 24 right ADC mixer */
+       0x00, /* 25 left HP mixer */
+       0x00, /* 26 right HP mixer */
+       0x00, /* 27 HP control */
+       0x00, /* 28 left REC mixer */
+       0x00, /* 29 right REC mixer */
+       0x00, /* 2A REC control */
+       0x00, /* 2B left SPK mixer */
+       0x00, /* 2C right SPK mixer */
+       0x00, /* 2D SPK control */
+       0x00, /* 2E sidetone */
+       0x00, /* 2F DAI1 playback level */
+
+       0x00, /* 30 DAI1 playback level */
+       0x00, /* 31 DAI2 playback level */
+       0x00, /* 32 DAI2 playbakc level */
+       0x00, /* 33 left ADC level */
+       0x00, /* 34 right ADC level */
+       0x00, /* 35 MIC1 level */
+       0x00, /* 36 MIC2 level */
+       0x00, /* 37 INA level */
+       0x00, /* 38 INB level */
+       0x00, /* 39 left HP volume */
+       0x00, /* 3A right HP volume */
+       0x00, /* 3B left REC volume */
+       0x00, /* 3C right REC volume */
+       0x00, /* 3D left SPK volume */
+       0x00, /* 3E right SPK volume */
+       0x00, /* 3F MIC config */
+
+       0x00, /* 40 MIC threshold */
+       0x00, /* 41 excursion limiter filter */
+       0x00, /* 42 excursion limiter threshold */
+       0x00, /* 43 ALC */
+       0x00, /* 44 power limiter threshold */
+       0x00, /* 45 power limiter config */
+       0x00, /* 46 distortion limiter config */
+       0x00, /* 47 audio input */
+       0x00, /* 48 microphone */
+       0x00, /* 49 level control */
+       0x00, /* 4A bypass switches */
+       0x00, /* 4B jack detect */
+       0x00, /* 4C input enable */
+       0x00, /* 4D output enable */
+       0xF0, /* 4E bias control */
+       0x00, /* 4F DAC power */
+
+       0x0F, /* 50 DAC power */
+       0x00, /* 51 system */
+       0x00, /* 52 DAI1 EQ1 */
+       0x00, /* 53 DAI1 EQ1 */
+       0x00, /* 54 DAI1 EQ1 */
+       0x00, /* 55 DAI1 EQ1 */
+       0x00, /* 56 DAI1 EQ1 */
+       0x00, /* 57 DAI1 EQ1 */
+       0x00, /* 58 DAI1 EQ1 */
+       0x00, /* 59 DAI1 EQ1 */
+       0x00, /* 5A DAI1 EQ1 */
+       0x00, /* 5B DAI1 EQ1 */
+       0x00, /* 5C DAI1 EQ2 */
+       0x00, /* 5D DAI1 EQ2 */
+       0x00, /* 5E DAI1 EQ2 */
+       0x00, /* 5F DAI1 EQ2 */
+
+       0x00, /* 60 DAI1 EQ2 */
+       0x00, /* 61 DAI1 EQ2 */
+       0x00, /* 62 DAI1 EQ2 */
+       0x00, /* 63 DAI1 EQ2 */
+       0x00, /* 64 DAI1 EQ2 */
+       0x00, /* 65 DAI1 EQ2 */
+       0x00, /* 66 DAI1 EQ3 */
+       0x00, /* 67 DAI1 EQ3 */
+       0x00, /* 68 DAI1 EQ3 */
+       0x00, /* 69 DAI1 EQ3 */
+       0x00, /* 6A DAI1 EQ3 */
+       0x00, /* 6B DAI1 EQ3 */
+       0x00, /* 6C DAI1 EQ3 */
+       0x00, /* 6D DAI1 EQ3 */
+       0x00, /* 6E DAI1 EQ3 */
+       0x00, /* 6F DAI1 EQ3 */
+
+       0x00, /* 70 DAI1 EQ4 */
+       0x00, /* 71 DAI1 EQ4 */
+       0x00, /* 72 DAI1 EQ4 */
+       0x00, /* 73 DAI1 EQ4 */
+       0x00, /* 74 DAI1 EQ4 */
+       0x00, /* 75 DAI1 EQ4 */
+       0x00, /* 76 DAI1 EQ4 */
+       0x00, /* 77 DAI1 EQ4 */
+       0x00, /* 78 DAI1 EQ4 */
+       0x00, /* 79 DAI1 EQ4 */
+       0x00, /* 7A DAI1 EQ5 */
+       0x00, /* 7B DAI1 EQ5 */
+       0x00, /* 7C DAI1 EQ5 */
+       0x00, /* 7D DAI1 EQ5 */
+       0x00, /* 7E DAI1 EQ5 */
+       0x00, /* 7F DAI1 EQ5 */
+
+       0x00, /* 80 DAI1 EQ5 */
+       0x00, /* 81 DAI1 EQ5 */
+       0x00, /* 82 DAI1 EQ5 */
+       0x00, /* 83 DAI1 EQ5 */
+       0x00, /* 84 DAI2 EQ1 */
+       0x00, /* 85 DAI2 EQ1 */
+       0x00, /* 86 DAI2 EQ1 */
+       0x00, /* 87 DAI2 EQ1 */
+       0x00, /* 88 DAI2 EQ1 */
+       0x00, /* 89 DAI2 EQ1 */
+       0x00, /* 8A DAI2 EQ1 */
+       0x00, /* 8B DAI2 EQ1 */
+       0x00, /* 8C DAI2 EQ1 */
+       0x00, /* 8D DAI2 EQ1 */
+       0x00, /* 8E DAI2 EQ2 */
+       0x00, /* 8F DAI2 EQ2 */
+
+       0x00, /* 90 DAI2 EQ2 */
+       0x00, /* 91 DAI2 EQ2 */
+       0x00, /* 92 DAI2 EQ2 */
+       0x00, /* 93 DAI2 EQ2 */
+       0x00, /* 94 DAI2 EQ2 */
+       0x00, /* 95 DAI2 EQ2 */
+       0x00, /* 96 DAI2 EQ2 */
+       0x00, /* 97 DAI2 EQ2 */
+       0x00, /* 98 DAI2 EQ3 */
+       0x00, /* 99 DAI2 EQ3 */
+       0x00, /* 9A DAI2 EQ3 */
+       0x00, /* 9B DAI2 EQ3 */
+       0x00, /* 9C DAI2 EQ3 */
+       0x00, /* 9D DAI2 EQ3 */
+       0x00, /* 9E DAI2 EQ3 */
+       0x00, /* 9F DAI2 EQ3 */
+
+       0x00, /* A0 DAI2 EQ3 */
+       0x00, /* A1 DAI2 EQ3 */
+       0x00, /* A2 DAI2 EQ4 */
+       0x00, /* A3 DAI2 EQ4 */
+       0x00, /* A4 DAI2 EQ4 */
+       0x00, /* A5 DAI2 EQ4 */
+       0x00, /* A6 DAI2 EQ4 */
+       0x00, /* A7 DAI2 EQ4 */
+       0x00, /* A8 DAI2 EQ4 */
+       0x00, /* A9 DAI2 EQ4 */
+       0x00, /* AA DAI2 EQ4 */
+       0x00, /* AB DAI2 EQ4 */
+       0x00, /* AC DAI2 EQ5 */
+       0x00, /* AD DAI2 EQ5 */
+       0x00, /* AE DAI2 EQ5 */
+       0x00, /* AF DAI2 EQ5 */
+
+       0x00, /* B0 DAI2 EQ5 */
+       0x00, /* B1 DAI2 EQ5 */
+       0x00, /* B2 DAI2 EQ5 */
+       0x00, /* B3 DAI2 EQ5 */
+       0x00, /* B4 DAI2 EQ5 */
+       0x00, /* B5 DAI2 EQ5 */
+       0x00, /* B6 DAI1 biquad */
+       0x00, /* B7 DAI1 biquad */
+       0x00, /* B8 DAI1 biquad */
+       0x00, /* B9 DAI1 biquad */
+       0x00, /* BA DAI1 biquad */
+       0x00, /* BB DAI1 biquad */
+       0x00, /* BC DAI1 biquad */
+       0x00, /* BD DAI1 biquad */
+       0x00, /* BE DAI1 biquad */
+       0x00, /* BF DAI1 biquad */
+
+       0x00, /* C0 DAI2 biquad */
+       0x00, /* C1 DAI2 biquad */
+       0x00, /* C2 DAI2 biquad */
+       0x00, /* C3 DAI2 biquad */
+       0x00, /* C4 DAI2 biquad */
+       0x00, /* C5 DAI2 biquad */
+       0x00, /* C6 DAI2 biquad */
+       0x00, /* C7 DAI2 biquad */
+       0x00, /* C8 DAI2 biquad */
+       0x00, /* C9 DAI2 biquad */
+       0x00, /* CA */
+       0x00, /* CB */
+       0x00, /* CC */
+       0x00, /* CD */
+       0x00, /* CE */
+       0x00, /* CF */
+
+       0x00, /* D0 */
+       0x00, /* D1 */
+       0x00, /* D2 */
+       0x00, /* D3 */
+       0x00, /* D4 */
+       0x00, /* D5 */
+       0x00, /* D6 */
+       0x00, /* D7 */
+       0x00, /* D8 */
+       0x00, /* D9 */
+       0x00, /* DA */
+       0x70, /* DB */
+       0x00, /* DC */
+       0x00, /* DD */
+       0x00, /* DE */
+       0x00, /* DF */
+
+       0x00, /* E0 */
+       0x00, /* E1 */
+       0x00, /* E2 */
+       0x00, /* E3 */
+       0x00, /* E4 */
+       0x00, /* E5 */
+       0x00, /* E6 */
+       0x00, /* E7 */
+       0x00, /* E8 */
+       0x00, /* E9 */
+       0x00, /* EA */
+       0x00, /* EB */
+       0x00, /* EC */
+       0x00, /* ED */
+       0x00, /* EE */
+       0x00, /* EF */
+
+       0x00, /* F0 */
+       0x00, /* F1 */
+       0x00, /* F2 */
+       0x00, /* F3 */
+       0x00, /* F4 */
+       0x00, /* F5 */
+       0x00, /* F6 */
+       0x00, /* F7 */
+       0x00, /* F8 */
+       0x00, /* F9 */
+       0x00, /* FA */
+       0x00, /* FB */
+       0x00, /* FC */
+       0x00, /* FD */
+       0x00, /* FE */
+       0x00, /* FF */
+};
+
+static struct {
+       int readable;
+       int writable;
+       int vol;
+} max98088_access[M98088_REG_CNT] = {
+       { 0xFF, 0xFF, 1 }, /* 00 IRQ status */
+       { 0xFF, 0x00, 1 }, /* 01 MIC status */
+       { 0xFF, 0x00, 1 }, /* 02 jack status */
+       { 0x1F, 0x1F, 1 }, /* 03 battery voltage */
+       { 0xFF, 0xFF, 0 }, /* 04 */
+       { 0xFF, 0xFF, 0 }, /* 05 */
+       { 0xFF, 0xFF, 0 }, /* 06 */
+       { 0xFF, 0xFF, 0 }, /* 07 */
+       { 0xFF, 0xFF, 0 }, /* 08 */
+       { 0xFF, 0xFF, 0 }, /* 09 */
+       { 0xFF, 0xFF, 0 }, /* 0A */
+       { 0xFF, 0xFF, 0 }, /* 0B */
+       { 0xFF, 0xFF, 0 }, /* 0C */
+       { 0xFF, 0xFF, 0 }, /* 0D */
+       { 0xFF, 0xFF, 0 }, /* 0E */
+       { 0xFF, 0xFF, 0 }, /* 0F interrupt enable */
+
+       { 0xFF, 0xFF, 0 }, /* 10 master clock */
+       { 0xFF, 0xFF, 0 }, /* 11 DAI1 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 12 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 13 DAI1 clock control */
+       { 0xFF, 0xFF, 0 }, /* 14 DAI1 format */
+       { 0xFF, 0xFF, 0 }, /* 15 DAI1 clock */
+       { 0xFF, 0xFF, 0 }, /* 16 DAI1 config */
+       { 0xFF, 0xFF, 0 }, /* 17 DAI1 TDM */
+       { 0xFF, 0xFF, 0 }, /* 18 DAI1 filters */
+       { 0xFF, 0xFF, 0 }, /* 19 DAI2 clock mode */
+       { 0xFF, 0xFF, 0 }, /* 1A DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1B DAI2 clock control */
+       { 0xFF, 0xFF, 0 }, /* 1C DAI2 format */
+       { 0xFF, 0xFF, 0 }, /* 1D DAI2 clock */
+       { 0xFF, 0xFF, 0 }, /* 1E DAI2 config */
+       { 0xFF, 0xFF, 0 }, /* 1F DAI2 TDM */
+
+       { 0xFF, 0xFF, 0 }, /* 20 DAI2 filters */
+       { 0xFF, 0xFF, 0 }, /* 21 data config */
+       { 0xFF, 0xFF, 0 }, /* 22 DAC mixer */
+       { 0xFF, 0xFF, 0 }, /* 23 left ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 24 right ADC mixer */
+       { 0xFF, 0xFF, 0 }, /* 25 left HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 26 right HP mixer */
+       { 0xFF, 0xFF, 0 }, /* 27 HP control */
+       { 0xFF, 0xFF, 0 }, /* 28 left REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 29 right REC mixer */
+       { 0xFF, 0xFF, 0 }, /* 2A REC control */
+       { 0xFF, 0xFF, 0 }, /* 2B left SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2C right SPK mixer */
+       { 0xFF, 0xFF, 0 }, /* 2D SPK control */
+       { 0xFF, 0xFF, 0 }, /* 2E sidetone */
+       { 0xFF, 0xFF, 0 }, /* 2F DAI1 playback level */
+
+       { 0xFF, 0xFF, 0 }, /* 30 DAI1 playback level */
+       { 0xFF, 0xFF, 0 }, /* 31 DAI2 playback level */
+       { 0xFF, 0xFF, 0 }, /* 32 DAI2 playbakc level */
+       { 0xFF, 0xFF, 0 }, /* 33 left ADC level */
+       { 0xFF, 0xFF, 0 }, /* 34 right ADC level */
+       { 0xFF, 0xFF, 0 }, /* 35 MIC1 level */
+       { 0xFF, 0xFF, 0 }, /* 36 MIC2 level */
+       { 0xFF, 0xFF, 0 }, /* 37 INA level */
+       { 0xFF, 0xFF, 0 }, /* 38 INB level */
+       { 0xFF, 0xFF, 0 }, /* 39 left HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3A right HP volume */
+       { 0xFF, 0xFF, 0 }, /* 3B left REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3C right REC volume */
+       { 0xFF, 0xFF, 0 }, /* 3D left SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3E right SPK volume */
+       { 0xFF, 0xFF, 0 }, /* 3F MIC config */
+
+       { 0xFF, 0xFF, 0 }, /* 40 MIC threshold */
+       { 0xFF, 0xFF, 0 }, /* 41 excursion limiter filter */
+       { 0xFF, 0xFF, 0 }, /* 42 excursion limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 43 ALC */
+       { 0xFF, 0xFF, 0 }, /* 44 power limiter threshold */
+       { 0xFF, 0xFF, 0 }, /* 45 power limiter config */
+       { 0xFF, 0xFF, 0 }, /* 46 distortion limiter config */
+       { 0xFF, 0xFF, 0 }, /* 47 audio input */
+       { 0xFF, 0xFF, 0 }, /* 48 microphone */
+       { 0xFF, 0xFF, 0 }, /* 49 level control */
+       { 0xFF, 0xFF, 0 }, /* 4A bypass switches */
+       { 0xFF, 0xFF, 0 }, /* 4B jack detect */
+       { 0xFF, 0xFF, 0 }, /* 4C input enable */
+       { 0xFF, 0xFF, 0 }, /* 4D output enable */
+       { 0xFF, 0xFF, 0 }, /* 4E bias control */
+       { 0xFF, 0xFF, 0 }, /* 4F DAC power */
+
+       { 0xFF, 0xFF, 0 }, /* 50 DAC power */
+       { 0xFF, 0xFF, 0 }, /* 51 system */
+       { 0xFF, 0xFF, 0 }, /* 52 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 53 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 54 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 55 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 56 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 57 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 58 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 59 DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5A DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5B DAI1 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 5C DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5D DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5E DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 5F DAI1 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 60 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 61 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 62 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 63 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 64 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 65 DAI1 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 66 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 67 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 68 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 69 DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6A DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6B DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6C DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6D DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6E DAI1 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 6F DAI1 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* 70 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 71 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 72 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 73 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 74 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 75 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 76 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 77 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 78 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 79 DAI1 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* 7A DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7B DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7C DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7D DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7E DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 7F DAI1 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* 80 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 81 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 82 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 83 DAI1 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* 84 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 85 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 86 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 87 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 88 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 89 DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8A DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8B DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8C DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8D DAI2 EQ1 */
+       { 0xFF, 0xFF, 0 }, /* 8E DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 8F DAI2 EQ2 */
+
+       { 0xFF, 0xFF, 0 }, /* 90 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 91 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 92 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 93 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 94 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 95 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 96 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 97 DAI2 EQ2 */
+       { 0xFF, 0xFF, 0 }, /* 98 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 99 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9A DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9B DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9C DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9D DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9E DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* 9F DAI2 EQ3 */
+
+       { 0xFF, 0xFF, 0 }, /* A0 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A1 DAI2 EQ3 */
+       { 0xFF, 0xFF, 0 }, /* A2 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A3 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A4 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A5 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A6 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A7 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A8 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* A9 DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AA DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AB DAI2 EQ4 */
+       { 0xFF, 0xFF, 0 }, /* AC DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AD DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AE DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* AF DAI2 EQ5 */
+
+       { 0xFF, 0xFF, 0 }, /* B0 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B1 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B2 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B3 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B4 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B5 DAI2 EQ5 */
+       { 0xFF, 0xFF, 0 }, /* B6 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B7 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B8 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* B9 DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BA DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BB DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BC DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BD DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BE DAI1 biquad */
+       { 0xFF, 0xFF, 0 }, /* BF DAI1 biquad */
+
+       { 0xFF, 0xFF, 0 }, /* C0 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C1 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C2 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C3 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C4 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C5 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C6 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C7 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C8 DAI2 biquad */
+       { 0xFF, 0xFF, 0 }, /* C9 DAI2 biquad */
+       { 0x00, 0x00, 0 }, /* CA */
+       { 0x00, 0x00, 0 }, /* CB */
+       { 0x00, 0x00, 0 }, /* CC */
+       { 0x00, 0x00, 0 }, /* CD */
+       { 0x00, 0x00, 0 }, /* CE */
+       { 0x00, 0x00, 0 }, /* CF */
+
+       { 0x00, 0x00, 0 }, /* D0 */
+       { 0x00, 0x00, 0 }, /* D1 */
+       { 0x00, 0x00, 0 }, /* D2 */
+       { 0x00, 0x00, 0 }, /* D3 */
+       { 0x00, 0x00, 0 }, /* D4 */
+       { 0x00, 0x00, 0 }, /* D5 */
+       { 0x00, 0x00, 0 }, /* D6 */
+       { 0x00, 0x00, 0 }, /* D7 */
+       { 0x00, 0x00, 0 }, /* D8 */
+       { 0x00, 0x00, 0 }, /* D9 */
+       { 0x00, 0x00, 0 }, /* DA */
+       { 0x70, 0x70, 0 }, /* DB */
+       { 0x00, 0x00, 0 }, /* DC */
+       { 0x00, 0x00, 0 }, /* DD */
+       { 0x00, 0x00, 0 }, /* DE */
+       { 0x00, 0x00, 0 }, /* DF */
+
+       { 0x00, 0x00, 0 }, /* E0 */
+       { 0x00, 0x00, 0 }, /* E1 */
+       { 0x00, 0x00, 0 }, /* E2 */
+       { 0x00, 0x00, 0 }, /* E3 */
+       { 0x00, 0x00, 0 }, /* E4 */
+       { 0x00, 0x00, 0 }, /* E5 */
+       { 0x00, 0x00, 0 }, /* E6 */
+       { 0x00, 0x00, 0 }, /* E7 */
+       { 0x00, 0x00, 0 }, /* E8 */
+       { 0x00, 0x00, 0 }, /* E9 */
+       { 0x00, 0x00, 0 }, /* EA */
+       { 0x00, 0x00, 0 }, /* EB */
+       { 0x00, 0x00, 0 }, /* EC */
+       { 0x00, 0x00, 0 }, /* ED */
+       { 0x00, 0x00, 0 }, /* EE */
+       { 0x00, 0x00, 0 }, /* EF */
+
+       { 0x00, 0x00, 0 }, /* F0 */
+       { 0x00, 0x00, 0 }, /* F1 */
+       { 0x00, 0x00, 0 }, /* F2 */
+       { 0x00, 0x00, 0 }, /* F3 */
+       { 0x00, 0x00, 0 }, /* F4 */
+       { 0x00, 0x00, 0 }, /* F5 */
+       { 0x00, 0x00, 0 }, /* F6 */
+       { 0x00, 0x00, 0 }, /* F7 */
+       { 0x00, 0x00, 0 }, /* F8 */
+       { 0x00, 0x00, 0 }, /* F9 */
+       { 0x00, 0x00, 0 }, /* FA */
+       { 0x00, 0x00, 0 }, /* FB */
+       { 0x00, 0x00, 0 }, /* FC */
+       { 0x00, 0x00, 0 }, /* FD */
+       { 0x00, 0x00, 0 }, /* FE */
+       { 0xFF, 0x00, 1 }, /* FF */
+};
+
+
+static int max98088_volatile_register(unsigned int reg)
+{
+       return max98088_access[reg].vol;
+}
+
+/*
+ * Read the MAX98088 I2C register space
+ * Note: this driver source code is backward compatible to kernel
+ * version 2.6.32.
+ */
+static unsigned int max98088_read(struct snd_soc_codec *codec,
+                                 unsigned int reg)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct i2c_msg msg[2];
+       struct i2c_client *client;
+       u8 data[2];
+       int ret;
+
+       client = (struct i2c_client *)codec->control_data;
+
+       if (max98088_volatile_register(reg)) {
+               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;
+
+               ret = i2c_transfer(client->adapter, &msg[0], 2);
+               return (ret == 2) ? data[1] : -EIO;
+       } else {
+               if (reg > (ARRAY_SIZE(max98088_reg)))
+                       return -EIO;
+               return max98088->reg_cache[reg];
+       }
+}
+
+/* Write to the MAX98088 register space (cached) */
+static int max98088_write(struct snd_soc_codec *codec, unsigned int reg,
+                         unsigned int value)
+{
+       u8 data[2];
+       struct max98088_priv *max98088 = codec->private_data;
+
+       data[0] = reg;
+       data[1] = value;
+       if (codec->hw_write(codec->control_data, data, 2) == 2) {
+               max98088->reg_cache[reg] =
+                       (value & max98088_access[reg].writable);
+               return 0;
+       } else {
+               return -EIO;
+       }
+}
+
+/* Write to the MAX98088 I2C register space */
+static int max98088_hw_write(struct snd_soc_codec *codec, unsigned int reg,
+                            unsigned int value)
+{
+       u8 data[2];
+
+       data[0] = reg;
+       data[1] = value;
+       if (codec->hw_write(codec->control_data, data, 2) == 2)
+               return 0;
+       else
+               return -EIO;
+}
+
+/*
+ * For kernels compiled without unsigned long long int division
+ */
+unsigned long long int ulldiv(unsigned long long int dividend,
+                             unsigned long long int divisor)
+{
+       unsigned long long int quotient = 0;
+       int shift = 1;
+
+       if (divisor == 0)
+               return 0;
+
+       /* result is 1.0 if divisor and dividend are equal */
+       if (divisor == dividend)
+               return 1;
+
+       /* Normalize divisor */
+       while (!(divisor & 0x8000000000000000ULL)) {
+               divisor <<= 1;
+               ++shift;
+       }
+
+       /* Shift and subtract */
+       while (shift--) {
+               quotient <<= 1;
+
+               if (divisor <= dividend) {
+                       dividend -= divisor;
+                       ++quotient;
+               }
+               divisor >>= 1;
+       }
+
+       /* Round up */
+       if (dividend > divisor)
+               ++quotient;
+
+       return quotient;
+}
+
+#define INA1_PGA_BIT 0x01
+#define INA2_PGA_BIT 0x02
+#define INB1_PGA_BIT 0x04
+#define INB2_PGA_BIT 0x08
+/*
+ * The INx1 and INx2 PGAs 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.
+ */
+static int max98088_pga_event(struct snd_soc_dapm_widget *w,
+                             struct snd_kcontrol *k, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       u8 *state = &max98088->power_state;
+       unsigned int val;
+       unsigned int pga;
+       unsigned int mask;
+
+       BUG_ON(w->reg != M98088_REG_4C_PWR_EN_IN);
+
+       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;
+       }
+
+       if (event == SND_SOC_DAPM_POST_PMU) {
+               /* ON */
+               *state |= pga;
+
+               /* Turn on, avoiding unnecessary writes */
+               val = max98088_read(codec, w->reg);
+               if (!(val & (1 << w->shift))) {
+                       val |= (1 << w->shift);
+                       max98088_write(codec, w->reg, val);
+               }
+       } else if (event == SND_SOC_DAPM_POST_PMD) {
+               /* OFF */
+               *state &= ~pga;
+
+               /* Turn off if both are off, avoiding unnecessary writes */
+               if (!(*state & mask)) {
+                       val = max98088_read(codec, w->reg);
+                       if (val & (1 << w->shift)) {
+                               val &= ~(1 << w->shift);
+                               max98088_write(codec, w->reg, val);
+                       }
+               }
+       } else {
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/*
+ * Load equalizer DSP coefficient configurations registers
+ */
+void m98088_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+                   unsigned int band, u16 *coefs)
+{
+       unsigned int eq_reg;
+       unsigned int i;
+
+       if (band > 4)
+               return;
+
+       if (dai > 1)
+               return;
+
+       /* Load the base register address */
+       eq_reg = dai ? M98088_REG_84_DAI2_EQ_BASE : M98088_REG_52_DAI1_EQ_BASE;
+
+       /* Add the band address offset, note adjustment for word address */
+       eq_reg += band * (M98088_COEFS_PER_BAND << 1);
+
+       /* Step through the registers and coefs */
+       for (i = 0; i < M98088_COEFS_PER_BAND; i++) {
+               max98088_write(codec, eq_reg++, M98088_BYTE1(coefs[i]));
+               max98088_write(codec, eq_reg++, M98088_BYTE0(coefs[i]));
+       }
+
+       return;
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max98088_ex_mode[] = {
+       "Off",
+       "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,           /* disabled */
+       (0<<4)|3,       /* 100Hz */
+       (1<<4)|0,       /* 400Hz */
+       (2<<4)|0,       /* 600Hz */
+       (3<<4)|0,       /* 800Hz */
+       (4<<4)|0,       /* 1000Hz */
+       (1<<4)|1,       /* 200-400Hz */
+       (2<<4)|2,       /* 400-600Hz */
+       (3<<4)|2,       /* 400-800Hz */
+       (1<<4)|3,       /* user-400Hz */
+       (2<<4)|3,       /* user-600Hz */
+       (3<<4)|3,       /* user-800Hz */
+       (4<<4)|3    /* user-1000Hz */
+};
+
+static const struct soc_enum max98088_ex_mode_enum[] = {
+       SOC_ENUM_SINGLE_EXT(13, max98088_ex_mode),
+};
+
+/*
+ * Excursion limiter mode - set mode
+ */
+static int max98088_ex_mode_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->ex_mode;
+
+       *mode = ucontrol->value.integer.value[0];
+
+       if (*mode <= ARRAY_SIZE(ex_mode_table))
+               max98088_write(codec, M98088_REG_41_SPKDHP,
+                       ex_mode_table[*mode]);
+
+       return 0;
+}
+
+/*
+ * Excursion limiter mode - get mode
+ */
+static int max98088_ex_mode_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       unsigned int *mode = &max98088->ex_mode;
+
+       ucontrol->value.integer.value[0] = *mode;
+       return 0;
+}
+
+/*
+ * Load user programmable mode excursion limiter filter coefficients
+ */
+static void max98088_ex_resp_control(struct snd_soc_codec *codec,
+                                    int reg, u16 *param)
+{
+       max98088_write(codec,   reg, M98088_BYTE1(param[0]));
+       max98088_write(codec, ++reg, M98088_BYTE0(param[0]));
+       max98088_write(codec, ++reg, M98088_BYTE1(param[1]));
+       max98088_write(codec, ++reg, M98088_BYTE0(param[1]));
+       max98088_write(codec, ++reg, M98088_BYTE1(param[2]));
+       max98088_write(codec, ++reg, M98088_BYTE0(param[2]));
+       max98088_write(codec, ++reg, M98088_BYTE1(param[3]));
+       max98088_write(codec, ++reg, M98088_BYTE0(param[3]));
+       max98088_write(codec, ++reg, M98088_BYTE1(param[4]));
+       max98088_write(codec, ++reg, M98088_BYTE0(param[4]));
+}
+
+static const char *max98088_hp_spk_mute[] = {"disable", "enable"};
+static const struct soc_enum max98088_hp_spk_mute_enum[] = {
+       SOC_ENUM_SINGLE_EXT(2, max98088_hp_spk_mute),
+};
+/* These defines correlate to the position of the like named value in
+ * max98088_hp_spk_mute[] */
+#define MAX98088_HP_SPK_MUTE_DISABLE 0
+#define MAX98088_HP_SPK_MUTE_ENABLE 1
+
+/*
+ * Mute the speaker
+ */
+static int max98088_spk_mute_set(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 lReg = max98088_read(codec, M98088_REG_3D_LVL_SPK_L);
+       u8 rReg = max98088_read(codec, M98088_REG_3E_LVL_SPK_R);
+
+       if (MAX98088_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) {
+               lReg |= M98088_SP_MUTE;
+               rReg |= M98088_SP_MUTE;
+       } else {
+               lReg &= ~M98088_SP_MUTE;
+               rReg &= ~M98088_SP_MUTE;
+       }
+
+       max98088_write(codec, M98088_REG_3D_LVL_SPK_L, lReg);
+       max98088_write(codec, M98088_REG_3E_LVL_SPK_R, rReg);
+
+       return 0;
+}
+
+/*
+ * Get speaker mute status
+ */
+static int max98088_spk_mute_get(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 reg = max98088_read(codec, M98088_REG_3D_LVL_SPK_L);
+
+       ucontrol->value.integer.value[0] = (reg & M98088_SP_MUTE) ? 1 : 0;
+
+       return 0;
+}
+
+/*
+ * Mute the headphone
+ */
+static int max98088_hp_mute_set(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 lReg = max98088_read(codec, M98088_REG_39_LVL_HP_L);
+       u8 rReg = max98088_read(codec, M98088_REG_3A_LVL_HP_R);
+
+       if (MAX98088_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) {
+               lReg |= M98088_HP_MUTE;
+               rReg |= M98088_HP_MUTE;
+       } else {
+               lReg &= ~M98088_HP_MUTE;
+               rReg &= ~M98088_HP_MUTE;
+       }
+
+       max98088_write(codec, M98088_REG_39_LVL_HP_L, lReg);
+       max98088_write(codec, M98088_REG_3A_LVL_HP_R, rReg);
+
+       return 0;
+}
+
+/*
+ * Get current headphone mute status
+ */
+static int max98088_hp_mute_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 reg = max98088_read(codec, M98088_REG_39_LVL_HP_L);
+
+       ucontrol->value.integer.value[0] = (reg & M98088_HP_MUTE) ? 1 : 0;
+
+       return 0;
+}
+
+/*
+ * Mute the receiver
+ */
+static int max98088_rec_mute_set(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 lReg = max98088_read(codec, M98088_REG_3B_LVL_REC_L);
+       u8 rReg = max98088_read(codec, M98088_REG_3C_LVL_REC_R);
+
+       if (MAX98088_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) {
+               lReg |= M98088_REC_MUTE;
+               rReg |= M98088_REC_MUTE;
+       } else {
+               lReg &= ~M98088_REC_MUTE;
+               rReg &= ~M98088_REC_MUTE;
+       }
+
+       max98088_write(codec, M98088_REG_3B_LVL_REC_L, lReg);
+       max98088_write(codec, M98088_REG_3C_LVL_REC_R, rReg);
+
+       return 0;
+}
+
+/*
+ * Get current receiver mute status
+ */
+static int max98088_rec_mute_get(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       u8 reg = max98088_read(codec, M98088_REG_3B_LVL_REC_L);
+
+       ucontrol->value.integer.value[0] = (reg & M98088_REC_MUTE) ? 1 : 0;
+       return 0;
+}
+
+static const char *max98088_ex_thresh[] = { /* volts PP */
+       "0.6", "1.2", "1.8", "2.4", "3.0", "3.6", "4.2", "4.8"};
+static const struct soc_enum max98088_ex_thresh_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_42_SPKDHP_THRESH, 0, 8,
+               max98088_ex_thresh),
+};
+
+static const char *max98088_fltr_mode[] = {"Voice", "Music" };
+static const struct soc_enum max98088_filter_mode_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 7, 2, max98088_fltr_mode),
+};
+
+static const char *max98088_dai1_fltr[] = {
+       "Off", "fc=258/fs=16k", "fc=500/fs=16k",
+       "fc=258/fs=8k", "fc=500/fs=8k", "fc=200"};
+static const struct soc_enum max98088_dai1_dac_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 0, 6, max98088_dai1_fltr),
+};
+static const struct soc_enum max98088_dai1_adc_filter_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_18_DAI1_FILTERS, 4, 6, max98088_dai1_fltr),
+};
+
+static const char *max98088_dcblk[] =  {"Off", "On"};
+static const struct soc_enum max98088_dcblk_enum[] = {
+       SOC_ENUM_SINGLE(M98088_REG_20_DAI2_FILTERS, 0, 2, max98088_dcblk),
+};
+
+/* amixer controls (non-dapm) */
+static const struct snd_kcontrol_new max98088_snd_controls[] = {
+
+       /* analog output levels */
+
+       SOC_DOUBLE_R("Headphone volume", M98088_REG_39_LVL_HP_L,
+               M98088_REG_3A_LVL_HP_R, 0, 31, 0),
+       SOC_DOUBLE_R("Speaker volume", M98088_REG_3D_LVL_SPK_L,
+               M98088_REG_3E_LVL_SPK_R, 0, 31, 0),
+       SOC_DOUBLE_R("Receiver volume", M98088_REG_3B_LVL_REC_L,
+               M98088_REG_3C_LVL_REC_R, 0, 31, 0),
+
+       SOC_ENUM_EXT("Headphone mute", max98088_hp_spk_mute_enum,
+               max98088_hp_mute_get, max98088_hp_mute_set),
+       SOC_ENUM_EXT("Speaker mute", max98088_hp_spk_mute_enum,
+               max98088_spk_mute_get, max98088_spk_mute_set),
+       SOC_ENUM_EXT("Receiver mute", max98088_hp_spk_mute_enum,
+               max98088_rec_mute_get, max98088_rec_mute_set),
+
+       /* analog input levels */
+
+       SOC_SINGLE("MIC1 gain", M98088_REG_35_LVL_MIC1, 0, 31, 1),
+       SOC_SINGLE("MIC2 gain", M98088_REG_36_LVL_MIC2, 0, 31, 1),
+
+       SOC_SINGLE("MIC1 pre", M98088_REG_35_LVL_MIC1, 5, 3, 0),
+       SOC_SINGLE("MIC2 pre", M98088_REG_36_LVL_MIC2, 5, 3, 0),
+
+       SOC_SINGLE("INA gain", M98088_REG_37_LVL_INA, 0, 7, 1),
+       SOC_SINGLE("INB gain", M98088_REG_38_LVL_INB, 0, 7, 1),
+
+       /* ADC input digital gains and volume control */
+
+       SOC_SINGLE("ADCL volume", M98088_REG_33_LVL_ADC_L, 0, 15, 0),
+       SOC_SINGLE("ADCR volume", M98088_REG_34_LVL_ADC_R, 0, 15, 0),
+
+       SOC_SINGLE("ADCL gain", M98088_REG_33_LVL_ADC_L, 4, 3, 0),
+       SOC_SINGLE("ADCR gain", M98088_REG_34_LVL_ADC_R, 4, 3, 0),
+
+       /* equalizer */
+
+       SOC_SINGLE("EQ1 switch", M98088_REG_49_CFG_LEVEL, 0, 1, 0),
+       SOC_SINGLE("EQ2 switch", M98088_REG_49_CFG_LEVEL, 1, 1, 0),
+
+       /* excursion limiter */
+
+       SOC_ENUM_EXT("EX limiter mode", max98088_ex_mode_enum,
+               max98088_ex_mode_get, max98088_ex_mode_set),
+       SOC_ENUM("EX limiter threshold", max98088_ex_thresh_enum),
+
+       /* voice/music filters */
+
+       SOC_ENUM("DAI1 filter mode", max98088_filter_mode_enum),
+       SOC_ENUM("DAI1 DAC filter", max98088_dai1_dac_filter_enum),
+       SOC_ENUM("DAI1 ADC filter", max98088_dai1_adc_filter_enum),
+       SOC_ENUM("DAI2 DC block", max98088_dcblk_enum),
+
+       /* automatic level control (for both DAI1/DAI2) */
+
+       SOC_SINGLE("ALC switch", M98088_REG_43_SPKALC_COMP, 7, 1, 0),
+       SOC_SINGLE("ALC threshold", M98088_REG_43_SPKALC_COMP, 0, 7, 0),
+       SOC_SINGLE("ALC multiband", M98088_REG_43_SPKALC_COMP, 3, 1, 0),
+       SOC_SINGLE("ALC release time", M98088_REG_43_SPKALC_COMP, 4, 7, 0),
+
+       /* power limiter */
+
+       SOC_SINGLE("PWR limiter threshold", M98088_REG_44_PWRLMT_CFG,
+               4, 15, 0),
+       SOC_SINGLE("PWR limiter weight", M98088_REG_44_PWRLMT_CFG, 0, 7, 0),
+       SOC_SINGLE("PWR limiter time1", M98088_REG_45_PWRLMT_TIME, 0, 15, 0),
+       SOC_SINGLE("PWR limiter time2", M98088_REG_45_PWRLMT_TIME, 4, 15, 0),
+
+       /* THD distortion limiter */
+
+       SOC_SINGLE("THD limiter thresh", M98088_REG_46_THDLMT_CFG, 4, 15, 0),
+       SOC_SINGLE("THD limiter time", M98088_REG_46_THDLMT_CFG, 0, 7, 0),
+};
+
+/* DAPM sub-tables of the main dapm_widgets */
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max98088_left_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_2B_MIX_SPK_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_2B_MIX_SPK_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_2B_MIX_SPK_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_2B_MIX_SPK_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_2B_MIX_SPK_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_2B_MIX_SPK_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_2B_MIX_SPK_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_2B_MIX_SPK_LEFT, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max98088_right_speaker_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_2C_MIX_SPK_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_2C_MIX_SPK_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_2C_MIX_SPK_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_2C_MIX_SPK_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_2C_MIX_SPK_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_2C_MIX_SPK_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_2C_MIX_SPK_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_2C_MIX_SPK_RIGHT, 4, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max98088_left_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_25_MIX_HP_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_25_MIX_HP_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_25_MIX_HP_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_25_MIX_HP_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_25_MIX_HP_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_25_MIX_HP_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_25_MIX_HP_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_25_MIX_HP_LEFT, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new max98088_right_hp_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_26_MIX_HP_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_26_MIX_HP_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_26_MIX_HP_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_26_MIX_HP_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_26_MIX_HP_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_26_MIX_HP_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_26_MIX_HP_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_26_MIX_HP_RIGHT, 4, 1, 0),
+};
+
+/* Left earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_left_receiver_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_28_MIX_REC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_28_MIX_REC_LEFT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_28_MIX_REC_LEFT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_28_MIX_REC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_28_MIX_REC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_28_MIX_REC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_28_MIX_REC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_28_MIX_REC_LEFT, 4, 1, 0),
+};
+
+/* Right earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max98088_right_rec_mixer_controls[] = {
+       SOC_DAPM_SINGLE("Left DAC1", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC1", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("Left DAC2", M98088_REG_29_MIX_REC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("Right DAC2", M98088_REG_29_MIX_REC_RIGHT, 0, 1, 0),
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_29_MIX_REC_RIGHT, 5, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_29_MIX_REC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_29_MIX_REC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_29_MIX_REC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_29_MIX_REC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_29_MIX_REC_RIGHT, 4, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max98088_left_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_23_MIX_ADC_LEFT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_23_MIX_ADC_LEFT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_23_MIX_ADC_LEFT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_23_MIX_ADC_LEFT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_23_MIX_ADC_LEFT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_23_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max98088_right_ADC_mixer_controls[] = {
+       SOC_DAPM_SINGLE("MIC1", M98088_REG_24_MIX_ADC_RIGHT, 7, 1, 0),
+       SOC_DAPM_SINGLE("MIC2", M98088_REG_24_MIX_ADC_RIGHT, 6, 1, 0),
+       SOC_DAPM_SINGLE("INA1", M98088_REG_24_MIX_ADC_RIGHT, 3, 1, 0),
+       SOC_DAPM_SINGLE("INA2", M98088_REG_24_MIX_ADC_RIGHT, 2, 1, 0),
+       SOC_DAPM_SINGLE("INB1", M98088_REG_24_MIX_ADC_RIGHT, 1, 1, 0),
+       SOC_DAPM_SINGLE("INB2", M98088_REG_24_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static int max98088_hp_event(struct snd_soc_dapm_widget *w,
+                            struct snd_kcontrol *kcontrol, int event)
+{
+       struct snd_soc_codec *codec = w->codec;
+       u16 status;
+
+       BUG_ON(event != SND_SOC_DAPM_PRE_PMD);
+
+       /* powering down headphone gracefully */
+       status = max98088_read(codec, M98088_REG_4D_PWR_EN_OUT);
+       if ((status & M98088_HPEN) == M98088_HPEN) {
+               max98088_hw_write(codec, M98088_REG_4D_PWR_EN_OUT,
+                       (status & ~M98088_HPEN));
+       }
+       schedule_timeout_interruptible(msecs_to_jiffies(20));
+
+       return 0;
+}
+
+/* DAPM widgets top level */
+static const struct snd_soc_dapm_widget max98088_dapm_widgets[] = {
+
+       SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 1, 0),
+       SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M98088_REG_4C_PWR_EN_IN, 0, 0),
+
+       SND_SOC_DAPM_DAC("DACL1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR1", "HiFi Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+       SND_SOC_DAPM_DAC("DACL2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 1, 0),
+       SND_SOC_DAPM_DAC("DACR2", "Aux Playback",
+               M98088_REG_4D_PWR_EN_OUT, 0, 0),
+
+       SND_SOC_DAPM_PGA_E("HP Left Out", M98088_REG_4D_PWR_EN_OUT,
+               7, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+       SND_SOC_DAPM_PGA_E("HP Right Out", M98088_REG_4D_PWR_EN_OUT,
+               6, 0, NULL, 0, max98088_hp_event, SND_SOC_DAPM_PRE_PMD),
+
+       SND_SOC_DAPM_PGA("SPK Left Out", M98088_REG_4D_PWR_EN_OUT,
+               5, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("SPK Right Out", M98088_REG_4D_PWR_EN_OUT,
+               4, 0, NULL, 0),
+
+       SND_SOC_DAPM_PGA("REC Left Out", M98088_REG_4D_PWR_EN_OUT,
+               3, 0, NULL, 0),
+       SND_SOC_DAPM_PGA("REC Right Out", M98088_REG_4D_PWR_EN_OUT,
+               2, 0, NULL, 0),
+
+       SND_SOC_DAPM_MIXER("Left HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right HP Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_hp_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_hp_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right SPK Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_speaker_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_speaker_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_left_receiver_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_receiver_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right REC Mixer", SND_SOC_NOPM, 0, 0,
+         &max98088_right_rec_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_rec_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Left ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_left_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_left_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_MIXER("Right ADC Mixer", SND_SOC_NOPM, 0, 0,
+               &max98088_right_ADC_mixer_controls[0],
+               ARRAY_SIZE(max98088_right_ADC_mixer_controls)),
+
+       SND_SOC_DAPM_PGA_E("INA1 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INA2 Input", M98088_REG_4C_PWR_EN_IN,
+               7, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB1 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_PGA_E("INB2 Input", M98088_REG_4C_PWR_EN_IN,
+               6, 0, NULL, 0, max98088_pga_event,
+               SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+       SND_SOC_DAPM_MICBIAS("Mic Bias", M98088_REG_4C_PWR_EN_IN, 3, 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("RECL"),
+       SND_SOC_DAPM_OUTPUT("RECR"),
+
+       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"),
+};
+
+/* DAPM AUDIO_MAP: */
+static const struct snd_soc_dapm_route audio_map[] = {
+       /* Left headphone output mixer */
+       {"Left HP Mixer", "Left DAC1", "DACL1"},
+       {"Left HP Mixer", "Left DAC2", "DACL2"},
+       {"Left HP Mixer", "Right DAC1", "DACR1"},
+       {"Left HP Mixer", "Right DAC2", "DACR2"},
+       {"Left HP Mixer", "MIC1", "Mic Bias"},
+       {"Left HP Mixer", "MIC2", "Mic Bias"},
+       {"Left HP Mixer", "INA1", "INA1 Input"},
+       {"Left HP Mixer", "INA2", "INA2 Input"},
+       {"Left HP Mixer", "INB1", "INB1 Input"},
+       {"Left HP Mixer", "INB2", "INB2 Input"},
+
+       /* Right headphone output mixer */
+       {"Right HP Mixer", "Left DAC1", "DACL1"},
+       {"Right HP Mixer", "Left DAC2", "DACL2" },
+       {"Right HP Mixer", "Right DAC1", "DACR1"},
+       {"Right HP Mixer", "Right DAC2", "DACR2"},
+       {"Right HP Mixer", "MIC1", "Mic Bias"},
+       {"Right HP Mixer", "MIC2", "Mic Bias"},
+       {"Right HP Mixer", "INA1", "INA1 Input"},
+       {"Right HP Mixer", "INA2", "INA2 Input"},
+       {"Right HP Mixer", "INB1", "INB1 Input"},
+       {"Right HP Mixer", "INB2", "INB2 Input"},
+
+       /* Left speaker output mixer */
+       {"Left SPK Mixer", "Left DAC1", "DACL1"},
+       {"Left SPK Mixer", "Left DAC2", "DACL2"},
+       {"Left SPK Mixer", "Right DAC1", "DACR1"},
+       {"Left SPK Mixer", "Right DAC2", "DACR2"},
+       {"Left SPK Mixer", "MIC1", "Mic Bias"},
+       {"Left SPK Mixer", "MIC2", "Mic Bias"},
+       {"Left SPK Mixer", "INA1", "INA1 Input"},
+       {"Left SPK Mixer", "INA2", "INA2 Input"},
+       {"Left SPK Mixer", "INB1", "INB1 Input"},
+       {"Left SPK Mixer", "INB2", "INB2 Input"},
+
+       /* Right speaker output mixer */
+       {"Right SPK Mixer", "Left DAC1", "DACL1"},
+       {"Right SPK Mixer", "Left DAC2", "DACL2"},
+       {"Right SPK Mixer", "Right DAC1", "DACR1"},
+       {"Right SPK Mixer", "Right DAC2", "DACR2"},
+       {"Right SPK Mixer", "MIC1", "Mic Bias"},
+       {"Right SPK Mixer", "MIC2", "Mic Bias"},
+       {"Right SPK Mixer", "INA1", "INA1 Input"},
+       {"Right SPK Mixer", "INA2", "INA2 Input"},
+       {"Right SPK Mixer", "INB1", "INB1 Input"},
+       {"Right SPK Mixer", "INB2", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Left REC Mixer", "Left DAC1", "DACL1"},
+       {"Left REC Mixer", "Left DAC2", "DACL2"},
+       {"Left REC Mixer", "Right DAC1", "DACR1"},
+       {"Left REC Mixer", "Right DAC2", "DACR2"},
+       {"Left REC Mixer", "MIC1", "Mic Bias"},
+       {"Left REC Mixer", "MIC2", "Mic Bias"},
+       {"Left REC Mixer", "INA1", "INA1 Input"},
+       {"Left REC Mixer", "INA2", "INA2 Input"},
+       {"Left REC Mixer", "INB1", "INB1 Input"},
+       {"Left REC Mixer", "INB2", "INB2 Input"},
+
+       /* Earpiece/Receiver output mixer */
+       {"Right REC Mixer", "Left DAC1", "DACL1"},
+       {"Right REC Mixer", "Left DAC2", "DACL2"},
+       {"Right REC Mixer", "Right DAC1", "DACR1"},
+       {"Right REC Mixer", "Right DAC2", "DACR2"},
+       {"Right REC Mixer", "MIC1", "Mic Bias"},
+       {"Right REC Mixer", "MIC2", "Mic Bias"},
+       {"Right REC Mixer", "INA1", "INA1 Input"},
+       {"Right REC Mixer", "INA2", "INA2 Input"},
+       {"Right REC Mixer", "INB1", "INB1 Input"},
+       {"Right REC Mixer", "INB2", "INB2 Input"},
+
+       {"HP Left Out", NULL, "Left HP Mixer"},
+       {"HP Right Out", NULL, "Right HP Mixer"},
+       {"SPK Left Out", NULL, "Left SPK Mixer"},
+       {"SPK Right Out", NULL, "Right SPK Mixer"},
+       {"REC Left Out", NULL, "Left REC Mixer"},
+       {"REC Right Out", NULL, "Right REC Mixer"},
+
+       {"HPL", NULL, "HP Left Out"},
+       {"HPR", NULL, "HP Right Out"},
+       {"SPKL", NULL, "SPK Left Out"},
+       {"SPKR", NULL, "SPK Right Out"},
+       {"RECL", NULL, "REC Left Out"},
+       {"RECR", NULL, "REC Right 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"},
+
+       /* 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"},
+
+       {"Mic Bias", NULL, "MIC1"},
+       {"Mic Bias", NULL, "MIC2"},
+};
+
+/*
+ * Add widgets
+ */
+static int max98088_add_widgets(struct snd_soc_codec *codec)
+{
+       snd_soc_dapm_new_controls(codec, max98088_dapm_widgets,
+                                 ARRAY_SIZE(max98088_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;
+}
+
+/*
+ * Setup DAI1 format
+ */
+static int max98088_dai1_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       u8 regval;
+
+       cdata = &max98088->dai[0];
+
+       if (fmt != cdata->fmt) {
+               cdata->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 */
+                       /* MAS: slave */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               M98088_DAI_MAS, 0);
+                       /* slave mode PLL */
+                       max98088_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                               0x80);
+                       max98088_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM: /* codec clk, frm master */
+                       /* MAS: master */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               0, M98088_DAI_MAS);
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slv, frm mas */
+               case SND_SOC_DAIFMT_CBM_CFS: /* codec clk mas, frm slv */
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;;
+               }
+
+               /* I2S or TDM */
+               if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S) {
+                       /* TDM: I2S */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               M98088_DAI_TDM, 0);
+               } else {
+                       /* TDM: PCM/TDM */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               0, M98088_DAI_TDM);
+               }
+
+               /* DAI hardware signal inversions */
+               switch (fmt & SND_SOC_DAIFMT_NB_NF) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       /* BCI: normal bclk (rise) */
+                       /* WCI: normal frame */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               M98088_DAI_BCI|M98088_DAI_WCI, 0);
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       /* BCI: normal bclk (rise) */
+                       /* WCI: invert frame */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               M98088_DAI_BCI, M98088_DAI_WCI);
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       /* BCI: invert bclk (fall) */
+                       /* WCI: normal frame */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               M98088_DAI_WCI, M98088_DAI_BCI);
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       /* BCI: invert bclk (fall) */
+                       /* WCI: invert frame */
+                       snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                               0, M98088_DAI_BCI|M98088_DAI_WCI);
+                       break;
+               }
+
+               regval = (1<<0); /* BSEL : 64*LRCLK (for master mode only) */
+               if (max98088->digmic_mode)
+                       regval |= (1<<6); /* OSR : oversample ratio */
+               max98088_write(codec, M98088_REG_15_DAI1_CLOCK, regval);
+
+               max98088_write(codec, M98088_REG_16_DAI1_IOCFG,
+                       (1<<6) |  /* SEL : map DAI1 to S1 */
+                       (0<<5) |  /* LTEN : ADC->DAC loop-through enable */
+                       (0<<4) |  /* LBEN : loopback (0=disable, 1=enable) */
+                       (0<<3) |  /* DMONO : DAC SDIN (0=stereo, 1=mono) */
+                       (0<<2) |  /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive) */
+                       (1<<1) |  /* SDOEN : serial data out ENABLE */
+                       (1<<0));  /* SDIEN : serial data in ENABLE */
+
+               max98088_write(codec, M98088_REG_17_DAI1_TDM,
+                       (0<<6) |  /* SLOTL : L in 1st slot */
+                       (1<<4) |  /* SLOTR : R in 2nd slot */
+                       (0<<0));  /* SLOTDLY : no delay */
+       }
+
+       return 0;
+}
+
+/*
+ * Setup DAI2 format
+ */
+static int max98088_dai2_set_fmt(struct snd_soc_dai *codec_dai,
+                                unsigned int fmt)
+{
+       struct snd_soc_codec *codec = codec_dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (fmt != cdata->fmt) {
+               cdata->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 */
+                       /* MAS: slave */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               M98088_DAI_MAS, 0);
+                       /* slave mode PLL */
+                       max98088_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                               0x80);
+                       max98088_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                               0x00);
+                       break;
+               case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */
+                       /* MAS: master */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               0, M98088_DAI_MAS);
+                       break;
+               case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slv & frm mas */
+               case SND_SOC_DAIFMT_CBM_CFS: /* codec clk mas & frm slv */
+                       dev_err(codec->dev, "Clock mode unsupported");
+                       return -EINVAL;;
+               }
+
+               /* I2S or TDM */
+               if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S) {
+                       /* TDM: I2S */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               M98088_DAI_TDM, 0);
+               } else {
+                       /* TDM: PCM/TDM */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               0, M98088_DAI_TDM);
+               }
+
+               /* DAI hardware signal inversions */
+               switch (fmt & SND_SOC_DAIFMT_NB_NF) {
+               case SND_SOC_DAIFMT_NB_NF:
+                       /* BCI: normal bclk (rise) */
+                       /* WCI: normal frame */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               M98088_DAI_BCI|M98088_DAI_WCI, 0);
+                       break;
+               case SND_SOC_DAIFMT_NB_IF:
+                       /* BCI: normal bclk (rise) */
+                       /* WCI: invert frame */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               M98088_DAI_BCI, M98088_DAI_WCI);
+                       break;
+               case SND_SOC_DAIFMT_IB_NF:
+                       /* BCI: invert bclk (fall) */
+                       /* WCI: normal frame */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               M98088_DAI_WCI, M98088_DAI_BCI);
+                       break;
+               case SND_SOC_DAIFMT_IB_IF:
+                       /* BCI: invert bclk (fall) */
+                       /* WCI: invert frame */
+                       snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                               0, M98088_DAI_BCI|M98088_DAI_WCI);
+                       break;
+               }
+
+               max98088_write(codec, M98088_REG_1D_DAI2_CLOCK,
+                       (1<<0)); /* BSEL: 64*LRCLK (for master mode only) */
+
+               max98088_write(codec, M98088_REG_1E_DAI2_IOCFG,
+                       (2<<6) |  /* SEL : map DAI2 to S2 */
+                       (0<<4) |  /* LBEN : loopback (0=disable) */
+                       (0<<3) |  /* DMONO : DAC SDIN input */
+                       (0<<2) |  /* HIZOFF : ADC SDOUT (0=HiZ, 1=Drive) */
+                       (1<<1) |  /* SDOEN : serial data out ENABLE */
+                       (1<<0));  /* SDIEN : serial data in ENABLE */
+
+               max98088_write(codec, M98088_REG_1F_DAI2_TDM,
+                       (0<<6) |  /* SLOTL : L in 1st slot */
+                       (1<<4) |  /* SLOTR : R in 2nd slot */
+                       (0<<0));  /* SLOTDLY : no delay */
+       }
+
+       return 0;
+}
+
+static int max98088_dai_set_sysclk(struct snd_soc_dai *dai,
+                                  int clk_id, unsigned int freq, int dir)
+{
+       struct snd_soc_codec *codec = dai->codec;
+       struct max98088_priv *max98088 = codec->private_data;
+
+       if (freq != max98088->sysclk) {
+               max98088->sysclk = freq; /* remember current sysclk */
+
+               /* setup clocks for slave mode, and using the PLL
+                * PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+                *         0x02 (when master clk is 20MHz to 30MHz)..
+                */
+               if ((freq >= 10000000) && (freq < 20000000)) {
+                       max98088_write(codec, M98088_REG_10_SYS_CLK, 0x10);
+               } else if ((freq >= 20000000) && (freq < 30000000)) {
+                       max98088_write(codec, M98088_REG_10_SYS_CLK, 0x20);
+               } else {
+                       dev_err(codec->dev, "Invalid master clock frequency\n");
+                       return -EINVAL;
+               }
+
+               /* If codec is currently running, toggle reset */
+               if (max98088_read(codec, M98088_REG_51_PWR_SYS)
+                       & M98088_SHDNRUN) {
+                       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                               M98088_SHDNRUN, 0);
+                       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS,
+                               0, M98088_SHDNRUN);
+               }
+       }
+
+       dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq);
+
+       max98088->sysclk = freq;
+       return 0;
+}
+
+
+struct rate_table_struct {
+       u32 rate;
+       u8  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 and sample rate */
+static int max98088_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 max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 reg11val;
+       u16 ni;
+
+       cdata = &max98088->dai[0];
+
+       rate = params_rate(params);
+
+       /* data 16/24 bit width */
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               /* WS: 16bit */
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               /* WS: 24bit */
+               snd_soc_update_bits(codec, M98088_REG_14_DAI1_FORMAT,
+                       0, M98088_DAI_WS);
+               break;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
+               reg11val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
+               max98088_write(codec, M98088_REG_11_DAI1_CLKMODE, reg11val);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (max98088_read(codec, M98088_REG_14_DAI1_FORMAT)
+               & M98088_DAI_MAS) {
+               BUG_ON(max98088->sysclk == 0);
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               max98088_write(codec, M98088_REG_12_DAI1_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               max98088_write(codec, M98088_REG_13_DAI1_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       (1<<3), 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_18_DAI1_FILTERS,
+                       0, (1<<3));
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+/* Setup hw params and sample rate */
+static int max98088_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 max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+       unsigned int rate;
+       u8 reg19val;
+       u16 ni;
+
+       cdata = &max98088->dai[1];
+
+       rate = params_rate(params);
+
+       /* data 16/24 bit width */
+       switch (params_format(params)) {
+       case SNDRV_PCM_FORMAT_S16_LE:
+               /* WS: 16bit */
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       M98088_DAI_WS, 0);
+               break;
+       case SNDRV_PCM_FORMAT_S24_LE:
+               /* WS: 24bit */
+               snd_soc_update_bits(codec, M98088_REG_1C_DAI2_FORMAT,
+                       0, M98088_DAI_WS);
+               break;
+       }
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN, 0);
+
+       if (rate != cdata->rate) {
+               /* set DAI2 SR2 value for the DSP; FREQ1:0=anyclock */
+               reg19val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
+               max98088_write(codec, M98088_REG_19_DAI2_CLKMODE, reg19val);
+               cdata->rate = rate;
+       }
+
+       /* Configure NI when operating as master */
+       if (max98088_read(codec, M98088_REG_1C_DAI2_FORMAT)
+               & M98088_DAI_MAS) {
+               BUG_ON(max98088->sysclk == 0);
+               ni = (u16)ulldiv(65536ULL * (rate < 50000 ? 96ULL : 48ULL)
+                               * (unsigned long long int)rate,
+                               (unsigned long long int)max98088->sysclk);
+               max98088_write(codec, M98088_REG_1A_DAI2_CLKCFG_HI,
+                       (ni >> 8) & 0x7f);
+               max98088_write(codec, M98088_REG_1B_DAI2_CLKCFG_LO,
+                       ni & 0xff);
+       }
+
+       /* Update sample rate mode */
+       if (rate < 50000)
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       (1<<3), 0);
+       else
+               snd_soc_update_bits(codec, M98088_REG_20_DAI2_FILTERS,
+                       0, (1<<3));
+
+       snd_soc_update_bits(codec, M98088_REG_51_PWR_SYS, 0, M98088_SHDNRUN);
+
+       return 0;
+}
+
+static void max98088_sync_cache(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       int i;
+
+       /* write back cached values if they're writeable and
+        * different from the hardware default.
+        */
+       for (i = 1; i < ARRAY_SIZE(max98088->reg_cache); i++) {
+               if (!max98088_access[i].writable)
+                       continue;
+
+               if (max98088->reg_cache[i] == max98088_reg[i])
+                       continue;
+
+               snd_soc_write(codec, i, max98088->reg_cache[i]);
+       }
+}
+
+static int max98088_set_bias_level(struct snd_soc_codec *codec,
+                                 enum snd_soc_bias_level level)
+{
+       switch (level) {
+       case SND_SOC_BIAS_ON:
+               max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN);
+               break;
+
+       case SND_SOC_BIAS_PREPARE:
+               max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_SHDNRUN);
+               break;
+
+       case SND_SOC_BIAS_STANDBY:
+               max98088_sync_cache(codec);
+               max98088_write(codec, M98088_REG_51_PWR_SYS,
+                       M98088_SHDNRUN|M98088_PWRSV);
+               break;
+       case SND_SOC_BIAS_OFF:
+               max98088_write(codec, M98088_REG_51_PWR_SYS, M98088_PWRSV);
+               break;
+       }
+       codec->bias_level = level;
+       return 0;
+}
+
+#define MAX98088_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX98088_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max98088_dai1_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai1_set_fmt,
+       .hw_params = max98088_dai1_hw_params,
+};
+
+static struct snd_soc_dai_ops max98088_dai2_ops = {
+       .set_sysclk = max98088_dai_set_sysclk,
+       .set_fmt = max98088_dai2_set_fmt,
+       .hw_params = max98088_dai2_hw_params,
+};
+
+struct snd_soc_dai max98088_dai[] = {
+{
+       .name = "HiFi",
+       .playback = {
+               .stream_name = "HiFi Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .capture = {
+               .stream_name = "HiFi Capture",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+        .ops = &max98088_dai1_ops,
+},
+{
+       .name = "Aux",
+       .playback = {
+               .stream_name = "Aux Playback",
+               .channels_min = 1,
+               .channels_max = 2,
+               .rates = MAX98088_RATES,
+               .formats = MAX98088_FORMATS,
+       },
+       .ops = &max98088_dai2_ops,
+}
+};
+EXPORT_SYMBOL_GPL(max98088_dai);
+
+
+static void max98088_setup_eq1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+       sel = cdata->eq_sel;
+
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               if (strcmp(pdata->eq1_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq1_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq1_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq1_cfg[best].name,
+               pdata->eq1_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, 0);
+
+       coef_set = &pdata->eq1_cfg[sel];
+
+       m98088_eq_band(codec, 0, 0, coef_set->band1);
+       m98088_eq_band(codec, 0, 1, coef_set->band2);
+       m98088_eq_band(codec, 0, 2, coef_set->band3);
+       m98088_eq_band(codec, 0, 3, coef_set->band4);
+       m98088_eq_band(codec, 0, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ1EN, save);
+}
+
+static void max98088_setup_eq2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_eq_cfg *coef_set;
+       int best, best_val, save, i, sel, fs;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !cdata->eq_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->eq_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               if (strcmp(pdata->eq2_cfg[i].name, cdata->eq_texts[sel]) == 0 &&
+                   abs(pdata->eq2_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->eq2_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->eq2_cfg[best].name,
+               pdata->eq2_cfg[best].rate, fs);
+
+       /* Disable EQ while configuring, and save current on/off state */
+       save = snd_soc_read(codec, M98088_REG_49_CFG_LEVEL);
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN, 0);
+
+       coef_set = &pdata->eq2_cfg[sel];
+
+       m98088_eq_band(codec, 1, 0, coef_set->band1);
+       m98088_eq_band(codec, 1, 1, coef_set->band2);
+       m98088_eq_band(codec, 1, 2, coef_set->band3);
+       m98088_eq_band(codec, 1, 3, coef_set->band4);
+       m98088_eq_band(codec, 1, 4, coef_set->band5);
+
+       /* restore original on/off state */
+       snd_soc_update_bits(codec, M98088_REG_49_CFG_LEVEL, M98088_EQ2EN,
+               save);
+}
+
+
+static int max98088_put_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[0];
+
+       if (sel >= pdata->eq1_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq1(codec);
+       return 0;
+}
+
+static int max98088_put_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[1];
+
+       if (sel >= pdata->eq2_cfgcnt)
+               return -EINVAL;
+
+       cdata->eq_sel = sel;
+       max98088_setup_eq2(codec);
+       return 0;
+}
+
+static int max98088_get_eq1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static int max98088_get_eq2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+
+       ucontrol->value.enumerated.item[0] = cdata->eq_sel;
+       return 0;
+}
+
+static void max98088_handle_eq1_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq1control =
+               SOC_ENUM_EXT("EQ1 Mode",
+                       max98088->dai[0].eq_enum,
+                       max98088_get_eq1_enum,
+                       max98088_put_eq1_enum);
+
+       cdata = &max98088->dai[0];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq1_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq1_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               /* ...and remember the new version. */
+               cdata->eq_texts[i] = pdata->eq1_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ1 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       BUG_ON(&max98088->codec == 0);
+       BUG_ON((max98088->codec).card == 0);
+
+       ret = snd_soc_add_controls(&max98088->codec, &eq1control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add EQ control: %d\n", ret);
+}
+
+static void max98088_handle_eq2_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new eq2control =
+               SOC_ENUM_EXT("EQ2 Mode",
+                       max98088->dai[1].eq_enum,
+                       max98088_get_eq2_enum,
+                       max98088_put_eq2_enum);
+
+       cdata = &max98088->dai[1];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->eq_textcnt = 0;
+       for (i = 0; i < pdata->eq2_cfgcnt; i++) {
+               for (j = 0; j < cdata->eq_textcnt; j++) {
+                       if (strcmp(pdata->eq2_cfg[i].name,
+                                  cdata->eq_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->eq_textcnt)
+                       continue;
+
+               cdata->eq_texts[i] = pdata->eq2_cfg[i].name;
+               cdata->eq_textcnt++;
+
+               if (cdata->eq_textcnt >= EQ_CFG_MAX) {
+                       dev_err(codec->dev, "Too many EQ config entries\n");
+                       cdata->eq_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EQ2 configurations\n",
+               cdata->eq_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->eq_enum.texts = cdata->eq_texts;
+       cdata->eq_enum.max = cdata->eq_textcnt;
+
+       ret = snd_soc_add_controls(&max98088->codec, &eq2control, 1);
+       if (ret != 0)
+               printk(KERN_ERR "Failed to add EQ control: %d\n", ret);
+}
+
+
+static void max98088_setup_ex1(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       struct max98088_excursion_cfg *coef_set;
+       int best, best_val, i, sel, fs;
+
+       cdata = &max98088->dai[0];
+
+       if (!pdata || !cdata->ex_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->ex_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->ex1_cfgcnt; i++) {
+               if (strcmp(pdata->ex1_cfg[i].name,
+                          cdata->ex_texts[sel]) == 0 &&
+                   abs(pdata->ex1_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->ex1_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->ex1_cfg[best].name,
+               pdata->ex1_cfg[best].rate, fs);
+
+       coef_set = &pdata->ex1_cfg[sel];
+
+       max98088_ex_resp_control(codec, M98088_REG_B6_DAI1_BIQUAD_BASE,
+               coef_set->resp);
+}
+
+static int max98088_put_ex1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[0];
+
+       if (sel >= pdata->ex1_cfgcnt)
+               return -EINVAL;
+
+       cdata->ex_sel = sel;
+       max98088_setup_ex1(codec);
+       return 0;
+}
+
+
+static int max98088_get_ex1_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[0];
+       ucontrol->value.enumerated.item[0] = cdata->ex_sel;
+
+       return 0;
+}
+
+static void max98088_handle_ex1_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new ex1control =
+               SOC_ENUM_EXT("EX1 Limiter Config",
+                            max98088->dai[0].ex_enum,
+                            max98088_get_ex1_enum,
+                            max98088_put_ex1_enum);
+
+       cdata = &max98088->dai[0];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->ex_textcnt = 0;
+       for (i = 0; i < pdata->ex1_cfgcnt; i++) {
+               for (j = 0; j < cdata->ex_textcnt; j++) {
+                       if (strcmp(pdata->ex1_cfg[i].name,
+                                  cdata->ex_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->ex_textcnt)
+                       continue;
+
+               /* ...and remember the new version. */
+               cdata->ex_texts[i] = pdata->ex1_cfg[i].name;
+               cdata->ex_textcnt++;
+
+               if (cdata->ex_textcnt >= EX_CFG_MAX) {
+                       dev_err(codec->dev, "Too many limiter configs\n");
+                       cdata->ex_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EX1 configurations\n",
+               cdata->ex_textcnt);
+
+       /* now point the soc_enum to .texts array */
+       cdata->ex_enum.texts = cdata->ex_texts;
+       cdata->ex_enum.max = cdata->ex_textcnt;
+       ret = snd_soc_add_controls(&max98088->codec, &ex1control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add limiter control\n");
+}
+
+
+static void max98088_setup_ex2(struct snd_soc_codec *codec)
+{
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       struct max98088_excursion_cfg *coef_set;
+       int best, best_val, i, sel, fs;
+
+       cdata = &max98088->dai[1];
+
+       if (!pdata || !cdata->ex_textcnt)
+               return;
+
+       /* Find the selected configuration with nearest sample rate */
+       fs = cdata->rate;
+
+       sel = cdata->ex_sel;
+       best = 0;
+       best_val = INT_MAX;
+       for (i = 0; i < pdata->ex2_cfgcnt; i++) {
+               if (strcmp(pdata->ex2_cfg[i].name,
+                          cdata->ex_texts[sel]) == 0 &&
+                   abs(pdata->ex2_cfg[i].rate - fs) < best_val) {
+                       best = i;
+                       best_val = abs(pdata->ex2_cfg[i].rate - fs);
+               }
+       }
+
+       dev_dbg(codec->dev, "Selected %s/%dHz for %dHz sample rate\n",
+               pdata->ex2_cfg[best].name,
+               pdata->ex2_cfg[best].rate, fs);
+
+       coef_set = &pdata->ex2_cfg[sel];
+
+       max98088_ex_resp_control(codec, M98088_REG_C0_DAI2_BIQUAD_BASE,
+               coef_set->resp);
+}
+
+static int max98088_put_ex2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int sel = ucontrol->value.integer.value[0];
+
+       cdata = &max98088->dai[1];
+
+       if (sel >= pdata->ex2_cfgcnt)
+               return -EINVAL;
+
+       cdata->ex_sel = sel;
+       max98088_setup_ex2(codec);
+       return 0;
+}
+
+static int max98088_get_ex2_enum(struct snd_kcontrol *kcontrol,
+                                struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct max98088_priv *max98088 = codec->private_data;
+       struct max98088_cdata *cdata;
+
+       cdata = &max98088->dai[1];
+       ucontrol->value.enumerated.item[0] = cdata->ex_sel;
+
+       return 0;
+}
+
+static void max98088_handle_ex2_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       struct max98088_cdata *cdata;
+       int ret, i, j;
+
+       struct snd_kcontrol_new ex2control =
+               SOC_ENUM_EXT("EX2 Limiter Config",
+                            max98088->dai[1].ex_enum,
+                            max98088_get_ex2_enum,
+                            max98088_put_ex2_enum);
+
+       cdata = &max98088->dai[1];
+
+       /* Build an array of texts for the enum API. The number
+        * of texts is likely fewer than the number of configurations
+        * due to multiple sample rates for the same text name. */
+       cdata->ex_textcnt = 0;
+       for (i = 0; i < pdata->ex2_cfgcnt; i++) {
+               for (j = 0; j < cdata->ex_textcnt; j++) {
+                       if (strcmp(pdata->ex2_cfg[i].name,
+                                  cdata->ex_texts[j]) == 0) {
+                               break;
+                       }
+               }
+
+               if (j != cdata->ex_textcnt)
+                       continue;
+
+               /* ...and remember the new version. */
+               cdata->ex_texts[i] = pdata->ex2_cfg[i].name;
+               cdata->ex_textcnt++;
+
+               if (cdata->ex_textcnt >= EX_CFG_MAX) {
+                       dev_err(codec->dev, "Too many limiter configs\n");
+                       cdata->ex_textcnt--;
+                       break;
+               }
+       }
+
+       dev_dbg(codec->dev, "Installed %d EX2 configurations\n",
+               cdata->ex_textcnt);
+
+       /* now point the soc_enum to .texts array items */
+       cdata->ex_enum.texts = cdata->ex_texts;
+       cdata->ex_enum.max = cdata->ex_textcnt;
+
+       ret = snd_soc_add_controls(&max98088->codec, &ex2control, 1);
+       if (ret != 0)
+               dev_err(codec->dev, "Failed to add limiter control\n");
+}
+
+static void max98088_handle_pdata(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_pdata *pdata = max98088->pdata;
+       u8 regval;
+
+       if (!pdata)
+               return;
+
+       /* Configure digital mic / external mic */
+       /* Digital mic needs REG_15 OSR1=1 */
+       regval = ((0<<6) | /* digital mic clock freq = PCLK/8 */
+                 (0<<5) | /* DIGMICL enable */
+                 (0<<4) | /* DIGMICR enable */
+                 (0<<0)); /* external mic enable */
+
+       if (pdata->digmic_left_enable)
+               regval |= (1<<5); /* digital left mic enabled */
+
+       if (pdata->digmic_right_enable)
+               regval |= (1<<4); /* digital right mic enabled */
+
+       max98088->digmic_mode = (regval ? 1 : 0);
+
+       if (pdata->extmic_mode < 3)
+               regval |= (pdata->extmic_mode << 0); /* external mic enabled */
+
+       max98088_write(codec, M98088_REG_48_CFG_MIC, regval);
+
+       /* configure external mic to mic1 bypass, mic1 to mic2 bypass */
+       regval = 0;
+
+       if (pdata->ina_to_mic1_bypass)
+               regval |= (1<<7);
+
+       if (pdata->mic1_to_mic2_bypass)
+               regval |= (1<<4);
+
+       max98088_write(codec, M98088_REG_4A_CFG_BYPASS, regval);
+
+       /* configure receiver output */
+       regval = ((pdata->receiver_mode) ? (1<<7) : 0);
+       snd_soc_update_bits(codec, M98088_REG_2A_MIC_REC_CNTL,
+               (1<<7), regval);
+
+       /* configure equalizers */
+       if (pdata->eq1_cfgcnt)
+               max98088_handle_eq1_pdata(max98088);
+
+       if (pdata->eq2_cfgcnt)
+               max98088_handle_eq2_pdata(max98088);
+
+       /* configure excursion limiters */
+       if (pdata->ex1_cfgcnt)
+               max98088_handle_ex1_pdata(max98088);
+
+       if (pdata->ex2_cfgcnt)
+               max98088_handle_ex2_pdata(max98088);
+}
+
+static int max98088_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;
+
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       return 0;
+}
+
+static int max98088_resume(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec = socdev->card->codec;
+       int i;
+       u8 *cache = codec->reg_cache;
+
+       /* toggle reset the shutdown bit of the codec hardware */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       /* Sync reg_cache with the hardware */
+       for (i = 0; i < M98088_REG_CNT; i++) {
+               if (i == M98088_REG_51_PWR_SYS)
+                       continue;
+
+               if (!max98088_access[i].writable)
+                       continue;
+
+               max98088_hw_write(codec, i, cache[i]);
+       }
+
+       /* now enter into the resume mode bias level */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       return 0;
+}
+
+/*
+ * Make sure that a max98088 is attached to the I2C bus.
+ */
+static int max98088_probe(struct platform_device *pdev)
+{
+       struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
+       struct max98088_priv *max98088;
+       int ret = 0;
+
+       dev_printk(KERN_INFO, &pdev->dev, "MAX98088 Audio CODEC\n");
+
+       if (!max98088_codec) {
+               dev_err(&pdev->dev, "Codec device is not registered\n");
+               return -EINVAL;
+       }
+
+       socdev->card->codec = max98088_codec;
+       codec = max98088_codec;
+       max98088 = codec->private_data;
+
+       /* register pcms */
+       ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to create pcms: %d\n", ret);
+               goto pcm_err;
+       }
+
+       snd_soc_add_controls(codec, max98088_snd_controls,
+                            ARRAY_SIZE(max98088_snd_controls));
+
+       /* install additional amixer controls: EQ and excursion limiter */
+       if (max98088->pdata)
+               max98088_handle_pdata(max98088);
+       else
+               dev_err(&pdev->dev, "No platform data\n");
+
+       max98088_add_widgets(codec);
+
+       ret = snd_soc_init_card(socdev);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "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;
+}
+
+static int max98088_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)
+               max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       snd_soc_free_pcms(socdev);
+       snd_soc_dapm_free(socdev);
+
+       return 0;
+}
+
+/* expose to machine driver */
+struct snd_soc_codec_device soc_codec_dev_max98088 = {
+       .probe   = max98088_probe,
+       .remove  = max98088_remove,
+       .suspend = max98088_suspend,
+       .resume  = max98088_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_max98088);
+
+
+static int max98088_register(struct max98088_priv *max98088,
+                            enum snd_soc_control_type control)
+
+{
+       int ret, i, version;
+       struct snd_soc_codec *codec = &max98088->codec;
+       struct max98088_cdata *cdata;
+
+       if (max98088_codec) {
+               dev_err(codec->dev, "Another MAX98088 is registered\n");
+               ret = -EINVAL;
+               goto err;
+       }
+
+       mutex_init(&codec->mutex);
+       INIT_LIST_HEAD(&codec->dapm_widgets);
+       INIT_LIST_HEAD(&codec->dapm_paths);
+
+       codec->name = "MAX98088";
+       codec->owner = THIS_MODULE;
+       codec->read = max98088_read;
+       codec->write = max98088_write;
+
+       /* setup DAPM event bias level and handler function */
+       codec->bias_level = SND_SOC_BIAS_STANDBY;
+       codec->set_bias_level = max98088_set_bias_level;
+
+       codec->dai = max98088_dai;
+       codec->num_dai = ARRAY_SIZE(max98088_dai);
+       codec->private_data = max98088;
+
+       codec->reg_cache_size = M98088_REG_CNT;
+       codec->reg_cache = &max98088->reg_cache;
+       codec->volatile_register = max98088_volatile_register;
+
+       memcpy(codec->reg_cache, max98088_reg, sizeof(max98088_reg));
+
+       version = max98088_read(codec, M98088_REG_FF_REV_ID);
+       dev_dbg(codec->dev, "MAX98088 revision 0x%02X\n", version);
+
+       /* initalize private data */
+
+       max98088->sysclk = (unsigned)-1;
+
+       cdata = &max98088->dai[0];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+       cdata->ex_textcnt = 0;
+       cdata->ex_sel = 0;
+
+       cdata = &max98088->dai[1];
+       cdata->rate = (unsigned)-1;
+       cdata->fmt  = (unsigned)-1;
+       cdata->eq_textcnt = 0;
+       cdata->eq_sel = 0;
+       cdata->ex_textcnt = 0;
+       cdata->ex_sel = 0;
+
+       max98088_write(codec, M98088_REG_14_DAI1_FORMAT, M98088_DAI_DLY);
+       max98088_write(codec, M98088_REG_1C_DAI2_FORMAT, M98088_DAI_DLY);
+
+       max98088->power_state = 0; /* INA INB power enable state */
+       max98088->ex_mode = 0; /* excursion limiter mode */
+       max98088->digmic_mode = 0; /* 0=normal, 1=digital mic used */
+
+       /* disable device via dapm interface */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+       /* initialize registers cache to hardware default */
+       max98088_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+       max98088_write(codec, M98088_REG_0F_IRQ_ENABLE, 0x00);
+
+       max98088_write(codec, M98088_REG_22_MIX_DAC,
+               M98088_DAI1L_TO_DACL|M98088_DAI2L_TO_DACL|
+               M98088_DAI1R_TO_DACR|M98088_DAI2R_TO_DACR);
+
+       max98088_write(codec, M98088_REG_4E_BIAS_CNTL, 0xF0);
+       max98088_write(codec, M98088_REG_50_DAC_BIAS2, 0x0F);
+
+       /* power on device */
+       max98088_codec = codec;
+       for (i = 0; i < codec->num_dai; i++)
+               max98088_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(&max98088_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(max98088);
+
+       return ret;
+}
+
+static void max98088_unregister(struct max98088_priv *max98088)
+{
+       struct snd_soc_codec *codec = &max98088->codec;
+       snd_soc_unregister_dais(max98088_dai, codec->num_dai);
+       snd_soc_unregister_codec(codec);
+       kfree(max98088);
+       max98088_codec = NULL;
+}
+
+static int max98088_i2c_probe(struct i2c_client *i2c,
+                            const struct i2c_device_id *id)
+{
+       struct snd_soc_codec *codec;
+       struct max98088_priv *max98088;
+
+       max98088 = kzalloc(sizeof(struct max98088_priv), GFP_KERNEL);
+       if (max98088 == NULL)
+               return -ENOMEM;
+
+       /* codec structure is inside the private data */
+       codec = &(max98088->codec);
+
+       codec->private_data = max98088;
+
+       codec->hw_write = (hw_write_t) i2c_master_send;
+       i2c_set_clientdata(i2c, max98088);
+       codec->control_data = i2c;
+
+       max98088->pdata = i2c->dev.platform_data;
+
+       codec->dev = &i2c->dev;
+
+       return max98088_register(max98088, SND_SOC_I2C);
+}
+
+static int max98088_i2c_remove(struct i2c_client *client)
+{
+       struct max98088_priv *max98088 = i2c_get_clientdata(client);
+       max98088_unregister(max98088);
+       return 0;
+}
+
+static const struct i2c_device_id max98088_i2c_id[] = {
+       { "max98088", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, max98088_i2c_id);
+
+
+static struct i2c_driver max98088_i2c_driver = {
+       .driver = {
+               .name = "MAX98088 I2C Codec",
+               .owner = THIS_MODULE,
+       },
+       .probe  = max98088_i2c_probe,
+       .remove = __devexit_p(max98088_i2c_remove),
+       .id_table = max98088_i2c_id,
+};
+
+static int __init max98088_init(void)
+{
+       int ret;
+
+       ret = i2c_add_driver(&max98088_i2c_driver);
+       if (ret)
+               pr_err("Failed to register max98088 I2C driver: %d\n", ret);
+
+       return ret;
+}
+
+static void __exit max98088_exit(void)
+{
+       i2c_del_driver(&max98088_i2c_driver);
+}
+
+module_init(max98088_init);
+module_exit(max98088_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX98088 driver");
+MODULE_AUTHOR("Peter Hsiang <peter.hsiang@maxim-ic.com>");
+MODULE_AUTHOR("Jesse Marroquin <jesse.marroquin@maxim-ic.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max98088.h b/sound/soc/codecs/max98088.h
new file mode 100644
index 0000000..f7ef4c2
--- /dev/null
+++ b/sound/soc/codecs/max98088.h
@@ -0,0 +1,161 @@
+/*
+ * max98088.h -- MAX98088 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 _MAX98088_H
+#define _MAX98088_H
+
+/*
+ * MAX98088 Registers Definition
+ */
+#define M98088_REG_00_IRQ_STATUS            0x00
+#define M98088_REG_01_MIC_STATUS            0x01
+#define M98088_REG_02_JACK_STAUS            0x02
+#define M98088_REG_03_BATTERY_VOLTAGE       0x03
+#define M98088_REG_0F_IRQ_ENABLE            0x0F
+#define M98088_REG_10_SYS_CLK               0x10
+#define M98088_REG_11_DAI1_CLKMODE          0x11
+#define M98088_REG_12_DAI1_CLKCFG_HI        0x12
+#define M98088_REG_13_DAI1_CLKCFG_LO        0x13
+#define M98088_REG_14_DAI1_FORMAT           0x14
+#define M98088_REG_15_DAI1_CLOCK            0x15
+#define M98088_REG_16_DAI1_IOCFG            0x16
+#define M98088_REG_17_DAI1_TDM              0x17
+#define M98088_REG_18_DAI1_FILTERS          0x18
+#define M98088_REG_19_DAI2_CLKMODE          0x19
+#define M98088_REG_1A_DAI2_CLKCFG_HI        0x1A
+#define M98088_REG_1B_DAI2_CLKCFG_LO        0x1B
+#define M98088_REG_1C_DAI2_FORMAT           0x1C
+#define M98088_REG_1D_DAI2_CLOCK            0x1D
+#define M98088_REG_1E_DAI2_IOCFG            0x1E
+#define M98088_REG_1F_DAI2_TDM              0x1F
+#define M98088_REG_20_DAI2_FILTERS          0x20
+#define M98088_REG_21_SRC                   0x21
+#define M98088_REG_22_MIX_DAC               0x22
+#define M98088_REG_23_MIX_ADC_LEFT          0x23
+#define M98088_REG_24_MIX_ADC_RIGHT         0x24
+#define M98088_REG_25_MIX_HP_LEFT           0x25
+#define M98088_REG_26_MIX_HP_RIGHT          0x26
+#define M98088_REG_27_MIX_HP_CNTL           0x27
+#define M98088_REG_28_MIX_REC_LEFT          0x28
+#define M98088_REG_29_MIX_REC_RIGHT         0x29
+#define M98088_REG_2A_MIC_REC_CNTL          0x2A
+#define M98088_REG_2B_MIX_SPK_LEFT          0x2B
+#define M98088_REG_2C_MIX_SPK_RIGHT         0x2C
+#define M98088_REG_2D_MIX_SPK_CNTL          0x2D
+#define M98088_REG_2E_LVL_SIDETONE          0x2E
+#define M98088_REG_2F_LVL_DAI1_PLAY         0x2F
+#define M98088_REG_30_LVL_DAI1_PLAY_EQ      0x30
+#define M98088_REG_31_LVL_DAI2_PLAY         0x31
+#define M98088_REG_32_LVL_DAI2_PLAY_EQ      0x32
+#define M98088_REG_33_LVL_ADC_L             0x33
+#define M98088_REG_34_LVL_ADC_R             0x34
+#define M98088_REG_35_LVL_MIC1              0x35
+#define M98088_REG_36_LVL_MIC2              0x36
+#define M98088_REG_37_LVL_INA               0x37
+#define M98088_REG_38_LVL_INB               0x38
+#define M98088_REG_39_LVL_HP_L              0x39
+#define M98088_REG_3A_LVL_HP_R              0x3A
+#define M98088_REG_3B_LVL_REC_L             0x3B
+#define M98088_REG_3C_LVL_REC_R             0x3C
+#define M98088_REG_3D_LVL_SPK_L             0x3D
+#define M98088_REG_3E_LVL_SPK_R             0x3E
+#define M98088_REG_3F_MICAGC_CFG            0x3F
+#define M98088_REG_40_MICAGC_THRESH         0x40
+#define M98088_REG_41_SPKDHP                0x41
+#define M98088_REG_42_SPKDHP_THRESH         0x42
+#define M98088_REG_43_SPKALC_COMP           0x43
+#define M98088_REG_44_PWRLMT_CFG            0x44
+#define M98088_REG_45_PWRLMT_TIME           0x45
+#define M98088_REG_46_THDLMT_CFG            0x46
+#define M98088_REG_47_CFG_AUDIO_IN          0x47
+#define M98088_REG_48_CFG_MIC               0x48
+#define M98088_REG_49_CFG_LEVEL             0x49
+#define M98088_REG_4A_CFG_BYPASS            0x4A
+#define M98088_REG_4B_CFG_JACKDET           0x4B
+#define M98088_REG_4C_PWR_EN_IN             0x4C
+#define M98088_REG_4D_PWR_EN_OUT            0x4D
+#define M98088_REG_4E_BIAS_CNTL             0x4E
+#define M98088_REG_4F_DAC_BIAS1             0x4F
+#define M98088_REG_50_DAC_BIAS2             0x50
+#define M98088_REG_51_PWR_SYS               0x51
+#define M98088_REG_52_DAI1_EQ_BASE          0x52
+#define M98088_REG_84_DAI2_EQ_BASE          0x84
+#define M98088_REG_B6_DAI1_BIQUAD_BASE      0xB6
+#define M98088_REG_C0_DAI2_BIQUAD_BASE      0xC0
+#define M98088_REG_FF_REV_ID                0xFF
+
+#define M98088_REG_CNT                      (0xFF+1)
+
+/* MAX98088 Registers Bit Fields */
+
+/* M98088_REG_14_DAI1_FORMAT, M98088_REG_1C_DAI2_FORMAT */
+       #define M98088_DAI_MAS                  (1<<7)
+       #define M98088_DAI_WCI                  (1<<6)
+       #define M98088_DAI_BCI                  (1<<5)
+       #define M98088_DAI_DLY                  (1<<4)
+       #define M98088_DAI_TDM                  (1<<2)
+       #define M98088_DAI_FSW                  (1<<1)
+       #define M98088_DAI_WS                   (1<<0)
+
+/* M98088_REG_22_MIX_DAC */
+       #define M98088_DAI1L_TO_DACL            (1<<7)
+       #define M98088_DAI1R_TO_DACL            (1<<6)
+       #define M98088_DAI2L_TO_DACL            (1<<5)
+       #define M98088_DAI2R_TO_DACL            (1<<4)
+       #define M98088_DAI1L_TO_DACR            (1<<3)
+       #define M98088_DAI1R_TO_DACR            (1<<2)
+       #define M98088_DAI2L_TO_DACR            (1<<1)
+       #define M98088_DAI2R_TO_DACR            (1<<0)
+
+/* M98088_REG_3A_LVL_HEADPHONE_R */
+       #define M98088_HP_MUTE                  (1<<7)
+
+/* M98088_REG_3C_LVL_RECEIVER_R */
+       #define M98088_REC_MUTE                 (1<<7)
+
+/* M98088_REG_3E_LVL_SPEAKER_R */
+       #define M98088_SP_MUTE                  (1<<7)
+
+/* M98088_REG_49_CFG_LEVEL */
+       #define M98088_VSEN                     (1<<6)
+       #define M98088_ZDEN                     (1<<5)
+       #define M98088_EQ2EN                    (1<<1)
+       #define M98088_EQ1EN                    (1<<0)
+
+/* M98088_REG_4D_PWR_EN_OUT */
+       #define M98088_HPLEN                    (1<<7)
+       #define M98088_HPREN                    (1<<6)
+       #define M98088_HPEN                     ((1<<7)|(1<<6))
+       #define M98088_SPLEN                    (1<<5)
+       #define M98088_SPREN                    (1<<4)
+       #define M98088_RECEN                    (1<<3)
+       #define M98088_DALEN                    (1<<1)
+       #define M98088_DAREN                    (1<<0)
+
+/* M98088_REG_51_PWR_SYS */
+       #define M98088_SHDNRUN                  (1<<7)
+       #define M98088_PERFMODE                 (1<<3)
+       #define M98088_HPPLYBACK                (1<<2)
+       #define M98088_PWRSV8K                  (1<<1)
+       #define M98088_PWRSV                    (1<<0)
+
+#define M98088_COEFS_PER_BAND               5
+
+#define M98088_BYTE1(w) ((w >> 8) & 0xff)
+#define M98088_BYTE0(w) (w & 0xff)
+
+struct max98088_setup_data {
+       unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai max98088_dai[];
+extern struct snd_soc_codec_device soc_codec_dev_max98088;
+
+#endif
--
1.6.3.3

^ permalink raw reply related	[flat|nested] 46+ messages in thread

end of thread, other threads:[~2010-10-15 17:22 UTC | newest]

Thread overview: 46+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-08-31 21:08 [PATCH] ASoC: Add max98088 CODEC driver Peter Hsiang
2010-09-01 11:14 ` Mark Brown
2010-09-01 11:14   ` Mark Brown
2010-09-02 23:30   ` Peter Hsiang
2010-09-03 10:17     ` Mark Brown
2010-09-03 10:17       ` Mark Brown
2010-09-22  2:49       ` Peter Hsiang
2010-09-22  2:49         ` Peter Hsiang
2010-09-22 10:38         ` Mark Brown
2010-09-22 10:38           ` Mark Brown
2010-08-31 21:08 Peter Hsiang
2010-09-23  2:58 Peter Hsiang
2010-09-23  2:58 ` Peter Hsiang
2010-09-23 12:04 ` Mark Brown
2010-09-23 12:04   ` Mark Brown
2010-09-23 17:56   ` Peter Hsiang
2010-09-23 17:56     ` Peter Hsiang
2010-09-23 18:38     ` Mark Brown
2010-09-23 18:38       ` Mark Brown
2010-09-29  2:34 Peter Hsiang
2010-09-29  2:34 ` Peter Hsiang
2010-09-29  3:37 ` Mark Brown
2010-09-29  3:37   ` Mark Brown
2010-09-29 21:42   ` Peter Hsiang
2010-09-29 21:42     ` Peter Hsiang
2010-09-29 22:18     ` Mark Brown
2010-09-29 22:18       ` Mark Brown
2010-09-30  0:52       ` Peter Hsiang
2010-09-30  0:58         ` Mark Brown
2010-09-30  0:58           ` Mark Brown
2010-09-30  1:20           ` Peter Hsiang
2010-10-13  1:20 ` Peter Hsiang
2010-10-13  1:47   ` Joe Perches
2010-10-13  8:24     ` Mark Brown
2010-10-13  8:24       ` Mark Brown
2010-10-13 10:32   ` Mark Brown
2010-10-13 10:32     ` Mark Brown
2010-10-14  3:18     ` Peter Hsiang
2010-10-14  3:18       ` Peter Hsiang
2010-10-14  3:30   ` Peter Hsiang
2010-10-15 10:04     ` Liam Girdwood
2010-10-15 10:04       ` Liam Girdwood
2010-10-15 10:55     ` Mark Brown
2010-10-15 10:55       ` Mark Brown
2010-10-15 17:23       ` Peter Hsiang
2010-10-15 17:23         ` Peter Hsiang

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.