summaryrefslogtreecommitdiff
path: root/sound
diff options
context:
space:
mode:
authorLaura Lawrence <Laura.Lawrence@freescale.com>2008-01-22 15:30:13 -0600
committerDaniel Schaeffer <daniel.schaeffer@timesys.com>2008-08-25 15:20:35 -0400
commit1d42b93db5289ea18739fa4c0fb704f95a1ac3ad (patch)
treef113434770311c4f92c0fa94c2fe4b0e3f9186c6 /sound
parentd85fc70a64153e118bb704a411044dfe9a4e4f8e (diff)
ENGR00060846 Add Wolfson WM8350 Chip Support
Add WM8350 AudioPlus driver support http://opensource.wolfsonmicro.com/node/8
Diffstat (limited to 'sound')
-rw-r--r--sound/soc/codecs/Kconfig4
-rw-r--r--sound/soc/codecs/Makefile2
-rw-r--r--sound/soc/codecs/wm8350.c1462
-rw-r--r--sound/soc/codecs/wm8350.h49
4 files changed, 1517 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 78248808a9d8..900d8fc741d0 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -2,6 +2,10 @@ config SND_SOC_AC97_CODEC
tristate
depends on SND_SOC
+config SND_SOC_WM8350
+ tristate
+ depends on SND_SOC
+
config SND_SOC_WM8731
tristate
depends on SND_SOC
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 7ad78e36d506..62bd70728475 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -1,4 +1,5 @@
snd-soc-ac97-objs := ac97.o
+snd-soc-wm8350-objs := wm8350.o
snd-soc-wm8731-objs := wm8731.o
snd-soc-wm8750-objs := wm8750.o
snd-soc-wm8753-objs := wm8753.o
@@ -6,6 +7,7 @@ snd-soc-wm9712-objs := wm9712.o
snd-soc-cs4270-objs := cs4270.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
+obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o
obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o
obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o
obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o
diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c
new file mode 100644
index 000000000000..a855a4eaa21f
--- /dev/null
+++ b/sound/soc/codecs/wm8350.c
@@ -0,0 +1,1462 @@
+/*
+ * wm8350.c -- WM8350 ALSA SoC audio driver
+ *
+ * Copyright (C) 2007 Wolfson Microelectronics PLC.
+ *
+ * Author: Liam Girdwood <lg@opensource.wolfsonmicro.com>
+ *
+ * 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/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/pmic/wm8350.h>
+#include <sound/driver.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 "wm8350.h"
+
+#define AUDIO_NAME "WM8350"
+#define WM8350_VERSION "0.4"
+
+#define WM8350_OUTn_0dB 0x39
+
+/*
+ * Debug
+ */
+
+#define WM8350_DEBUG 0
+
+#ifdef WM8350_DEBUG
+#define dbg(format, arg...) \
+ printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg)
+#else
+#define dbg(format, arg...) do {} while (0)
+#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)
+
+#define WM8350_RAMP_NONE 0
+#define WM8350_RAMP_UP 1
+#define WM8350_RAMP_DOWN 2
+
+struct wm8350_output {
+ u16 left_vol;
+ u16 right_vol;
+ u16 ramp;
+ u16 mute;
+};
+
+struct wm8350_out_ramp {
+ struct wm8350_output out1;
+ struct wm8350_output out2;
+};
+
+static unsigned int wm8350_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct wm8350* wm8350 = codec->control_data;
+ return wm8350_reg_read(wm8350, reg);
+}
+
+static int wm8350_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct wm8350* wm8350 = codec->control_data;
+ return wm8350_reg_write(wm8350, reg, value);
+}
+
+/*
+ * Ramp OUT1 PGA volume to minimise pops at stream startup and shutdown.
+ */
+static inline int wm8350_out1_ramp_step(struct snd_soc_codec *codec)
+{
+ struct wm8350_out_ramp *or = codec->private_data;
+ struct wm8350_output *output = &or->out1;
+ struct wm8350 *wm8350 = codec->control_data;
+ int left_complete = 0, right_complete = 0;
+ u16 reg, val;
+
+ /* left channel */
+ reg = wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME);
+ val = (reg & WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+
+ if (output->ramp == WM8350_RAMP_UP) {
+ /* ramp step up */
+ if (val < output->left_vol) {
+ val++;
+ reg &= ~WM8350_OUT1L_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME,
+ reg | (val << WM8350_OUT1L_VOL_SHIFT));
+ } else
+ left_complete = 1;
+ } else if (output->ramp == WM8350_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0) {
+ val--;
+ reg &= ~WM8350_OUT1L_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME,
+ reg | (val << WM8350_OUT1L_VOL_SHIFT));
+ } else
+ left_complete = 1;
+ } else
+ return 1;
+
+ /* right channel */
+ reg = wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME);
+ val = (reg & WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+ if (output->ramp == WM8350_RAMP_UP) {
+ /* ramp step up */
+ if (val < output->right_vol) {
+ val++;
+ reg &= ~WM8350_OUT1R_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME,
+ reg | (val << WM8350_OUT1R_VOL_SHIFT));
+ } else
+ right_complete = 1;
+ } else if (output->ramp == WM8350_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0) {
+ val--;
+ reg &= ~WM8350_OUT1R_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME,
+ reg | (val << WM8350_OUT1R_VOL_SHIFT));
+ } else
+ right_complete = 1;
+ }
+
+ /* only hit the update bit if either volume has changed this step */
+ if (!left_complete || !right_complete)
+ wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, WM8350_OUT1_VU);
+
+ return left_complete & right_complete;
+}
+
+/*
+ * Ramp OUT2 PGA volume to minimise pops at stream startup and shutdown.
+ */
+static inline int wm8350_out2_ramp_step(struct snd_soc_codec *codec)
+{
+ struct wm8350_out_ramp *or = codec->private_data;
+ struct wm8350_output *output = &or->out2;
+ struct wm8350 *wm8350 = codec->control_data;
+ int left_complete = 0, right_complete = 0;
+ u16 reg, val;
+
+ /* left channel */
+ reg = wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME);
+ val = (reg & WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+ if (output->ramp == WM8350_RAMP_UP) {
+ /* ramp step up */
+ if (val < output->left_vol) {
+ val++;
+ reg &= ~WM8350_OUT2L_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME,
+ reg | (val << WM8350_OUT1L_VOL_SHIFT));
+ } else
+ left_complete = 1;
+ } else if (output->ramp == WM8350_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0) {
+ val--;
+ reg &= ~WM8350_OUT2L_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME,
+ reg | (val << WM8350_OUT1L_VOL_SHIFT));
+ } else
+ left_complete = 1;
+ } else
+ return 1;
+
+ /* right channel */
+ reg = wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME);
+ val = (reg & WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+ if (output->ramp == WM8350_RAMP_UP) {
+ /* ramp step up */
+ if (val < output->right_vol) {
+ val++;
+ reg &= ~WM8350_OUT2R_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME,
+ reg | (val << WM8350_OUT1R_VOL_SHIFT));
+ } else
+ right_complete = 1;
+ } else if (output->ramp == WM8350_RAMP_DOWN) {
+ /* ramp step down */
+ if (val > 0) {
+ val--;
+ reg &= ~WM8350_OUT2R_VOL_MASK;
+ wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME,
+ reg | (val << WM8350_OUT1R_VOL_SHIFT));
+ } else
+ right_complete = 1;
+ }
+
+ /* only hit the update bit if either volume has changed this step */
+ if (!left_complete || !right_complete)
+ wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, WM8350_OUT2_VU);
+
+ return left_complete & right_complete;
+}
+
+/*
+ * This work ramps both output PGA's at stream start/stop time to
+ * minimise pop associated with DAPM power switching.
+ * It's best to enable Zero Cross when ramping occurs to minimise any
+ * zipper noises.
+ */
+static void wm8350_pga_work(struct work_struct *work)
+{
+ struct snd_soc_codec *codec =
+ container_of(work, struct snd_soc_codec, delayed_work.work);
+ struct wm8350_out_ramp *or = codec->private_data;
+ struct wm8350_output *out1 = &or->out1, *out2 = &or->out2;
+ int i, out1_complete, out2_complete;
+
+ /* do we need to ramp at all ? */
+ if (out1->ramp == WM8350_RAMP_NONE && out2->ramp == WM8350_RAMP_NONE)
+ return;
+
+ /* PGA volumes have 6 bits of resolution to ramp */
+ for (i = 0; i <= 63; i++) {
+
+ out1_complete = 1, out2_complete = 1;
+ if (out1->ramp != WM8350_RAMP_NONE)
+ out1_complete = wm8350_out1_ramp_step(codec);
+ if (out2->ramp != WM8350_RAMP_NONE)
+ out2_complete = wm8350_out2_ramp_step(codec);
+
+ /* ramp finished ? */
+ if (out1_complete && out2_complete)
+ break;
+
+ /* we need to delay longer on the up ramp */
+ if (out1->ramp == WM8350_RAMP_UP ||
+ out2->ramp == WM8350_RAMP_UP) {
+ /* delay is longer over 0dB as increases are larger */
+ if (i >= WM8350_OUTn_0dB)
+ schedule_timeout_interruptible(
+ msecs_to_jiffies(2));
+ else
+ schedule_timeout_interruptible(
+ msecs_to_jiffies(1));
+ } else
+ udelay(50); /* doesn't matter if we delay longer */
+ }
+
+ out1->ramp = WM8350_RAMP_NONE;
+ out2->ramp = WM8350_RAMP_NONE;
+}
+
+/*
+ * WM8350 Controls
+ */
+
+static int pga_event(struct snd_soc_dapm_widget *w, int event)
+{
+ struct snd_soc_codec *codec = w->codec;
+ struct wm8350_out_ramp *or = codec->private_data;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ /* ramp vol up */
+ switch (w->shift) {
+ case 0:
+ case 1:
+ or->out1.ramp = WM8350_RAMP_UP;
+ break;
+ case 2:
+ case 3:
+ or->out2.ramp = WM8350_RAMP_UP;
+ break;
+ }
+ if (!delayed_work_pending(&codec->delayed_work))
+ schedule_delayed_work(&codec->delayed_work,
+ msecs_to_jiffies(1));
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ /* ramp down */
+ switch (w->shift) {
+ case 0:
+ case 1:
+ or->out1.ramp = WM8350_RAMP_DOWN;
+ break;
+ case 2:
+ case 3:
+ or->out2.ramp = WM8350_RAMP_DOWN;
+ break;
+ }
+ if (!delayed_work_pending(&codec->delayed_work))
+ schedule_delayed_work(&codec->delayed_work,
+ msecs_to_jiffies(1));
+ break;
+ }
+ return 0;
+}
+
+static int wm8350_put_volsw_2r_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8350_out_ramp *or = codec->private_data;
+ struct wm8350_output *out1 = &or->out1, *out2 = &or->out2;
+ int ret, reg = kcontrol->private_value & 0xff;
+ u16 val;
+
+ /* we cache the OUT1/2 volumes when the codec is inactive to keep
+ * pops to a low during ramping */
+ if (reg == WM8350_LOUT1_VOLUME) {
+ out1->left_vol = ucontrol->value.integer.value[0];
+ out1->right_vol = ucontrol->value.integer.value[1];
+ if (!codec->active)
+ return 1;
+ } else if (reg == WM8350_LOUT2_VOLUME) {
+ out2->left_vol = ucontrol->value.integer.value[0];
+ out2->right_vol = ucontrol->value.integer.value[1];
+ if (!codec->active)
+ return 1;
+ }
+
+ ret = snd_soc_put_volsw_2r(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* now hit the volume update bits (always bit 8) */
+ val = wm8350_codec_read(codec, reg);
+ wm8350_codec_write(codec, reg, val | WM8350_OUT1_VU);
+ return 1;
+}
+
+static int wm8350_get_volsw_2r(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct wm8350_out_ramp *or = codec->private_data;
+ struct wm8350_output *out1 = &or->out1, *out2 = &or->out2;
+ int reg = kcontrol->private_value & 0xff;
+ int reg2 = (kcontrol->private_value >> 24) & 0xff;
+ int shift = (kcontrol->private_value >> 8) & 0x0f;
+ int max = (kcontrol->private_value >> 12) & 0xff;
+ int mask = (1 << fls(max)) - 1;
+
+ /* if the codec is inactive then read back the cached values */
+ if (!codec->active) {
+ if (reg == WM8350_LOUT1_VOLUME) {
+ ucontrol->value.integer.value[0] = out1->left_vol;
+ ucontrol->value.integer.value[1] = out1->right_vol;
+ return 0;
+ } else if (reg == WM8350_LOUT2_VOLUME) {
+ ucontrol->value.integer.value[0] = out2->left_vol;
+ ucontrol->value.integer.value[1] = out2->right_vol;
+ return 0;
+ }
+ }
+
+ ucontrol->value.integer.value[0] =
+ (snd_soc_read(codec, reg) >> shift) & mask;
+ ucontrol->value.integer.value[1] =
+ (snd_soc_read(codec, reg2) >> shift) & mask;
+
+ return 0;
+}
+
+/* double control with volume update */
+#define SOC_WM8350_DOUBLE_R_TLV(xname, reg_left, reg_right, shift, mask, invert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | SNDRV_CTL_ELEM_ACCESS_READWRITE |\
+ SNDRV_CTL_ELEM_ACCESS_VOLATILE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw_2r, \
+ .get = wm8350_get_volsw_2r, .put = wm8350_put_volsw_2r_vu, \
+ .private_value = (reg_left) | ((shift) << 8) | \
+ ((mask) << 12) | ((invert) << 20) | ((reg_right) << 24) }
+
+static const char *wm8350_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz"};
+static const char *wm8350_pol[] = {"Normal", "Inv R", "Inv L", "Inv L & R"};
+static const char *wm8350_dacmutem[] = {"Normal", "Soft"};
+static const char *wm8350_dacmutes[] = {"Fast", "Slow"};
+static const char *wm8350_dacfilter[] = {"Normal", "Sloping"};
+static const char *wm8350_adcfilter[] = {"None", "High Pass"};
+static const char *wm8350_adchp[] = {"44.1kHz", "8kHz", "16kHz", "32kHz"};
+static const char *wm8350_lr[] = {"Left", "Right"};
+
+static const struct soc_enum wm8350_enum[] = {
+SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 4, 4, wm8350_deemp),
+SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol),
+SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem),
+SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes),
+SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 12, 2, wm8350_dacfilter),
+SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter),
+SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp),
+SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol),
+SOC_ENUM_SINGLE(WM8350_INPUT_MIXER_VOLUME, 15, 2, wm8350_lr),
+};
+
+DECLARE_TLV_DB_LINEAR(pre_amp_tlv, -1200, 3525);
+DECLARE_TLV_DB_LINEAR(out_pga_tlv, -5700, 600);
+DECLARE_TLV_DB_SCALE(dac_pcm_tlv, -7163, 36, 1);
+DECLARE_TLV_DB_SCALE(adc_pcm_tlv, -12700, 50, 1);
+DECLARE_TLV_DB_SCALE(out_mix_tlv, -1200, 300, 1);
+
+static const unsigned int capture_sd_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0,12, TLV_DB_SCALE_ITEM(-3600, 300, 1),
+ 13,15, TLV_DB_SCALE_ITEM(0, 0, 0),
+};
+
+static const struct snd_kcontrol_new wm8350_snd_controls[] = {
+SOC_ENUM("Playback Deemphasis", wm8350_enum[0]),
+SOC_ENUM("Playback DAC Inversion", wm8350_enum[1]),
+SOC_WM8350_DOUBLE_R_TLV("Playback PCM Volume", WM8350_DAC_DIGITAL_VOLUME_L,
+ WM8350_DAC_DIGITAL_VOLUME_R, 0, 255, 0, dac_pcm_tlv),
+SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]),
+SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]),
+SOC_ENUM("Playback PCM Filter", wm8350_enum[4]),
+SOC_ENUM("Capture PCM Filter", wm8350_enum[5]),
+SOC_ENUM("Capture PCM HP Filter", wm8350_enum[6]),
+SOC_ENUM("Capture ADC Inversion", wm8350_enum[7]),
+SOC_WM8350_DOUBLE_R_TLV("Capture PCM Volume", WM8350_ADC_DIGITAL_VOLUME_L,
+ WM8350_ADC_DIGITAL_VOLUME_R, 0, 255, 0, adc_pcm_tlv),
+SOC_DOUBLE_TLV("Capture Sidetone Volume", WM8350_ADC_DIVIDER, 8, 4, 15, 1, capture_sd_tlv),
+SOC_WM8350_DOUBLE_R_TLV("Capture Volume", WM8350_LEFT_INPUT_VOLUME,
+ WM8350_RIGHT_INPUT_VOLUME, 2, 63, 0, pre_amp_tlv),
+SOC_DOUBLE_R("Capture ZC Switch", WM8350_LEFT_INPUT_VOLUME,
+ WM8350_RIGHT_INPUT_VOLUME, 13, 1, 0),
+SOC_SINGLE_TLV("Left Input Left Sidetone Volume", WM8350_OUTPUT_LEFT_MIXER_VOLUME,
+ 1, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("Left Input Right Sidetone Volume", WM8350_OUTPUT_LEFT_MIXER_VOLUME,
+ 5, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("Left Input Bypass Volume", WM8350_OUTPUT_LEFT_MIXER_VOLUME,
+ 9, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("Right Input Left Sidetone Volume", WM8350_OUTPUT_RIGHT_MIXER_VOLUME,
+ 1, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("Right Input Right Sidetone Volume", WM8350_OUTPUT_RIGHT_MIXER_VOLUME,
+ 5, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("Right Input Bypass Volume", WM8350_OUTPUT_RIGHT_MIXER_VOLUME,
+ 13, 7, 0, out_mix_tlv),
+SOC_SINGLE("Left Input Mixer +20dB Switch", WM8350_INPUT_MIXER_VOLUME_L,
+ 0, 1, 0),
+SOC_SINGLE("Right Input Mixer +20dB Switch", WM8350_INPUT_MIXER_VOLUME_R,
+ 0, 1, 0),
+SOC_SINGLE_TLV("Out4 Capture Volume", WM8350_INPUT_MIXER_VOLUME, 1, 7, 0, out_mix_tlv),
+SOC_WM8350_DOUBLE_R_TLV("Out1 Playback Volume", WM8350_LOUT1_VOLUME,
+ WM8350_ROUT1_VOLUME, 2, 63, 0, out_pga_tlv),
+SOC_DOUBLE_R("Out1 Playback ZC Switch", WM8350_LOUT1_VOLUME,
+ WM8350_ROUT1_VOLUME, 13, 1, 0),
+SOC_WM8350_DOUBLE_R_TLV("Out2 Playback Volume", WM8350_LOUT2_VOLUME,
+ WM8350_ROUT2_VOLUME, 2, 63, 0, out_pga_tlv),
+SOC_DOUBLE_R("Out2 Playback ZC Switch", WM8350_LOUT2_VOLUME,
+ WM8350_ROUT2_VOLUME, 13, 1, 0),
+SOC_SINGLE("Out2 Right Invert Switch", WM8350_ROUT2_VOLUME, 10, 1, 0),
+SOC_SINGLE_TLV("Out2 Beep Volume", WM8350_BEEP_VOLUME, 5, 7, 0, out_mix_tlv),
+
+SOC_DOUBLE_R("Out1 Playback Switch", WM8350_LOUT1_VOLUME, WM8350_ROUT1_VOLUME,
+ 14, 1, 1),
+SOC_DOUBLE_R("Out2 Playback Switch", WM8350_LOUT2_VOLUME, WM8350_ROUT2_VOLUME,
+ 14, 1, 1),
+};
+
+/* add non dapm controls */
+static int wm8350_add_controls(struct snd_soc_codec *codec,
+ struct snd_card *card)
+{
+ int err, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm8350_snd_controls); i++) {
+ err = snd_ctl_add(card,
+ snd_soc_cnew(&wm8350_snd_controls[i],
+ codec, NULL));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * DAPM Controls
+ */
+
+/* Left Playback Mixer */
+static const struct snd_kcontrol_new wm8350_left_play_mixer_controls[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8350_LEFT_MIXER_CONTROL, 11, 1, 0),
+SOC_DAPM_SINGLE("Left Bypass Switch", WM8350_LEFT_MIXER_CONTROL, 2, 1, 0),
+SOC_DAPM_SINGLE("Right Playback Switch", WM8350_LEFT_MIXER_CONTROL, 12, 1, 0),
+SOC_DAPM_SINGLE("Left Sidetone Switch", WM8350_LEFT_MIXER_CONTROL, 0, 1, 0),
+SOC_DAPM_SINGLE("Right Sidetone Switch", WM8350_LEFT_MIXER_CONTROL, 1, 1, 0),
+};
+
+/* Right Playback Mixer */
+static const struct snd_kcontrol_new wm8350_right_play_mixer_controls[] = {
+SOC_DAPM_SINGLE("Playback Switch", WM8350_RIGHT_MIXER_CONTROL, 12, 1, 0),
+SOC_DAPM_SINGLE("Right Bypass Switch", WM8350_RIGHT_MIXER_CONTROL, 3, 1, 0),
+SOC_DAPM_SINGLE("Left Playback Switch", WM8350_RIGHT_MIXER_CONTROL, 11, 1, 0),
+SOC_DAPM_SINGLE("Left Sidetone Switch", WM8350_RIGHT_MIXER_CONTROL, 0, 1, 0),
+SOC_DAPM_SINGLE("Right Sidetone Switch", WM8350_RIGHT_MIXER_CONTROL, 1, 1, 0),
+};
+
+/* Out4 Mixer */
+static const struct snd_kcontrol_new wm8350_out4_mixer_controls[] = {
+SOC_DAPM_SINGLE("Right Playback Switch", WM8350_OUT4_MIXER_CONTROL, 12, 1, 0),
+SOC_DAPM_SINGLE("Left Playback Switch", WM8350_OUT4_MIXER_CONTROL, 11, 1, 0),
+SOC_DAPM_SINGLE("Right Capture Switch", WM8350_OUT4_MIXER_CONTROL, 9, 1, 0),
+SOC_DAPM_SINGLE("Out3 Playback Switch", WM8350_OUT4_MIXER_CONTROL, 2, 1, 0),
+SOC_DAPM_SINGLE("Right Mixer Switch", WM8350_OUT4_MIXER_CONTROL, 1, 1, 0),
+SOC_DAPM_SINGLE("Left Mixer Switch", WM8350_OUT4_MIXER_CONTROL, 0, 1, 0),
+};
+
+/* Out3 Mixer */
+static const struct snd_kcontrol_new wm8350_out3_mixer_controls[] = {
+SOC_DAPM_SINGLE("Left Playback Switch", WM8350_OUT3_MIXER_CONTROL, 11, 1, 0),
+SOC_DAPM_SINGLE("Left Capture Switch", WM8350_OUT3_MIXER_CONTROL, 8, 1, 0),
+SOC_DAPM_SINGLE("Out4 Playback Switch", WM8350_OUT3_MIXER_CONTROL, 3, 1, 0),
+SOC_DAPM_SINGLE("Left Mixer Switch", WM8350_OUT3_MIXER_CONTROL, 0, 1, 0),
+};
+
+/* Left Input Mixer */
+static const struct snd_kcontrol_new wm8350_left_capt_mixer_controls[] = {
+SOC_DAPM_SINGLE_TLV("L2 Capture Volume",
+ WM8350_INPUT_MIXER_VOLUME_L, 1, 7, 0, out_mix_tlv),
+SOC_DAPM_SINGLE_TLV("AUX Capture Volume",
+ WM8350_INPUT_MIXER_VOLUME_L, 9, 7, 0, out_mix_tlv),
+SOC_DAPM_SINGLE("PGA Capture Switch",
+ WM8350_LEFT_INPUT_VOLUME, 14, 1, 0),
+};
+
+/* Right Input Mixer */
+static const struct snd_kcontrol_new wm8350_right_capt_mixer_controls[] = {
+SOC_DAPM_SINGLE_TLV("L2 Capture Volume",
+ WM8350_INPUT_MIXER_VOLUME_R, 5, 7, 0, out_mix_tlv),
+SOC_DAPM_SINGLE_TLV("AUX Capture Volume",
+ WM8350_INPUT_MIXER_VOLUME_R, 13, 7, 0, out_mix_tlv),
+SOC_DAPM_SINGLE("PGA Capture Switch",
+ WM8350_RIGHT_INPUT_VOLUME, 14, 1, 0),
+};
+
+/* Left Mic Mixer */
+static const struct snd_kcontrol_new wm8350_left_mic_mixer_controls[] = {
+SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 1, 1, 0),
+SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 0, 1, 0),
+SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 2, 1, 0),
+};
+
+/* Right Mic Mixer */
+static const struct snd_kcontrol_new wm8350_right_mic_mixer_controls[] = {
+SOC_DAPM_SINGLE("INN Capture Switch", WM8350_INPUT_CONTROL, 9, 1, 0),
+SOC_DAPM_SINGLE("INP Capture Switch", WM8350_INPUT_CONTROL, 8, 1, 0),
+SOC_DAPM_SINGLE("IN2 Capture Switch", WM8350_INPUT_CONTROL, 10, 1, 0),
+};
+
+/* Beep Switch */
+static const struct snd_kcontrol_new wm8350_beep_switch_controls =
+SOC_DAPM_SINGLE("Switch", WM8350_BEEP_VOLUME, 15, 1, 1);
+
+/* Out4 Capture Mux */
+static const struct snd_kcontrol_new wm8350_out4_capture_controls =
+SOC_DAPM_ENUM("Route", wm8350_enum[8]);
+
+
+static const struct snd_soc_dapm_widget wm8350_dapm_widgets[] = {
+
+ SND_SOC_DAPM_PGA("Right Aux PGA", WM8350_POWER_MGMT_2, 11, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Aux PGA", WM8350_POWER_MGMT_2, 10, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Right Input PGA", WM8350_POWER_MGMT_2, 9, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Left Input PGA", WM8350_POWER_MGMT_2, 8, 0, NULL, 0),
+ SND_SOC_DAPM_PGA_E("Right Out2 PGA", WM8350_POWER_MGMT_3, 3, 0, NULL, 0,
+ pga_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("Left Out2 PGA", WM8350_POWER_MGMT_3, 2, 0, NULL, 0,
+ pga_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("Right Out1 PGA", WM8350_POWER_MGMT_3, 1, 0, NULL, 0,
+ pga_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_PGA_E("Left Out1 PGA", WM8350_POWER_MGMT_3, 0, 0, NULL, 0,
+ pga_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_MIXER("Right Capture Mixer", WM8350_POWER_MGMT_2,
+ 7, 0, &wm8350_right_capt_mixer_controls[0],
+ ARRAY_SIZE(wm8350_right_capt_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left Capture Mixer", WM8350_POWER_MGMT_2,
+ 6, 0, &wm8350_left_capt_mixer_controls[0],
+ ARRAY_SIZE(wm8350_left_capt_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Out4 Mixer", WM8350_POWER_MGMT_2, 5, 0,
+ &wm8350_out4_mixer_controls[0],
+ ARRAY_SIZE(wm8350_out4_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Out3 Mixer", WM8350_POWER_MGMT_2, 4, 0,
+ &wm8350_out3_mixer_controls[0],
+ ARRAY_SIZE(wm8350_out3_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right Playback Mixer", WM8350_POWER_MGMT_2, 1, 0,
+ &wm8350_right_play_mixer_controls[0],
+ ARRAY_SIZE(wm8350_right_play_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left Playback Mixer", WM8350_POWER_MGMT_2, 0, 0,
+ &wm8350_left_play_mixer_controls[0],
+ ARRAY_SIZE(wm8350_left_play_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Left Mic Mixer", WM8350_POWER_MGMT_2, 8, 0,
+ &wm8350_left_mic_mixer_controls[0],
+ ARRAY_SIZE(wm8350_left_mic_mixer_controls)),
+
+ SND_SOC_DAPM_MIXER("Right Mic Mixer", WM8350_POWER_MGMT_2, 9, 0,
+ &wm8350_right_mic_mixer_controls[0],
+ ARRAY_SIZE(wm8350_right_mic_mixer_controls)),
+
+ /* virtual mixer for Beep and Out2R */
+ SND_SOC_DAPM_MIXER("Out2 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_SWITCH("Beep", WM8350_POWER_MGMT_3, 7, 0,
+ &wm8350_beep_switch_controls),
+
+ SND_SOC_DAPM_ADC("Right ADC", "Right Capture",
+ WM8350_POWER_MGMT_4, 3, 0),
+ SND_SOC_DAPM_ADC("Left ADC", "Left Capture",
+ WM8350_POWER_MGMT_4, 2, 0),
+ SND_SOC_DAPM_DAC("Right DAC", "Right Playback",
+ WM8350_POWER_MGMT_4, 5, 0),
+ SND_SOC_DAPM_DAC("Left DAC", "Left Playback",
+ WM8350_POWER_MGMT_4, 4, 0),
+
+ SND_SOC_DAPM_MICBIAS("Mic Bias", WM8350_POWER_MGMT_1, 4, 0),
+
+ SND_SOC_DAPM_MUX("Out4 Capture Channel", SND_SOC_NOPM, 0, 0,
+ &wm8350_out4_capture_controls),
+
+ SND_SOC_DAPM_OUTPUT("OUT1R"),
+ SND_SOC_DAPM_OUTPUT("OUT1L"),
+ SND_SOC_DAPM_OUTPUT("OUT2R"),
+ SND_SOC_DAPM_OUTPUT("OUT2L"),
+ SND_SOC_DAPM_OUTPUT("OUT3"),
+ SND_SOC_DAPM_OUTPUT("OUT4"),
+
+ SND_SOC_DAPM_INPUT("IN1RN"),
+ SND_SOC_DAPM_INPUT("IN1RP"),
+ SND_SOC_DAPM_INPUT("IN2R"),
+ SND_SOC_DAPM_INPUT("IN1LP"),
+ SND_SOC_DAPM_INPUT("IN1LN"),
+ SND_SOC_DAPM_INPUT("IN2L"),
+ SND_SOC_DAPM_INPUT("IN3R"),
+ SND_SOC_DAPM_INPUT("IN3L"),
+};
+
+static const char *audio_map[][3] = {
+
+ /* left playback mixer */
+ {"Left Playback Mixer", "Playback Switch", "Left DAC"},
+ {"Left Playback Mixer", "Left Bypass Switch", "Left Aux PGA"},
+ {"Left Playback Mixer", "Right Playback Switch", "Right DAC"},
+ {"Left Playback Mixer", "Left Sidetone Switch", "Left Input PGA"},
+ {"Left Playback Mixer", "Right Sidetone Switch", "Right Input PGA"},
+
+ /* right playback mixer */
+ {"Right Playback Mixer", "Playback Switch", "Right DAC"},
+ {"Right Playback Mixer", "Right Bypass Switch", "Right Aux PGA"},
+ {"Right Playback Mixer", "Left Playback Switch", "Left DAC"},
+ {"Right Playback Mixer", "Left Sidetone Switch", "Left Input PGA"},
+ {"Right Playback Mixer", "Right Sidetone Switch", "Right Input PGA"},
+
+ /* out4 playback mixer */
+ {"Out4 Mixer", "Right Playback Switch", "Right DAC"},
+ {"Out4 Mixer", "Left Playback Switch", "Left DAC"},
+ {"Out4 Mixer", "Right Capture Switch", "Right Capture Mixer"},
+ {"Out4 Mixer", "Out3 Playback Switch", "Out3 Mixer"},
+ {"Out4 Mixer", "Right Mixer Switch", "Right Playback Mixer"},
+ {"Out4 Mixer", "Left Mixer Switch", "Left Playback Mixer"},
+ {"OUT4", NULL, "Out4 Mixer"},
+
+ /* out3 playback mixer */
+ {"Out3 Mixer", "Left Playback Switch", "Left DAC"},
+ {"Out3 Mixer", "Left Capture Switch", "Left Capture Mixer"},
+ {"Out3 Mixer", "Left Mixer Switch", "Left Playback Mixer "},
+ {"Out3 Mixer", "Out4 Playback Switch", "Out4 Mixer"},
+ {"OUT3", NULL, "Out3 Mixer"},
+
+ /* out2 */
+ {"Right Out2 PGA", NULL, "Right Playback Mixer"},
+ {"Left Out2 PGA", NULL, "Left Playback Mixer"},
+ {"OUT2L", NULL, "Left Out2 PGA"},
+ {"OUT2R", NULL, "Right Out2 PGA"},
+
+ /* out1 */
+ {"Right Out1 PGA", NULL, "Right Playback Mixer"},
+ {"Left Out1 PGA", NULL, "Left Playback Mixer"},
+ {"OUT1L", NULL, "Left Out1 PGA"},
+ {"OUT1R", NULL, "Right Out1 PGA"},
+
+ /* ADC's */
+ {"Left ADC", NULL, "Left Capture Mixer"},
+ {"Right ADC", NULL, "Right Capture Mixer"},
+
+ /* Left capture mixer */
+ {"Left Capture Mixer", "L2 Capture Volume", "IN2L"},
+ {"Left Capture Mixer", "AUX Capture Volume", "Left Aux PGA"},
+ {"Left Capture Mixer", "PGA Capture Switch", "Left Mic Mixer"},
+ {"Left Capture Mixer", "Left", "Out4 Capture Mux"},
+
+ /* Right capture mixer */
+ {"Right Capture Mixer", "L2 Capture Volume", "IN2R"},
+ {"Right Capture Mixer", "AUX Capture Volume", "Right Aux PGA"},
+ {"Right Capture Mixer", "PGA Capture Switch", "Right Mic Mixer"},
+ {"Right Capture Mixer", "Right", "Out4 Capture Mux"},
+
+ /* AUX Inputs */
+ {"Left AUX PGA", NULL, "IN3L"},
+ {"Right AUX PGA", NULL, "IN3R"},
+
+ /* Left Mic mixer */
+ {"Left Mic Mixer", "INN Capture Switch", "IN1LN"},
+ {"Left Mic Mixer", "INP Capture Switch", "IN1LP"},
+ {"Left Mic Mixer", "IN2 Capture Switch", "IN2L"},
+
+ /* Right Mic mixer */
+ {"Right Mic Mixer", "INN Capture Switch", "IN1RN"},
+ {"Right Mic Mixer", "INP Capture Switch", "IN1RP"},
+ {"Right Mic Mixer", "IN2 Capture Switch", "IN2R"},
+
+ /* out 4 capture */
+ {"Out4 Capture Mux", NULL, "Out4 Mixer"},
+
+ /* Beep */
+ {"Beep", NULL, "Right Aux PGA"},
+
+ /* terminator */
+ {NULL, NULL, NULL},
+};
+
+static int wm8350_add_widgets(struct snd_soc_codec *codec,
+ struct snd_soc_machine *machine)
+{
+ int i;
+
+ for(i = 0; i < ARRAY_SIZE(wm8350_dapm_widgets); i++) {
+ snd_soc_dapm_new_control(machine, codec,
+ &wm8350_dapm_widgets[i]);
+ }
+
+ /* set up audio path audio_mapnects */
+ for(i = 0; audio_map[i][0] != NULL; i++) {
+ snd_soc_dapm_connect_input(machine, audio_map[i][0],
+ audio_map[i][1], audio_map[i][2]);
+ }
+
+ snd_soc_dapm_new_widgets(machine);
+ return 0;
+}
+
+static int wm8350_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8350* wm8350 = codec->control_data;
+ u16 fll_4;
+
+ switch(clk_id) {
+ case WM3850_MCLK_SEL_MCLK:
+ wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_1,
+ WM8350_MCLK_SEL);
+ break;
+ case WM3850_MCLK_SEL_PLL_MCLK:
+ case WM3850_MCLK_SEL_PLL_DAC:
+ case WM3850_MCLK_SEL_PLL_ADC:
+ case WM3850_MCLK_SEL_PLL_32K:
+ wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_1,
+ WM8350_MCLK_SEL);
+ fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) &
+ ~WM8350_FLL_CLK_SRC_MASK;
+ wm8350_codec_write(codec, WM8350_FLL_CONTROL_4,
+ fll_4 | clk_id);
+ break;
+ }
+
+ /* MCLK direction */
+ if (dir == WM3850_MCLK_DIR_OUT)
+ wm8350_set_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+ WM8350_MCLK_DIR);
+ else
+ wm8350_clear_bits(wm8350, WM8350_CLOCK_CONTROL_2,
+ WM8350_MCLK_DIR);
+
+ return 0;
+}
+
+static int wm8350_set_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 val;
+
+ switch (div_id) {
+ case WM8350_ADC_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_ADC_DIVIDER) &
+ ~WM8350_ADC_CLKDIV_MASK;
+ wm8350_codec_write(codec, WM8350_ADC_DIVIDER, val | div);
+ break;
+ case WM8350_DAC_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_DAC_CLOCK_CONTROL) &
+ ~WM8350_DAC_CLKDIV_MASK;
+ wm8350_codec_write(codec, WM8350_DAC_CLOCK_CONTROL, val | div);
+ break;
+ case WM8350_BCLK_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) &
+ ~WM8350_BCLK_DIV_MASK;
+ wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div);
+ break;
+ case WM8350_OPCLK_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) &
+ ~WM8350_OPCLK_DIV_MASK;
+ wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div);
+ break;
+ case WM8350_SYS_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_CLOCK_CONTROL_1) &
+ ~WM8350_MCLK_DIV_MASK;
+ wm8350_codec_write(codec, WM8350_CLOCK_CONTROL_1, val | div);
+ break;
+ case WM8350_DACLR_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_DAC_LR_RATE) &
+ ~WM8350_DACLRC_RATE_MASK;
+ wm8350_codec_write(codec, WM8350_DAC_LR_RATE, val | div);
+ break;
+ case WM8350_ADCLR_CLKDIV:
+ val = wm8350_codec_read(codec, WM8350_ADC_LR_RATE) &
+ ~WM8350_ADCLRC_RATE_MASK;
+ wm8350_codec_write(codec, WM8350_ADC_LR_RATE, val | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int wm8350_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) &
+ ~(WM8350_AIF_BCLK_INV | WM8350_AIF_LRCLK_INV |
+ WM8350_AIF_FMT_MASK);
+ u16 master = wm8350_codec_read(codec, WM8350_AI_DAC_CONTROL) &
+ ~WM8350_BCLK_MSTR;
+ u16 dac_lrc = wm8350_codec_read(codec, WM8350_DAC_LR_RATE) &
+ ~WM8350_DACLRC_ENA;
+ u16 adc_lrc = wm8350_codec_read(codec, WM8350_ADC_LR_RATE) &
+ ~WM8350_ADCLRC_ENA;
+
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ master |= WM8350_BCLK_MSTR;
+ dac_lrc |= WM8350_DACLRC_ENA;
+ adc_lrc |= WM8350_ADCLRC_ENA;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= 0x2 << 8;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ iface |= 0x1 << 8;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ iface |= 0x3 << 8;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ iface |= 0x3 << 8; // lg not sure which mode
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* clock inversion */
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ iface |= WM8350_AIF_LRCLK_INV | WM8350_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ iface |= WM8350_AIF_BCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ iface |= WM8350_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8350_codec_write(codec, WM8350_AI_FORMATING, iface);
+ wm8350_codec_write(codec, WM8350_AI_DAC_CONTROL, master);
+ wm8350_codec_write(codec, WM8350_DAC_LR_RATE, dac_lrc);
+ wm8350_codec_write(codec, WM8350_ADC_LR_RATE, adc_lrc);
+ return 0;
+}
+
+static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_link *pcm_link = substream->private_data;
+ struct snd_soc_codec *codec = pcm_link->codec;
+ u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) &
+ ~WM8350_AIF_WL_MASK;
+
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ iface |= 0x1 << 10;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ iface |= 0x2 << 10;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ iface |= 0x3 << 10;
+ break;
+ }
+
+ wm8350_codec_write(codec, WM8350_AI_FORMATING, iface);
+ return 0;
+}
+
+static int wm8350_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ struct wm8350* wm8350 = codec->control_data;
+
+ if (mute)
+ wm8350_set_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA);
+ else
+ wm8350_clear_bits(wm8350, WM8350_DAC_MUTE, WM8350_DAC_MUTE_ENA);
+ return 0;
+}
+
+/* FLL divisors */
+struct _fll_div {
+ int div; /* FLL_OUTDIV */
+ int n;
+ int k;
+ int ratio; /* FLL_FRATIO */
+};
+
+/* The size in bits of the fll divide multiplied by 10
+ * to allow rounding later */
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static inline int fll_factors(struct _fll_div *fll_div, unsigned int input,
+ unsigned int output)
+{
+ u64 Kpart;
+ unsigned int t1, t2, K, Nmod;
+
+ if (output >= 2815250 && output <= 3125000)
+ fll_div->div = 0x4;
+ else if (output >= 5625000 && output <= 6250000)
+ fll_div->div = 0x3;
+ else if (output >= 11250000 && output <= 12500000)
+ fll_div->div = 0x2;
+ else if (output >= 22500000 && output <= 25000000)
+ fll_div->div = 0x1;
+ else {
+ printk(KERN_ERR "wm8350: fll freq %d out of range\n", output);
+ return -EINVAL;
+ }
+
+ if (input > 48000)
+ fll_div->ratio = 1;
+ else
+ fll_div->ratio = 8;
+
+ t1 = output * (1 << (fll_div->div + 1));
+ t2 = input * fll_div->ratio;
+
+ fll_div->n = t1 / t2;
+ Nmod = t1 % t2;
+
+ if (Nmod) {
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+ do_div(Kpart, t2);
+ K = Kpart & 0xFFFFFFFF;
+
+ /* Check if we need to round */
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ K /= 10;
+ fll_div->k = K;
+ } else
+ fll_div->k = 0;
+
+ return 0;
+}
+
+
+static int wm8350_set_fll(struct snd_soc_dai *codec_dai,
+ int pll_id, unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8350* wm8350 = codec->control_data;
+ struct _fll_div fll_div;
+ int ret = 0;
+ u16 fll_1, fll_4;
+
+ if (freq_out == 0 || freq_in == 0) {
+ /* power down FLL */
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4,
+ WM8350_FLL_ENA | WM8350_FLL_OSC_ENA);
+ return ret;
+ }
+
+ ret = fll_factors(&fll_div, freq_in, freq_out);
+ if (ret < 0)
+ return ret;
+
+ /* set up N.K & dividers */
+ fll_1 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_1) &
+ ~(WM8350_FLL_OUTDIV_MASK | 0xc000);
+ wm8350_codec_write(codec, WM8350_FLL_CONTROL_1,
+ fll_1 | (fll_div.div << 8));
+ wm8350_codec_write(codec, WM8350_FLL_CONTROL_2,
+ (fll_div.ratio << 11) | (fll_div.n & WM8350_FLL_N_MASK));
+ wm8350_codec_write(codec, WM8350_FLL_CONTROL_3, fll_div.k);
+ fll_4 = wm8350_codec_read(codec, WM8350_FLL_CONTROL_4) &
+ ~(WM8350_FLL_FRAC | WM8350_FLL_SLOW_LOCK_REF);
+ wm8350_codec_write(codec, WM8350_FLL_CONTROL_4,
+ fll_4 | (fll_div.k ? WM8350_FLL_FRAC : 0) |
+ (fll_div.ratio == 8 ? WM8350_FLL_SLOW_LOCK_REF : 0));
+
+ /* power FLL on */
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_OSC_ENA);
+ /* do we need to wait here ? */
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_ENA);
+ return 0;
+}
+
+static int wm8350_set_tdm_slot(struct snd_soc_dai *codec_dai,
+ unsigned int mask, int slots)
+{
+ //struct snd_soc_codec *codec = codec_dai->codec;
+
+ return 0;
+}
+
+
+static int wm8350_set_tristate(struct snd_soc_dai *codec_dai,
+ int tristate)
+{
+ //struct snd_soc_codec *codec = codec_dai->codec;
+
+ return 0;
+}
+
+static int wm8350_dapm_event(struct snd_soc_codec *codec, int event)
+{
+ struct wm8350 *wm8350 = codec->control_data;
+ struct wm8350_platform_data *platform = codec->platform_data;
+ u16 pm1;
+
+ snd_assert(wm8350 != NULL, return -EINVAL);
+ snd_assert(platform != NULL, return -EINVAL);
+
+ switch (event) {
+ case SNDRV_CTL_POWER_D0: /* full On */
+ /* set vmid to 10k and current to 1.0x */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1 | WM8350_VMID_10K |
+ platform->codec_current_d0 << 14);
+ break;
+ case SNDRV_CTL_POWER_D1: /* partial On */
+ case SNDRV_CTL_POWER_D2: /* partial On */
+ /* set vmid to 40k for quick power up */
+ if (codec->dapm_state == SNDRV_CTL_POWER_D3hot) {
+ /* D3hot --> D0 */
+ /* enable bias */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1 | WM8350_BIASEN);
+ }
+ break;
+ case SNDRV_CTL_POWER_D3hot: /* Off, with power */
+ if (codec->dapm_state == SNDRV_CTL_POWER_D3cold) {
+ /* D3cold --> D3hot */
+ /* mute DAC & outputs */
+ wm8350_set_bits(wm8350, WM8350_DAC_MUTE,
+ WM8350_DAC_MUTE_ENA);
+
+ /* discharge cap memory */
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+ platform->dis_out1 |
+ (platform->dis_out2 << 2) |
+ (platform->dis_out3 << 4) |
+ (platform->dis_out4 << 6));
+
+ /* wait for discharge */
+ schedule_timeout_interruptible(msecs_to_jiffies(
+ platform->cap_discharge_msecs));
+
+ /* enable antipop */
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+ (platform->vmid_s_curve << 8));
+
+ /* ramp up vmid */
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ (platform->codec_current_charge << 14) |
+ WM8350_VMID_10K | WM8350_VMIDEN | WM8350_VBUFEN);
+
+ /* wait for vmid */
+ schedule_timeout_interruptible(msecs_to_jiffies(
+ platform->vmid_charge_msecs));
+
+ /* turn on vmid 500k */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1 | WM8350_VMID_500K |
+ (platform->codec_current_d3 << 14));
+
+ /* disable antipop */
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, 0);
+
+ } else {
+ /* D1,D2 --> D3hot */
+ /* turn on vmid 500k and reduce current */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~(WM8350_VMID_MASK | WM8350_CODEC_ISEL_MASK);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1 | WM8350_VMID_500K |
+ (platform->codec_current_d3 << 14));
+
+ /* disable bias */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~WM8350_BIASEN;
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1);
+ }
+
+ break;
+ case SNDRV_CTL_POWER_D3cold: /* Off, without power */
+
+ /* mute DAC & enable outputs */
+ wm8350_set_bits(wm8350, WM8350_DAC_MUTE,
+ WM8350_DAC_MUTE_ENA);
+
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_3,
+ WM8350_OUT1L_ENA | WM8350_OUT1R_ENA |
+ WM8350_OUT2L_ENA | WM8350_OUT2R_ENA);
+
+ /* enable anti pop S curve */
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+ (platform->vmid_s_curve << 8));
+
+ /* turn off vmid */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~WM8350_VMIDEN;
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1, pm1);
+
+ /* wait */
+ schedule_timeout_interruptible(msecs_to_jiffies(
+ platform->vmid_discharge_msecs));
+
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL,
+ (platform->vmid_s_curve << 8) |
+ platform->dis_out1 |
+ (platform->dis_out2 << 2) |
+ (platform->dis_out3 << 4) |
+ (platform->dis_out4 << 6));
+
+ /* turn off VBuf and drain */
+ pm1 = wm8350_reg_read(wm8350, WM8350_POWER_MGMT_1) &
+ ~(WM8350_VBUFEN | WM8350_VMID_MASK);
+ wm8350_reg_write(wm8350, WM8350_POWER_MGMT_1,
+ pm1 | WM8350_OUTPUT_DRAIN_EN);
+
+ /* wait */
+ schedule_timeout_interruptible(msecs_to_jiffies(
+ platform->drain_msecs));
+
+ /* disable anti-pop */
+ wm8350_reg_write(wm8350, WM8350_ANTI_POP_CONTROL, 0);
+
+ wm8350_clear_bits(wm8350, WM8350_LOUT1_VOLUME,
+ WM8350_OUT1L_ENA);
+ wm8350_clear_bits(wm8350, WM8350_ROUT1_VOLUME,
+ WM8350_OUT1R_ENA);
+ wm8350_clear_bits(wm8350, WM8350_LOUT2_VOLUME,
+ WM8350_OUT2L_ENA);
+ wm8350_clear_bits(wm8350, WM8350_ROUT2_VOLUME,
+ WM8350_OUT2R_ENA);
+
+ /* disable clock gen */
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_SYSCLK_ENA);
+
+ break;
+ }
+ codec->dapm_state = event;
+ return 0;
+}
+
+#define WM8350_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | \
+ SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
+ SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_32000 | \
+ SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \
+ SNDRV_PCM_RATE_96000)
+
+
+#define WM8350_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+static const struct snd_soc_pcm_stream wm8350_hifi_dai_playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8350_RATES,
+ .formats = WM8350_FORMATS,
+};
+
+static const struct snd_soc_pcm_stream wm8350_hifi_dai_capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = WM8350_RATES,
+ .formats = WM8350_FORMATS,
+};
+
+/* dai ops, called by machine drivers */
+static const struct snd_soc_dai_ops wm8350_hifi_dai_ops = {
+ .digital_mute = wm8350_mute,
+ .set_fmt = wm8350_set_dai_fmt,
+ .set_sysclk = wm8350_set_dai_sysclk,
+ .set_tristate = wm8350_set_tristate,
+ .set_pll = wm8350_set_fll,
+ .set_tdm_slot = wm8350_set_tdm_slot,
+ .set_clkdiv = wm8350_set_clkdiv,
+};
+
+/* audio ops, called by alsa */
+static const struct snd_soc_ops wm8350_hifi_dai_audio_ops = {
+ .hw_params = wm8350_pcm_hw_params,
+};
+
+static int wm8350_suspend(struct device *dev, pm_message_t state)
+{
+ struct snd_soc_codec *codec = to_snd_soc_codec(dev);
+// lg save status
+ wm8350_dapm_event(codec, SNDRV_CTL_POWER_D3cold);
+ return 0;
+}
+
+static int wm8350_resume(struct device *dev)
+{
+ struct snd_soc_codec *codec = to_snd_soc_codec(dev);
+
+ wm8350_dapm_event(codec, SNDRV_CTL_POWER_D3hot);
+
+ /* charge wm8350 caps */
+ if (codec->suspend_dapm_state == SNDRV_CTL_POWER_D0) {
+ wm8350_dapm_event(codec, SNDRV_CTL_POWER_D3hot);
+ codec->dapm_state = SNDRV_CTL_POWER_D0;
+ }
+
+ return 0;
+}
+
+static int wm8350_codec_io_probe(struct snd_soc_codec *codec,
+ struct snd_soc_machine *machine)
+{
+ struct wm8350* wm8350 = codec->control_data;
+ struct wm8350_out_ramp *or = codec->private_data;
+ struct wm8350_output *out1 = &or->out1, *out2 = &or->out2;
+
+ snd_assert(wm8350 != NULL, return -EINVAL);
+
+ /* reset codec */
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CODEC_ENA);
+
+ /* enable clock gen - lg need to move in new silicon */
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_SYSCLK_ENA);
+
+ /* charge output caps */
+ codec->dapm_state = SNDRV_CTL_POWER_D3cold;
+ wm8350_dapm_event(codec, SNDRV_CTL_POWER_D3hot);
+
+ wm8350_add_controls(codec, machine->card);
+ wm8350_add_widgets(codec, machine);
+
+ /* read OUT1 & OUT2 volumes */
+ out1->left_vol = (wm8350_reg_read(wm8350, WM8350_LOUT1_VOLUME) &
+ WM8350_OUT1L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+ out1->right_vol = (wm8350_reg_read(wm8350, WM8350_ROUT1_VOLUME) &
+ WM8350_OUT1R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+ out2->left_vol = (wm8350_reg_read(wm8350, WM8350_LOUT2_VOLUME) &
+ WM8350_OUT2L_VOL_MASK) >> WM8350_OUT1L_VOL_SHIFT;
+ out2->right_vol = (wm8350_reg_read(wm8350, WM8350_ROUT2_VOLUME) &
+ WM8350_OUT2R_VOL_MASK) >> WM8350_OUT1R_VOL_SHIFT;
+ wm8350_reg_write(wm8350, WM8350_LOUT1_VOLUME, 0);
+ wm8350_reg_write(wm8350, WM8350_ROUT1_VOLUME, 0);
+ wm8350_set_bits(wm8350, WM8350_LOUT1_VOLUME, WM8350_OUT1_VU);
+ wm8350_reg_write(wm8350, WM8350_LOUT2_VOLUME, 0);
+ wm8350_reg_write(wm8350, WM8350_ROUT2_VOLUME, 0);
+ wm8350_set_bits(wm8350, WM8350_LOUT2_VOLUME, WM8350_OUT2_VU);
+
+ return 0;
+}
+
+/*
+ * This function forces any delayed work to be queued and run.
+ */
+static int run_delayed_work(struct delayed_work *dwork)
+{
+ int ret;
+
+ /* cancel any work waiting to be queued. */
+ ret = cancel_delayed_work(dwork);
+
+ /* if there was any work waiting then we run it now and
+ * wait for it's completion */
+ if (ret) {
+ schedule_delayed_work(dwork, 0);
+ flush_scheduled_work();
+ }
+ return ret;
+}
+
+static int wm8350_codec_io_remove(struct snd_soc_codec *codec,
+ struct snd_soc_machine *machine)
+{
+ struct wm8350* wm8350 = codec->control_data;
+
+ snd_assert(wm8350 != NULL, return -EINVAL);
+ run_delayed_work(&codec->delayed_work);
+ wm8350_dapm_event(codec, SNDRV_CTL_POWER_D3cold);
+ return 0;
+}
+
+static const struct snd_soc_codec_ops wm8350_codec_ops = {
+ .dapm_event = wm8350_dapm_event,
+ .read = wm8350_codec_read,
+ .write = wm8350_codec_write,
+ .io_probe = wm8350_codec_io_probe,
+ .io_remove = wm8350_codec_io_remove,
+};
+
+static int wm8350_codec_probe(struct device *dev)
+{
+ struct snd_soc_codec *codec = to_snd_soc_codec(dev);
+ struct wm8350_out_ramp *or;
+ info("WM8350 Audio Codec %s", WM8350_VERSION);
+
+ or = kzalloc(sizeof(struct wm8350_out_ramp), GFP_KERNEL);
+ if (or == NULL)
+ return -ENOMEM;
+
+ codec->owner = THIS_MODULE;
+ codec->ops = &wm8350_codec_ops;
+ codec->reg_cache_size = WM8350_MAX_REGISTER;
+ codec->reg_cache_step = 1;
+ codec->private_data = or;
+ INIT_DELAYED_WORK(&codec->delayed_work, wm8350_pga_work);
+ snd_soc_register_codec(codec);
+ return 0;
+}
+
+static int wm8350_codec_remove(struct device *dev)
+{
+ struct snd_soc_codec *codec = to_snd_soc_codec(dev);
+ kfree(codec->private_data);
+ return 0;
+}
+
+static int wm8350_hifi_dai_probe(struct device *dev)
+{
+ struct snd_soc_dai *dai = to_snd_soc_dai(dev);
+
+ dai->ops = &wm8350_hifi_dai_ops;
+ dai->audio_ops = &wm8350_hifi_dai_audio_ops;
+ dai->capture = &wm8350_hifi_dai_capture;
+ dai->playback = &wm8350_hifi_dai_playback;
+ snd_soc_register_codec_dai(dai);
+ return 0;
+}
+
+const char wm8350_codec[SND_SOC_CODEC_NAME_SIZE] = "wm8350-codec";
+EXPORT_SYMBOL_GPL(wm8350_codec);
+
+static struct snd_soc_device_driver wm8350_codec_driver = {
+ .type = SND_SOC_BUS_TYPE_CODEC,
+ .driver = {
+ .name = wm8350_codec,
+ .owner = THIS_MODULE,
+ .bus = &asoc_bus_type,
+ .probe = wm8350_codec_probe,
+ .remove = __devexit_p(wm8350_codec_remove),
+ .suspend = wm8350_suspend,
+ .resume = wm8350_resume,
+ },
+};
+
+const char wm8350_hifi_dai[SND_SOC_CODEC_NAME_SIZE] = "wm8350-hifi-dai";
+EXPORT_SYMBOL_GPL(wm8350_hifi_dai);
+
+static struct snd_soc_device_driver wm8350_hifi_dai_driver = {
+ .type = SND_SOC_BUS_TYPE_DAI,
+ .driver = {
+ .name = wm8350_hifi_dai,
+ .owner = THIS_MODULE,
+ .bus = &asoc_bus_type,
+ .probe = wm8350_hifi_dai_probe,
+ },
+};
+
+static __init int wm8350_init(void)
+{
+ int ret = 0;
+
+ ret = driver_register(&wm8350_codec_driver.driver);
+ if (ret < 0)
+ return ret;
+ ret = driver_register(&wm8350_hifi_dai_driver.driver);
+ if (ret < 0) {
+ driver_unregister(&wm8350_codec_driver.driver);
+ return ret;
+ }
+ return ret;
+}
+
+static __exit void wm8350_exit(void)
+{
+ driver_unregister(&wm8350_hifi_dai_driver.driver);
+ driver_unregister(&wm8350_codec_driver.driver);
+}
+
+module_init(wm8350_init);
+module_exit(wm8350_exit);
+
+MODULE_DESCRIPTION("ASoC WM8350 driver");
+MODULE_AUTHOR("Liam Girdwood");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/wm8350.h b/sound/soc/codecs/wm8350.h
new file mode 100644
index 000000000000..0edfdc9a093b
--- /dev/null
+++ b/sound/soc/codecs/wm8350.h
@@ -0,0 +1,49 @@
+/*
+ * 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 _WM8350_H
+#define _WM8350_H
+
+#define WM8350_SYSCLK 0
+
+#define WM8350_S_CURVE_NONE 0x0 /* will pop */
+#define WM8350_S_CURVE_FAST 0x1
+#define WM8350_S_CURVE_MEDIUM 0x2
+#define WM8350_S_CURVE_SLOW 0x3
+
+#define WM8350_DISCHARGE_OFF 0x0
+#define WM8350_DISCHARGE_FAST 0x1
+#define WM8350_DISCHARGE_MEDIUM 0x2
+#define WM8350_DISCHARGE_SLOW 0x3
+
+#define WM8350_TIE_OFF_500R 0x0
+#define WM8350_TIE_OFF_30K 0x1
+
+struct wm8350_platform_data {
+ int vmid_discharge_msecs; /* VMID D3Cold discharge time */
+ int drain_msecs; /* D3Cold drain time */
+ int cap_discharge_msecs; /* Cap D3Hot (from off) discharge time */
+ int vmid_charge_msecs; /* vmid power up time */
+ u32 vmid_s_curve:2; /* vmid enable s curve speed */
+ u32 dis_out4:2; /* out4 discharge speed */
+ u32 dis_out3:2; /* out3 discharge speed */
+ u32 dis_out2:2; /* out2 discharge speed */
+ u32 dis_out1:2; /* out1 discharge speed */
+ u32 vroi_out4:1; /* out4 tie off */
+ u32 vroi_out3:1; /* out3 tie off */
+ u32 vroi_out2:1; /* out2 tie off */
+ u32 vroi_out1:1; /* out1 tie off */
+ u32 vroi_enable:1; /* enable tie off */
+ u32 codec_current_d0:2; /* current level D0*/
+ u32 codec_current_d3:2; /* current level D3 */
+ u32 codec_current_charge:2; /* codec current @ vmid charge */
+};
+
+extern const char wm8350_codec[SND_SOC_CODEC_NAME_SIZE];
+extern const char wm8350_hifi_dai[SND_SOC_DAI_NAME_SIZE];
+
+#endif