From: Jesse Marroquin <[email protected]>
This patch adds max9888 CODEC driver.
Signed-off-by: Jesse Marroquin <[email protected]>
Signed-off-by: Peter Hsiang <[email protected]>
---
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/max9888.c | 1939 ++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/max9888.h | 180 ++++
4 files changed, 2125 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/codecs/max9888.c
create mode 100644 sound/soc/codecs/max9888.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index c37c844..7bbdf74 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -71,6 +71,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_WM9705 if SND_SOC_AC97_BUS
select SND_SOC_WM9712 if SND_SOC_AC97_BUS
select SND_SOC_WM9713 if SND_SOC_AC97_BUS
+ select SND_SOC_MAX9888 if I2C
help
Normally ASoC codec drivers are only built if a machine driver which
uses them is also built since they are only usable with a machine
@@ -273,6 +274,9 @@ config SND_SOC_WM9712
config SND_SOC_WM9713
tristate
+config SND_SOC_MAX9888
+ tristate
+
# Amp
config SND_SOC_MAX9877
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 4a9c205..e2eab0f 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -57,6 +57,7 @@ snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
+snd-soc-max9888-objs := max9888.o
# Amp
snd-soc-max9877-objs := max9877.o
@@ -123,6 +124,7 @@ obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o
obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
+obj-$(CONFIG_SND_SOC_MAX9888) += snd-soc-max9888.o
# Amp
obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o
diff --git a/sound/soc/codecs/max9888.c b/sound/soc/codecs/max9888.c
new file mode 100644
index 0000000..c81f177
--- /dev/null
+++ b/sound/soc/codecs/max9888.c
@@ -0,0 +1,1939 @@
+/*
+ * max9888.c -- MAX9888 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "max9888.h"
+
+#define AUDIO_NAME "MAX9888"
+#define MAX9888_DRIVER_VERSION "1.0"
+
+#ifdef MAX9888_DEBUG
+#define dprintk(x...) printk(KERN_WARNING x)
+#else
+#define dprintk(x...)
+#endif
+
+#define err(format, arg...) \
+ printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg)
+
+#define info(format, arg...) \
+ printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg)
+
+#define warn(format, arg...) \
+ printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg)
+
+struct snd_soc_codec_device soc_codec_dev_max9888;
+
+/* codec private data */
+struct max9888_priv {
+ unsigned int sysclk;
+ unsigned int dai1_rate;
+ unsigned int dai1_fmt;
+ unsigned int reg_14_val;
+ unsigned int dai2_rate;
+ unsigned int dai2_fmt;
+ unsigned int reg_1c_val;
+ unsigned int rev_version;
+ struct snd_soc_codec codec;
+};
+
+/*
+ * Equalizer DSP filter coefficients generated from the evkit software tool
+ */
+static unsigned int param_eq_band_1_voice[] = {
+ 0x2027, 0xC001, 0x3FFB, 0x0042, 0x0184 };
+static unsigned int param_eq_band_2_voice[] = {
+ 0x2027, 0xC003, 0x3FE7, 0x012F, 0x037B };
+static unsigned int param_eq_band_3_voice[] = {
+ 0x3EC6, 0xC024, 0x3C2E, 0x043D, 0x15C7 };
+static unsigned int param_eq_band_4_voice[] = {
+ 0x3EC6, 0xC261, 0x344F, 0x1145, 0x24DF };
+static unsigned int param_eq_band_5_voice[] = {
+ 0x2027, 0xEEB0, 0x38F9, 0x3D9C, 0x1D26 };
+static unsigned int param_eq_band_1_music[] = {
+ 0x1027, 0xC001, 0x3F9F, 0x006C, 0x06F2, };
+static unsigned int param_eq_band_2_music[] = {
+ 0x2894, 0xC003, 0x3F32, 0x0110, 0x0A1A, };
+static unsigned int param_eq_band_3_music[] = {
+ 0x2D25, 0xC072, 0x39F3, 0x0782, 0x1B29, };
+static unsigned int param_eq_band_4_music[] = {
+ 0x1463, 0xC59A, 0x2876, 0x1A2E, 0x3195, };
+static unsigned int param_eq_band_5_music[] = {
+ 0x4000, 0xD468, 0x30EA, 0x2EDB, 0x2944, };
+
+static unsigned int param_eq_band_1_flat[] = {
+ 0x2027, 0xc001, 0x3ff1, 0x006a, 0x02b4 };
+static unsigned int param_eq_band_2_flat[] = {
+ 0x2027, 0xc008, 0x3fcc, 0x01e4, 0x0512 };
+static unsigned int param_eq_band_3_flat[] = {
+ 0x2027, 0xc052, 0x3f77, 0x0661, 0x083e };
+static unsigned int param_eq_band_4_flat[] = {
+ 0x2027, 0xc2dd, 0x3e60, 0x12eb, 0x0e54 };
+static unsigned int param_eq_band_5_flat[] = {
+ 0x2027, 0xe611, 0x36fa, 0x3a82, 0x20c2 };
+
+static unsigned int param_eq_band_1_trebel_reduce[] = {
+ 0x2027, 0xC001, 0x3FFB, 0x0042, 0x0184, };
+static unsigned int param_eq_band_2_trebel_reduce[] = {
+ 0x2027, 0xC003, 0x3FE7, 0x012F, 0x037B, };
+static unsigned int param_eq_band_3_trebel_reduce[] = {
+ 0x2027, 0xC028, 0x3FA4, 0x0471, 0x06BC, };
+static unsigned int param_eq_band_4_trebel_reduce[] = {
+ 0x16AE, 0xC22A, 0x2FAF, 0x1080, 0x2AAF, };
+static unsigned int param_eq_band_5_trebel_reduce[] = {
+ 0x1027, 0xEBF3, 0xFC34, 0x3CC6, 0x3FE3, };
+
+static unsigned int param_eq_band_1_loudness[] = {
+ 0x3F62, 0xC001, 0x3FDF, 0x0042, 0x040B, };
+static unsigned int param_eq_band_2_loudness[] = {
+ 0x2D25, 0xC003, 0x3F08, 0x0101, 0x0B17, };
+static unsigned int param_eq_band_3_loudness[] = {
+ 0x2027, 0xC028, 0x3FA4, 0x0471, 0x06BC, };
+static unsigned int param_eq_band_4_loudness[] = {
+ 0x2D25, 0xC245, 0x3290, 0x10E2, 0x273B, };
+static unsigned int param_eq_band_5_loudness[] = {
+ 0x4000, 0xEBF3, 0x149E, 0x3CC6, 0x3C96, };
+
+/*
+ * Dynamic high pass excursion limiter filter coefficients.
+ * The coefficients define the user programmable frequency response for the
+ * excursion limiter.
+ * Use the PC evkit software to generate the coefficients and put it here.
+ */
+static unsigned int param_ex_resp_a[] = { 0, 0, 0, 0, 0 };
+static unsigned int param_ex_resp_b[] = { 0, 0, 0, 0, 0 };
+static unsigned int param_ex_resp_c[] = { 0, 0, 0, 0, 0 };
+
+/*
+ * Read the MAX9888 register space
+ */
+static unsigned int max9888_read(struct snd_soc_codec *codec, unsigned int reg)
+{
+ struct i2c_msg msg[2];
+ struct i2c_client *client;
+ u8 data[2];
+ int ret = 0;
+
+ client = (struct i2c_client *)codec->control_data;
+ data[0] = reg;
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].buf = &data[0];
+ msg[0].len = 1;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = &data[1];
+ msg[1].len = 1;
+#if defined(CONFIG_I2C)
+ ret = i2c_transfer(client->adapter, &msg[0], 2);
+#endif
+ return (ret == 2) ? data[1] : -EIO;
+}
+
+/*
+ * Write to the MAX9888 register space
+ */
+static int max9888_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 data[2];
+
+ data[0] = reg;
+ data[1] = value;
+
+ dprintk("%s 0x%02x=0x%02x\n", __func__, reg, value);
+ if (codec->hw_write(codec->control_data, data, 2) == 2)
+ return 0;
+ else
+ return -EIO;
+}
+
+#define INA1_PGA_BIT 0x01
+#define INA2_PGA_BIT 0x02
+#define INB1_PGA_BIT 0x04
+#define INB2_PGA_BIT 0x08
+
+/*
+ * The INx1 and INx2 PGA's share a power control signal.
+ * This function OR's the two power events to keep an unpowered INx
+ * from turning off it's counterpart.
+ * The control names are used to identify the PGA.
+ */
+int max9888_pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k,
+ int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ unsigned int val;
+ unsigned int pga;
+ unsigned int mask;
+ static int power_state;
+
+ if (w->reg != M9888_REG_4A_PWR_EN_IN)
+ return -EINVAL;
+
+ if (strncmp(w->name, "INA1", 4) == 0) {
+ pga = INA1_PGA_BIT;
+ mask = INA1_PGA_BIT | INA2_PGA_BIT;
+ } else if (strncmp(w->name, "INA2", 4) == 0) {
+ pga = INA2_PGA_BIT;
+ mask = INA1_PGA_BIT | INA2_PGA_BIT;
+ } else if (strncmp(w->name, "INB1", 4) == 0) {
+ pga = INB1_PGA_BIT;
+ mask = INB1_PGA_BIT | INB2_PGA_BIT;
+ } else if (strncmp(w->name, "INB2", 4) == 0) {
+ pga = INB2_PGA_BIT;
+ mask = INB1_PGA_BIT | INB2_PGA_BIT;
+ } else {
+ return -EINVAL;
+ }
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+
+ power_state |= pga;
+
+ /* Turn on, avoiding unnecessary writes */
+ val = max9888_read(codec, w->reg);
+
+ if (!(val & (1 << w->shift))) {
+ val |= (1 << w->shift);
+ max9888_write(codec, w->reg, val | MBEN);
+ }
+ break;
+
+ case SND_SOC_DAPM_PRE_PMD:
+ power_state &= ~pga;
+
+ /* Turn off if both are off, avoiding unnecessary writes */
+ if (!(power_state & mask)) {
+ val = max9888_read(codec, w->reg);
+ if (val & (1 << w->shift)) {
+ val &= ~(1 << w->shift);
+ max9888_write(codec, w->reg, val & ~MBEN);
+ }
+ }
+ break;
+ default:
+ val = max9888_read(codec, w->reg);
+ max9888_write(codec, w->reg, val | MBEN);
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * Define a few static eq settings
+ */
+static const char *max9888_eq[] = {
+ "voice", "music", "flat", "treble reduce", "loudness" };
+
+static const struct soc_enum max9888_eq_enum[] = {
+ SOC_ENUM_SINGLE_EXT(5, max9888_eq),
+};
+
+enum max9888_eq_enumeration {
+ MAX9888_EQ_VOICE = 0,
+ MAX9888_EQ_MUSIC,
+ MAX9888_EQ_FLAT,
+ MAX9888_EQ_TREBLE_REDUCE,
+ MAX9888_EQ_LOUDNESS
+};
+
+static int max9888_eq1_value = MAX9888_EQ_FLAT;
+static int max9888_eq2_value = MAX9888_EQ_FLAT;
+
+/*
+ * Do work of loading regs from table
+ */
+int m9888_eq_band(struct snd_soc_codec *codec, unsigned int dai,
+ unsigned int band, unsigned int *coefs)
+{
+ unsigned int eq_reg;
+ unsigned int i;
+
+ if (band > 4)
+ return 1;
+
+ if (dai > 1)
+ return 1;
+
+ /* Load the base register address */
+ eq_reg = dai ? M9888_REG_82_DAI2_EQ_BASE : M9888_REG_50_DAI1_EQ_BASE;
+
+ /* Add the band address offset, note adjustment for word address */
+ eq_reg += band * (REGS_PER_BAND << 1);
+
+ /* Step through the registers and coefs */
+ for (i = 0; i < REGS_PER_BAND; i++) {
+ max9888_write(codec, eq_reg++, HIGH_BYTE(coefs[i]));
+ max9888_write(codec, eq_reg++, LOW_BYTE(coefs[i]));
+ }
+
+ return 0;
+}
+
+/*
+ * Load selected eq from coefficient table
+ */
+int m9888_eq_control(struct snd_soc_codec *codec, unsigned int dai,
+ unsigned int curve)
+{
+ switch (curve) {
+ case MAX9888_EQ_VOICE:
+ m9888_eq_band(codec, dai, 0, param_eq_band_1_voice);
+ m9888_eq_band(codec, dai, 1, param_eq_band_2_voice);
+ m9888_eq_band(codec, dai, 2, param_eq_band_3_voice);
+ m9888_eq_band(codec, dai, 3, param_eq_band_4_voice);
+ m9888_eq_band(codec, dai, 4, param_eq_band_5_voice);
+ break;
+ case MAX9888_EQ_MUSIC:
+ m9888_eq_band(codec, dai, 0, param_eq_band_1_music);
+ m9888_eq_band(codec, dai, 1, param_eq_band_2_music);
+ m9888_eq_band(codec, dai, 2, param_eq_band_3_music);
+ m9888_eq_band(codec, dai, 3, param_eq_band_4_music);
+ m9888_eq_band(codec, dai, 4, param_eq_band_5_music);
+ break;
+ case MAX9888_EQ_FLAT:
+ m9888_eq_band(codec, dai, 0, param_eq_band_1_flat);
+ m9888_eq_band(codec, dai, 1, param_eq_band_2_flat);
+ m9888_eq_band(codec, dai, 2, param_eq_band_3_flat);
+ m9888_eq_band(codec, dai, 3, param_eq_band_4_flat);
+ m9888_eq_band(codec, dai, 4, param_eq_band_5_flat);
+ break;
+ case MAX9888_EQ_TREBLE_REDUCE:
+ m9888_eq_band(codec, dai, 0, param_eq_band_1_trebel_reduce);
+ m9888_eq_band(codec, dai, 1, param_eq_band_2_trebel_reduce);
+ m9888_eq_band(codec, dai, 2, param_eq_band_3_trebel_reduce);
+ m9888_eq_band(codec, dai, 3, param_eq_band_4_trebel_reduce);
+ m9888_eq_band(codec, dai, 4, param_eq_band_5_trebel_reduce);
+ break;
+ case MAX9888_EQ_LOUDNESS:
+ m9888_eq_band(codec, dai, 0, param_eq_band_1_loudness);
+ m9888_eq_band(codec, dai, 1, param_eq_band_2_loudness);
+ m9888_eq_band(codec, dai, 2, param_eq_band_3_loudness);
+ m9888_eq_band(codec, dai, 3, param_eq_band_4_loudness);
+ m9888_eq_band(codec, dai, 4, param_eq_band_5_loudness);
+ break;
+ }
+
+ return 1;
+}
+
+/*
+ * Load new eq settings
+ */
+static int max9888_eq1_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned char reg;
+
+ max9888_eq1_value = ucontrol->value.integer.value[0];
+
+ reg = max9888_read(codec, M9888_REG_47_CFG_LEVEL);
+ reg &= ~EQ1EN;
+ max9888_write(codec, M9888_REG_47_CFG_LEVEL, reg);
+
+ m9888_eq_control(codec, 0, max9888_eq1_value);
+
+ reg |= EQ1EN;
+ max9888_write(codec, M9888_REG_47_CFG_LEVEL, reg);
+
+ return 1;
+}
+
+/*
+ * Return current eq setting
+ */
+static int max9888_eq1_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = max9888_eq1_value;
+ return 0;
+}
+
+/*
+ * Load new eq settings
+ */
+static int max9888_eq2_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ unsigned char reg;
+
+ max9888_eq2_value = ucontrol->value.integer.value[0];
+
+ reg = max9888_read(codec, M9888_REG_47_CFG_LEVEL);
+ reg &= ~EQ2EN;
+ max9888_write(codec, M9888_REG_47_CFG_LEVEL, reg);
+
+ m9888_eq_control(codec, 1, max9888_eq2_value);
+
+ reg |= EQ2EN;
+ max9888_write(codec, M9888_REG_47_CFG_LEVEL, reg);
+
+ return 1;
+}
+
+/*
+ * Return current eq setting
+ */
+static int max9888_eq2_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = max9888_eq2_value;
+ return 0;
+}
+
+/*
+ * Excursion limiter modes
+ */
+static const char *max9888_ex_mode[] = {
+ "disable",
+ "100Hz",
+ "400Hz",
+ "600Hz",
+ "800Hz",
+ "1000Hz",
+ "200-400Hz",
+ "400-600Hz",
+ "400-800Hz",
+ "user-400Hz",
+ "user-600Hz",
+ "user-800Hz",
+ "user-1000Hz"
+};
+
+static const unsigned int ex_mode_table[] = {
+ 0x00,
+ (0 << 4) | 3,
+ (1 << 4) | 0,
+ (2 << 4) | 0,
+ (3 << 4) | 0,
+ (4 << 4) | 0,
+ (1 << 4) | 1,
+ (2 << 4) | 2,
+ (3 << 4) | 2,
+ (1 << 4) | 3,
+ (2 << 4) | 3,
+ (3 << 4) | 3,
+ (4 << 4) | 3
+};
+
+static const struct soc_enum max9888_ex_mode_enum[] = {
+ SOC_ENUM_SINGLE_EXT(13, max9888_ex_mode),
+};
+
+static unsigned int max9888_ex_mode_value;
+
+/*
+ * Excursion limiter mode - set mode
+ */
+static int max9888_ex_mode_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ max9888_ex_mode_value = ucontrol->value.integer.value[0];
+
+ if (max9888_ex_mode_value <= 13) {
+ max9888_write(codec, M9888_REG_3F_SPKDHP,
+ ex_mode_table[max9888_ex_mode_value]);
+ }
+
+ return 1;
+}
+
+/*
+ * Excursion limiter mode - get mode
+ */
+static int max9888_ex_mode_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = max9888_ex_mode_value;
+ return 0;
+}
+
+/*
+ * Functions and control structures for loading
+ * static coefficients into the bi-quad filter.
+ *
+ * As it is the user has a choice of three filters - a, b, or c.
+ */
+static const char *max9888_ex_resp[] = { "a", "b", "c" };
+
+static const struct soc_enum max9888_ex_resp_enum[] = {
+ SOC_ENUM_SINGLE_EXT(3, max9888_ex_resp),
+};
+
+enum max9888_ex_resp_enumeration {
+ MAX9888_BIQUAD_A = 0,
+ MAX9888_BIQUAD_B,
+ MAX9888_BIQUAD_C
+};
+
+static int max9888_ex1_resp_value = MAX9888_BIQUAD_A;
+static int max9888_ex2_resp_value = MAX9888_BIQUAD_A;
+
+/*
+ * Load user programmable mode excursion limiter filter coefficients
+ */
+static void max9888_ex_resp_control(struct snd_soc_codec *codec, int reg,
+ unsigned int *param)
+{
+ max9888_write(codec, reg, HIGH_BYTE(param[0]));
+ max9888_write(codec, ++reg, LOW_BYTE(param[0]));
+ max9888_write(codec, ++reg, HIGH_BYTE(param[1]));
+ max9888_write(codec, ++reg, LOW_BYTE(param[1]));
+ max9888_write(codec, ++reg, HIGH_BYTE(param[2]));
+ max9888_write(codec, ++reg, LOW_BYTE(param[2]));
+ max9888_write(codec, ++reg, HIGH_BYTE(param[3]));
+ max9888_write(codec, ++reg, LOW_BYTE(param[3]));
+ max9888_write(codec, ++reg, HIGH_BYTE(param[4]));
+ max9888_write(codec, ++reg, LOW_BYTE(param[4]));
+}
+
+/*
+ * Select and load excursion limiter user mode coefficient selection
+ */
+static int max9888_ex1_resp_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ max9888_ex1_resp_value = ucontrol->value.integer.value[0];
+
+ switch (max9888_ex1_resp_value) {
+ case MAX9888_BIQUAD_A:
+ max9888_ex_resp_control(codec, M9888_REG_B4_DAI1_BIQUAD_BASE,
+ param_ex_resp_a);
+ break;
+
+ case MAX9888_BIQUAD_B:
+ max9888_ex_resp_control(codec, M9888_REG_B4_DAI1_BIQUAD_BASE,
+ param_ex_resp_b);
+ break;
+
+ case MAX9888_BIQUAD_C:
+ max9888_ex_resp_control(codec, M9888_REG_B4_DAI1_BIQUAD_BASE,
+ param_ex_resp_c);
+ break;
+ }
+
+ return 1;
+}
+
+/*
+ * Return current excursion limiter user mode coefficient selection
+ */
+static int max9888_ex1_resp_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = max9888_ex1_resp_value;
+ return 0;
+}
+
+/*
+ * Select and load excursion limiter user mode coefficient selection
+ */
+static int max9888_ex2_resp_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ max9888_ex2_resp_value = ucontrol->value.integer.value[0];
+
+ switch (max9888_ex2_resp_value) {
+ case MAX9888_BIQUAD_A:
+ max9888_ex_resp_control(codec, M9888_REG_BE_DAI2_BIQUAD_BASE,
+ param_ex_resp_a);
+ break;
+
+ case MAX9888_BIQUAD_B:
+ max9888_ex_resp_control(codec, M9888_REG_BE_DAI2_BIQUAD_BASE,
+ param_ex_resp_b);
+ break;
+
+ case MAX9888_BIQUAD_C:
+ max9888_ex_resp_control(codec, M9888_REG_BE_DAI2_BIQUAD_BASE,
+ param_ex_resp_c);
+ break;
+ }
+
+ return 1;
+}
+
+/*
+ * Return current excursion limiter user mode coefficient selection
+ */
+static int max9888_ex2_resp_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ ucontrol->value.integer.value[0] = max9888_ex2_resp_value;
+ return 0;
+}
+
+/*
+ * Control mic1 analog amplifier circuit on/off, and gain
+ * Typical setting: register value of 0x7F for 30dB mic gain
+ */
+void m9888_mic1_gain_control(struct snd_soc_codec *codec, unsigned int gain,
+ unsigned int power_on)
+{
+ /* power off the amplifier */
+ if (power_on == 0) {
+ max9888_write(codec, M9888_REG_31_LVL_MIC1, 0x00);
+ return;
+ }
+ /* 0dB..19dB */
+ if (gain < 20) {
+ max9888_write(codec, M9888_REG_31_LVL_MIC1,
+ (1 << 5) | (20 - gain));
+ return;
+ }
+ /* 20dB..29dB */
+ if (gain < 30) {
+ max9888_write(codec, M9888_REG_31_LVL_MIC1,
+ (2 << 5) | (40 - gain));
+ return;
+ }
+ /* 30dB..49B */
+ if (gain < 50) {
+ max9888_write(codec, M9888_REG_31_LVL_MIC1,
+ (3 << 5) | (50 - gain));
+ return;
+ }
+ /* set to maximum 50dB */
+ max9888_write(codec, M9888_REG_31_LVL_MIC1, (3 << 5) | 0);
+}
+
+/*
+ * Control mic2 analog amplifier circuit on/off, and gain
+ * Typical setting: register value of 0x7F for 30dB mic gain
+ */
+void m9888_mic2_gain_control(struct snd_soc_codec *codec, unsigned int gain,
+ unsigned int power_on)
+{
+ /* power off the amplifier */
+ if (power_on == 0) {
+ max9888_write(codec, M9888_REG_32_LVL_MIC2, 0x00);
+ return;
+ }
+ /* 0dB..19dB */
+ if (gain < 20) {
+ max9888_write(codec, M9888_REG_32_LVL_MIC2,
+ (1 << 5) | (20 - gain));
+ return;
+ }
+ /* 20dB..29dB */
+ if (gain < 30) {
+ max9888_write(codec, M9888_REG_32_LVL_MIC2,
+ (2 << 5) | (40 - gain));
+ return;
+ }
+ /* 30dB..49B */
+ if (gain < 50) {
+ max9888_write(codec, M9888_REG_32_LVL_MIC2,
+ (3 << 5) | (50 - gain));
+ return;
+ }
+ /* set to maximum 50dB */
+ max9888_write(codec, M9888_REG_32_LVL_MIC2, (3 << 5) | 0);
+}
+
+/*
+ * Set headphone output volume and mute setting
+ */
+void headphone_set_volume(struct snd_soc_codec *codec, u8 vol, u8 mute)
+{
+
+ if (vol > 0x1F)
+ vol = 0x1F;
+
+ if (mute > 1)
+ mute = 1;
+
+ max9888_write(codec, M9888_REG_38_LVL_HEADPHONE_L,
+ (mute << 7) | (vol << 0));
+ max9888_write(codec, M9888_REG_39_LVL_HEADPHONE_R,
+ (mute << 7) | (vol << 0));
+}
+
+/*
+ * set receiver output volume and mute setting
+ */
+void receiver_set_volume(struct snd_soc_codec *codec, u8 vol, u8 mute)
+{
+
+ if (vol > 0x1F)
+ vol = 0x1F;
+
+ if (mute > 1)
+ mute = 1;
+
+ max9888_write(codec, M9888_REG_3A_LVL_RECEIVER,
+ (mute << 7) | (vol << 0));
+}
+
+/*
+ * set speaker output volume and mute setting
+ */
+void speaker_set_volume(struct snd_soc_codec *codec, u8 vol, u8 mute)
+{
+
+ if (vol > 0x1F)
+ vol = 0x1F;
+
+ if (mute > 1)
+ mute = 1;
+
+ max9888_write(codec, M9888_REG_3B_LVL_SPEAKER_L,
+ (mute << 7) | (vol << 0));
+ max9888_write(codec, M9888_REG_3C_LVL_SPEAKER_R,
+ (mute << 7) | (vol << 0));
+}
+
+/*
+ * configure speaker ALC
+ */
+void m9888_speaker_ALC_control(struct snd_soc_codec *codec)
+{
+ /* compressor enable */
+ max9888_write(codec, M9888_REG_41_SPKALC_COMP, (0 << 7) |
+ (7 << 4) | (0 << 3) | (0 << 0));
+}
+
+/*
+ * Control mic AGC for both microphone inputs.
+ */
+void m9888_mic_AGC_control(struct snd_soc_codec *codec)
+{
+ max9888_write(codec, M9888_REG_3D_MICAGC_CFG, (0 << 7) |
+ (0 << 4) | (0 << 2) | (0 << 0));
+
+ max9888_write(codec, M9888_REG_3E_MICAGC_THRESHOLD, (0 << 4) |
+ (0 << 0));
+}
+
+/*
+ * Mute the speaker
+ */
+static const char *max9888_hp_spk_mute[] = { "disable", "enable" };
+
+static const struct soc_enum max9888_hp_spk_mute_enum[] = {
+ SOC_ENUM_SINGLE_EXT(2, max9888_hp_spk_mute),
+};
+
+#define MAX9888_HP_SPK_MUTE_DISABLE 0
+#define MAX9888_HP_SPK_MUTE_ENABLE 1
+
+static int max9888_spk_mute_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ u8 lReg = max9888_read(codec, M9888_REG_3B_LVL_SPEAKER_L);
+ u8 rReg = max9888_read(codec, M9888_REG_3C_LVL_SPEAKER_R);
+
+ if (ucontrol->value.integer.value[0] == MAX9888_HP_SPK_MUTE_ENABLE) {
+ /* Enable mute for both channels */
+ lReg |= SPXM;
+ rReg |= SPXM;
+ } else if (ucontrol->value.integer.value[0]
+ == MAX9888_HP_SPK_MUTE_DISABLE) {
+ /* Disable mute for both channels */
+ lReg &= ~SPXM;
+ rReg &= ~SPXM;
+ } else {
+ return -1;
+ }
+
+ max9888_write(codec, M9888_REG_3B_LVL_SPEAKER_L, lReg);
+ max9888_write(codec, M9888_REG_3C_LVL_SPEAKER_R, rReg);
+
+ return 1;
+}
+
+static int max9888_spk_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u8 reg = max9888_read(codec, M9888_REG_3B_LVL_SPEAKER_L);
+
+ ucontrol->value.integer.value[0] = (reg & SPXM) ? 1 : 0;
+
+ return 0;
+}
+
+/*
+ * Mute the headphone
+ */
+static int max9888_hp_mute_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u8 lReg = max9888_read(codec, M9888_REG_38_LVL_HEADPHONE_L);
+ u8 rReg = max9888_read(codec, M9888_REG_39_LVL_HEADPHONE_R);
+
+ if (MAX9888_HP_SPK_MUTE_ENABLE == ucontrol->value.integer.value[0]) {
+ /* Enable mute for both channels */
+ lReg |= HPXM;
+ rReg |= HPXM;
+ } else {
+ /* Disable mute for both channels */
+ lReg &= ~HPXM;
+ rReg &= ~HPXM;
+ }
+
+ max9888_write(codec, M9888_REG_38_LVL_HEADPHONE_L, lReg);
+ max9888_write(codec, M9888_REG_39_LVL_HEADPHONE_R, rReg);
+
+ return 1;
+}
+
+/*
+ * Get current headphone mute hardware register setting
+ */
+static int max9888_hp_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ u8 reg = max9888_read(codec, M9888_REG_38_LVL_HEADPHONE_L);
+
+ ucontrol->value.integer.value[0] = (reg & HPXM) ? 1 : 0;
+ return 0;
+}
+
+static const char *max9888_rx_mute[] = { "disable", "enable" };
+static const char *max9888_fltr_mode[] = { "voice", "music" };
+static const char *max9888_highrate[] = { ">50khz", "<50khz" };
+
+static const char *max9888_dai1_fltr[] = {
+ "disable", "fc=258/fs=16k", "fc=500/fs=16k",
+ "fc=258/fs=8k", "fc=500/fs=8k", "fc=200" };
+static const char *max9888_dc_block[] = { "disable", "enable" };
+static const char *max9888_exlimit_threshold[] = {
+ "0.6v", "1.2v", "1.8v", "2.4v", "3.0", "3.6v", "4.2v", "4.8v" };
+
+static const struct soc_enum max9888_enum[] = {
+ SOC_ENUM_SINGLE(M9888_REG_3A_LVL_RECEIVER, 7, 2, max9888_rx_mute),
+ SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 7, 2, max9888_fltr_mode),
+ SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 3, 2, max9888_highrate),
+ SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 0, 6, max9888_dai1_fltr),
+ SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 4, 6, max9888_dai1_fltr),
+ SOC_ENUM_SINGLE(M9888_REG_20_DAI2_FILTERS, 3, 2, max9888_highrate),
+ SOC_ENUM_SINGLE(M9888_REG_20_DAI2_FILTERS, 0, 2, max9888_dc_block),
+ SOC_ENUM_SINGLE(M9888_REG_40_SPKDHP_THRESHOLD, 0, 8,
+ max9888_exlimit_threshold)
+};
+
+static const struct snd_kcontrol_new max9888_snd_controls[] = {
+
+ /* analog output levels */
+ SOC_DOUBLE_R("hp_vol", M9888_REG_38_LVL_HEADPHONE_L,
+ M9888_REG_39_LVL_HEADPHONE_R, 0, 31, 0),
+ SOC_DOUBLE_R("spk_vol", M9888_REG_3B_LVL_SPEAKER_L,
+ M9888_REG_3C_LVL_SPEAKER_R, 0, 31, 0),
+ SOC_SINGLE("rec_vol", M9888_REG_3A_LVL_RECEIVER, 0, 31, 0),
+
+ SOC_ENUM_EXT("hp_mute", max9888_hp_spk_mute_enum, max9888_hp_mute_get,
+ max9888_hp_mute_set),
+ SOC_ENUM_EXT("spk_mute", max9888_hp_spk_mute_enum, max9888_spk_mute_get,
+ max9888_spk_mute_set),
+ SOC_ENUM("rec_mute", max9888_enum[0]),
+
+ /* analog input levels */
+ SOC_SINGLE("mic1_gain", M9888_REG_31_LVL_MIC1, 0, 31, 1),
+ SOC_SINGLE("mic2_gain", M9888_REG_32_LVL_MIC2, 0, 31, 1),
+
+ SOC_SINGLE("mic1_pre", M9888_REG_31_LVL_MIC1, 5, 3, 0),
+ SOC_SINGLE("mic2_pre", M9888_REG_32_LVL_MIC2, 5, 3, 0),
+
+ SOC_SINGLE("ina_gain", M9888_REG_33_LVL_INA, 0, 7, 1),
+ SOC_SINGLE("inb_gain", M9888_REG_34_LVL_INB, 0, 7, 1),
+
+ /* digital gains */
+ SOC_SINGLE("adcl_dvol", M9888_REG_2F_LVL_ADC_L, 0, 15, 0),
+ SOC_SINGLE("adcr_dvol", M9888_REG_30_LVL_ADC_R, 0, 15, 0),
+
+ SOC_SINGLE("adcl_dgain", M9888_REG_2F_LVL_ADC_L, 4, 3, 0),
+ SOC_SINGLE("adcr_dgain", M9888_REG_30_LVL_ADC_R, 4, 3, 0),
+
+ /* equalizer */
+ SOC_SINGLE("eq1_enable", M9888_REG_47_CFG_LEVEL, 0, 1, 0),
+ SOC_SINGLE("eq2_enable", M9888_REG_47_CFG_LEVEL, 1, 1, 0),
+
+ SOC_SINGLE("eq1_atten", M9888_REG_2C_LVL_DAI1_PLAY_EQ, 0, 15, 0),
+ SOC_SINGLE("eq2_atten", M9888_REG_2E_LVL_DAI2_PLAY_EQ, 0, 15, 0),
+
+ SOC_ENUM_EXT("eq1_resp", max9888_eq_enum, max9888_eq1_get,
+ max9888_eq1_set),
+ SOC_ENUM_EXT("eq2_resp", max9888_eq_enum, max9888_eq2_get,
+ max9888_eq2_set),
+
+ /* excursion limiter */
+ SOC_ENUM_EXT("exlmt_mode", max9888_ex_mode_enum, max9888_ex_mode_get,
+ max9888_ex_mode_set),
+ SOC_ENUM("exlmt_thresh", max9888_enum[7]),
+ SOC_ENUM_EXT("exlmt1_resp", max9888_ex_resp_enum, max9888_ex1_resp_get,
+ max9888_ex1_resp_set),
+ SOC_ENUM_EXT("exlmt2_resp", max9888_ex_resp_enum, max9888_ex2_resp_get,
+ max9888_ex2_resp_set),
+
+ /* voice/music filters */
+ SOC_ENUM("dai1_flt_mode", max9888_enum[1]),
+ SOC_ENUM("dai1_flt_dac", max9888_enum[3]),
+ SOC_ENUM("dai1_flt_adc", max9888_enum[4]),
+
+ SOC_ENUM("dai1_highrate", max9888_enum[2]),
+ SOC_ENUM("dai2_highrate", max9888_enum[5]),
+ SOC_ENUM("dai2_dcblock", max9888_enum[6]),
+
+ /* automatic level control (for both DAI1/DAI2) */
+ SOC_SINGLE("alc_enable", M9888_REG_41_SPKALC_COMP, 7, 1, 0),
+ SOC_SINGLE("alc_thresh", M9888_REG_41_SPKALC_COMP, 0, 7, 0),
+ SOC_SINGLE("alc_multiband", M9888_REG_41_SPKALC_COMP, 3, 1, 0),
+ SOC_SINGLE("alc_rlstime", M9888_REG_41_SPKALC_COMP, 4, 7, 0),
+
+ /* power limiter */
+ SOC_SINGLE("pwlmt_thresh", M9888_REG_42_PWRLMT_CFG, 4, 15, 0),
+ SOC_SINGLE("pwlmt_weight", M9888_REG_42_PWRLMT_CFG, 0, 7, 0),
+ SOC_SINGLE("pwlmt_time1", M9888_REG_43_PWRLMT_TIME, 0, 15, 0),
+ SOC_SINGLE("pwlmt_time2", M9888_REG_43_PWRLMT_TIME, 4, 15, 0),
+
+ /* THD distortion limiter */
+ SOC_SINGLE("thdlmt_thresh", M9888_REG_44_THDLMT_CFG, 4, 15, 0),
+ SOC_SINGLE("thdlmt_time", M9888_REG_44_THDLMT_CFG, 0, 7, 0),
+};
+
+/* Left speaker mixer switch */
+static const struct snd_kcontrol_new max9888_left_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("left_dac", M9888_REG_29_MIX_SPEAKER, 7, 1, 0),
+ SOC_DAPM_SINGLE("right_dac", M9888_REG_29_MIX_SPEAKER, 6, 1, 0),
+ SOC_DAPM_SINGLE("preout3", M9888_REG_29_MIX_SPEAKER, 4, 1, 0),
+};
+
+/* Right speaker mixer switch */
+static const struct snd_kcontrol_new max9888_right_speaker_mixer_controls[] = {
+ SOC_DAPM_SINGLE("left_dac", M9888_REG_29_MIX_SPEAKER, 3, 1, 0),
+ SOC_DAPM_SINGLE("right_dac", M9888_REG_29_MIX_SPEAKER, 2, 1, 0),
+ SOC_DAPM_SINGLE("preout2", M9888_REG_29_MIX_SPEAKER, 0, 1, 0),
+};
+
+/* Left headphone mixer switch */
+static const struct snd_kcontrol_new max9888_left_headphone_mixer_controls[] = {
+ SOC_DAPM_SINGLE("left_dac", M9888_REG_27_MIX_HEADPHONE, 7, 1, 0),
+ SOC_DAPM_SINGLE("right_dac", M9888_REG_27_MIX_HEADPHONE, 6, 1, 0),
+ SOC_DAPM_SINGLE("preout1", M9888_REG_27_MIX_HEADPHONE, 4, 1, 0),
+};
+
+/* Right headphone mixer switch */
+static const struct snd_kcontrol_new
+ max9888_right_headphone_mixer_controls[] = {
+ SOC_DAPM_SINGLE("left_dac", M9888_REG_27_MIX_HEADPHONE, 3, 1, 0),
+ SOC_DAPM_SINGLE("right_dac", M9888_REG_27_MIX_HEADPHONE, 2, 1, 0),
+ SOC_DAPM_SINGLE("preout2", M9888_REG_27_MIX_HEADPHONE, 0, 1, 0),
+};
+
+/* Earpiece/receiver mixer switch */
+static const struct snd_kcontrol_new max9888_receiver_mixer_controls[] = {
+ SOC_DAPM_SINGLE("left_dac", M9888_REG_28_MIX_RECEIVER, 3, 1, 0),
+ SOC_DAPM_SINGLE("right_dac", M9888_REG_28_MIX_RECEIVER, 2, 1, 0),
+ SOC_DAPM_SINGLE("preout1", M9888_REG_28_MIX_RECEIVER, 1, 1, 0),
+ SOC_DAPM_SINGLE("preout2", M9888_REG_28_MIX_RECEIVER, 0, 1, 0),
+};
+
+/* Pre-output mixer 1 switch */
+static const struct snd_kcontrol_new max9888_preout_mixer_1_controls[] = {
+ SOC_DAPM_SINGLE("ina1", M9888_REG_24_MIX_PREOUT_A, 3, 1, 0),
+ SOC_DAPM_SINGLE("ina2", M9888_REG_24_MIX_PREOUT_A, 2, 1, 0),
+ SOC_DAPM_SINGLE("inb1", M9888_REG_24_MIX_PREOUT_A, 1, 1, 0),
+ SOC_DAPM_SINGLE("inb2", M9888_REG_24_MIX_PREOUT_A, 0, 1, 0),
+};
+
+/* Pre-output mixer 2 switch */
+static const struct snd_kcontrol_new max9888_preout_mixer_2_controls[] = {
+ SOC_DAPM_SINGLE("ina1", M9888_REG_25_MIX_PREOUT_B, 3, 1, 0),
+ SOC_DAPM_SINGLE("ina2", M9888_REG_25_MIX_PREOUT_B, 2, 1, 0),
+ SOC_DAPM_SINGLE("inb1", M9888_REG_25_MIX_PREOUT_B, 1, 1, 0),
+ SOC_DAPM_SINGLE("inb2", M9888_REG_25_MIX_PREOUT_B, 0, 1, 0),
+};
+
+/* Pre-output mixer 3 switch */
+static const struct snd_kcontrol_new max9888_preout_mixer_3_controls[] = {
+ SOC_DAPM_SINGLE("ina1", M9888_REG_26_MIX_PREOUT_C, 3, 1, 0),
+ SOC_DAPM_SINGLE("ina2", M9888_REG_26_MIX_PREOUT_C, 2, 1, 0),
+ SOC_DAPM_SINGLE("inb1", M9888_REG_26_MIX_PREOUT_C, 1, 1, 0),
+ SOC_DAPM_SINGLE("inb2", M9888_REG_26_MIX_PREOUT_C, 0, 1, 0),
+};
+
+/* Left ADC mixer switch */
+static const struct snd_kcontrol_new max9888_left_ADC_mixer_controls[] = {
+ SOC_DAPM_SINGLE("mic1", M9888_REG_22_MIX_ADC_LEFT, 7, 1, 0),
+ SOC_DAPM_SINGLE("mic2", M9888_REG_22_MIX_ADC_LEFT, 6, 1, 0),
+ SOC_DAPM_SINGLE("ina1", M9888_REG_22_MIX_ADC_LEFT, 3, 1, 0),
+ SOC_DAPM_SINGLE("ina2", M9888_REG_22_MIX_ADC_LEFT, 2, 1, 0),
+ SOC_DAPM_SINGLE("inb1", M9888_REG_22_MIX_ADC_LEFT, 1, 1, 0),
+ SOC_DAPM_SINGLE("inb2", M9888_REG_22_MIX_ADC_LEFT, 0, 1, 0),
+};
+
+/* Right ADC mixer switch */
+static const struct snd_kcontrol_new max9888_right_ADC_mixer_controls[] = {
+ SOC_DAPM_SINGLE("mic1", M9888_REG_23_MIX_ADC_RIGHT, 7, 1, 0),
+ SOC_DAPM_SINGLE("mic2", M9888_REG_23_MIX_ADC_RIGHT, 6, 1, 0),
+ SOC_DAPM_SINGLE("ina1", M9888_REG_23_MIX_ADC_RIGHT, 3, 1, 0),
+ SOC_DAPM_SINGLE("ina2", M9888_REG_23_MIX_ADC_RIGHT, 2, 1, 0),
+ SOC_DAPM_SINGLE("inb1", M9888_REG_23_MIX_ADC_RIGHT, 1, 1, 0),
+ SOC_DAPM_SINGLE("inb2", M9888_REG_23_MIX_ADC_RIGHT, 0, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget max9888_dapm_widgets[] = {
+
+ SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", M9888_REG_4A_PWR_EN_IN, 1, 0),
+ SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", M9888_REG_4A_PWR_EN_IN, 0, 0),
+
+ SND_SOC_DAPM_DAC("DACL", "HiFi Playback", M9888_REG_4B_PWR_EN_OUT, 1,
+ 0),
+ SND_SOC_DAPM_DAC("DACR", "HiFi Playback", M9888_REG_4B_PWR_EN_OUT, 0,
+ 0),
+
+ SND_SOC_DAPM_PGA("HP Left Out", M9888_REG_4B_PWR_EN_OUT, 7, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("HP Right Out", M9888_REG_4B_PWR_EN_OUT, 6, 0, NULL,
+ 0),
+
+ SND_SOC_DAPM_PGA("SPK Left Out", M9888_REG_4B_PWR_EN_OUT, 5, 0, NULL,
+ 0),
+ SND_SOC_DAPM_PGA("SPK Right Out", M9888_REG_4B_PWR_EN_OUT, 4, 0, NULL,
+ 0),
+
+ SND_SOC_DAPM_PGA("REC Out", M9888_REG_4B_PWR_EN_OUT, 3, 0, NULL, 0),
+
+ SND_SOC_DAPM_MIXER("hp_left_mixer", SND_SOC_NOPM, 0, 0,
+ &max9888_left_headphone_mixer_controls[0],
+ ARRAY_SIZE(max9888_left_headphone_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("hp_right_mixer", SND_SOC_NOPM, 0, 0,
+ &max9888_right_headphone_mixer_controls[0],
+ ARRAY_SIZE(max9888_right_headphone_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("spk_left_mixer", SND_SOC_NOPM, 0, 0,
+ &max9888_left_speaker_mixer_controls[0],
+ ARRAY_SIZE(max9888_left_speaker_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("spk_right_mixer", SND_SOC_NOPM, 0, 0,
+ &max9888_right_speaker_mixer_controls[0],
+ ARRAY_SIZE(max9888_left_speaker_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("rec_mixer", SND_SOC_NOPM, 0, 0,
+ &max9888_receiver_mixer_controls[0],
+ ARRAY_SIZE(max9888_receiver_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("preout1_mixer", SND_SOC_NOPM, 0, 0,
+ &max9888_preout_mixer_1_controls[0],
+ ARRAY_SIZE(max9888_preout_mixer_1_controls)),
+
+ SND_SOC_DAPM_MIXER("preout2_mixer", SND_SOC_NOPM, 0, 0,
+ &max9888_preout_mixer_2_controls[0],
+ ARRAY_SIZE(max9888_preout_mixer_2_controls)),
+
+ SND_SOC_DAPM_MIXER("preout3_mixer", SND_SOC_NOPM, 0, 0,
+ &max9888_preout_mixer_3_controls[0],
+ ARRAY_SIZE(max9888_preout_mixer_1_controls)),
+
+ SND_SOC_DAPM_MIXER("left_adc_mixer", SND_SOC_NOPM, 0, 0,
+ &max9888_left_ADC_mixer_controls[0],
+ ARRAY_SIZE(max9888_left_ADC_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("right_adc_mixer", SND_SOC_NOPM, 0, 0,
+ &max9888_right_ADC_mixer_controls[0],
+ ARRAY_SIZE(max9888_right_ADC_mixer_controls)),
+
+ SND_SOC_DAPM_PGA_E("INA1 Input", M9888_REG_4A_PWR_EN_IN,
+ 7, 0, NULL, 0, max9888_pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INA2 Input", M9888_REG_4A_PWR_EN_IN,
+ 7, 0, NULL, 0, max9888_pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INB1 Input", M9888_REG_4A_PWR_EN_IN,
+ 6, 0, NULL, 0, max9888_pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_PGA_E("INB2 Input", M9888_REG_4A_PWR_EN_IN,
+ 6, 0, NULL, 0, max9888_pga_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", M9888_REG_4A_PWR_EN_IN, 3, 0),
+ SND_SOC_DAPM_MICBIAS("Digmic_l", M9888_REG_46_CFG_MIC, 5, 0),
+ SND_SOC_DAPM_MICBIAS("Digmic_r", M9888_REG_46_CFG_MIC, 4, 0),
+
+ SND_SOC_DAPM_OUTPUT("HPL"),
+ SND_SOC_DAPM_OUTPUT("HPR"),
+ SND_SOC_DAPM_OUTPUT("SPKL"),
+ SND_SOC_DAPM_OUTPUT("SPKR"),
+ SND_SOC_DAPM_OUTPUT("REC"),
+
+ SND_SOC_DAPM_INPUT("DIGMIC"),
+ SND_SOC_DAPM_INPUT("MIC1"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+ SND_SOC_DAPM_INPUT("INA1"),
+ SND_SOC_DAPM_INPUT("INA2"),
+ SND_SOC_DAPM_INPUT("INB1"),
+ SND_SOC_DAPM_INPUT("INB2"),
+};
+
+static const struct snd_soc_dapm_route audio_map[] = {
+ /* Left headphone output mixer */
+ {"hp_left_mixer", "left_dac", "DACL"},
+ {"hp_left_mixer", "right_dac", "DACR"},
+ {"hp_left_mixer", "preout1", "preout1_mixer"},
+
+ /* Right headphone output mixer */
+ {"hp_right_mixer", "left_dac", "DACL"},
+ {"hp_right_mixer", "right_dac", "DACR"},
+ {"hp_right_mixer", "preout2", "preout2_mixer"},
+
+ /* Left speaker output mixer */
+ {"spk_left_mixer", "left_dac", "DACL"},
+ {"spk_left_mixer", "right_dac", "DACR"},
+ {"spk_left_mixer", "preout3", "preout3_mixer"},
+
+ /* Right speaker output mixer */
+ {"spk_right_mixer", "left_dac", "DACL"},
+ {"spk_right_mixer", "right_dac", "DACR"},
+ {"spk_right_mixer", "preout2", "preout3_mixer"},
+
+ /* Earpiece/Receiver output mixer */
+ {"rec_mixer", "left_dac", "DACL"},
+ {"rec_mixer", "right_dac", "DACR"},
+ {"rec_mixer", "preout1", "preout1_mixer"},
+ {"rec_mixer", "preout2", "preout2_mixer"},
+
+ {"HP Left Out", NULL, "hp_left_mixer"},
+ {"HPL", NULL, "HP Left Out"},
+
+ {"HP Right Out", NULL, "hp_right_mixer"},
+ {"HPR", NULL, "HP Right Out"},
+
+ {"SPK Left Out", NULL, "spk_left_mixer"},
+ {"SPKL", NULL, "SPK Left Out"},
+
+ {"SPK Right Out", NULL, "spk_right_mixer"},
+ {"SPKR", NULL, "SPK Right Out"},
+
+ {"REC Out", NULL, "rec_mixer"},
+ {"REC", NULL, "REC Out"},
+
+ /* Left ADC input mixer */
+ {"left_adc_mixer", "mic1", "Mic Bias"},
+ {"left_adc_mixer", "mic2", "Mic Bias"},
+ {"left_adc_mixer", "ina1", "INA1 Input"},
+ {"left_adc_mixer", "ina2", "INA2 Input"},
+ {"left_adc_mixer", "inb1", "INB1 Input"},
+ {"left_adc_mixer", "inb2", "INB2 Input"},
+
+ /* Right ADC input mixer */
+ {"right_adc_mixer", "mic1", "Mic Bias"},
+ {"right_adc_mixer", "mic2", "Mic Bias"},
+ {"right_adc_mixer", "ina1", "INA1 Input"},
+ {"right_adc_mixer", "ina2", "INA2 Input"},
+ {"right_adc_mixer", "inb1", "INB1 Input"},
+ {"right_adc_mixer", "inb2", "INB2 Input"},
+
+ /* Pre-output 1 mixer */
+ {"preout1_mixer", "ina1", "INA1 Input"},
+ {"preout1_mixer", "ina2", "INA2 Input"},
+ {"preout1_mixer", "inb1", "INB1 Input"},
+ {"preout1_mixer", "inb2", "INB2 Input"},
+
+ /* Pre-output 2 mixer */
+ {"preout2_mixer", "ina1", "INA1 Input"},
+ {"preout2_mixer", "ina2", "INA2 Input"},
+ {"preout2_mixer", "inb1", "INB1 Input"},
+ {"preout2_mixer", "inb2", "INB2 Input"},
+
+ /* Pre-output 3 mixer */
+ {"preout3_mixer", "ina1", "INA1 Input"},
+ {"preout3_mixer", "ina2", "INA2 Input"},
+ {"preout3_mixer", "inb1", "INB1 Input"},
+ {"preout3_mixer", "inb2", "INB2 Input"},
+
+ /* inputs */
+ {"ADCL", NULL, "left_adc_mixer"},
+ {"ADCR", NULL, "right_adc_mixer"},
+
+ {"INA1 Input", NULL, "INA1"},
+ {"INA2 Input", NULL, "INA2"},
+ {"INB1 Input", NULL, "INB1"},
+ {"INB2 Input", NULL, "INB2"},
+
+ {"Digmic_l", NULL, "DIGMIC"},
+ {"Digmic_r", NULL, "DIGMIC"},
+ {"Mic Bias", NULL, "MIC1"},
+ {"Mic Bias", NULL, "MIC2"},
+};
+
+/*
+ * Add widgets
+ */
+static int max9888_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, max9888_dapm_widgets,
+ ARRAY_SIZE(max9888_dapm_widgets));
+
+ /* set up audio path interconnects */
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+/*
+ * Set bias level
+ */
+static int max9888_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ case SND_SOC_BIAS_STANDBY:
+ max9888_write(codec, M9888_REG_4C_PWR_SYS, SHDN|JDWK);
+ break;
+ case SND_SOC_BIAS_OFF:
+ max9888_write(codec, M9888_REG_4C_PWR_SYS, JDWK);
+ break;
+ }
+ codec->bias_level = level;
+ return 0;
+}
+
+/*
+ * mute CODEC
+ */
+int max9888_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+
+ if (mute)
+ max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ else
+ max9888_set_bias_level(codec, SND_SOC_BIAS_ON);
+ return 0;
+}
+
+/*
+ * Power down CODEC
+ */
+void max9888_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+/*
+ * Setup DAI1 format
+ */
+static int max9888_dai1_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max9888_priv *max9888 = codec->private_data;
+
+ if (fmt != max9888->dai1_fmt) {
+ max9888->dai1_fmt = fmt;
+
+ /* DAI clock master/slave wrt the codec */
+ switch (fmt & SND_SOC_DAIFMT_CBS_CFS) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* codec clk & frm slave */
+ max9888->reg_14_val &= ~(1 << 7);
+ max9888_write(codec, M9888_REG_12_DAI1_CLKCFG_HI, 0x80);
+ max9888_write(codec, M9888_REG_13_DAI1_CLKCFG_LO, 0x00);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* codec clk & frm master */
+ max9888->reg_14_val |= (1 << 7);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ /* codec clk slave & frm master */
+ case SND_SOC_DAIFMT_CBM_CFS:
+ /* codec clk master & frame slave */
+ info("Clock mode unsupported");
+ return -1;
+ }
+
+ /* I2S or TDM */
+ if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S)
+ max9888->reg_14_val &= ~(1 << 2);
+ else
+ max9888->reg_14_val |= (1 << 2);
+
+ /* DAI hardware signal inversions */
+ switch (fmt & SND_SOC_DAIFMT_NB_NF) {
+ case SND_SOC_DAIFMT_NB_NF:
+ max9888->reg_14_val &= ~(1 << 5);
+ max9888->reg_14_val &= ~(1 << 6);
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ max9888->reg_14_val |= (1 << 5);
+ max9888->reg_14_val |= (1 << 6);
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ max9888->reg_14_val |= (1 << 5);
+ max9888->reg_14_val &= ~(1 << 6);
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ max9888->reg_14_val |= (1 << 5);
+ max9888->reg_14_val &= ~(1 << 6);
+ break;
+ }
+ max9888_write(codec, M9888_REG_14_DAI1_FORMAT,
+ max9888->reg_14_val);
+
+ max9888_write(codec, M9888_REG_15_DAI1_CLOCK,
+ (0 << 6) | (0 << 0));
+
+ max9888_write(codec, M9888_REG_16_DAI1_IOCFG,
+ (1 << 6) |
+ (0 << 5) |
+ (0 << 4) |
+ (0 << 3) | (0 << 2) | (1 << 1) | (1 << 0));
+
+ max9888_write(codec, M9888_REG_17_DAI1_TDM,
+ (0 << 6) | (1 << 4) | (0 << 0));
+ }
+
+ return 0;
+}
+
+/*
+ * Setup DAI2 format
+ */
+static int max9888_dai2_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max9888_priv *max9888 = codec->private_data;
+
+ if (fmt != max9888->dai2_fmt) {
+ max9888->dai2_fmt = fmt;
+ /* DAI clock master/slave wrt the codec */
+ switch (fmt & SND_SOC_DAIFMT_CBS_CFS) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ /* codec clk & frm slave */
+ max9888->reg_1c_val &= ~(1 << 7);
+ max9888_write(codec, M9888_REG_1A_DAI2_CLKCFG_HI, 0x80);
+ max9888_write(codec, M9888_REG_1B_DAI2_CLKCFG_LO, 0x00);
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ /* codec clk & frm master */
+ max9888->reg_1c_val |= (1 << 7);
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ /* codec clk slave & frm master */
+ case SND_SOC_DAIFMT_CBM_CFS:
+ /* codec clk master & frame slave */
+ info("Clock mode unsupported");
+ return -1;
+ }
+
+ /* I2S or TDM */
+ if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S)
+ max9888->reg_1c_val &= ~(1 << 2);
+ else
+ max9888->reg_1c_val |= (1 << 2);
+
+ /* DAI hardware signal inversions */
+ switch (fmt & SND_SOC_DAIFMT_NB_NF) {
+ case SND_SOC_DAIFMT_NB_NF:
+ max9888->reg_1c_val &= ~(1 << 5);
+ max9888->reg_1c_val &= ~(1 << 6);
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ max9888->reg_1c_val |= (1 << 5);
+ max9888->reg_1c_val |= (1 << 6);
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ max9888->reg_1c_val |= (1 << 5);
+ max9888->reg_1c_val &= ~(1 << 6);
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ max9888->reg_1c_val |= (1 << 5);
+ max9888->reg_1c_val &= ~(1 << 6);
+ break;
+ }
+ max9888_write(codec, M9888_REG_1C_DAI2_FORMAT,
+ max9888->reg_1c_val);
+
+ max9888_write(codec, M9888_REG_1D_DAI2_CLOCK, (0 << 0));
+
+ max9888_write(codec, M9888_REG_1E_DAI2_IOCFG,
+ (2 << 6) |
+ (0 << 4) |
+ (0 << 3) | (0 << 2) | (1 << 1) | (1 << 0));
+
+ max9888_write(codec, M9888_REG_1F_DAI2_TDM,
+ (0 << 6) | (1 << 4) | (0 << 0));
+ }
+
+ return 0;
+}
+
+/*
+ * Setup clock
+ */
+static int max9888_dai_set_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct max9888_priv *max9888 = codec->private_data;
+
+ if (freq != max9888->sysclk) {
+ max9888->sysclk = freq;
+
+ /* setup clocks for slave mode,
+ and using the PLL anyclock feature
+ PSCLK = 0x01 (when master clk is 10MHz to 20MHz)
+ 0x02 (when master clk is 20MHz to 30MHz) */
+ if ((freq >= 10000000) && (freq < 20000000)) {
+ max9888_write(codec, M9888_REG_10_SYS_CLK, 0x10);
+ } else if ((freq >= 20000000) && (freq < 30000000)) {
+ max9888_write(codec, M9888_REG_10_SYS_CLK, 0x20);
+ } else {
+ info("Invalid master clock frequency %u", freq);
+ return 1;
+ }
+
+ /* these registers writes take effect with SHDN cycle */
+ if (max9888_read(codec, M9888_REG_4C_PWR_SYS) & 0x80) {
+ max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x00);
+ max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x80);
+ }
+ }
+
+ max9888->sysclk = freq;
+ return 0;
+}
+
+struct rate_table_struct {
+ u32 rate;
+ u16 sr1;
+};
+
+/* codec mclk clock divider coefficients */
+static const struct rate_table_struct rate_table[] = {
+ {8000, 0x1},
+ {11025, 0x2},
+ {16000, 0x3},
+ {22050, 0x4},
+ {24000, 0x5},
+ {32000, 0x6},
+ {44100, 0x7},
+ {48000, 0x8},
+ {88200, 0x9},
+ {96000, 0xA},
+};
+
+static inline int rate_index(int rate)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rate_table); i++) {
+ if (rate_table[i].rate >= rate)
+ return i;
+ }
+ return 0;
+}
+
+/*
+ * Setup hw params, sample rate
+ */
+static int max9888_dai1_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct max9888_priv *max9888 = codec->private_data;
+ unsigned int rate;
+ unsigned int reg14val;
+ u16 reg11val;
+
+ rate = params_rate(params);
+
+ /* data 16/24 bit width */
+ reg14val = max9888->reg_14_val;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ reg14val &= ~(1 << 0);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ reg14val |= (1 << 0);
+ break;
+ }
+
+ if ((rate != max9888->dai1_rate) || (reg14val != max9888->reg_14_val)) {
+ /* set DAI1 SR1 value for the DSP; FREQ1:0=anyclock */
+ reg11val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
+ max9888_write(codec, M9888_REG_11_DAI1_CLKMODE, reg11val);
+ max9888->dai1_rate = rate;
+
+ max9888_write(codec, M9888_REG_14_DAI1_FORMAT, reg14val);
+ max9888->reg_14_val = reg14val;
+
+ /* these registers writes take effect with SHDN cycle */
+ if (max9888_read(codec, M9888_REG_4C_PWR_SYS) & 0x80) {
+ max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x00);
+ max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x80);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Setup hw params, sample rate
+ */
+static int max9888_dai2_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct max9888_priv *max9888 = codec->private_data;
+ unsigned int rate;
+ unsigned int reg1Cval;
+ u16 reg19val;
+
+ rate = params_rate(params);
+
+ /* data 16/24 bit width */
+ reg1Cval = max9888->reg_1c_val;
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ reg1Cval &= ~(1 << 0);
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ reg1Cval |= (1 << 0);
+ break;
+ }
+
+ if ((rate != max9888->dai2_rate) || (reg1Cval != max9888->reg_1c_val)) {
+ /* set DAI2 SR2 value for the DSP; FREQ1:0=anyclock */
+ reg19val = (rate_table[rate_index(rate)].sr1 << 4) | 0;
+ max9888_write(codec, M9888_REG_19_DAI2_CLKMODE, reg19val);
+ max9888->dai1_rate = rate;
+
+ max9888_write(codec, M9888_REG_1C_DAI2_FORMAT, reg1Cval);
+ max9888->reg_1c_val = reg1Cval;
+
+ /* these registers writes take effect with SHDN cycle */
+ if (max9888_read(codec, M9888_REG_4C_PWR_SYS) & 0x80) {
+ max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x00);
+ max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x80);
+ }
+ }
+
+ return 0;
+}
+
+#define MAX9888_RATES SNDRV_PCM_RATE_8000_96000
+#define MAX9888_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_ops max9888_dai_ops_hifi_mode = {
+ .shutdown = max9888_shutdown,
+ .digital_mute = max9888_mute,
+ .set_sysclk = max9888_dai_set_sysclk,
+ .set_fmt = max9888_dai1_set_fmt,
+ .hw_params = max9888_dai1_hw_params
+};
+
+static struct snd_soc_dai_ops max9888_dai_ops_voice_mode = {
+ .shutdown = max9888_shutdown,
+ .digital_mute = max9888_mute,
+ .set_sysclk = max9888_dai_set_sysclk,
+ .set_fmt = max9888_dai2_set_fmt,
+ .hw_params = max9888_dai2_hw_params
+};
+
+struct snd_soc_dai max9888_dai[] = {
+/* DAI HIFI mode 1 */
+ {
+ .name = "max9888 DAI0",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX9888_RATES,
+ .formats = MAX9888_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = MAX9888_RATES,
+ .formats = MAX9888_FORMATS,
+ },
+ .ops = &max9888_dai_ops_hifi_mode,
+ },
+
+/* DAI Voice mode 2 */
+ {.name = "max9888 DAI1",
+ .id = 2,
+ .playback = {
+ .stream_name = "Voice Playback",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = MAX9888_RATES,
+ .formats = MAX9888_FORMATS,},
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 1,
+ .rates = MAX9888_RATES,
+ .formats = MAX9888_FORMATS,},
+ .ops = &max9888_dai_ops_voice_mode,
+ },
+};
+EXPORT_SYMBOL_GPL(max9888_dai);
+
+
+/*
+ * Restore previous state if power was removed.
+ * As it is now the CODEC is brought out of shut down.
+ */
+static void max9888_init_private_data(struct max9888_priv *max9888)
+{
+ max9888->sysclk = (unsigned)-1;
+ max9888->dai1_rate = (unsigned)-1;
+ max9888->dai2_rate = (unsigned)-1;
+ max9888->dai1_fmt = (unsigned)-1;
+ max9888->dai2_fmt = (unsigned)-1;
+
+ max9888->reg_14_val =
+ max9888->reg_1c_val =
+ (0 << 7) |
+ (0 << 6) | (0 << 5) | (1 << 4) | (0 << 2) | (0 << 1) | (0 << 0);
+}
+
+/*
+ * The current state should be saved if power is removed.
+ * As it is now the CODEC is just shut down.
+ */
+static int max9888_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ dprintk("%s\n", __func__);
+ /* we only need to suspend if we are a valid card */
+ if (!codec->card)
+ return 0;
+
+ max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ return 0;
+}
+
+/*
+ * Restore previous state if power was removed.
+ * As it is now the CODEC is brought out of shut down.
+ */
+static int max9888_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+ struct max9888_priv *max9888 = codec->private_data;
+
+ max9888_init_private_data(max9888);
+
+ max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+ max9888_set_bias_level(codec, SND_SOC_BIAS_ON);
+
+ return 0;
+}
+
+/*
+ * Initialize the codec.
+ */
+static struct snd_soc_codec *max9888_codec;
+
+static int max9888_register(struct max9888_priv *max9888)
+{
+ int ret, i;
+ struct snd_soc_codec *codec = &max9888->codec;
+
+ if (max9888_codec) {
+ dev_err(codec->dev, "Multiple max9888 devices not supported\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->name = "MAX9888";
+ codec->owner = THIS_MODULE;
+ codec->read = max9888_read;
+ codec->write = max9888_write;
+ codec->bias_level = SND_SOC_BIAS_STANDBY;
+ codec->set_bias_level = max9888_set_bias_level;
+ codec->dai = max9888_dai;
+ codec->num_dai = 1;
+ codec->private_data = max9888;
+
+ /* disable device via dapm interface */
+ max9888->rev_version = max9888_read(codec, M9888_REG_FF_REV_ID);
+ max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ max9888_init_private_data(max9888);
+
+ /* Jack detection function only works well on Revision C */
+ if (max9888->rev_version == MAX9888_REVC) {
+ max9888_write(codec, M9888_REG_0F_IRQ_ENABLE, IJDET);
+ max9888_write(codec, M9888_REG_49_CFG_JACKDET,
+ JDETEN | JDEB_200ms);
+ max9888_write(codec, M9888_REG_4C_PWR_SYS, JDWK);
+ }
+
+ max9888_write(codec, M9888_REG_22_MIX_ADC_LEFT, MIX_MIC1 | MIX_INA1);
+ max9888_write(codec, M9888_REG_23_MIX_ADC_RIGHT, MIX_MIC2 | MIX_INA1);
+ max9888_write(codec, M9888_REG_21_MIX_DAC,
+ MIX_DAI1L_TO_DACL | MIX_DAI2L_TO_DACL | MIX_DAI1R_TO_DACR
+ | MIX_DAI2R_TO_DACR);
+ max9888_write(codec, M9888_REG_29_MIX_SPEAKER,
+ MIX_LDAC_TO_SPL | MIX_RDAC_TO_SPR);
+ max9888_write(codec, M9888_REG_27_MIX_HEADPHONE,
+ MIX_LDAC_TO_HPL | MIX_RDAC_TO_HPR);
+
+ headphone_set_volume(codec, 0x1f, 0);
+ speaker_set_volume(codec, 0x1f, 0);
+ m9888_mic1_gain_control(codec, 20, 1);
+ m9888_mic2_gain_control(codec, 20, 1);
+
+ /* power on device */
+ max9888_codec = codec;
+ for (i = 0; i < codec->num_dai; i++)
+ max9888_dai[i].dev = codec->dev;
+
+ ret = snd_soc_register_codec(codec);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register codec: %d\n", ret);
+ goto err;
+ }
+
+ ret = snd_soc_register_dais(&max9888_dai[0], codec->num_dai);
+ if (ret != 0) {
+ dev_err(codec->dev, "Failed to register DAIs: %d\n", ret);
+ goto err_codec;
+ }
+
+ return ret;
+
+err_codec:
+ snd_soc_unregister_codec(codec);
+err:
+ kfree(max9888);
+
+ return ret;
+}
+
+static void max9888_unregister(struct max9888_priv *max9888)
+{
+ struct snd_soc_codec *codec = &max9888->codec;
+ snd_soc_unregister_dais(max9888_dai, codec->num_dai);
+ snd_soc_unregister_codec(codec);
+ kfree(max9888);
+ max9888_codec = NULL;
+}
+
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+
+static unsigned short normal_i2c[] = { 0, I2C_CLIENT_END };
+
+/* Magic definition of all other variables and things */
+I2C_CLIENT_INSMOD;
+
+static struct i2c_driver max9888_i2c_driver;
+
+static int max9888_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct snd_soc_codec *codec;
+ struct max9888_priv *max9888;
+
+ max9888 = kzalloc(sizeof(struct max9888_priv), GFP_KERNEL);
+ if (max9888 == NULL)
+ return -ENOMEM;
+
+ codec = &max9888->codec;
+ codec->hw_write = (hw_write_t) i2c_master_send;
+ codec->control_data = i2c;
+ i2c_set_clientdata(i2c, max9888);
+
+ codec->dev = &i2c->dev;
+
+ return max9888_register(max9888);
+}
+
+static int max9888_i2c_remove(struct i2c_client *client)
+{
+ struct max9888_priv *max9888 = i2c_get_clientdata(client);
+ max9888_unregister(max9888);
+ return 0;
+}
+
+static const struct i2c_device_id max9888_i2c_id[] = {
+ {"max9888", 0},
+ {}
+};
+
+/*
+ * gumstix i2c codec control layer
+ */
+static struct i2c_driver max9888_i2c_driver = {
+ .driver = {
+ .name = "MAX9888 I2C CODEC",
+ .owner = THIS_MODULE,
+ },
+ .probe = max9888_i2c_probe,
+ .remove = max9888_i2c_remove,
+ .id_table = max9888_i2c_id,
+};
+#endif
+
+/*
+ * Make sure that a max9888 is attached to the I2C bus.
+ */
+
+static int max9888_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec;
+ struct max9888_priv *max9888;
+ int ret = 0;
+
+ if (!max9888_codec) {
+ dev_err(&pdev->dev, "max9888 codec not yet registered\n");
+ return -EINVAL;
+ }
+
+ socdev->card->codec = max9888_codec;
+ codec = max9888_codec;
+ max9888 = codec->private_data;
+
+ if (max9888->rev_version == MAX9888_REVB)
+ info("RevB Audio CODEC %s", MAX9888_DRIVER_VERSION);
+ else if (max9888->rev_version == MAX9888_REVC)
+ info("RevC Audio CODEC %s", MAX9888_DRIVER_VERSION);
+ else
+ warn("Not supported MAX9888 : %d", max9888->rev_version);
+
+ /* register pcms */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ printk(KERN_ERR "max9888: failed to create pcms\n");
+ goto pcm_err;
+ }
+
+ snd_soc_add_controls(codec, max9888_snd_controls,
+ ARRAY_SIZE(max9888_snd_controls));
+ max9888_add_widgets(codec);
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ printk(KERN_ERR "max9888: failed to register card\n");
+ goto card_err;
+ }
+
+ return 0;
+
+card_err:
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+pcm_err:
+ return ret;
+}
+
+/*
+ * Driver is being unloaded, power down the codec and free allocated resources
+ */
+static int max9888_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ if (codec->control_data)
+ max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_max9888 = {
+ .probe = max9888_probe,
+ .remove = max9888_remove,
+ .suspend = max9888_suspend,
+ .resume = max9888_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_max9888);
+
+static int __init max9888_modinit(void)
+{
+ int ret;
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ ret = i2c_add_driver(&max9888_i2c_driver);
+ if (ret != 0)
+ pr_err("Failed to register max9888 I2C driver: %d\n", ret);
+#endif
+ return 0;
+}
+
+module_init(max9888_modinit);
+
+static void __exit max9888_exit(void)
+{
+#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
+ i2c_del_driver(&max9888_i2c_driver);
+#endif
+}
+
+module_exit(max9888_exit);
+
+MODULE_DESCRIPTION("ALSA SoC MAX9888 driver");
+MODULE_AUTHOR("Jesse Marroquin <[email protected]>");
+MODULE_AUTHOR("Peter Hsiang <[email protected]>");
+MODULE_AUTHOR("BooJin Kim <[email protected]>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/max9888.h b/sound/soc/codecs/max9888.h
new file mode 100644
index 0000000..1053854
--- /dev/null
+++ b/sound/soc/codecs/max9888.h
@@ -0,0 +1,180 @@
+/*
+ * max9888.h -- MAX9888 ALSA SoC Audio driver
+ *
+ * Copyright 2010 Maxim Integrated Products
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __LINUX_MAX9888_H
+#define __LINUX_MAX9888_H __FILE__
+
+/*
+ *
+ * MAX9888 Registers Definition and Bit Fields
+ *
+ */
+#define M9888_REG_00_IRQ_STATUS 0x00
+#define M9888_REG_01_MIC_STATUS 0x01
+#define M9888_REG_02_JACK_STAUS 0x02
+#define M9888_REG_0F_IRQ_ENABLE 0x0F
+#define IJDET (1<<1)
+
+#define M9888_REG_10_SYS_CLK 0x10
+#define M9888_REG_11_DAI1_CLKMODE 0x11
+#define M9888_REG_12_DAI1_CLKCFG_HI 0x12
+#define M9888_REG_13_DAI1_CLKCFG_LO 0x13
+#define M9888_REG_14_DAI1_FORMAT 0x14
+#define M9888_REG_15_DAI1_CLOCK 0x15
+#define M9888_REG_16_DAI1_IOCFG 0x16
+#define M9888_REG_17_DAI1_TDM 0x17
+#define M9888_REG_18_DAI1_FILTERS 0x18
+#define M9888_REG_19_DAI2_CLKMODE 0x19
+#define M9888_REG_1A_DAI2_CLKCFG_HI 0x1A
+#define M9888_REG_1B_DAI2_CLKCFG_LO 0x1B
+#define M9888_REG_1C_DAI2_FORMAT 0x1C
+#define M9888_REG_1D_DAI2_CLOCK 0x1D
+#define M9888_REG_1E_DAI2_IOCFG 0x1E
+#define M9888_REG_1F_DAI2_TDM 0x1F
+#define M9888_REG_20_DAI2_FILTERS 0x20
+
+#define M9888_REG_21_MIX_DAC 0x21
+#define MIX_DAI1L_TO_DACL (1<<7)
+#define MIX_DAI1R_TO_DACL (1<<6)
+#define MIX_DAI2L_TO_DACL (1<<5)
+#define MIX_DAI2R_TO_DACL (1<<4)
+#define MIX_DAI1L_TO_DACR (1<<3)
+#define MIX_DAI1R_TO_DACR (1<<2)
+#define MIX_DAI2L_TO_DACR (1<<1)
+#define MIX_DAI2R_TO_DACR (1<<0)
+
+#define M9888_REG_22_MIX_ADC_LEFT 0x22
+#define M9888_REG_23_MIX_ADC_RIGHT 0x23
+#define MIX_MIC1 (1<<7)
+#define MIX_MIC2 (1<<6)
+#define MIX_INA1 (1<<3)
+#define MIX_INA2 (1<<2)
+#define MIX_INB1 (1<<1)
+#define MIX_INB2 (1<<0)
+#define MIX_INA_DIFFERENTIAL (1<<2)
+#define MIX_INB_DIFFERENTIAL (1<<0)
+
+#define M9888_REG_24_MIX_PREOUT_A 0x24
+#define M9888_REG_25_MIX_PREOUT_B 0x25
+#define M9888_REG_26_MIX_PREOUT_C 0x26
+#define MIX_INA1 (1<<3)
+#define MIX_INA2 (1<<2)
+#define MIX_INB1 (1<<1)
+#define MIX_INB2 (1<<0)
+#define MIX_INA_DIFFERENTIAL (1<<2)
+#define MIX_INB_DIFFERENTIAL (1<<0)
+
+#define M9888_REG_27_MIX_HEADPHONE 0x27
+#define MIX_LDAC_TO_HPL (3<<6)
+#define MIX_RDAC_TO_HPR (3<<2)
+
+#define M9888_REG_28_MIX_RECEIVER 0x28
+#define M9888_REG_29_MIX_SPEAKER 0x29
+#define MIX_LDAC_TO_SPL (1<<7)
+#define MIX_RDAC_TO_SPR (1<<2)
+
+#define M9888_REG_2A_LVL_SIDETONE 0x2A
+#define M9888_REG_2B_LVL_DAI1_PLAY 0x2B
+#define M9888_REG_2C_LVL_DAI1_PLAY_EQ 0x2C
+#define M9888_REG_2D_LVL_DAI2_PLAY 0x2D
+#define M9888_REG_2E_LVL_DAI2_PLAY_EQ 0x2E
+#define M9888_REG_2F_LVL_ADC_L 0x2F
+#define M9888_REG_30_LVL_ADC_R 0x30
+#define M9888_REG_31_LVL_MIC1 0x31
+#define M9888_REG_32_LVL_MIC2 0x32
+#define M9888_REG_33_LVL_INA 0x33
+#define M9888_REG_34_LVL_INB 0x34
+#define M9888_REG_35_LVL_PREOUT_A 0x35
+#define M9888_REG_36_LVL_PREOUT_B 0x36
+#define M9888_REG_37_LVL_PREOUT_C 0x37
+#define M9888_REG_38_LVL_HEADPHONE_L 0x38
+#define M9888_REG_39_LVL_HEADPHONE_R 0x39
+#define HPXM (1<<7)
+
+#define M9888_REG_3A_LVL_RECEIVER 0x3A
+#define RECM (1<<7)
+
+#define M9888_REG_3B_LVL_SPEAKER_L 0x3B
+#define M9888_REG_3C_LVL_SPEAKER_R 0x3C
+#define SPXM (1<<7)
+
+#define M9888_REG_3D_MICAGC_CFG 0x3D
+#define M9888_REG_3E_MICAGC_THRESHOLD 0x3E
+#define M9888_REG_3F_SPKDHP 0x3F
+#define M9888_REG_40_SPKDHP_THRESHOLD 0x40
+#define M9888_REG_41_SPKALC_COMP 0x41
+#define M9888_REG_42_PWRLMT_CFG 0x42
+#define M9888_REG_43_PWRLMT_TIME 0x43
+#define M9888_REG_44_THDLMT_CFG 0x44
+#define M9888_REG_45_CFG_AUDIO_IN 0x45
+#define M9888_REG_46_CFG_MIC 0x46
+#define DIGMICLEN (1<<5)
+#define DIGMICREN (1<<4)
+
+#define M9888_REG_47_CFG_LEVEL 0x47
+#define VSEN (1<<6)
+#define ZDEN (1<<5)
+#define EQ2EN (1<<1)
+#define EQ1EN (1<<0)
+
+#define M9888_REG_48_CFG_BYPASS 0x48
+#define M9888_REG_49_CFG_JACKDET 0x49
+#define JDETEN (1<<7)
+#define JDEB_25ms (0<<0)
+#define JDEB_50ms (1<<0)
+#define JDEB_100ms (2<<0)
+#define JDEB_200ms (3<<0)
+
+#define M9888_REG_4A_PWR_EN_IN 0x4A
+#define INAEN (1<<7)
+#define INBEN (1<<6)
+#define MBEN (1<<3)
+#define ADLEN (1<<1)
+#define ADREN (1<<0)
+
+#define M9888_REG_4B_PWR_EN_OUT 0x4B
+#define HPLEN (1<<7)
+#define HPREN (1<<6)
+#define SPLEN (1<<5)
+#define SPREN (1<<4)
+#define RECEN (1<<3)
+#define DALEN (1<<1)
+#define DAREN (1<<0)
+
+#define M9888_REG_4C_PWR_SYS 0x4C
+#define SHDN (1<<7)
+#define JDWK (1<<0)
+
+#define M9888_REG_50_DAI1_EQ_BASE 0x50
+#define M9888_REG_82_DAI2_EQ_BASE 0x82
+#define M9888_REG_B4_DAI1_BIQUAD_BASE 0xB4
+#define M9888_REG_BE_DAI2_BIQUAD_BASE 0xBE
+#define M9888_REG_FF_REV_ID 0xFF
+
+#define MAX9888_REVB 0x42
+#define MAX9888_REVC 0x43
+
+#define REGS_PER_BAND 5
+#define REGS_PER_BIQUAD 5
+
+#define HIGH_BYTE(w) ((w >> 8) & 0x00ff)
+#define LOW_BYTE(w) (w & 0x00ff)
+
+#define MAX9888_DAI_HIFI 0
+#define MAX9888_DAI_VOICE 1
+
+struct max9888_setup_data {
+ unsigned short i2c_address;
+};
+
+extern struct snd_soc_dai max9888_dai[2];
+extern struct snd_soc_codec_device soc_codec_dev_max9888;
+
+#endif /* __LINUX_MAX9888_H */
--
1.5.5.6
On Wed, Jun 16, 2010 at 01:40:57PM -0500, [email protected] wrote:
> From: Jesse Marroquin <[email protected]>
>
> This patch adds max9888 CODEC driver.
So, the major comment I have here is that this driver feels very much
like it's fighting with the standard APIs for things rather than working
with them more than it should - there's an awful lot of open coded stuff
that I can't see a real need for here (and if there is a need it should
be factored out of the driver), and some things that are just straight
reimplementations of standard kernel functionality.
More detailed comments as I go through:
> select SND_SOC_WM9712 if SND_SOC_AC97_BUS
> select SND_SOC_WM9713 if SND_SOC_AC97_BUS
> + select SND_SOC_MAX9888 if I2C
> help
Keep the entries in this file and the Makefile sorted.
> +#define AUDIO_NAME "MAX9888"
> +#define MAX9888_DRIVER_VERSION "1.0"
In general version numbers in the kernel code are bad karma - they tend
to just bitrot with the version number of the kernel being the only
signifgcant bit.
> +#ifdef MAX9888_DEBUG
> +#define dprintk(x...) printk(KERN_WARNING x)
> +#else
> +#define dprintk(x...)
> +#endif
> +
> +#define err(format, arg...) \
> + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg)
> +
> +#define info(format, arg...) \
> + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg)
> +
> +#define warn(format, arg...) \
> + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg)
This stuff should all be using the standard dev_ macros (or pr_ ones
where no device is available). Those give more informative output too.
> +/*
> + * Equalizer DSP filter coefficients generated from the evkit software tool
> + */
So, what happens if a user goes and uses this tool to generate their own
register settings? The way the software is structured it's going to be
fairly involved for them to add additional configurations. This all
looks like it ought to be implemented in a much more generic fashion
with a data table being parsed by the driver to give the coefficient
settings - at the minute pretty much everywhere the coefficient settings
are handled the set of possible settings is manually coded.
This would then mean that platform data could be supplied adding
additional settings to or replacing the default ones provided by the
driver.
> +/*
> + * Dynamic high pass excursion limiter filter coefficients.
> + * The coefficients define the user programmable frequency response for the
> + * excursion limiter.
> + * Use the PC evkit software to generate the coefficients and put it here.
> + */
> +static unsigned int param_ex_resp_a[] = { 0, 0, 0, 0, 0 };
> +static unsigned int param_ex_resp_b[] = { 0, 0, 0, 0, 0 };
> +static unsigned int param_ex_resp_c[] = { 0, 0, 0, 0, 0 };
This needs to be platform data, users should not have to modify the
driver code in order to use the device since obviously that's not
sustainable when the driver is shared by multiple boards.
> +/*
> + * Read the MAX9888 register space
> + */
> +static unsigned int max9888_read(struct snd_soc_codec *codec, unsigned int reg)
> +{
This looks like you can just use snd_soc_set_cache_io() and use the
standard register I/O functions.
> +/*
> + * The INx1 and INx2 PGA's share a power control signal.
Extra ' here.
> + * 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.
Some more comments about how you've actually implemented this might be a
little clearer. One thing you might wish to consider which would
simplify this workaround is to have the individual PGAs have no power
management and use a SND_SOC_DAPM_SUPPLY for the actual register
control. This will mean that you don't need to open code anything, DAPM
will do the power up for you. It does mean the sequencing will be wrong
but since these are input PGAs you'll probably not experience any
negative side effects.
Actually, looking further down the driver where this is used I'm
confused about what you're doing here since all the users also have
their own individual power bits.
> + */
> +int max9888_pga_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k,
> + int event)
> +{
> + struct snd_soc_codec *codec = w->codec;
> + unsigned int val;
> + unsigned int pga;
> + unsigned int mask;
> + static int power_state;
This should be in the CODEC private data rather than a static - if
someone wants to put two of the chips down on one board no need to make
problems for them!
> + if (w->reg != M9888_REG_4A_PWR_EN_IN)
> + return -EINVAL;
I'd upgrade this to a BUG_ON(), if the driver is calling this with the
wrong register it's buggy.
> + /* Turn on, avoiding unnecessary writes */
> + val = max9888_read(codec, w->reg);
> +
> + if (!(val & (1 << w->shift))) {
> + val |= (1 << w->shift);
> + max9888_write(codec, w->reg, val | MBEN);
You're looking for snd_soc_update_bits() here.
> +/*
> + * Define a few static eq settings
> + */
> +static const char *max9888_eq[] = {
> + "voice", "music", "flat", "treble reduce", "loudness" };
The names should all be capitalised to work well in UIs.
> +static int max9888_eq1_value = MAX9888_EQ_FLAT;
> +static int max9888_eq2_value = MAX9888_EQ_FLAT;
Again, these should be CODEC private data rather than static.
> +static const char *max9888_ex_mode[] = {
> + "disable",
> + "100Hz",
> + "400Hz",
> + "600Hz",
> + "800Hz",
> + "1000Hz",
> + "200-400Hz",
> + "400-600Hz",
> + "400-800Hz",
> + "user-400Hz",
> + "user-600Hz",
> + "user-800Hz",
> + "user-1000Hz"
If the user has specified modes I'd rather expect they'd just override
the standard settings. Conversely, if the user hasn't specified modes
I'd not expect the driver to offer to use them.
> +/*
> + * Functions and control structures for loading
> + * static coefficients into the bi-quad filter.
> + *
> + * As it is the user has a choice of three filters - a, b, or c.
> + */
> +static const char *max9888_ex_resp[] = { "a", "b", "c" };
If there's no more meaningful name for the options it'd be nice to at
least include a comment explaining why we're just calling them A, B and
C.
> +static int max9888_ex1_resp_value = MAX9888_BIQUAD_A;
> +static int max9888_ex2_resp_value = MAX9888_BIQUAD_A;
Same comment as before about use of private data.
> +
> +/*
> + * Control mic1 analog amplifier circuit on/off, and gain
> + * Typical setting: register value of 0x7F for 30dB mic gain
> + */
> +void m9888_mic1_gain_control(struct snd_soc_codec *codec, unsigned int gain,
> + unsigned int power_on)
I really can't tell why this has been open coded - you probably just
need an appropraite TLV table. If it needs to be open coded please
document why.
> + /* power off the amplifier */
> + if (power_on == 0) {
Is the power really controlled by the gain? If so, why isn't this
hooked in to DAPM?
> +/*
> + * Set headphone output volume and mute setting
> + */
> +void headphone_set_volume(struct snd_soc_codec *codec, u8 vol, u8 mute)
Similar comments about open coding for this and all the other
set_volume() implementations - they all look even more standard than the
microphone PGAs so you should be able to code them in a much more
standard fashion.
> +/*
> + * configure speaker ALC
> + */
> +void m9888_speaker_ALC_control(struct snd_soc_codec *codec)
> +{
> + /* compressor enable */
> + max9888_write(codec, M9888_REG_41_SPKALC_COMP, (0 << 7) |
> + (7 << 4) | (0 << 3) | (0 << 0));
This and several of the other functions look like they can only ever set
one value - what's the purpose of these functions?
> +static const char *max9888_hp_spk_mute[] = { "disable", "enable" };
> +
> +static const struct soc_enum max9888_hp_spk_mute_enum[] = {
> + SOC_ENUM_SINGLE_EXT(2, max9888_hp_spk_mute),
> +};
This is not how ALSA implements mute controls - you should have a
control with a name "Speaker Switch" (something Switch, anyway) almost
certainly using one of the standard register access macros. See other
CODEC drivers for examples.
The same thing applies to all your other mute controls, all this mute
handling can probably be replaced with a single line of code for each of
the mutes. This will also have the advantage of allowing individual
control of the channels.
> +static const struct soc_enum max9888_enum[] = {
> + SOC_ENUM_SINGLE(M9888_REG_3A_LVL_RECEIVER, 7, 2, max9888_rx_mute),
> + SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 7, 2, max9888_fltr_mode),
> + SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 3, 2, max9888_highrate),
> + SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 0, 6, max9888_dai1_fltr),
> + SOC_ENUM_SINGLE(M9888_REG_18_DAI1_FILTERS, 4, 6, max9888_dai1_fltr),
> + SOC_ENUM_SINGLE(M9888_REG_20_DAI2_FILTERS, 3, 2, max9888_highrate),
> + SOC_ENUM_SINGLE(M9888_REG_20_DAI2_FILTERS, 0, 2, max9888_dc_block),
> + SOC_ENUM_SINGLE(M9888_REG_40_SPKDHP_THRESHOLD, 0, 8,
> + max9888_exlimit_threshold)
> +};
Define individual static variables for each of these, indexing into a
table is very error prone and brings no benefit.
> + /* analog output levels */
> + SOC_DOUBLE_R("hp_vol", M9888_REG_38_LVL_HEADPHONE_L,
> + M9888_REG_39_LVL_HEADPHONE_R, 0, 31, 0),
This should be "Headphone Volume". Pretty much all your controls have
naming problems - you should try to follow the guidlines in the kernel
in Documentation/sound/alsa/ControlNames.txt. This will allow
applications to parse them and render them appropriately. Since there's
so many errors here I'll not point out further ones.
> + SND_SOC_DAPM_PGA_E("INA2 Input", M9888_REG_4A_PWR_EN_IN,
> + 7, 0, NULL, 0, max9888_pga_event,
> + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
> +
> + SND_SOC_DAPM_PGA_E("INB1 Input", M9888_REG_4A_PWR_EN_IN,
> + 6, 0, NULL, 0, max9888_pga_event,
> + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
Looking at this in conjunction with the event above things seem really
odd - you've got this event which implements shared power bits for
something, but all of these controls also have individually managed
power bits that are handled in the normal fashion.
> +/*
> + * Set bias level
> + */
> +static int max9888_set_bias_level(struct snd_soc_codec *codec,
> + enum snd_soc_bias_level level)
> +{
> + switch (level) {
> + case SND_SOC_BIAS_ON:
> + case SND_SOC_BIAS_PREPARE:
> + case SND_SOC_BIAS_STANDBY:
> + max9888_write(codec, M9888_REG_4C_PWR_SYS, SHDN|JDWK);
You probably want to use snd_soc_update_bits() here (or just make _ON
and _PREPARE noops).
> + break;
> + case SND_SOC_BIAS_OFF:
> + max9888_write(codec, M9888_REG_4C_PWR_SYS, JDWK);
It's a bit odd that you're enabling something in _OFF, especially
something that's enabled when the device is active - what does JDWK do?
> +int max9888_mute(struct snd_soc_dai *dai, int mute)
> +{
> + struct snd_soc_codec *codec = dai->codec;
> +
> + if (mute)
> + max9888_set_bias_level(codec, SND_SOC_BIAS_OFF);
> + else
> + max9888_set_bias_level(codec, SND_SOC_BIAS_ON);
> + return 0;
> +}
This is wrong - the bias management function should be managing the
global bias levels for the CODEC and the digital mute function should be
muting the digital audio interface. One or both of these functions is
doing the wrong thing.
Note in particular that bypass paths need to work while the DAI is
muted.
> + case SND_SOC_DAIFMT_CBS_CFM:
> + /* codec clk slave & frm master */
> + case SND_SOC_DAIFMT_CBM_CFS:
> + /* codec clk master & frame slave */
> + info("Clock mode unsupported");
> + return -1;
You should return a real error code here, not a random negative number,
and the error message should be an error not an info statement.
> + /* I2S or TDM */
> + if ((fmt & SND_SOC_DAIFMT_I2S) == SND_SOC_DAIFMT_I2S)
> + max9888->reg_14_val &= ~(1 << 2);
> + else
> + max9888->reg_14_val |= (1 << 2);
I don't think that comparison does what you wanted - look at what the
value of SND_SOC_DAIFMT_I2S is for one thing.
> + max9888_write(codec, M9888_REG_14_DAI1_FORMAT,
> + max9888->reg_14_val);
> +
> + max9888_write(codec, M9888_REG_15_DAI1_CLOCK,
> + (0 << 6) | (0 << 0));
> +
> + max9888_write(codec, M9888_REG_16_DAI1_IOCFG,
> + (1 << 6) |
> + (0 << 5) |
> + (0 << 4) |
> + (0 << 3) | (0 << 2) | (1 << 1) | (1 << 0));
> +
> + max9888_write(codec, M9888_REG_17_DAI1_TDM,
> + (0 << 6) | (1 << 4) | (0 << 0));
These statements unconditionally write some fixed magic numbers to some
registers. That's a bit odd - if nothing else I'd expect that any
static configuration would be done once on system startup.
> + * Setup DAI2 format
> + */
> +static int max9888_dai2_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
Similar comments apply to the above; it looks awfully like these can be
generalised. I also note that while you have two DAIs here there's no
mention of them in your DAPM configuration - you have everything wired
directly to the DAC and ADC. I'd not expect this to work terribly well
if someone tries to use both DAIs (see below for some more on this).
> + } else {
> + info("Invalid master clock frequency %u", freq);
> + return 1;
dev_err() and return a proper error code.
> + /* these registers writes take effect with SHDN cycle */
> + if (max9888_read(codec, M9888_REG_4C_PWR_SYS) & 0x80) {
> + max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x00);
> + max9888_write(codec, M9888_REG_4C_PWR_SYS, 0x80);
...and they do...?
> +/* DAI Voice mode 2 */
> + {.name = "max9888 DAI1",
> + .id = 2,
> + .playback = {
> + .stream_name = "Voice Playback",
> + .channels_min = 1,
> + .channels_max = 1,
> + .rates = MAX9888_RATES,
> + .formats = MAX9888_FORMATS,},
So, Voice Playback isn't actually wired up to anything. This means that
when you try to start playback from a voice stream nothing will actually
happen to power up any of the output paths, which probably isn't what's
wanted.
> + .capture = {
> + .stream_name = "Capture",
> + .channels_min = 1,
> + .channels_max = 1,
> + .rates = MAX9888_RATES,
> + .formats = MAX9888_FORMATS,},
This, on the other hand, uses the same name as the primary DAI so I'd
expect some confusion in DAPM if both are simultaneously active.
> + /* Jack detection function only works well on Revision C */
> + if (max9888->rev_version == MAX9888_REVC) {
> + max9888_write(codec, M9888_REG_0F_IRQ_ENABLE, IJDET);
> + max9888_write(codec, M9888_REG_49_CFG_JACKDET,
> + JDETEN | JDEB_200ms);
> + max9888_write(codec, M9888_REG_4C_PWR_SYS, JDWK);
> + }
I see no support for jack detection in the driver...
> + max9888_write(codec, M9888_REG_22_MIX_ADC_LEFT, MIX_MIC1 | MIX_INA1);
> + max9888_write(codec, M9888_REG_23_MIX_ADC_RIGHT, MIX_MIC2 | MIX_INA1);
> + max9888_write(codec, M9888_REG_21_MIX_DAC,
> + MIX_DAI1L_TO_DACL | MIX_DAI2L_TO_DACL | MIX_DAI1R_TO_DACR
> + | MIX_DAI2R_TO_DACR);
> + max9888_write(codec, M9888_REG_29_MIX_SPEAKER,
> + MIX_LDAC_TO_SPL | MIX_RDAC_TO_SPR);
> + max9888_write(codec, M9888_REG_27_MIX_HEADPHONE,
> + MIX_LDAC_TO_HPL | MIX_RDAC_TO_HPR);
> +
> + headphone_set_volume(codec, 0x1f, 0);
> + speaker_set_volume(codec, 0x1f, 0);
> + m9888_mic1_gain_control(codec, 20, 1);
> + m9888_mic2_gain_control(codec, 20, 1);
These should all use the chip defaults and allow the user to configure
things appropriately for their own system via the ALSA controls. The
driver has to work with all boards using the device which means that
it's not possible to pick sane default setups.
> +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
Is I2C control really optional?
> +/*
> + * gumstix i2c codec control layer
> + */
Cut'n'paste.
> +#define IJDET (1<<1)
This needs to be namespaced.
> +#define M9888_REG_21_MIX_DAC 0x21
> +#define MIX_DAI1L_TO_DACL (1<<7)
> +#define MIX_DAI1R_TO_DACL (1<<6)
> +#define MIX_DAI2L_TO_DACL (1<<5)
> +#define MIX_DAI2R_TO_DACL (1<<4)
> +#define MIX_DAI1L_TO_DACR (1<<3)
> +#define MIX_DAI1R_TO_DACR (1<<2)
> +#define MIX_DAI2L_TO_DACR (1<<1)
> +#define MIX_DAI2R_TO_DACR (1<<0)
This too; all the individual register bit definitions in this header
have this problem.
> +#define HIGH_BYTE(w) ((w >> 8) & 0x00ff)
> +#define LOW_BYTE(w) (w & 0x00ff)
I suspect there are standard defines for these. If not there possibly
ought to be (and you have a namespace issue if defining locally).