2010-01-30 00:47:12 +07:00
|
|
|
/*
|
|
|
|
* wm8994.c -- WM8994 ALSA SoC Audio driver
|
|
|
|
*
|
|
|
|
* Copyright 2009 Wolfson Microelectronics plc
|
|
|
|
*
|
|
|
|
* Author: Mark Brown <broonie@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/i2c.h>
|
|
|
|
#include <linux/platform_device.h>
|
2010-11-27 00:23:43 +07:00
|
|
|
#include <linux/pm_runtime.h>
|
2010-01-30 00:47:12 +07:00
|
|
|
#include <linux/regulator/consumer.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 15:04:11 +07:00
|
|
|
#include <linux/slab.h>
|
2010-01-30 00:47:12 +07:00
|
|
|
#include <sound/core.h>
|
2010-11-26 22:21:09 +07:00
|
|
|
#include <sound/jack.h>
|
2010-01-30 00:47:12 +07:00
|
|
|
#include <sound/pcm.h>
|
|
|
|
#include <sound/pcm_params.h>
|
|
|
|
#include <sound/soc.h>
|
|
|
|
#include <sound/initval.h>
|
|
|
|
#include <sound/tlv.h>
|
2010-12-05 19:50:12 +07:00
|
|
|
#include <trace/events/asoc.h>
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
#include <linux/mfd/wm8994/core.h>
|
|
|
|
#include <linux/mfd/wm8994/registers.h>
|
|
|
|
#include <linux/mfd/wm8994/pdata.h>
|
|
|
|
#include <linux/mfd/wm8994/gpio.h>
|
|
|
|
|
|
|
|
#include "wm8994.h"
|
|
|
|
#include "wm_hubs.h"
|
|
|
|
|
|
|
|
#define WM8994_NUM_DRC 3
|
|
|
|
#define WM8994_NUM_EQ 3
|
|
|
|
|
|
|
|
static int wm8994_drc_base[] = {
|
|
|
|
WM8994_AIF1_DRC1_1,
|
|
|
|
WM8994_AIF1_DRC2_1,
|
|
|
|
WM8994_AIF2_DRC_1,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int wm8994_retune_mobile_base[] = {
|
|
|
|
WM8994_AIF1_DAC1_EQ_GAINS_1,
|
|
|
|
WM8994_AIF1_DAC2_EQ_GAINS_1,
|
|
|
|
WM8994_AIF2_EQ_GAINS_1,
|
|
|
|
};
|
|
|
|
|
2011-01-13 19:20:37 +07:00
|
|
|
static int wm8994_readable(struct snd_soc_codec *codec, unsigned int reg)
|
2010-01-30 00:47:12 +07:00
|
|
|
{
|
2011-03-17 04:05:06 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2011-11-04 22:52:31 +07:00
|
|
|
struct wm8994 *control = codec->control_data;
|
2011-03-17 04:05:06 +07:00
|
|
|
|
2010-07-08 22:12:08 +07:00
|
|
|
switch (reg) {
|
|
|
|
case WM8994_GPIO_1:
|
|
|
|
case WM8994_GPIO_2:
|
|
|
|
case WM8994_GPIO_3:
|
|
|
|
case WM8994_GPIO_4:
|
|
|
|
case WM8994_GPIO_5:
|
|
|
|
case WM8994_GPIO_6:
|
|
|
|
case WM8994_GPIO_7:
|
|
|
|
case WM8994_GPIO_8:
|
|
|
|
case WM8994_GPIO_9:
|
|
|
|
case WM8994_GPIO_10:
|
|
|
|
case WM8994_GPIO_11:
|
|
|
|
case WM8994_INTERRUPT_STATUS_1:
|
|
|
|
case WM8994_INTERRUPT_STATUS_2:
|
|
|
|
case WM8994_INTERRUPT_RAW_STATUS_2:
|
|
|
|
return 1;
|
2011-03-17 04:05:06 +07:00
|
|
|
|
|
|
|
case WM8958_DSP2_PROGRAM:
|
|
|
|
case WM8958_DSP2_CONFIG:
|
|
|
|
case WM8958_DSP2_EXECCONTROL:
|
|
|
|
if (control->type == WM8958)
|
|
|
|
return 1;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
|
2010-07-08 22:12:08 +07:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-11-17 03:11:40 +07:00
|
|
|
if (reg >= WM8994_CACHE_SIZE)
|
2010-01-30 00:47:12 +07:00
|
|
|
return 0;
|
2010-11-17 03:11:40 +07:00
|
|
|
return wm8994_access_masks[reg].readable != 0;
|
2010-01-30 00:47:12 +07:00
|
|
|
}
|
|
|
|
|
2011-01-13 19:20:37 +07:00
|
|
|
static int wm8994_volatile(struct snd_soc_codec *codec, unsigned int reg)
|
2010-01-30 00:47:12 +07:00
|
|
|
{
|
2010-11-27 00:23:41 +07:00
|
|
|
if (reg >= WM8994_CACHE_SIZE)
|
2010-01-30 00:47:12 +07:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
switch (reg) {
|
|
|
|
case WM8994_SOFTWARE_RESET:
|
|
|
|
case WM8994_CHIP_REVISION:
|
|
|
|
case WM8994_DC_SERVO_1:
|
|
|
|
case WM8994_DC_SERVO_READBACK:
|
|
|
|
case WM8994_RATE_STATUS:
|
|
|
|
case WM8994_LDO_1:
|
|
|
|
case WM8994_LDO_2:
|
2010-11-26 22:21:08 +07:00
|
|
|
case WM8958_DSP2_EXECCONTROL:
|
2010-11-26 22:21:09 +07:00
|
|
|
case WM8958_MIC_DETECT_3:
|
2011-08-01 11:02:17 +07:00
|
|
|
case WM8994_DC_SERVO_4E:
|
2010-01-30 00:47:12 +07:00
|
|
|
return 1;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg,
|
|
|
|
unsigned int value)
|
|
|
|
{
|
2010-11-27 00:23:41 +07:00
|
|
|
int ret;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
BUG_ON(reg > WM8994_MAX_REGISTER);
|
|
|
|
|
2011-01-13 19:20:37 +07:00
|
|
|
if (!wm8994_volatile(codec, reg)) {
|
2010-11-27 00:23:41 +07:00
|
|
|
ret = snd_soc_cache_write(codec, reg, value);
|
|
|
|
if (ret != 0)
|
|
|
|
dev_err(codec->dev, "Cache write to %x failed: %d\n",
|
|
|
|
reg, ret);
|
|
|
|
}
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
return wm8994_reg_write(codec->control_data, reg, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int wm8994_read(struct snd_soc_codec *codec,
|
|
|
|
unsigned int reg)
|
|
|
|
{
|
2010-11-27 00:23:41 +07:00
|
|
|
unsigned int val;
|
|
|
|
int ret;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
BUG_ON(reg > WM8994_MAX_REGISTER);
|
|
|
|
|
2011-01-13 19:20:37 +07:00
|
|
|
if (!wm8994_volatile(codec, reg) && wm8994_readable(codec, reg) &&
|
2010-11-27 00:23:41 +07:00
|
|
|
reg < codec->driver->reg_cache_size) {
|
|
|
|
ret = snd_soc_cache_read(codec, reg, &val);
|
|
|
|
if (ret >= 0)
|
|
|
|
return val;
|
|
|
|
else
|
|
|
|
dev_err(codec->dev, "Cache read from %x failed: %d\n",
|
|
|
|
reg, ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
return wm8994_reg_read(codec->control_data, reg);
|
2010-01-30 00:47:12 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int configure_aif_clock(struct snd_soc_codec *codec, int aif)
|
|
|
|
{
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-01-30 00:47:12 +07:00
|
|
|
int rate;
|
|
|
|
int reg1 = 0;
|
|
|
|
int offset;
|
|
|
|
|
|
|
|
if (aif)
|
|
|
|
offset = 4;
|
|
|
|
else
|
|
|
|
offset = 0;
|
|
|
|
|
|
|
|
switch (wm8994->sysclk[aif]) {
|
|
|
|
case WM8994_SYSCLK_MCLK1:
|
|
|
|
rate = wm8994->mclk[0];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM8994_SYSCLK_MCLK2:
|
|
|
|
reg1 |= 0x8;
|
|
|
|
rate = wm8994->mclk[1];
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM8994_SYSCLK_FLL1:
|
|
|
|
reg1 |= 0x10;
|
|
|
|
rate = wm8994->fll[0].out;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM8994_SYSCLK_FLL2:
|
|
|
|
reg1 |= 0x18;
|
|
|
|
rate = wm8994->fll[1].out;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (rate >= 13500000) {
|
|
|
|
rate /= 2;
|
|
|
|
reg1 |= WM8994_AIF1CLK_DIV;
|
|
|
|
|
|
|
|
dev_dbg(codec->dev, "Dividing AIF%d clock to %dHz\n",
|
|
|
|
aif + 1, rate);
|
|
|
|
}
|
2010-04-25 18:20:30 +07:00
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
wm8994->aifclk[aif] = rate;
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1 + offset,
|
|
|
|
WM8994_AIF1CLK_SRC_MASK | WM8994_AIF1CLK_DIV,
|
|
|
|
reg1);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int configure_clock(struct snd_soc_codec *codec)
|
|
|
|
{
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2011-10-04 19:07:03 +07:00
|
|
|
int change, new;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
/* Bring up the AIF clocks first */
|
|
|
|
configure_aif_clock(codec, 0);
|
|
|
|
configure_aif_clock(codec, 1);
|
|
|
|
|
|
|
|
/* Then switch CLK_SYS over to the higher of them; a change
|
|
|
|
* can only happen as a result of a clocking change which can
|
|
|
|
* only be made outside of DAPM so we can safely redo the
|
|
|
|
* clocking.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* If they're equal it doesn't matter which is used */
|
|
|
|
if (wm8994->aifclk[0] == wm8994->aifclk[1])
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (wm8994->aifclk[0] < wm8994->aifclk[1])
|
|
|
|
new = WM8994_SYSCLK_SRC;
|
|
|
|
else
|
|
|
|
new = 0;
|
|
|
|
|
2011-10-04 19:07:03 +07:00
|
|
|
change = snd_soc_update_bits(codec, WM8994_CLOCKING_1,
|
|
|
|
WM8994_SYSCLK_SRC, new);
|
|
|
|
if (!change)
|
2010-01-30 00:47:12 +07:00
|
|
|
return 0;
|
|
|
|
|
2010-11-05 20:53:46 +07:00
|
|
|
snd_soc_dapm_sync(&codec->dapm);
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int check_clk_sys(struct snd_soc_dapm_widget *source,
|
|
|
|
struct snd_soc_dapm_widget *sink)
|
|
|
|
{
|
|
|
|
int reg = snd_soc_read(source->codec, WM8994_CLOCKING_1);
|
|
|
|
const char *clk;
|
|
|
|
|
|
|
|
/* Check what we're currently using for CLK_SYS */
|
|
|
|
if (reg & WM8994_SYSCLK_SRC)
|
|
|
|
clk = "AIF2CLK";
|
|
|
|
else
|
|
|
|
clk = "AIF1CLK";
|
|
|
|
|
|
|
|
return strcmp(source->name, clk) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *sidetone_hpf_text[] = {
|
|
|
|
"2.7kHz", "1.35kHz", "675Hz", "370Hz", "180Hz", "90Hz", "45Hz"
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct soc_enum sidetone_hpf =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_SIDETONE, 7, 7, sidetone_hpf_text);
|
|
|
|
|
2010-12-07 20:58:40 +07:00
|
|
|
static const char *adc_hpf_text[] = {
|
|
|
|
"HiFi", "Voice 1", "Voice 2", "Voice 3"
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct soc_enum aif1adc1_hpf =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF1_ADC1_FILTERS, 13, 4, adc_hpf_text);
|
|
|
|
|
|
|
|
static const struct soc_enum aif1adc2_hpf =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF1_ADC2_FILTERS, 13, 4, adc_hpf_text);
|
|
|
|
|
|
|
|
static const struct soc_enum aif2adc_hpf =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF2_ADC_FILTERS, 13, 4, adc_hpf_text);
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
static const DECLARE_TLV_DB_SCALE(aif_tlv, 0, 600, 0);
|
|
|
|
static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1);
|
|
|
|
static const DECLARE_TLV_DB_SCALE(st_tlv, -3600, 300, 0);
|
|
|
|
static const DECLARE_TLV_DB_SCALE(wm8994_3d_tlv, -1600, 183, 0);
|
|
|
|
static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0);
|
2011-08-16 08:08:48 +07:00
|
|
|
static const DECLARE_TLV_DB_SCALE(ng_tlv, -10200, 600, 0);
|
2011-05-24 16:35:53 +07:00
|
|
|
static const DECLARE_TLV_DB_SCALE(mixin_boost_tlv, 0, 900, 0);
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
#define WM8994_DRC_SWITCH(xname, reg, shift) \
|
|
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
|
|
|
.info = snd_soc_info_volsw, .get = snd_soc_get_volsw,\
|
|
|
|
.put = wm8994_put_drc_sw, \
|
|
|
|
.private_value = SOC_SINGLE_VALUE(reg, shift, 1, 0) }
|
|
|
|
|
|
|
|
static int wm8994_put_drc_sw(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct soc_mixer_control *mc =
|
|
|
|
(struct soc_mixer_control *)kcontrol->private_value;
|
|
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
int mask, ret;
|
|
|
|
|
|
|
|
/* Can't enable both ADC and DAC paths simultaneously */
|
|
|
|
if (mc->shift == WM8994_AIF1DAC1_DRC_ENA_SHIFT)
|
|
|
|
mask = WM8994_AIF1ADC1L_DRC_ENA_MASK |
|
|
|
|
WM8994_AIF1ADC1R_DRC_ENA_MASK;
|
|
|
|
else
|
|
|
|
mask = WM8994_AIF1DAC1_DRC_ENA_MASK;
|
|
|
|
|
|
|
|
ret = snd_soc_read(codec, mc->reg);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
if (ret & mask)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return snd_soc_put_volsw(kcontrol, ucontrol);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wm8994_set_drc(struct snd_soc_codec *codec, int drc)
|
|
|
|
{
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-01-30 00:47:12 +07:00
|
|
|
struct wm8994_pdata *pdata = wm8994->pdata;
|
|
|
|
int base = wm8994_drc_base[drc];
|
|
|
|
int cfg = wm8994->drc_cfg[drc];
|
|
|
|
int save, i;
|
|
|
|
|
|
|
|
/* Save any enables; the configuration should clear them. */
|
|
|
|
save = snd_soc_read(codec, base);
|
|
|
|
save &= WM8994_AIF1DAC1_DRC_ENA | WM8994_AIF1ADC1L_DRC_ENA |
|
|
|
|
WM8994_AIF1ADC1R_DRC_ENA;
|
|
|
|
|
|
|
|
for (i = 0; i < WM8994_DRC_REGS; i++)
|
|
|
|
snd_soc_update_bits(codec, base + i, 0xffff,
|
|
|
|
pdata->drc_cfgs[cfg].regs[i]);
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, base, WM8994_AIF1DAC1_DRC_ENA |
|
|
|
|
WM8994_AIF1ADC1L_DRC_ENA |
|
|
|
|
WM8994_AIF1ADC1R_DRC_ENA, save);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Icky as hell but saves code duplication */
|
|
|
|
static int wm8994_get_drc(const char *name)
|
|
|
|
{
|
|
|
|
if (strcmp(name, "AIF1DRC1 Mode") == 0)
|
|
|
|
return 0;
|
|
|
|
if (strcmp(name, "AIF1DRC2 Mode") == 0)
|
|
|
|
return 1;
|
|
|
|
if (strcmp(name, "AIF2DRC Mode") == 0)
|
|
|
|
return 2;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wm8994_put_drc_enum(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
2010-03-18 03:15:21 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-01-30 00:47:12 +07:00
|
|
|
struct wm8994_pdata *pdata = wm8994->pdata;
|
|
|
|
int drc = wm8994_get_drc(kcontrol->id.name);
|
|
|
|
int value = ucontrol->value.integer.value[0];
|
|
|
|
|
|
|
|
if (drc < 0)
|
|
|
|
return drc;
|
|
|
|
|
|
|
|
if (value >= pdata->num_drc_cfgs)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
wm8994->drc_cfg[drc] = value;
|
|
|
|
|
|
|
|
wm8994_set_drc(codec, drc);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wm8994_get_drc_enum(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-01-30 00:47:12 +07:00
|
|
|
int drc = wm8994_get_drc(kcontrol->id.name);
|
|
|
|
|
|
|
|
ucontrol->value.enumerated.item[0] = wm8994->drc_cfg[drc];
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wm8994_set_retune_mobile(struct snd_soc_codec *codec, int block)
|
|
|
|
{
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-01-30 00:47:12 +07:00
|
|
|
struct wm8994_pdata *pdata = wm8994->pdata;
|
|
|
|
int base = wm8994_retune_mobile_base[block];
|
|
|
|
int iface, best, best_val, save, i, cfg;
|
|
|
|
|
|
|
|
if (!pdata || !wm8994->num_retune_mobile_texts)
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch (block) {
|
|
|
|
case 0:
|
|
|
|
case 1:
|
|
|
|
iface = 0;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
iface = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Find the version of the currently selected configuration
|
|
|
|
* with the nearest sample rate. */
|
|
|
|
cfg = wm8994->retune_mobile_cfg[block];
|
|
|
|
best = 0;
|
|
|
|
best_val = INT_MAX;
|
|
|
|
for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
|
|
|
|
if (strcmp(pdata->retune_mobile_cfgs[i].name,
|
|
|
|
wm8994->retune_mobile_texts[cfg]) == 0 &&
|
|
|
|
abs(pdata->retune_mobile_cfgs[i].rate
|
|
|
|
- wm8994->dac_rates[iface]) < best_val) {
|
|
|
|
best = i;
|
|
|
|
best_val = abs(pdata->retune_mobile_cfgs[i].rate
|
|
|
|
- wm8994->dac_rates[iface]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_dbg(codec->dev, "ReTune Mobile %d %s/%dHz for %dHz sample rate\n",
|
|
|
|
block,
|
|
|
|
pdata->retune_mobile_cfgs[best].name,
|
|
|
|
pdata->retune_mobile_cfgs[best].rate,
|
|
|
|
wm8994->dac_rates[iface]);
|
|
|
|
|
|
|
|
/* The EQ will be disabled while reconfiguring it, remember the
|
|
|
|
* current configuration.
|
|
|
|
*/
|
|
|
|
save = snd_soc_read(codec, base);
|
|
|
|
save &= WM8994_AIF1DAC1_EQ_ENA;
|
|
|
|
|
|
|
|
for (i = 0; i < WM8994_EQ_REGS; i++)
|
|
|
|
snd_soc_update_bits(codec, base + i, 0xffff,
|
|
|
|
pdata->retune_mobile_cfgs[best].regs[i]);
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, base, WM8994_AIF1DAC1_EQ_ENA, save);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Icky as hell but saves code duplication */
|
|
|
|
static int wm8994_get_retune_mobile_block(const char *name)
|
|
|
|
{
|
|
|
|
if (strcmp(name, "AIF1.1 EQ Mode") == 0)
|
|
|
|
return 0;
|
|
|
|
if (strcmp(name, "AIF1.2 EQ Mode") == 0)
|
|
|
|
return 1;
|
|
|
|
if (strcmp(name, "AIF2 EQ Mode") == 0)
|
|
|
|
return 2;
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wm8994_put_retune_mobile_enum(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
2010-03-18 03:15:21 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-01-30 00:47:12 +07:00
|
|
|
struct wm8994_pdata *pdata = wm8994->pdata;
|
|
|
|
int block = wm8994_get_retune_mobile_block(kcontrol->id.name);
|
|
|
|
int value = ucontrol->value.integer.value[0];
|
|
|
|
|
|
|
|
if (block < 0)
|
|
|
|
return block;
|
|
|
|
|
|
|
|
if (value >= pdata->num_retune_mobile_cfgs)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
wm8994->retune_mobile_cfg[block] = value;
|
|
|
|
|
|
|
|
wm8994_set_retune_mobile(codec, block);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
2011-02-17 05:57:17 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-01-30 00:47:12 +07:00
|
|
|
int block = wm8994_get_retune_mobile_block(kcontrol->id.name);
|
|
|
|
|
|
|
|
ucontrol->value.enumerated.item[0] = wm8994->retune_mobile_cfg[block];
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-11-18 22:49:38 +07:00
|
|
|
static const char *aif_chan_src_text[] = {
|
2010-09-01 01:39:48 +07:00
|
|
|
"Left", "Right"
|
|
|
|
};
|
|
|
|
|
2010-11-18 22:49:38 +07:00
|
|
|
static const struct soc_enum aif1adcl_src =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_1, 15, 2, aif_chan_src_text);
|
|
|
|
|
|
|
|
static const struct soc_enum aif1adcr_src =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_1, 14, 2, aif_chan_src_text);
|
|
|
|
|
|
|
|
static const struct soc_enum aif2adcl_src =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_1, 15, 2, aif_chan_src_text);
|
|
|
|
|
|
|
|
static const struct soc_enum aif2adcr_src =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_1, 14, 2, aif_chan_src_text);
|
|
|
|
|
2010-09-01 01:39:48 +07:00
|
|
|
static const struct soc_enum aif1dacl_src =
|
2010-11-18 22:49:38 +07:00
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_2, 15, 2, aif_chan_src_text);
|
2010-09-01 01:39:48 +07:00
|
|
|
|
|
|
|
static const struct soc_enum aif1dacr_src =
|
2010-11-18 22:49:38 +07:00
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_2, 14, 2, aif_chan_src_text);
|
2010-09-01 01:39:48 +07:00
|
|
|
|
|
|
|
static const struct soc_enum aif2dacl_src =
|
2010-11-18 22:49:38 +07:00
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 15, 2, aif_chan_src_text);
|
2010-09-01 01:39:48 +07:00
|
|
|
|
|
|
|
static const struct soc_enum aif2dacr_src =
|
2010-11-18 22:49:38 +07:00
|
|
|
SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 14, 2, aif_chan_src_text);
|
2010-09-01 01:39:48 +07:00
|
|
|
|
2010-12-09 19:07:44 +07:00
|
|
|
static const char *osr_text[] = {
|
|
|
|
"Low Power", "High Performance",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct soc_enum dac_osr =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_OVERSAMPLING, 0, 2, osr_text);
|
|
|
|
|
|
|
|
static const struct soc_enum adc_osr =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_OVERSAMPLING, 1, 2, osr_text);
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
static const struct snd_kcontrol_new wm8994_snd_controls[] = {
|
|
|
|
SOC_DOUBLE_R_TLV("AIF1ADC1 Volume", WM8994_AIF1_ADC1_LEFT_VOLUME,
|
|
|
|
WM8994_AIF1_ADC1_RIGHT_VOLUME,
|
|
|
|
1, 119, 0, digital_tlv),
|
|
|
|
SOC_DOUBLE_R_TLV("AIF1ADC2 Volume", WM8994_AIF1_ADC2_LEFT_VOLUME,
|
|
|
|
WM8994_AIF1_ADC2_RIGHT_VOLUME,
|
|
|
|
1, 119, 0, digital_tlv),
|
|
|
|
SOC_DOUBLE_R_TLV("AIF2ADC Volume", WM8994_AIF2_ADC_LEFT_VOLUME,
|
|
|
|
WM8994_AIF2_ADC_RIGHT_VOLUME,
|
|
|
|
1, 119, 0, digital_tlv),
|
|
|
|
|
2010-11-18 22:49:38 +07:00
|
|
|
SOC_ENUM("AIF1ADCL Source", aif1adcl_src),
|
|
|
|
SOC_ENUM("AIF1ADCR Source", aif1adcr_src),
|
2010-12-08 20:49:43 +07:00
|
|
|
SOC_ENUM("AIF2ADCL Source", aif2adcl_src),
|
|
|
|
SOC_ENUM("AIF2ADCR Source", aif2adcr_src),
|
2010-11-18 22:49:38 +07:00
|
|
|
|
2010-09-01 01:39:48 +07:00
|
|
|
SOC_ENUM("AIF1DACL Source", aif1dacl_src),
|
|
|
|
SOC_ENUM("AIF1DACR Source", aif1dacr_src),
|
2010-12-08 20:49:43 +07:00
|
|
|
SOC_ENUM("AIF2DACL Source", aif2dacl_src),
|
|
|
|
SOC_ENUM("AIF2DACR Source", aif2dacr_src),
|
2010-09-01 01:39:48 +07:00
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
SOC_DOUBLE_R_TLV("AIF1DAC1 Volume", WM8994_AIF1_DAC1_LEFT_VOLUME,
|
|
|
|
WM8994_AIF1_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
|
|
|
|
SOC_DOUBLE_R_TLV("AIF1DAC2 Volume", WM8994_AIF1_DAC2_LEFT_VOLUME,
|
|
|
|
WM8994_AIF1_DAC2_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
|
|
|
|
SOC_DOUBLE_R_TLV("AIF2DAC Volume", WM8994_AIF2_DAC_LEFT_VOLUME,
|
|
|
|
WM8994_AIF2_DAC_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
|
|
|
|
|
|
|
|
SOC_SINGLE_TLV("AIF1 Boost Volume", WM8994_AIF1_CONTROL_2, 10, 3, 0, aif_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF2 Boost Volume", WM8994_AIF2_CONTROL_2, 10, 3, 0, aif_tlv),
|
|
|
|
|
|
|
|
SOC_SINGLE("AIF1DAC1 EQ Switch", WM8994_AIF1_DAC1_EQ_GAINS_1, 0, 1, 0),
|
|
|
|
SOC_SINGLE("AIF1DAC2 EQ Switch", WM8994_AIF1_DAC2_EQ_GAINS_1, 0, 1, 0),
|
|
|
|
SOC_SINGLE("AIF2 EQ Switch", WM8994_AIF2_EQ_GAINS_1, 0, 1, 0),
|
|
|
|
|
|
|
|
WM8994_DRC_SWITCH("AIF1DAC1 DRC Switch", WM8994_AIF1_DRC1_1, 2),
|
|
|
|
WM8994_DRC_SWITCH("AIF1ADC1L DRC Switch", WM8994_AIF1_DRC1_1, 1),
|
|
|
|
WM8994_DRC_SWITCH("AIF1ADC1R DRC Switch", WM8994_AIF1_DRC1_1, 0),
|
|
|
|
|
|
|
|
WM8994_DRC_SWITCH("AIF1DAC2 DRC Switch", WM8994_AIF1_DRC2_1, 2),
|
|
|
|
WM8994_DRC_SWITCH("AIF1ADC2L DRC Switch", WM8994_AIF1_DRC2_1, 1),
|
|
|
|
WM8994_DRC_SWITCH("AIF1ADC2R DRC Switch", WM8994_AIF1_DRC2_1, 0),
|
|
|
|
|
|
|
|
WM8994_DRC_SWITCH("AIF2DAC DRC Switch", WM8994_AIF2_DRC_1, 2),
|
|
|
|
WM8994_DRC_SWITCH("AIF2ADCL DRC Switch", WM8994_AIF2_DRC_1, 1),
|
|
|
|
WM8994_DRC_SWITCH("AIF2ADCR DRC Switch", WM8994_AIF2_DRC_1, 0),
|
|
|
|
|
|
|
|
SOC_SINGLE_TLV("DAC1 Right Sidetone Volume", WM8994_DAC1_MIXER_VOLUMES,
|
|
|
|
5, 12, 0, st_tlv),
|
|
|
|
SOC_SINGLE_TLV("DAC1 Left Sidetone Volume", WM8994_DAC1_MIXER_VOLUMES,
|
|
|
|
0, 12, 0, st_tlv),
|
|
|
|
SOC_SINGLE_TLV("DAC2 Right Sidetone Volume", WM8994_DAC2_MIXER_VOLUMES,
|
|
|
|
5, 12, 0, st_tlv),
|
|
|
|
SOC_SINGLE_TLV("DAC2 Left Sidetone Volume", WM8994_DAC2_MIXER_VOLUMES,
|
|
|
|
0, 12, 0, st_tlv),
|
|
|
|
SOC_ENUM("Sidetone HPF Mux", sidetone_hpf),
|
|
|
|
SOC_SINGLE("Sidetone HPF Switch", WM8994_SIDETONE, 6, 1, 0),
|
|
|
|
|
2010-12-07 20:58:40 +07:00
|
|
|
SOC_ENUM("AIF1ADC1 HPF Mode", aif1adc1_hpf),
|
|
|
|
SOC_DOUBLE("AIF1ADC1 HPF Switch", WM8994_AIF1_ADC1_FILTERS, 12, 11, 1, 0),
|
|
|
|
|
|
|
|
SOC_ENUM("AIF1ADC2 HPF Mode", aif1adc2_hpf),
|
|
|
|
SOC_DOUBLE("AIF1ADC2 HPF Switch", WM8994_AIF1_ADC2_FILTERS, 12, 11, 1, 0),
|
|
|
|
|
|
|
|
SOC_ENUM("AIF2ADC HPF Mode", aif2adc_hpf),
|
|
|
|
SOC_DOUBLE("AIF2ADC HPF Switch", WM8994_AIF2_ADC_FILTERS, 12, 11, 1, 0),
|
|
|
|
|
2010-12-09 19:07:44 +07:00
|
|
|
SOC_ENUM("ADC OSR", adc_osr),
|
|
|
|
SOC_ENUM("DAC OSR", dac_osr),
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
SOC_DOUBLE_R_TLV("DAC1 Volume", WM8994_DAC1_LEFT_VOLUME,
|
|
|
|
WM8994_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
|
|
|
|
SOC_DOUBLE_R("DAC1 Switch", WM8994_DAC1_LEFT_VOLUME,
|
|
|
|
WM8994_DAC1_RIGHT_VOLUME, 9, 1, 1),
|
|
|
|
|
|
|
|
SOC_DOUBLE_R_TLV("DAC2 Volume", WM8994_DAC2_LEFT_VOLUME,
|
|
|
|
WM8994_DAC2_RIGHT_VOLUME, 1, 96, 0, digital_tlv),
|
|
|
|
SOC_DOUBLE_R("DAC2 Switch", WM8994_DAC2_LEFT_VOLUME,
|
|
|
|
WM8994_DAC2_RIGHT_VOLUME, 9, 1, 1),
|
|
|
|
|
|
|
|
SOC_SINGLE_TLV("SPKL DAC2 Volume", WM8994_SPKMIXL_ATTENUATION,
|
|
|
|
6, 1, 1, wm_hubs_spkmix_tlv),
|
|
|
|
SOC_SINGLE_TLV("SPKL DAC1 Volume", WM8994_SPKMIXL_ATTENUATION,
|
|
|
|
2, 1, 1, wm_hubs_spkmix_tlv),
|
|
|
|
|
|
|
|
SOC_SINGLE_TLV("SPKR DAC2 Volume", WM8994_SPKMIXR_ATTENUATION,
|
|
|
|
6, 1, 1, wm_hubs_spkmix_tlv),
|
|
|
|
SOC_SINGLE_TLV("SPKR DAC1 Volume", WM8994_SPKMIXR_ATTENUATION,
|
|
|
|
2, 1, 1, wm_hubs_spkmix_tlv),
|
|
|
|
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC1 3D Stereo Volume", WM8994_AIF1_DAC1_FILTERS_2,
|
|
|
|
10, 15, 0, wm8994_3d_tlv),
|
2010-12-20 21:35:09 +07:00
|
|
|
SOC_SINGLE("AIF1DAC1 3D Stereo Switch", WM8994_AIF1_DAC1_FILTERS_2,
|
2010-01-30 00:47:12 +07:00
|
|
|
8, 1, 0),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC2 3D Stereo Volume", WM8994_AIF1_DAC2_FILTERS_2,
|
|
|
|
10, 15, 0, wm8994_3d_tlv),
|
|
|
|
SOC_SINGLE("AIF1DAC2 3D Stereo Switch", WM8994_AIF1_DAC2_FILTERS_2,
|
|
|
|
8, 1, 0),
|
2010-12-20 21:35:09 +07:00
|
|
|
SOC_SINGLE_TLV("AIF2DAC 3D Stereo Volume", WM8994_AIF2_DAC_FILTERS_2,
|
2010-01-30 00:47:12 +07:00
|
|
|
10, 15, 0, wm8994_3d_tlv),
|
2010-12-20 21:35:09 +07:00
|
|
|
SOC_SINGLE("AIF2DAC 3D Stereo Switch", WM8994_AIF2_DAC_FILTERS_2,
|
2010-01-30 00:47:12 +07:00
|
|
|
8, 1, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new wm8994_eq_controls[] = {
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC1 EQ1 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 11, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC1 EQ2 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 6, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC1 EQ3 Volume", WM8994_AIF1_DAC1_EQ_GAINS_1, 1, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC1 EQ4 Volume", WM8994_AIF1_DAC1_EQ_GAINS_2, 11, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC1 EQ5 Volume", WM8994_AIF1_DAC1_EQ_GAINS_2, 6, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC2 EQ1 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 11, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC2 EQ2 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 6, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC2 EQ3 Volume", WM8994_AIF1_DAC2_EQ_GAINS_1, 1, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC2 EQ4 Volume", WM8994_AIF1_DAC2_EQ_GAINS_2, 11, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC2 EQ5 Volume", WM8994_AIF1_DAC2_EQ_GAINS_2, 6, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
|
|
|
|
SOC_SINGLE_TLV("AIF2 EQ1 Volume", WM8994_AIF2_EQ_GAINS_1, 11, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF2 EQ2 Volume", WM8994_AIF2_EQ_GAINS_1, 6, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF2 EQ3 Volume", WM8994_AIF2_EQ_GAINS_1, 1, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF2 EQ4 Volume", WM8994_AIF2_EQ_GAINS_2, 11, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
SOC_SINGLE_TLV("AIF2 EQ5 Volume", WM8994_AIF2_EQ_GAINS_2, 6, 31, 0,
|
|
|
|
eq_tlv),
|
|
|
|
};
|
|
|
|
|
2011-08-16 08:08:48 +07:00
|
|
|
static const char *wm8958_ng_text[] = {
|
|
|
|
"30ms", "125ms", "250ms", "500ms",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct soc_enum wm8958_aif1dac1_ng_hold =
|
|
|
|
SOC_ENUM_SINGLE(WM8958_AIF1_DAC1_NOISE_GATE,
|
|
|
|
WM8958_AIF1DAC1_NG_THR_SHIFT, 4, wm8958_ng_text);
|
|
|
|
|
|
|
|
static const struct soc_enum wm8958_aif1dac2_ng_hold =
|
|
|
|
SOC_ENUM_SINGLE(WM8958_AIF1_DAC2_NOISE_GATE,
|
|
|
|
WM8958_AIF1DAC2_NG_THR_SHIFT, 4, wm8958_ng_text);
|
|
|
|
|
|
|
|
static const struct soc_enum wm8958_aif2dac_ng_hold =
|
|
|
|
SOC_ENUM_SINGLE(WM8958_AIF2_DAC_NOISE_GATE,
|
|
|
|
WM8958_AIF2DAC_NG_THR_SHIFT, 4, wm8958_ng_text);
|
|
|
|
|
2010-11-26 22:21:07 +07:00
|
|
|
static const struct snd_kcontrol_new wm8958_snd_controls[] = {
|
|
|
|
SOC_SINGLE_TLV("AIF3 Boost Volume", WM8958_AIF3_CONTROL_2, 10, 3, 0, aif_tlv),
|
2011-08-16 08:08:48 +07:00
|
|
|
|
|
|
|
SOC_SINGLE("AIF1DAC1 Noise Gate Switch", WM8958_AIF1_DAC1_NOISE_GATE,
|
|
|
|
WM8958_AIF1DAC1_NG_ENA_SHIFT, 1, 0),
|
|
|
|
SOC_ENUM("AIF1DAC1 Noise Gate Hold Time", wm8958_aif1dac1_ng_hold),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC1 Noise Gate Threshold Volume",
|
|
|
|
WM8958_AIF1_DAC1_NOISE_GATE, WM8958_AIF1DAC1_NG_THR_SHIFT,
|
|
|
|
7, 1, ng_tlv),
|
|
|
|
|
|
|
|
SOC_SINGLE("AIF1DAC2 Noise Gate Switch", WM8958_AIF1_DAC2_NOISE_GATE,
|
|
|
|
WM8958_AIF1DAC2_NG_ENA_SHIFT, 1, 0),
|
|
|
|
SOC_ENUM("AIF1DAC2 Noise Gate Hold Time", wm8958_aif1dac2_ng_hold),
|
|
|
|
SOC_SINGLE_TLV("AIF1DAC2 Noise Gate Threshold Volume",
|
|
|
|
WM8958_AIF1_DAC2_NOISE_GATE, WM8958_AIF1DAC2_NG_THR_SHIFT,
|
|
|
|
7, 1, ng_tlv),
|
|
|
|
|
|
|
|
SOC_SINGLE("AIF2DAC Noise Gate Switch", WM8958_AIF2_DAC_NOISE_GATE,
|
|
|
|
WM8958_AIF2DAC_NG_ENA_SHIFT, 1, 0),
|
|
|
|
SOC_ENUM("AIF2DAC Noise Gate Hold Time", wm8958_aif2dac_ng_hold),
|
|
|
|
SOC_SINGLE_TLV("AIF2DAC Noise Gate Threshold Volume",
|
|
|
|
WM8958_AIF2_DAC_NOISE_GATE, WM8958_AIF2DAC_NG_THR_SHIFT,
|
|
|
|
7, 1, ng_tlv),
|
2010-11-26 22:21:07 +07:00
|
|
|
};
|
|
|
|
|
2011-05-24 16:35:53 +07:00
|
|
|
static const struct snd_kcontrol_new wm1811_snd_controls[] = {
|
|
|
|
SOC_SINGLE_TLV("MIXINL IN1LP Boost Volume", WM8994_INPUT_MIXER_1, 7, 1, 0,
|
|
|
|
mixin_boost_tlv),
|
|
|
|
SOC_SINGLE_TLV("MIXINL IN1RP Boost Volume", WM8994_INPUT_MIXER_1, 8, 1, 0,
|
|
|
|
mixin_boost_tlv),
|
|
|
|
};
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
static int clk_sys_event(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case SND_SOC_DAPM_PRE_PMU:
|
|
|
|
return configure_clock(codec);
|
|
|
|
|
|
|
|
case SND_SOC_DAPM_POST_PMD:
|
|
|
|
configure_clock(codec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-08-10 15:47:33 +07:00
|
|
|
static void vmid_reference(struct snd_soc_codec *codec)
|
|
|
|
{
|
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
|
|
|
|
wm8994->vmid_refcount++;
|
|
|
|
|
|
|
|
dev_dbg(codec->dev, "Referencing VMID, refcount is now %d\n",
|
|
|
|
wm8994->vmid_refcount);
|
|
|
|
|
|
|
|
if (wm8994->vmid_refcount == 1) {
|
|
|
|
/* Startup bias, VMID ramp & buffer */
|
|
|
|
snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
|
|
|
|
WM8994_STARTUP_BIAS_ENA |
|
|
|
|
WM8994_VMID_BUF_ENA |
|
|
|
|
WM8994_VMID_RAMP_MASK,
|
|
|
|
WM8994_STARTUP_BIAS_ENA |
|
|
|
|
WM8994_VMID_BUF_ENA |
|
|
|
|
(0x11 << WM8994_VMID_RAMP_SHIFT));
|
|
|
|
|
|
|
|
/* Main bias enable, VMID=2x40k */
|
|
|
|
snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
|
|
|
|
WM8994_BIAS_ENA |
|
|
|
|
WM8994_VMID_SEL_MASK,
|
|
|
|
WM8994_BIAS_ENA | 0x2);
|
|
|
|
|
|
|
|
msleep(20);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vmid_dereference(struct snd_soc_codec *codec)
|
|
|
|
{
|
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
|
|
|
|
wm8994->vmid_refcount--;
|
|
|
|
|
|
|
|
dev_dbg(codec->dev, "Dereferencing VMID, refcount is now %d\n",
|
|
|
|
wm8994->vmid_refcount);
|
|
|
|
|
|
|
|
if (wm8994->vmid_refcount == 0) {
|
|
|
|
/* Switch over to startup biases */
|
|
|
|
snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
|
|
|
|
WM8994_BIAS_SRC |
|
|
|
|
WM8994_STARTUP_BIAS_ENA |
|
|
|
|
WM8994_VMID_BUF_ENA |
|
|
|
|
WM8994_VMID_RAMP_MASK,
|
|
|
|
WM8994_BIAS_SRC |
|
|
|
|
WM8994_STARTUP_BIAS_ENA |
|
|
|
|
WM8994_VMID_BUF_ENA |
|
|
|
|
(1 << WM8994_VMID_RAMP_SHIFT));
|
|
|
|
|
|
|
|
/* Disable main biases */
|
|
|
|
snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_1,
|
|
|
|
WM8994_BIAS_ENA |
|
|
|
|
WM8994_VMID_SEL_MASK, 0);
|
|
|
|
|
|
|
|
/* Discharge line */
|
|
|
|
snd_soc_update_bits(codec, WM8994_ANTIPOP_1,
|
|
|
|
WM8994_LINEOUT1_DISCH |
|
|
|
|
WM8994_LINEOUT2_DISCH,
|
|
|
|
WM8994_LINEOUT1_DISCH |
|
|
|
|
WM8994_LINEOUT2_DISCH);
|
|
|
|
|
|
|
|
msleep(5);
|
|
|
|
|
|
|
|
/* Switch off startup biases */
|
|
|
|
snd_soc_update_bits(codec, WM8994_ANTIPOP_2,
|
|
|
|
WM8994_BIAS_SRC |
|
|
|
|
WM8994_STARTUP_BIAS_ENA |
|
|
|
|
WM8994_VMID_BUF_ENA |
|
|
|
|
WM8994_VMID_RAMP_MASK, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vmid_event(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case SND_SOC_DAPM_PRE_PMU:
|
|
|
|
vmid_reference(codec);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_DAPM_POST_PMD:
|
|
|
|
vmid_dereference(codec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
static void wm8994_update_class_w(struct snd_soc_codec *codec)
|
|
|
|
{
|
2010-10-28 03:48:36 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-01-30 00:47:12 +07:00
|
|
|
int enable = 1;
|
|
|
|
int source = 0; /* GCC flow analysis can't track enable */
|
|
|
|
int reg, reg_r;
|
|
|
|
|
|
|
|
/* Only support direct DAC->headphone paths */
|
|
|
|
reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_1);
|
|
|
|
if (!(reg & WM8994_DAC1L_TO_HPOUT1L)) {
|
2010-04-20 11:57:08 +07:00
|
|
|
dev_vdbg(codec->dev, "HPL connected to output mixer\n");
|
2010-01-30 00:47:12 +07:00
|
|
|
enable = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_2);
|
|
|
|
if (!(reg & WM8994_DAC1R_TO_HPOUT1R)) {
|
2010-04-20 11:57:08 +07:00
|
|
|
dev_vdbg(codec->dev, "HPR connected to output mixer\n");
|
2010-01-30 00:47:12 +07:00
|
|
|
enable = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* We also need the same setting for L/R and only one path */
|
|
|
|
reg = snd_soc_read(codec, WM8994_DAC1_LEFT_MIXER_ROUTING);
|
|
|
|
switch (reg) {
|
|
|
|
case WM8994_AIF2DACL_TO_DAC1L:
|
2010-04-20 11:57:08 +07:00
|
|
|
dev_vdbg(codec->dev, "Class W source AIF2DAC\n");
|
2010-01-30 00:47:12 +07:00
|
|
|
source = 2 << WM8994_CP_DYN_SRC_SEL_SHIFT;
|
|
|
|
break;
|
|
|
|
case WM8994_AIF1DAC2L_TO_DAC1L:
|
2010-04-20 11:57:08 +07:00
|
|
|
dev_vdbg(codec->dev, "Class W source AIF1DAC2\n");
|
2010-01-30 00:47:12 +07:00
|
|
|
source = 1 << WM8994_CP_DYN_SRC_SEL_SHIFT;
|
|
|
|
break;
|
|
|
|
case WM8994_AIF1DAC1L_TO_DAC1L:
|
2010-04-20 11:57:08 +07:00
|
|
|
dev_vdbg(codec->dev, "Class W source AIF1DAC1\n");
|
2010-01-30 00:47:12 +07:00
|
|
|
source = 0 << WM8994_CP_DYN_SRC_SEL_SHIFT;
|
|
|
|
break;
|
|
|
|
default:
|
2010-04-20 11:57:08 +07:00
|
|
|
dev_vdbg(codec->dev, "DAC mixer setting: %x\n", reg);
|
2010-01-30 00:47:12 +07:00
|
|
|
enable = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
reg_r = snd_soc_read(codec, WM8994_DAC1_RIGHT_MIXER_ROUTING);
|
|
|
|
if (reg_r != reg) {
|
2010-04-20 11:57:08 +07:00
|
|
|
dev_vdbg(codec->dev, "Left and right DAC mixers different\n");
|
2010-01-30 00:47:12 +07:00
|
|
|
enable = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (enable) {
|
|
|
|
dev_dbg(codec->dev, "Class W enabled\n");
|
|
|
|
snd_soc_update_bits(codec, WM8994_CLASS_W_1,
|
|
|
|
WM8994_CP_DYN_PWR |
|
|
|
|
WM8994_CP_DYN_SRC_SEL_MASK,
|
|
|
|
source | WM8994_CP_DYN_PWR);
|
2010-10-28 03:48:36 +07:00
|
|
|
wm8994->hubs.class_w = true;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
} else {
|
|
|
|
dev_dbg(codec->dev, "Class W disabled\n");
|
|
|
|
snd_soc_update_bits(codec, WM8994_CLASS_W_1,
|
|
|
|
WM8994_CP_DYN_PWR, 0);
|
2010-10-28 03:48:36 +07:00
|
|
|
wm8994->hubs.class_w = false;
|
2010-01-30 00:47:12 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-02-11 23:32:11 +07:00
|
|
|
static int late_enable_ev(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case SND_SOC_DAPM_PRE_PMU:
|
2011-03-01 00:24:11 +07:00
|
|
|
if (wm8994->aif1clk_enable) {
|
2011-02-11 23:32:11 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
|
|
|
|
WM8994_AIF1CLK_ENA_MASK,
|
|
|
|
WM8994_AIF1CLK_ENA);
|
2011-03-01 00:24:11 +07:00
|
|
|
wm8994->aif1clk_enable = 0;
|
|
|
|
}
|
|
|
|
if (wm8994->aif2clk_enable) {
|
2011-02-11 23:32:11 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
|
|
|
|
WM8994_AIF2CLK_ENA_MASK,
|
|
|
|
WM8994_AIF2CLK_ENA);
|
2011-03-01 00:24:11 +07:00
|
|
|
wm8994->aif2clk_enable = 0;
|
|
|
|
}
|
2011-02-11 23:32:11 +07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-03-12 01:13:12 +07:00
|
|
|
/* We may also have postponed startup of DSP, handle that. */
|
|
|
|
wm8958_aif_ev(w, kcontrol, event);
|
|
|
|
|
2011-02-11 23:32:11 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int late_disable_ev(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case SND_SOC_DAPM_POST_PMD:
|
2011-03-01 00:24:11 +07:00
|
|
|
if (wm8994->aif1clk_disable) {
|
2011-02-11 23:32:11 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
|
|
|
|
WM8994_AIF1CLK_ENA_MASK, 0);
|
2011-03-01 00:24:11 +07:00
|
|
|
wm8994->aif1clk_disable = 0;
|
2011-02-11 23:32:11 +07:00
|
|
|
}
|
2011-03-01 00:24:11 +07:00
|
|
|
if (wm8994->aif2clk_disable) {
|
2011-02-11 23:32:11 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
|
|
|
|
WM8994_AIF2CLK_ENA_MASK, 0);
|
2011-03-01 00:24:11 +07:00
|
|
|
wm8994->aif2clk_disable = 0;
|
2011-02-11 23:32:11 +07:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int aif1clk_ev(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case SND_SOC_DAPM_PRE_PMU:
|
|
|
|
wm8994->aif1clk_enable = 1;
|
|
|
|
break;
|
2011-03-01 00:24:11 +07:00
|
|
|
case SND_SOC_DAPM_POST_PMD:
|
|
|
|
wm8994->aif1clk_disable = 1;
|
|
|
|
break;
|
2011-02-11 23:32:11 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int aif2clk_ev(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case SND_SOC_DAPM_PRE_PMU:
|
|
|
|
wm8994->aif2clk_enable = 1;
|
|
|
|
break;
|
2011-03-01 00:24:11 +07:00
|
|
|
case SND_SOC_DAPM_POST_PMD:
|
|
|
|
wm8994->aif2clk_disable = 1;
|
|
|
|
break;
|
2011-02-11 23:32:11 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-03-01 18:47:10 +07:00
|
|
|
static int adc_mux_ev(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
|
|
|
late_enable_ev(w, kcontrol, event);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-03-01 19:54:39 +07:00
|
|
|
static int micbias_ev(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
|
|
|
late_enable_ev(w, kcontrol, event);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-02-11 23:32:12 +07:00
|
|
|
static int dac_ev(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
|
unsigned int mask = 1 << w->shift;
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5,
|
|
|
|
mask, mask);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
static const char *hp_mux_text[] = {
|
|
|
|
"Mixer",
|
|
|
|
"DAC",
|
|
|
|
};
|
|
|
|
|
|
|
|
#define WM8994_HP_ENUM(xname, xenum) \
|
|
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
|
|
|
.info = snd_soc_info_enum_double, \
|
|
|
|
.get = snd_soc_dapm_get_enum_double, \
|
|
|
|
.put = wm8994_put_hp_enum, \
|
|
|
|
.private_value = (unsigned long)&xenum }
|
|
|
|
|
|
|
|
static int wm8994_put_hp_enum(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
2011-05-13 23:16:52 +07:00
|
|
|
struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct snd_soc_dapm_widget *w = wlist->widgets[0];
|
2010-01-30 00:47:12 +07:00
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol);
|
|
|
|
|
|
|
|
wm8994_update_class_w(codec);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct soc_enum hpl_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_1, 8, 2, hp_mux_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new hpl_mux =
|
|
|
|
WM8994_HP_ENUM("Left Headphone Mux", hpl_enum);
|
|
|
|
|
|
|
|
static const struct soc_enum hpr_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_2, 8, 2, hp_mux_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new hpr_mux =
|
|
|
|
WM8994_HP_ENUM("Right Headphone Mux", hpr_enum);
|
|
|
|
|
|
|
|
static const char *adc_mux_text[] = {
|
|
|
|
"ADC",
|
|
|
|
"DMIC",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct soc_enum adc_enum =
|
|
|
|
SOC_ENUM_SINGLE(0, 0, 2, adc_mux_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new adcl_mux =
|
|
|
|
SOC_DAPM_ENUM_VIRT("ADCL Mux", adc_enum);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new adcr_mux =
|
|
|
|
SOC_DAPM_ENUM_VIRT("ADCR Mux", adc_enum);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new left_speaker_mixer[] = {
|
|
|
|
SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 9, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 7, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("IN1LP Switch", WM8994_SPEAKER_MIXER, 5, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 3, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 1, 1, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new right_speaker_mixer[] = {
|
|
|
|
SOC_DAPM_SINGLE("DAC2 Switch", WM8994_SPEAKER_MIXER, 8, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("Input Switch", WM8994_SPEAKER_MIXER, 6, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("IN1RP Switch", WM8994_SPEAKER_MIXER, 4, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("Output Switch", WM8994_SPEAKER_MIXER, 2, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("DAC1 Switch", WM8994_SPEAKER_MIXER, 0, 1, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Debugging; dump chip status after DAPM transitions */
|
|
|
|
static int post_ev(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
|
dev_dbg(codec->dev, "SRC status: %x\n",
|
|
|
|
snd_soc_read(codec,
|
|
|
|
WM8994_RATE_STATUS));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new aif1adc1l_mix[] = {
|
|
|
|
SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING,
|
|
|
|
1, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_LEFT_MIXER_ROUTING,
|
|
|
|
0, 1, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new aif1adc1r_mix[] = {
|
|
|
|
SOC_DAPM_SINGLE("ADC/DMIC Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING,
|
|
|
|
1, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC1_RIGHT_MIXER_ROUTING,
|
|
|
|
0, 1, 0),
|
|
|
|
};
|
|
|
|
|
2010-07-19 20:02:34 +07:00
|
|
|
static const struct snd_kcontrol_new aif1adc2l_mix[] = {
|
|
|
|
SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING,
|
|
|
|
1, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_LEFT_MIXER_ROUTING,
|
|
|
|
0, 1, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new aif1adc2r_mix[] = {
|
|
|
|
SOC_DAPM_SINGLE("DMIC Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING,
|
|
|
|
1, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("AIF2 Switch", WM8994_AIF1_ADC2_RIGHT_MIXER_ROUTING,
|
|
|
|
0, 1, 0),
|
|
|
|
};
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
static const struct snd_kcontrol_new aif2dac2l_mix[] = {
|
|
|
|
SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
|
|
|
|
5, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("Left Sidetone Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
|
|
|
|
4, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("AIF2 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
|
|
|
|
2, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("AIF1.2 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
|
|
|
|
1, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("AIF1.1 Switch", WM8994_DAC2_LEFT_MIXER_ROUTING,
|
|
|
|
0, 1, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new aif2dac2r_mix[] = {
|
|
|
|
SOC_DAPM_SINGLE("Right Sidetone Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
|
|
|
|
5, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("Left Sidetone Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
|
|
|
|
4, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("AIF2 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
|
|
|
|
2, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("AIF1.2 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
|
|
|
|
1, 1, 0),
|
|
|
|
SOC_DAPM_SINGLE("AIF1.1 Switch", WM8994_DAC2_RIGHT_MIXER_ROUTING,
|
|
|
|
0, 1, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
#define WM8994_CLASS_W_SWITCH(xname, reg, shift, max, invert) \
|
|
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
|
|
|
.info = snd_soc_info_volsw, \
|
|
|
|
.get = snd_soc_dapm_get_volsw, .put = wm8994_put_class_w, \
|
|
|
|
.private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
|
|
|
|
|
|
|
|
static int wm8994_put_class_w(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
2011-05-13 23:16:52 +07:00
|
|
|
struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
|
|
|
|
struct snd_soc_dapm_widget *w = wlist->widgets[0];
|
2010-01-30 00:47:12 +07:00
|
|
|
struct snd_soc_codec *codec = w->codec;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol);
|
|
|
|
|
|
|
|
wm8994_update_class_w(codec);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new dac1l_mix[] = {
|
|
|
|
WM8994_CLASS_W_SWITCH("Right Sidetone Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
|
|
|
|
5, 1, 0),
|
|
|
|
WM8994_CLASS_W_SWITCH("Left Sidetone Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
|
|
|
|
4, 1, 0),
|
|
|
|
WM8994_CLASS_W_SWITCH("AIF2 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
|
|
|
|
2, 1, 0),
|
|
|
|
WM8994_CLASS_W_SWITCH("AIF1.2 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
|
|
|
|
1, 1, 0),
|
|
|
|
WM8994_CLASS_W_SWITCH("AIF1.1 Switch", WM8994_DAC1_LEFT_MIXER_ROUTING,
|
|
|
|
0, 1, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new dac1r_mix[] = {
|
|
|
|
WM8994_CLASS_W_SWITCH("Right Sidetone Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
|
|
|
|
5, 1, 0),
|
|
|
|
WM8994_CLASS_W_SWITCH("Left Sidetone Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
|
|
|
|
4, 1, 0),
|
|
|
|
WM8994_CLASS_W_SWITCH("AIF2 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
|
|
|
|
2, 1, 0),
|
|
|
|
WM8994_CLASS_W_SWITCH("AIF1.2 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
|
|
|
|
1, 1, 0),
|
|
|
|
WM8994_CLASS_W_SWITCH("AIF1.1 Switch", WM8994_DAC1_RIGHT_MIXER_ROUTING,
|
|
|
|
0, 1, 0),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *sidetone_text[] = {
|
|
|
|
"ADC/DMIC1", "DMIC2",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct soc_enum sidetone1_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_SIDETONE, 0, 2, sidetone_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new sidetone1_mux =
|
|
|
|
SOC_DAPM_ENUM("Left Sidetone Mux", sidetone1_enum);
|
|
|
|
|
|
|
|
static const struct soc_enum sidetone2_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_SIDETONE, 1, 2, sidetone_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new sidetone2_mux =
|
|
|
|
SOC_DAPM_ENUM("Right Sidetone Mux", sidetone2_enum);
|
|
|
|
|
|
|
|
static const char *aif1dac_text[] = {
|
|
|
|
"AIF1DACDAT", "AIF3DACDAT",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct soc_enum aif1dac_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 0, 2, aif1dac_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new aif1dac_mux =
|
|
|
|
SOC_DAPM_ENUM("AIF1DAC Mux", aif1dac_enum);
|
|
|
|
|
|
|
|
static const char *aif2dac_text[] = {
|
|
|
|
"AIF2DACDAT", "AIF3DACDAT",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct soc_enum aif2dac_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 1, 2, aif2dac_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new aif2dac_mux =
|
|
|
|
SOC_DAPM_ENUM("AIF2DAC Mux", aif2dac_enum);
|
|
|
|
|
|
|
|
static const char *aif2adc_text[] = {
|
|
|
|
"AIF2ADCDAT", "AIF3DACDAT",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct soc_enum aif2adc_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 2, 2, aif2adc_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new aif2adc_mux =
|
|
|
|
SOC_DAPM_ENUM("AIF2ADC Mux", aif2adc_enum);
|
|
|
|
|
|
|
|
static const char *aif3adc_text[] = {
|
2010-11-26 22:21:07 +07:00
|
|
|
"AIF1ADCDAT", "AIF2ADCDAT", "AIF2DACDAT", "Mono PCM",
|
2010-01-30 00:47:12 +07:00
|
|
|
};
|
|
|
|
|
2010-11-26 22:21:07 +07:00
|
|
|
static const struct soc_enum wm8994_aif3adc_enum =
|
2010-01-30 00:47:12 +07:00
|
|
|
SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 3, 3, aif3adc_text);
|
|
|
|
|
2010-11-26 22:21:07 +07:00
|
|
|
static const struct snd_kcontrol_new wm8994_aif3adc_mux =
|
|
|
|
SOC_DAPM_ENUM("AIF3ADC Mux", wm8994_aif3adc_enum);
|
|
|
|
|
|
|
|
static const struct soc_enum wm8958_aif3adc_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 3, 4, aif3adc_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new wm8958_aif3adc_mux =
|
|
|
|
SOC_DAPM_ENUM("AIF3ADC Mux", wm8958_aif3adc_enum);
|
|
|
|
|
|
|
|
static const char *mono_pcm_out_text[] = {
|
|
|
|
"None", "AIF2ADCL", "AIF2ADCR",
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct soc_enum mono_pcm_out_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 9, 3, mono_pcm_out_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new mono_pcm_out_mux =
|
|
|
|
SOC_DAPM_ENUM("Mono PCM Out Mux", mono_pcm_out_enum);
|
|
|
|
|
|
|
|
static const char *aif2dac_src_text[] = {
|
|
|
|
"AIF2", "AIF3",
|
|
|
|
};
|
|
|
|
|
|
|
|
/* Note that these two control shouldn't be simultaneously switched to AIF3 */
|
|
|
|
static const struct soc_enum aif2dacl_src_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 7, 2, aif2dac_src_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new aif2dacl_src_mux =
|
|
|
|
SOC_DAPM_ENUM("AIF2DACL Mux", aif2dacl_src_enum);
|
|
|
|
|
|
|
|
static const struct soc_enum aif2dacr_src_enum =
|
|
|
|
SOC_ENUM_SINGLE(WM8994_POWER_MANAGEMENT_6, 8, 2, aif2dac_src_text);
|
|
|
|
|
|
|
|
static const struct snd_kcontrol_new aif2dacr_src_mux =
|
|
|
|
SOC_DAPM_ENUM("AIF2DACR Mux", aif2dacr_src_enum);
|
2010-01-30 00:47:12 +07:00
|
|
|
|
2011-02-11 23:32:11 +07:00
|
|
|
static const struct snd_soc_dapm_widget wm8994_lateclk_revd_widgets[] = {
|
|
|
|
SND_SOC_DAPM_SUPPLY("AIF1CLK", SND_SOC_NOPM, 0, 0, aif1clk_ev,
|
|
|
|
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
|
|
|
SND_SOC_DAPM_SUPPLY("AIF2CLK", SND_SOC_NOPM, 0, 0, aif2clk_ev,
|
|
|
|
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
|
|
|
|
|
|
|
SND_SOC_DAPM_PGA_E("Late DAC1L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
|
|
|
|
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
SND_SOC_DAPM_PGA_E("Late DAC1R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
|
|
|
|
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
SND_SOC_DAPM_PGA_E("Late DAC2L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
|
|
|
|
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
SND_SOC_DAPM_PGA_E("Late DAC2R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0,
|
|
|
|
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
2011-06-29 14:21:09 +07:00
|
|
|
SND_SOC_DAPM_PGA_E("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0,
|
|
|
|
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
|
|
|
|
SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
|
|
|
|
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer),
|
|
|
|
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
SND_SOC_DAPM_MIXER_E("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
|
|
|
|
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer),
|
|
|
|
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux,
|
|
|
|
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux,
|
|
|
|
late_enable_ev, SND_SOC_DAPM_PRE_PMU),
|
2011-02-11 23:32:11 +07:00
|
|
|
|
|
|
|
SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev)
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_soc_dapm_widget wm8994_lateclk_widgets[] = {
|
|
|
|
SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, NULL, 0),
|
2011-06-29 14:21:09 +07:00
|
|
|
SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0),
|
|
|
|
SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0),
|
|
|
|
SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0,
|
|
|
|
left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)),
|
|
|
|
SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0,
|
|
|
|
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
|
|
|
|
SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux),
|
|
|
|
SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux),
|
2011-02-11 23:32:11 +07:00
|
|
|
};
|
|
|
|
|
2011-02-11 23:32:12 +07:00
|
|
|
static const struct snd_soc_dapm_widget wm8994_dac_revd_widgets[] = {
|
|
|
|
SND_SOC_DAPM_DAC_E("DAC2L", NULL, SND_SOC_NOPM, 3, 0,
|
|
|
|
dac_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
SND_SOC_DAPM_DAC_E("DAC2R", NULL, SND_SOC_NOPM, 2, 0,
|
|
|
|
dac_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
SND_SOC_DAPM_DAC_E("DAC1L", NULL, SND_SOC_NOPM, 1, 0,
|
|
|
|
dac_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
SND_SOC_DAPM_DAC_E("DAC1R", NULL, SND_SOC_NOPM, 0, 0,
|
|
|
|
dac_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_soc_dapm_widget wm8994_dac_widgets[] = {
|
|
|
|
SND_SOC_DAPM_DAC("DAC2L", NULL, WM8994_POWER_MANAGEMENT_5, 3, 0),
|
2011-03-10 02:09:17 +07:00
|
|
|
SND_SOC_DAPM_DAC("DAC2R", NULL, WM8994_POWER_MANAGEMENT_5, 2, 0),
|
2011-02-11 23:32:12 +07:00
|
|
|
SND_SOC_DAPM_DAC("DAC1L", NULL, WM8994_POWER_MANAGEMENT_5, 1, 0),
|
|
|
|
SND_SOC_DAPM_DAC("DAC1R", NULL, WM8994_POWER_MANAGEMENT_5, 0, 0),
|
|
|
|
};
|
|
|
|
|
2011-03-01 18:47:10 +07:00
|
|
|
static const struct snd_soc_dapm_widget wm8994_adc_revd_widgets[] = {
|
|
|
|
SND_SOC_DAPM_MUX_E("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux,
|
|
|
|
adc_mux_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
SND_SOC_DAPM_MUX_E("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux,
|
|
|
|
adc_mux_ev, SND_SOC_DAPM_PRE_PMU),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_soc_dapm_widget wm8994_adc_widgets[] = {
|
|
|
|
SND_SOC_DAPM_MUX("ADCL Mux", WM8994_POWER_MANAGEMENT_4, 1, 0, &adcl_mux),
|
|
|
|
SND_SOC_DAPM_MUX("ADCR Mux", WM8994_POWER_MANAGEMENT_4, 0, 0, &adcr_mux),
|
|
|
|
};
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
static const struct snd_soc_dapm_widget wm8994_dapm_widgets[] = {
|
|
|
|
SND_SOC_DAPM_INPUT("DMIC1DAT"),
|
|
|
|
SND_SOC_DAPM_INPUT("DMIC2DAT"),
|
2010-07-08 09:25:43 +07:00
|
|
|
SND_SOC_DAPM_INPUT("Clock"),
|
2010-01-30 00:47:12 +07:00
|
|
|
|
2011-03-01 19:54:39 +07:00
|
|
|
SND_SOC_DAPM_SUPPLY_S("MICBIAS Supply", 1, SND_SOC_NOPM, 0, 0, micbias_ev,
|
|
|
|
SND_SOC_DAPM_PRE_PMU),
|
2011-08-10 15:47:33 +07:00
|
|
|
SND_SOC_DAPM_SUPPLY("VMID", SND_SOC_NOPM, 0, 0, vmid_event,
|
|
|
|
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
2011-03-01 19:54:39 +07:00
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
SND_SOC_DAPM_SUPPLY("CLK_SYS", SND_SOC_NOPM, 0, 0, clk_sys_event,
|
|
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
|
|
|
|
|
|
|
SND_SOC_DAPM_SUPPLY("DSP1CLK", WM8994_CLOCKING_1, 3, 0, NULL, 0),
|
|
|
|
SND_SOC_DAPM_SUPPLY("DSP2CLK", WM8994_CLOCKING_1, 2, 0, NULL, 0),
|
|
|
|
SND_SOC_DAPM_SUPPLY("DSPINTCLK", WM8994_CLOCKING_1, 1, 0, NULL, 0),
|
|
|
|
|
2011-02-03 23:27:34 +07:00
|
|
|
SND_SOC_DAPM_AIF_OUT("AIF1ADC1L", NULL,
|
2010-01-30 00:47:12 +07:00
|
|
|
0, WM8994_POWER_MANAGEMENT_4, 9, 0),
|
2011-02-03 23:27:34 +07:00
|
|
|
SND_SOC_DAPM_AIF_OUT("AIF1ADC1R", NULL,
|
2010-01-30 00:47:12 +07:00
|
|
|
0, WM8994_POWER_MANAGEMENT_4, 8, 0),
|
2010-11-26 22:21:08 +07:00
|
|
|
SND_SOC_DAPM_AIF_IN_E("AIF1DAC1L", NULL, 0,
|
|
|
|
WM8994_POWER_MANAGEMENT_5, 9, 0, wm8958_aif_ev,
|
2010-11-30 23:59:29 +07:00
|
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
|
2010-11-26 22:21:08 +07:00
|
|
|
SND_SOC_DAPM_AIF_IN_E("AIF1DAC1R", NULL, 0,
|
|
|
|
WM8994_POWER_MANAGEMENT_5, 8, 0, wm8958_aif_ev,
|
2010-11-30 23:59:29 +07:00
|
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
|
2010-01-30 00:47:12 +07:00
|
|
|
|
2011-02-03 23:27:34 +07:00
|
|
|
SND_SOC_DAPM_AIF_OUT("AIF1ADC2L", NULL,
|
2010-01-30 00:47:12 +07:00
|
|
|
0, WM8994_POWER_MANAGEMENT_4, 11, 0),
|
2011-02-03 23:27:34 +07:00
|
|
|
SND_SOC_DAPM_AIF_OUT("AIF1ADC2R", NULL,
|
2010-01-30 00:47:12 +07:00
|
|
|
0, WM8994_POWER_MANAGEMENT_4, 10, 0),
|
2010-11-26 22:21:08 +07:00
|
|
|
SND_SOC_DAPM_AIF_IN_E("AIF1DAC2L", NULL, 0,
|
|
|
|
WM8994_POWER_MANAGEMENT_5, 11, 0, wm8958_aif_ev,
|
2010-11-30 23:59:29 +07:00
|
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
|
2010-11-26 22:21:08 +07:00
|
|
|
SND_SOC_DAPM_AIF_IN_E("AIF1DAC2R", NULL, 0,
|
|
|
|
WM8994_POWER_MANAGEMENT_5, 10, 0, wm8958_aif_ev,
|
2010-11-30 23:59:29 +07:00
|
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
SND_SOC_DAPM_MIXER("AIF1ADC1L Mixer", SND_SOC_NOPM, 0, 0,
|
|
|
|
aif1adc1l_mix, ARRAY_SIZE(aif1adc1l_mix)),
|
|
|
|
SND_SOC_DAPM_MIXER("AIF1ADC1R Mixer", SND_SOC_NOPM, 0, 0,
|
|
|
|
aif1adc1r_mix, ARRAY_SIZE(aif1adc1r_mix)),
|
|
|
|
|
2010-07-19 20:02:34 +07:00
|
|
|
SND_SOC_DAPM_MIXER("AIF1ADC2L Mixer", SND_SOC_NOPM, 0, 0,
|
|
|
|
aif1adc2l_mix, ARRAY_SIZE(aif1adc2l_mix)),
|
|
|
|
SND_SOC_DAPM_MIXER("AIF1ADC2R Mixer", SND_SOC_NOPM, 0, 0,
|
|
|
|
aif1adc2r_mix, ARRAY_SIZE(aif1adc2r_mix)),
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
SND_SOC_DAPM_MIXER("AIF2DAC2L Mixer", SND_SOC_NOPM, 0, 0,
|
|
|
|
aif2dac2l_mix, ARRAY_SIZE(aif2dac2l_mix)),
|
|
|
|
SND_SOC_DAPM_MIXER("AIF2DAC2R Mixer", SND_SOC_NOPM, 0, 0,
|
|
|
|
aif2dac2r_mix, ARRAY_SIZE(aif2dac2r_mix)),
|
|
|
|
|
|
|
|
SND_SOC_DAPM_MUX("Left Sidetone", SND_SOC_NOPM, 0, 0, &sidetone1_mux),
|
|
|
|
SND_SOC_DAPM_MUX("Right Sidetone", SND_SOC_NOPM, 0, 0, &sidetone2_mux),
|
|
|
|
|
|
|
|
SND_SOC_DAPM_MIXER("DAC1L Mixer", SND_SOC_NOPM, 0, 0,
|
|
|
|
dac1l_mix, ARRAY_SIZE(dac1l_mix)),
|
|
|
|
SND_SOC_DAPM_MIXER("DAC1R Mixer", SND_SOC_NOPM, 0, 0,
|
|
|
|
dac1r_mix, ARRAY_SIZE(dac1r_mix)),
|
|
|
|
|
|
|
|
SND_SOC_DAPM_AIF_OUT("AIF2ADCL", NULL, 0,
|
|
|
|
WM8994_POWER_MANAGEMENT_4, 13, 0),
|
|
|
|
SND_SOC_DAPM_AIF_OUT("AIF2ADCR", NULL, 0,
|
|
|
|
WM8994_POWER_MANAGEMENT_4, 12, 0),
|
2010-11-26 22:21:08 +07:00
|
|
|
SND_SOC_DAPM_AIF_IN_E("AIF2DACL", NULL, 0,
|
|
|
|
WM8994_POWER_MANAGEMENT_5, 13, 0, wm8958_aif_ev,
|
|
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
|
|
|
SND_SOC_DAPM_AIF_IN_E("AIF2DACR", NULL, 0,
|
|
|
|
WM8994_POWER_MANAGEMENT_5, 12, 0, wm8958_aif_ev,
|
|
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
SND_SOC_DAPM_AIF_IN("AIF1DACDAT", "AIF1 Playback", 0, SND_SOC_NOPM, 0, 0),
|
|
|
|
SND_SOC_DAPM_AIF_IN("AIF2DACDAT", "AIF2 Playback", 0, SND_SOC_NOPM, 0, 0),
|
2011-02-03 23:27:34 +07:00
|
|
|
SND_SOC_DAPM_AIF_OUT("AIF1ADCDAT", "AIF1 Capture", 0, SND_SOC_NOPM, 0, 0),
|
2010-01-30 00:47:12 +07:00
|
|
|
SND_SOC_DAPM_AIF_OUT("AIF2ADCDAT", "AIF2 Capture", 0, SND_SOC_NOPM, 0, 0),
|
|
|
|
|
|
|
|
SND_SOC_DAPM_MUX("AIF1DAC Mux", SND_SOC_NOPM, 0, 0, &aif1dac_mux),
|
|
|
|
SND_SOC_DAPM_MUX("AIF2DAC Mux", SND_SOC_NOPM, 0, 0, &aif2dac_mux),
|
|
|
|
SND_SOC_DAPM_MUX("AIF2ADC Mux", SND_SOC_NOPM, 0, 0, &aif2adc_mux),
|
|
|
|
|
|
|
|
SND_SOC_DAPM_AIF_IN("AIF3DACDAT", "AIF3 Playback", 0, SND_SOC_NOPM, 0, 0),
|
2011-10-20 11:13:24 +07:00
|
|
|
SND_SOC_DAPM_AIF_OUT("AIF3ADCDAT", "AIF3 Capture", 0, SND_SOC_NOPM, 0, 0),
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
SND_SOC_DAPM_SUPPLY("TOCLK", WM8994_CLOCKING_1, 4, 0, NULL, 0),
|
|
|
|
|
|
|
|
SND_SOC_DAPM_ADC("DMIC2L", NULL, WM8994_POWER_MANAGEMENT_4, 5, 0),
|
|
|
|
SND_SOC_DAPM_ADC("DMIC2R", NULL, WM8994_POWER_MANAGEMENT_4, 4, 0),
|
|
|
|
SND_SOC_DAPM_ADC("DMIC1L", NULL, WM8994_POWER_MANAGEMENT_4, 3, 0),
|
|
|
|
SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0),
|
|
|
|
|
|
|
|
/* Power is done with the muxes since the ADC power also controls the
|
|
|
|
* downsampling chain, the chip will automatically manage the analogue
|
|
|
|
* specific portions.
|
|
|
|
*/
|
|
|
|
SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0),
|
|
|
|
SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0),
|
|
|
|
|
|
|
|
SND_SOC_DAPM_POST("Debug log", post_ev),
|
|
|
|
};
|
|
|
|
|
2010-11-26 22:21:07 +07:00
|
|
|
static const struct snd_soc_dapm_widget wm8994_specific_dapm_widgets[] = {
|
|
|
|
SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8994_aif3adc_mux),
|
|
|
|
};
|
2010-01-30 00:47:12 +07:00
|
|
|
|
2010-11-26 22:21:07 +07:00
|
|
|
static const struct snd_soc_dapm_widget wm8958_dapm_widgets[] = {
|
|
|
|
SND_SOC_DAPM_MUX("Mono PCM Out Mux", SND_SOC_NOPM, 0, 0, &mono_pcm_out_mux),
|
|
|
|
SND_SOC_DAPM_MUX("AIF2DACL Mux", SND_SOC_NOPM, 0, 0, &aif2dacl_src_mux),
|
|
|
|
SND_SOC_DAPM_MUX("AIF2DACR Mux", SND_SOC_NOPM, 0, 0, &aif2dacr_src_mux),
|
|
|
|
SND_SOC_DAPM_MUX("AIF3ADC Mux", SND_SOC_NOPM, 0, 0, &wm8958_aif3adc_mux),
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_soc_dapm_route intercon[] = {
|
2010-01-30 00:47:12 +07:00
|
|
|
{ "CLK_SYS", NULL, "AIF1CLK", check_clk_sys },
|
|
|
|
{ "CLK_SYS", NULL, "AIF2CLK", check_clk_sys },
|
|
|
|
|
|
|
|
{ "DSP1CLK", NULL, "CLK_SYS" },
|
|
|
|
{ "DSP2CLK", NULL, "CLK_SYS" },
|
|
|
|
{ "DSPINTCLK", NULL, "CLK_SYS" },
|
|
|
|
|
|
|
|
{ "AIF1ADC1L", NULL, "AIF1CLK" },
|
|
|
|
{ "AIF1ADC1L", NULL, "DSP1CLK" },
|
|
|
|
{ "AIF1ADC1R", NULL, "AIF1CLK" },
|
|
|
|
{ "AIF1ADC1R", NULL, "DSP1CLK" },
|
|
|
|
{ "AIF1ADC1R", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "AIF1DAC1L", NULL, "AIF1CLK" },
|
|
|
|
{ "AIF1DAC1L", NULL, "DSP1CLK" },
|
|
|
|
{ "AIF1DAC1R", NULL, "AIF1CLK" },
|
|
|
|
{ "AIF1DAC1R", NULL, "DSP1CLK" },
|
|
|
|
{ "AIF1DAC1R", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "AIF1ADC2L", NULL, "AIF1CLK" },
|
|
|
|
{ "AIF1ADC2L", NULL, "DSP1CLK" },
|
|
|
|
{ "AIF1ADC2R", NULL, "AIF1CLK" },
|
|
|
|
{ "AIF1ADC2R", NULL, "DSP1CLK" },
|
|
|
|
{ "AIF1ADC2R", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "AIF1DAC2L", NULL, "AIF1CLK" },
|
|
|
|
{ "AIF1DAC2L", NULL, "DSP1CLK" },
|
|
|
|
{ "AIF1DAC2R", NULL, "AIF1CLK" },
|
|
|
|
{ "AIF1DAC2R", NULL, "DSP1CLK" },
|
|
|
|
{ "AIF1DAC2R", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "AIF2ADCL", NULL, "AIF2CLK" },
|
|
|
|
{ "AIF2ADCL", NULL, "DSP2CLK" },
|
|
|
|
{ "AIF2ADCR", NULL, "AIF2CLK" },
|
|
|
|
{ "AIF2ADCR", NULL, "DSP2CLK" },
|
|
|
|
{ "AIF2ADCR", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "AIF2DACL", NULL, "AIF2CLK" },
|
|
|
|
{ "AIF2DACL", NULL, "DSP2CLK" },
|
|
|
|
{ "AIF2DACR", NULL, "AIF2CLK" },
|
|
|
|
{ "AIF2DACR", NULL, "DSP2CLK" },
|
|
|
|
{ "AIF2DACR", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "DMIC1L", NULL, "DMIC1DAT" },
|
|
|
|
{ "DMIC1L", NULL, "CLK_SYS" },
|
|
|
|
{ "DMIC1R", NULL, "DMIC1DAT" },
|
|
|
|
{ "DMIC1R", NULL, "CLK_SYS" },
|
|
|
|
{ "DMIC2L", NULL, "DMIC2DAT" },
|
|
|
|
{ "DMIC2L", NULL, "CLK_SYS" },
|
|
|
|
{ "DMIC2R", NULL, "DMIC2DAT" },
|
|
|
|
{ "DMIC2R", NULL, "CLK_SYS" },
|
|
|
|
|
|
|
|
{ "ADCL", NULL, "AIF1CLK" },
|
|
|
|
{ "ADCL", NULL, "DSP1CLK" },
|
|
|
|
{ "ADCL", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "ADCR", NULL, "AIF1CLK" },
|
|
|
|
{ "ADCR", NULL, "DSP1CLK" },
|
|
|
|
{ "ADCR", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "ADCL Mux", "ADC", "ADCL" },
|
|
|
|
{ "ADCL Mux", "DMIC", "DMIC1L" },
|
|
|
|
{ "ADCR Mux", "ADC", "ADCR" },
|
|
|
|
{ "ADCR Mux", "DMIC", "DMIC1R" },
|
|
|
|
|
|
|
|
{ "DAC1L", NULL, "AIF1CLK" },
|
|
|
|
{ "DAC1L", NULL, "DSP1CLK" },
|
|
|
|
{ "DAC1L", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "DAC1R", NULL, "AIF1CLK" },
|
|
|
|
{ "DAC1R", NULL, "DSP1CLK" },
|
|
|
|
{ "DAC1R", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "DAC2L", NULL, "AIF2CLK" },
|
|
|
|
{ "DAC2L", NULL, "DSP2CLK" },
|
|
|
|
{ "DAC2L", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "DAC2R", NULL, "AIF2DACR" },
|
|
|
|
{ "DAC2R", NULL, "AIF2CLK" },
|
|
|
|
{ "DAC2R", NULL, "DSP2CLK" },
|
|
|
|
{ "DAC2R", NULL, "DSPINTCLK" },
|
|
|
|
|
|
|
|
{ "TOCLK", NULL, "CLK_SYS" },
|
|
|
|
|
|
|
|
/* AIF1 outputs */
|
|
|
|
{ "AIF1ADC1L", NULL, "AIF1ADC1L Mixer" },
|
|
|
|
{ "AIF1ADC1L Mixer", "ADC/DMIC Switch", "ADCL Mux" },
|
|
|
|
{ "AIF1ADC1L Mixer", "AIF2 Switch", "AIF2DACL" },
|
|
|
|
|
|
|
|
{ "AIF1ADC1R", NULL, "AIF1ADC1R Mixer" },
|
|
|
|
{ "AIF1ADC1R Mixer", "ADC/DMIC Switch", "ADCR Mux" },
|
|
|
|
{ "AIF1ADC1R Mixer", "AIF2 Switch", "AIF2DACR" },
|
|
|
|
|
2010-07-19 20:02:34 +07:00
|
|
|
{ "AIF1ADC2L", NULL, "AIF1ADC2L Mixer" },
|
|
|
|
{ "AIF1ADC2L Mixer", "DMIC Switch", "DMIC2L" },
|
|
|
|
{ "AIF1ADC2L Mixer", "AIF2 Switch", "AIF2DACL" },
|
|
|
|
|
|
|
|
{ "AIF1ADC2R", NULL, "AIF1ADC2R Mixer" },
|
|
|
|
{ "AIF1ADC2R Mixer", "DMIC Switch", "DMIC2R" },
|
|
|
|
{ "AIF1ADC2R Mixer", "AIF2 Switch", "AIF2DACR" },
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
/* Pin level routing for AIF3 */
|
|
|
|
{ "AIF1DAC1L", NULL, "AIF1DAC Mux" },
|
|
|
|
{ "AIF1DAC1R", NULL, "AIF1DAC Mux" },
|
|
|
|
{ "AIF1DAC2L", NULL, "AIF1DAC Mux" },
|
|
|
|
{ "AIF1DAC2R", NULL, "AIF1DAC Mux" },
|
|
|
|
|
|
|
|
{ "AIF1DAC Mux", "AIF1DACDAT", "AIF1DACDAT" },
|
|
|
|
{ "AIF1DAC Mux", "AIF3DACDAT", "AIF3DACDAT" },
|
|
|
|
{ "AIF2DAC Mux", "AIF2DACDAT", "AIF2DACDAT" },
|
|
|
|
{ "AIF2DAC Mux", "AIF3DACDAT", "AIF3DACDAT" },
|
|
|
|
{ "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCL" },
|
|
|
|
{ "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCR" },
|
|
|
|
{ "AIF2ADC Mux", "AIF3DACDAT", "AIF3ADCDAT" },
|
|
|
|
|
|
|
|
/* DAC1 inputs */
|
|
|
|
{ "DAC1L Mixer", "AIF2 Switch", "AIF2DACL" },
|
|
|
|
{ "DAC1L Mixer", "AIF1.2 Switch", "AIF1DAC2L" },
|
|
|
|
{ "DAC1L Mixer", "AIF1.1 Switch", "AIF1DAC1L" },
|
|
|
|
{ "DAC1L Mixer", "Left Sidetone Switch", "Left Sidetone" },
|
|
|
|
{ "DAC1L Mixer", "Right Sidetone Switch", "Right Sidetone" },
|
|
|
|
|
|
|
|
{ "DAC1R Mixer", "AIF2 Switch", "AIF2DACR" },
|
|
|
|
{ "DAC1R Mixer", "AIF1.2 Switch", "AIF1DAC2R" },
|
|
|
|
{ "DAC1R Mixer", "AIF1.1 Switch", "AIF1DAC1R" },
|
|
|
|
{ "DAC1R Mixer", "Left Sidetone Switch", "Left Sidetone" },
|
|
|
|
{ "DAC1R Mixer", "Right Sidetone Switch", "Right Sidetone" },
|
|
|
|
|
|
|
|
/* DAC2/AIF2 outputs */
|
|
|
|
{ "AIF2ADCL", NULL, "AIF2DAC2L Mixer" },
|
|
|
|
{ "AIF2DAC2L Mixer", "AIF2 Switch", "AIF2DACL" },
|
|
|
|
{ "AIF2DAC2L Mixer", "AIF1.2 Switch", "AIF1DAC2L" },
|
|
|
|
{ "AIF2DAC2L Mixer", "AIF1.1 Switch", "AIF1DAC1L" },
|
|
|
|
{ "AIF2DAC2L Mixer", "Left Sidetone Switch", "Left Sidetone" },
|
|
|
|
{ "AIF2DAC2L Mixer", "Right Sidetone Switch", "Right Sidetone" },
|
|
|
|
|
|
|
|
{ "AIF2ADCR", NULL, "AIF2DAC2R Mixer" },
|
|
|
|
{ "AIF2DAC2R Mixer", "AIF2 Switch", "AIF2DACR" },
|
|
|
|
{ "AIF2DAC2R Mixer", "AIF1.2 Switch", "AIF1DAC2R" },
|
|
|
|
{ "AIF2DAC2R Mixer", "AIF1.1 Switch", "AIF1DAC1R" },
|
|
|
|
{ "AIF2DAC2R Mixer", "Left Sidetone Switch", "Left Sidetone" },
|
|
|
|
{ "AIF2DAC2R Mixer", "Right Sidetone Switch", "Right Sidetone" },
|
|
|
|
|
2011-02-03 23:27:34 +07:00
|
|
|
{ "AIF1ADCDAT", NULL, "AIF1ADC1L" },
|
|
|
|
{ "AIF1ADCDAT", NULL, "AIF1ADC1R" },
|
|
|
|
{ "AIF1ADCDAT", NULL, "AIF1ADC2L" },
|
|
|
|
{ "AIF1ADCDAT", NULL, "AIF1ADC2R" },
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
{ "AIF2ADCDAT", NULL, "AIF2ADC Mux" },
|
|
|
|
|
|
|
|
/* AIF3 output */
|
|
|
|
{ "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC1L" },
|
|
|
|
{ "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC1R" },
|
|
|
|
{ "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC2L" },
|
|
|
|
{ "AIF3ADCDAT", "AIF1ADCDAT", "AIF1ADC2R" },
|
|
|
|
{ "AIF3ADCDAT", "AIF2ADCDAT", "AIF2ADCL" },
|
|
|
|
{ "AIF3ADCDAT", "AIF2ADCDAT", "AIF2ADCR" },
|
|
|
|
{ "AIF3ADCDAT", "AIF2DACDAT", "AIF2DACL" },
|
|
|
|
{ "AIF3ADCDAT", "AIF2DACDAT", "AIF2DACR" },
|
|
|
|
|
|
|
|
/* Sidetone */
|
|
|
|
{ "Left Sidetone", "ADC/DMIC1", "ADCL Mux" },
|
|
|
|
{ "Left Sidetone", "DMIC2", "DMIC2L" },
|
|
|
|
{ "Right Sidetone", "ADC/DMIC1", "ADCR Mux" },
|
|
|
|
{ "Right Sidetone", "DMIC2", "DMIC2R" },
|
|
|
|
|
|
|
|
/* Output stages */
|
|
|
|
{ "Left Output Mixer", "DAC Switch", "DAC1L" },
|
|
|
|
{ "Right Output Mixer", "DAC Switch", "DAC1R" },
|
|
|
|
|
|
|
|
{ "SPKL", "DAC1 Switch", "DAC1L" },
|
|
|
|
{ "SPKL", "DAC2 Switch", "DAC2L" },
|
|
|
|
|
|
|
|
{ "SPKR", "DAC1 Switch", "DAC1R" },
|
|
|
|
{ "SPKR", "DAC2 Switch", "DAC2R" },
|
|
|
|
|
|
|
|
{ "Left Headphone Mux", "DAC", "DAC1L" },
|
|
|
|
{ "Right Headphone Mux", "DAC", "DAC1R" },
|
|
|
|
};
|
|
|
|
|
2011-02-11 23:32:11 +07:00
|
|
|
static const struct snd_soc_dapm_route wm8994_lateclk_revd_intercon[] = {
|
|
|
|
{ "DAC1L", NULL, "Late DAC1L Enable PGA" },
|
|
|
|
{ "Late DAC1L Enable PGA", NULL, "DAC1L Mixer" },
|
|
|
|
{ "DAC1R", NULL, "Late DAC1R Enable PGA" },
|
|
|
|
{ "Late DAC1R Enable PGA", NULL, "DAC1R Mixer" },
|
|
|
|
{ "DAC2L", NULL, "Late DAC2L Enable PGA" },
|
|
|
|
{ "Late DAC2L Enable PGA", NULL, "AIF2DAC2L Mixer" },
|
|
|
|
{ "DAC2R", NULL, "Late DAC2R Enable PGA" },
|
|
|
|
{ "Late DAC2R Enable PGA", NULL, "AIF2DAC2R Mixer" }
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_soc_dapm_route wm8994_lateclk_intercon[] = {
|
|
|
|
{ "DAC1L", NULL, "DAC1L Mixer" },
|
|
|
|
{ "DAC1R", NULL, "DAC1R Mixer" },
|
|
|
|
{ "DAC2L", NULL, "AIF2DAC2L Mixer" },
|
|
|
|
{ "DAC2R", NULL, "AIF2DAC2R Mixer" },
|
|
|
|
};
|
|
|
|
|
2011-02-03 23:27:35 +07:00
|
|
|
static const struct snd_soc_dapm_route wm8994_revd_intercon[] = {
|
|
|
|
{ "AIF1DACDAT", NULL, "AIF2DACDAT" },
|
|
|
|
{ "AIF2DACDAT", NULL, "AIF1DACDAT" },
|
|
|
|
{ "AIF1ADCDAT", NULL, "AIF2ADCDAT" },
|
|
|
|
{ "AIF2ADCDAT", NULL, "AIF1ADCDAT" },
|
2011-07-14 16:21:37 +07:00
|
|
|
{ "MICBIAS1", NULL, "CLK_SYS" },
|
|
|
|
{ "MICBIAS1", NULL, "MICBIAS Supply" },
|
|
|
|
{ "MICBIAS2", NULL, "CLK_SYS" },
|
|
|
|
{ "MICBIAS2", NULL, "MICBIAS Supply" },
|
2011-02-03 23:27:35 +07:00
|
|
|
};
|
|
|
|
|
2010-11-26 22:21:07 +07:00
|
|
|
static const struct snd_soc_dapm_route wm8994_intercon[] = {
|
|
|
|
{ "AIF2DACL", NULL, "AIF2DAC Mux" },
|
|
|
|
{ "AIF2DACR", NULL, "AIF2DAC Mux" },
|
2011-07-15 13:12:31 +07:00
|
|
|
{ "MICBIAS1", NULL, "VMID" },
|
|
|
|
{ "MICBIAS2", NULL, "VMID" },
|
2010-11-26 22:21:07 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static const struct snd_soc_dapm_route wm8958_intercon[] = {
|
|
|
|
{ "AIF2DACL", NULL, "AIF2DACL Mux" },
|
|
|
|
{ "AIF2DACR", NULL, "AIF2DACR Mux" },
|
|
|
|
|
|
|
|
{ "AIF2DACL Mux", "AIF2", "AIF2DAC Mux" },
|
|
|
|
{ "AIF2DACL Mux", "AIF3", "AIF3DACDAT" },
|
|
|
|
{ "AIF2DACR Mux", "AIF2", "AIF2DAC Mux" },
|
|
|
|
{ "AIF2DACR Mux", "AIF3", "AIF3DACDAT" },
|
|
|
|
|
|
|
|
{ "Mono PCM Out Mux", "AIF2ADCL", "AIF2ADCL" },
|
|
|
|
{ "Mono PCM Out Mux", "AIF2ADCR", "AIF2ADCR" },
|
|
|
|
|
|
|
|
{ "AIF3ADC Mux", "Mono PCM", "Mono PCM Out Mux" },
|
|
|
|
};
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
/* The size in bits of the FLL divide multiplied by 10
|
|
|
|
* to allow rounding later */
|
|
|
|
#define FIXED_FLL_SIZE ((1 << 16) * 10)
|
|
|
|
|
|
|
|
struct fll_div {
|
|
|
|
u16 outdiv;
|
|
|
|
u16 n;
|
|
|
|
u16 k;
|
|
|
|
u16 clk_ref_div;
|
|
|
|
u16 fll_fratio;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int wm8994_get_fll_config(struct fll_div *fll,
|
|
|
|
int freq_in, int freq_out)
|
|
|
|
{
|
|
|
|
u64 Kpart;
|
|
|
|
unsigned int K, Ndiv, Nmod;
|
|
|
|
|
|
|
|
pr_debug("FLL input=%dHz, output=%dHz\n", freq_in, freq_out);
|
|
|
|
|
|
|
|
/* Scale the input frequency down to <= 13.5MHz */
|
|
|
|
fll->clk_ref_div = 0;
|
|
|
|
while (freq_in > 13500000) {
|
|
|
|
fll->clk_ref_div++;
|
|
|
|
freq_in /= 2;
|
|
|
|
|
|
|
|
if (fll->clk_ref_div > 3)
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
pr_debug("CLK_REF_DIV=%d, Fref=%dHz\n", fll->clk_ref_div, freq_in);
|
|
|
|
|
|
|
|
/* Scale the output to give 90MHz<=Fvco<=100MHz */
|
|
|
|
fll->outdiv = 3;
|
|
|
|
while (freq_out * (fll->outdiv + 1) < 90000000) {
|
|
|
|
fll->outdiv++;
|
|
|
|
if (fll->outdiv > 63)
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
freq_out *= fll->outdiv + 1;
|
|
|
|
pr_debug("OUTDIV=%d, Fvco=%dHz\n", fll->outdiv, freq_out);
|
|
|
|
|
|
|
|
if (freq_in > 1000000) {
|
|
|
|
fll->fll_fratio = 0;
|
2010-04-20 11:36:11 +07:00
|
|
|
} else if (freq_in > 256000) {
|
|
|
|
fll->fll_fratio = 1;
|
|
|
|
freq_in *= 2;
|
|
|
|
} else if (freq_in > 128000) {
|
|
|
|
fll->fll_fratio = 2;
|
|
|
|
freq_in *= 4;
|
|
|
|
} else if (freq_in > 64000) {
|
2010-01-30 00:47:12 +07:00
|
|
|
fll->fll_fratio = 3;
|
|
|
|
freq_in *= 8;
|
2010-04-20 11:36:11 +07:00
|
|
|
} else {
|
|
|
|
fll->fll_fratio = 4;
|
|
|
|
freq_in *= 16;
|
2010-01-30 00:47:12 +07:00
|
|
|
}
|
|
|
|
pr_debug("FLL_FRATIO=%d, Fref=%dHz\n", fll->fll_fratio, freq_in);
|
|
|
|
|
|
|
|
/* Now, calculate N.K */
|
|
|
|
Ndiv = freq_out / freq_in;
|
|
|
|
|
|
|
|
fll->n = Ndiv;
|
|
|
|
Nmod = freq_out % freq_in;
|
|
|
|
pr_debug("Nmod=%d\n", Nmod);
|
|
|
|
|
|
|
|
/* Calculate fractional part - scale up so we can round. */
|
|
|
|
Kpart = FIXED_FLL_SIZE * (long long)Nmod;
|
|
|
|
|
|
|
|
do_div(Kpart, freq_in);
|
|
|
|
|
|
|
|
K = Kpart & 0xFFFFFFFF;
|
|
|
|
|
|
|
|
if ((K % 10) >= 5)
|
|
|
|
K += 5;
|
|
|
|
|
|
|
|
/* Move down to proper range now rounding is done */
|
|
|
|
fll->k = K / 10;
|
|
|
|
|
|
|
|
pr_debug("N=%x K=%x\n", fll->n, fll->k);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src,
|
2010-01-30 00:47:12 +07:00
|
|
|
unsigned int freq_in, unsigned int freq_out)
|
|
|
|
{
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2011-08-10 15:47:33 +07:00
|
|
|
struct wm8994 *control = codec->control_data;
|
2010-01-30 00:47:12 +07:00
|
|
|
int reg_offset, ret;
|
|
|
|
struct fll_div fll;
|
|
|
|
u16 reg, aif1, aif2;
|
2011-07-12 17:47:59 +07:00
|
|
|
unsigned long timeout;
|
2011-08-10 15:47:33 +07:00
|
|
|
bool was_enabled;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
aif1 = snd_soc_read(codec, WM8994_AIF1_CLOCKING_1)
|
|
|
|
& WM8994_AIF1CLK_ENA;
|
|
|
|
|
|
|
|
aif2 = snd_soc_read(codec, WM8994_AIF2_CLOCKING_1)
|
|
|
|
& WM8994_AIF2CLK_ENA;
|
|
|
|
|
|
|
|
switch (id) {
|
|
|
|
case WM8994_FLL1:
|
|
|
|
reg_offset = 0;
|
|
|
|
id = 0;
|
|
|
|
break;
|
|
|
|
case WM8994_FLL2:
|
|
|
|
reg_offset = 0x20;
|
|
|
|
id = 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2011-08-10 15:47:33 +07:00
|
|
|
reg = snd_soc_read(codec, WM8994_FLL1_CONTROL_1 + reg_offset);
|
|
|
|
was_enabled = reg & WM8994_FLL1_ENA;
|
|
|
|
|
2010-04-20 10:56:18 +07:00
|
|
|
switch (src) {
|
2010-04-22 00:29:01 +07:00
|
|
|
case 0:
|
|
|
|
/* Allow no source specification when stopping */
|
|
|
|
if (freq_out)
|
|
|
|
return -EINVAL;
|
2010-12-03 23:02:10 +07:00
|
|
|
src = wm8994->fll[id].src;
|
2010-04-22 00:29:01 +07:00
|
|
|
break;
|
2010-04-20 10:56:18 +07:00
|
|
|
case WM8994_FLL_SRC_MCLK1:
|
|
|
|
case WM8994_FLL_SRC_MCLK2:
|
|
|
|
case WM8994_FLL_SRC_LRCLK:
|
|
|
|
case WM8994_FLL_SRC_BCLK:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
/* Are we changing anything? */
|
|
|
|
if (wm8994->fll[id].src == src &&
|
|
|
|
wm8994->fll[id].in == freq_in && wm8994->fll[id].out == freq_out)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* If we're stopping the FLL redo the old config - no
|
|
|
|
* registers will actually be written but we avoid GCC flow
|
|
|
|
* analysis bugs spewing warnings.
|
|
|
|
*/
|
|
|
|
if (freq_out)
|
|
|
|
ret = wm8994_get_fll_config(&fll, freq_in, freq_out);
|
|
|
|
else
|
|
|
|
ret = wm8994_get_fll_config(&fll, wm8994->fll[id].in,
|
|
|
|
wm8994->fll[id].out);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
/* Gate the AIF clocks while we reclock */
|
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
|
|
|
|
WM8994_AIF1CLK_ENA, 0);
|
|
|
|
snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
|
|
|
|
WM8994_AIF2CLK_ENA, 0);
|
|
|
|
|
|
|
|
/* We always need to disable the FLL while reconfiguring */
|
|
|
|
snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset,
|
|
|
|
WM8994_FLL1_ENA, 0);
|
|
|
|
|
|
|
|
reg = (fll.outdiv << WM8994_FLL1_OUTDIV_SHIFT) |
|
|
|
|
(fll.fll_fratio << WM8994_FLL1_FRATIO_SHIFT);
|
|
|
|
snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_2 + reg_offset,
|
|
|
|
WM8994_FLL1_OUTDIV_MASK |
|
|
|
|
WM8994_FLL1_FRATIO_MASK, reg);
|
|
|
|
|
|
|
|
snd_soc_write(codec, WM8994_FLL1_CONTROL_3 + reg_offset, fll.k);
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_4 + reg_offset,
|
|
|
|
WM8994_FLL1_N_MASK,
|
|
|
|
fll.n << WM8994_FLL1_N_SHIFT);
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_5 + reg_offset,
|
2010-04-20 10:56:18 +07:00
|
|
|
WM8994_FLL1_REFCLK_DIV_MASK |
|
|
|
|
WM8994_FLL1_REFCLK_SRC_MASK,
|
|
|
|
(fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) |
|
|
|
|
(src - 1));
|
2010-01-30 00:47:12 +07:00
|
|
|
|
2011-07-16 01:12:18 +07:00
|
|
|
/* Clear any pending completion from a previous failure */
|
|
|
|
try_wait_for_completion(&wm8994->fll_locked[id]);
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
/* Enable (with fractional mode if required) */
|
|
|
|
if (freq_out) {
|
2011-08-10 15:47:33 +07:00
|
|
|
/* Enable VMID if we need it */
|
|
|
|
if (!was_enabled) {
|
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
|
|
|
vmid_reference(codec);
|
|
|
|
break;
|
|
|
|
case WM8958:
|
|
|
|
if (wm8994->revision < 1)
|
|
|
|
vmid_reference(codec);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
if (fll.k)
|
|
|
|
reg = WM8994_FLL1_ENA | WM8994_FLL1_FRAC;
|
|
|
|
else
|
|
|
|
reg = WM8994_FLL1_ENA;
|
|
|
|
snd_soc_update_bits(codec, WM8994_FLL1_CONTROL_1 + reg_offset,
|
|
|
|
WM8994_FLL1_ENA | WM8994_FLL1_FRAC,
|
|
|
|
reg);
|
2011-07-02 07:24:46 +07:00
|
|
|
|
2011-07-12 17:47:59 +07:00
|
|
|
if (wm8994->fll_locked_irq) {
|
|
|
|
timeout = wait_for_completion_timeout(&wm8994->fll_locked[id],
|
|
|
|
msecs_to_jiffies(10));
|
|
|
|
if (timeout == 0)
|
|
|
|
dev_warn(codec->dev,
|
|
|
|
"Timed out waiting for FLL lock\n");
|
|
|
|
} else {
|
|
|
|
msleep(5);
|
|
|
|
}
|
2011-08-10 15:47:33 +07:00
|
|
|
} else {
|
|
|
|
if (was_enabled) {
|
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
|
|
|
vmid_dereference(codec);
|
|
|
|
break;
|
|
|
|
case WM8958:
|
|
|
|
if (wm8994->revision < 1)
|
|
|
|
vmid_dereference(codec);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2010-01-30 00:47:12 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
wm8994->fll[id].in = freq_in;
|
|
|
|
wm8994->fll[id].out = freq_out;
|
2010-04-20 10:56:18 +07:00
|
|
|
wm8994->fll[id].src = src;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
/* Enable any gated AIF clocks */
|
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1,
|
|
|
|
WM8994_AIF1CLK_ENA, aif1);
|
|
|
|
snd_soc_update_bits(codec, WM8994_AIF2_CLOCKING_1,
|
|
|
|
WM8994_AIF2CLK_ENA, aif2);
|
|
|
|
|
|
|
|
configure_clock(codec);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-07-12 17:47:59 +07:00
|
|
|
static irqreturn_t wm8994_fll_locked_irq(int irq, void *data)
|
|
|
|
{
|
|
|
|
struct completion *completion = data;
|
|
|
|
|
|
|
|
complete(completion);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
2010-03-18 03:15:21 +07:00
|
|
|
|
2010-07-08 09:25:43 +07:00
|
|
|
static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 };
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src,
|
|
|
|
unsigned int freq_in, unsigned int freq_out)
|
|
|
|
{
|
|
|
|
return _wm8994_set_fll(dai->codec, id, src, freq_in, freq_out);
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
static int wm8994_set_dai_sysclk(struct snd_soc_dai *dai,
|
|
|
|
int clk_id, unsigned int freq, int dir)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = dai->codec;
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-07-08 09:25:43 +07:00
|
|
|
int i;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
switch (dai->id) {
|
|
|
|
case 1:
|
|
|
|
case 2:
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* AIF3 shares clocking with AIF1/2 */
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (clk_id) {
|
|
|
|
case WM8994_SYSCLK_MCLK1:
|
|
|
|
wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK1;
|
|
|
|
wm8994->mclk[0] = freq;
|
|
|
|
dev_dbg(dai->dev, "AIF%d using MCLK1 at %uHz\n",
|
|
|
|
dai->id, freq);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM8994_SYSCLK_MCLK2:
|
|
|
|
/* TODO: Set GPIO AF */
|
|
|
|
wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_MCLK2;
|
|
|
|
wm8994->mclk[1] = freq;
|
|
|
|
dev_dbg(dai->dev, "AIF%d using MCLK2 at %uHz\n",
|
|
|
|
dai->id, freq);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM8994_SYSCLK_FLL1:
|
|
|
|
wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_FLL1;
|
|
|
|
dev_dbg(dai->dev, "AIF%d using FLL1\n", dai->id);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM8994_SYSCLK_FLL2:
|
|
|
|
wm8994->sysclk[dai->id - 1] = WM8994_SYSCLK_FLL2;
|
|
|
|
dev_dbg(dai->dev, "AIF%d using FLL2\n", dai->id);
|
|
|
|
break;
|
|
|
|
|
2010-07-08 09:25:43 +07:00
|
|
|
case WM8994_SYSCLK_OPCLK:
|
|
|
|
/* Special case - a division (times 10) is given and
|
|
|
|
* no effect on main clocking.
|
|
|
|
*/
|
|
|
|
if (freq) {
|
|
|
|
for (i = 0; i < ARRAY_SIZE(opclk_divs); i++)
|
|
|
|
if (opclk_divs[i] == freq)
|
|
|
|
break;
|
|
|
|
if (i == ARRAY_SIZE(opclk_divs))
|
|
|
|
return -EINVAL;
|
|
|
|
snd_soc_update_bits(codec, WM8994_CLOCKING_2,
|
|
|
|
WM8994_OPCLK_DIV_MASK, i);
|
|
|
|
snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2,
|
|
|
|
WM8994_OPCLK_ENA, WM8994_OPCLK_ENA);
|
|
|
|
} else {
|
|
|
|
snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_2,
|
|
|
|
WM8994_OPCLK_ENA, 0);
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
configure_clock(codec);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wm8994_set_bias_level(struct snd_soc_codec *codec,
|
|
|
|
enum snd_soc_bias_level level)
|
|
|
|
{
|
2010-11-26 22:21:06 +07:00
|
|
|
struct wm8994 *control = codec->control_data;
|
2010-08-13 18:58:20 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
switch (level) {
|
|
|
|
case SND_SOC_BIAS_ON:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_BIAS_PREPARE:
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_BIAS_STANDBY:
|
2010-11-05 20:53:46 +07:00
|
|
|
if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) {
|
2010-11-27 00:23:43 +07:00
|
|
|
pm_runtime_get_sync(codec->dev);
|
|
|
|
|
2010-11-30 21:56:18 +07:00
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
|
|
|
if (wm8994->revision < 4) {
|
|
|
|
/* Tweak DC servo and DSP
|
|
|
|
* configuration for improved
|
|
|
|
* performance. */
|
|
|
|
snd_soc_write(codec, 0x102, 0x3);
|
|
|
|
snd_soc_write(codec, 0x56, 0x3);
|
|
|
|
snd_soc_write(codec, 0x817, 0);
|
|
|
|
snd_soc_write(codec, 0x102, 0);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WM8958:
|
|
|
|
if (wm8994->revision == 0) {
|
|
|
|
/* Optimise performance for rev A */
|
|
|
|
snd_soc_write(codec, 0x102, 0x3);
|
|
|
|
snd_soc_write(codec, 0xcb, 0x81);
|
|
|
|
snd_soc_write(codec, 0x817, 0);
|
|
|
|
snd_soc_write(codec, 0x102, 0);
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec,
|
|
|
|
WM8958_CHARGE_PUMP_2,
|
|
|
|
WM8958_CP_DISCH,
|
|
|
|
WM8958_CP_DISCH);
|
|
|
|
}
|
|
|
|
break;
|
2011-05-24 16:35:53 +07:00
|
|
|
|
|
|
|
case WM1811:
|
|
|
|
if (wm8994->revision < 2) {
|
|
|
|
snd_soc_write(codec, 0x102, 0x3);
|
|
|
|
snd_soc_write(codec, 0x5d, 0x7e);
|
|
|
|
snd_soc_write(codec, 0x5e, 0x0);
|
|
|
|
snd_soc_write(codec, 0x102, 0x0);
|
|
|
|
}
|
|
|
|
break;
|
2010-08-13 18:58:20 +07:00
|
|
|
}
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
/* Discharge LINEOUT1 & 2 */
|
|
|
|
snd_soc_update_bits(codec, WM8994_ANTIPOP_1,
|
|
|
|
WM8994_LINEOUT1_DISCH |
|
|
|
|
WM8994_LINEOUT2_DISCH,
|
|
|
|
WM8994_LINEOUT1_DISCH |
|
|
|
|
WM8994_LINEOUT2_DISCH);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_BIAS_OFF:
|
2010-11-05 20:53:46 +07:00
|
|
|
if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) {
|
2011-03-12 01:09:04 +07:00
|
|
|
wm8994->cur_fw = NULL;
|
|
|
|
|
2010-11-27 00:23:43 +07:00
|
|
|
pm_runtime_put(codec->dev);
|
2010-03-30 20:29:14 +07:00
|
|
|
}
|
2010-01-30 00:47:12 +07:00
|
|
|
break;
|
|
|
|
}
|
2010-11-05 20:53:46 +07:00
|
|
|
codec->dapm.bias_level = level;
|
2010-01-30 00:47:12 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int wm8994_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = dai->codec;
|
2010-11-26 22:21:07 +07:00
|
|
|
struct wm8994 *control = codec->control_data;
|
2010-01-30 00:47:12 +07:00
|
|
|
int ms_reg;
|
|
|
|
int aif1_reg;
|
|
|
|
int ms = 0;
|
|
|
|
int aif1 = 0;
|
|
|
|
|
|
|
|
switch (dai->id) {
|
|
|
|
case 1:
|
|
|
|
ms_reg = WM8994_AIF1_MASTER_SLAVE;
|
|
|
|
aif1_reg = WM8994_AIF1_CONTROL_1;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
ms_reg = WM8994_AIF2_MASTER_SLAVE;
|
|
|
|
aif1_reg = WM8994_AIF2_CONTROL_1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
|
|
ms = WM8994_AIF1_MSTR;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_DSP_B:
|
|
|
|
aif1 |= WM8994_AIF1_LRCLK_INV;
|
|
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
|
|
aif1 |= 0x18;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_I2S:
|
|
|
|
aif1 |= 0x10;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_RIGHT_J:
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
|
|
aif1 |= 0x8;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
|
|
case SND_SOC_DAIFMT_DSP_B:
|
|
|
|
/* frame inversion not valid for DSP modes */
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_IB_NF:
|
|
|
|
aif1 |= WM8994_AIF1_BCLK_INV;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SND_SOC_DAIFMT_I2S:
|
|
|
|
case SND_SOC_DAIFMT_RIGHT_J:
|
|
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_IB_IF:
|
|
|
|
aif1 |= WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_IB_NF:
|
|
|
|
aif1 |= WM8994_AIF1_BCLK_INV;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_NB_IF:
|
|
|
|
aif1 |= WM8994_AIF1_LRCLK_INV;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2010-11-26 22:21:07 +07:00
|
|
|
/* The AIF2 format configuration needs to be mirrored to AIF3
|
|
|
|
* on WM8958 if it's in use so just do it all the time. */
|
2011-05-24 16:35:53 +07:00
|
|
|
switch (control->type) {
|
|
|
|
case WM1811:
|
|
|
|
case WM8958:
|
|
|
|
if (dai->id == 2)
|
|
|
|
snd_soc_update_bits(codec, WM8958_AIF3_CONTROL_1,
|
|
|
|
WM8994_AIF1_LRCLK_INV |
|
|
|
|
WM8958_AIF3_FMT_MASK, aif1);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2010-11-26 22:21:07 +07:00
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
snd_soc_update_bits(codec, aif1_reg,
|
|
|
|
WM8994_AIF1_BCLK_INV | WM8994_AIF1_LRCLK_INV |
|
|
|
|
WM8994_AIF1_FMT_MASK,
|
|
|
|
aif1);
|
|
|
|
snd_soc_update_bits(codec, ms_reg, WM8994_AIF1_MSTR,
|
|
|
|
ms);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct {
|
|
|
|
int val, rate;
|
|
|
|
} srs[] = {
|
|
|
|
{ 0, 8000 },
|
|
|
|
{ 1, 11025 },
|
|
|
|
{ 2, 12000 },
|
|
|
|
{ 3, 16000 },
|
|
|
|
{ 4, 22050 },
|
|
|
|
{ 5, 24000 },
|
|
|
|
{ 6, 32000 },
|
|
|
|
{ 7, 44100 },
|
|
|
|
{ 8, 48000 },
|
|
|
|
{ 9, 88200 },
|
|
|
|
{ 10, 96000 },
|
|
|
|
};
|
|
|
|
|
|
|
|
static int fs_ratios[] = {
|
|
|
|
64, 128, 192, 256, 348, 512, 768, 1024, 1408, 1536
|
|
|
|
};
|
|
|
|
|
|
|
|
static int bclk_divs[] = {
|
|
|
|
10, 15, 20, 30, 40, 50, 60, 80, 110, 120, 160, 220, 240, 320, 440, 480,
|
|
|
|
640, 880, 960, 1280, 1760, 1920
|
|
|
|
};
|
|
|
|
|
|
|
|
static int wm8994_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = dai->codec;
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-01-30 00:47:12 +07:00
|
|
|
int aif1_reg;
|
2010-12-08 00:14:56 +07:00
|
|
|
int aif2_reg;
|
2010-01-30 00:47:12 +07:00
|
|
|
int bclk_reg;
|
|
|
|
int lrclk_reg;
|
|
|
|
int rate_reg;
|
|
|
|
int aif1 = 0;
|
2010-12-08 00:14:56 +07:00
|
|
|
int aif2 = 0;
|
2010-01-30 00:47:12 +07:00
|
|
|
int bclk = 0;
|
|
|
|
int lrclk = 0;
|
|
|
|
int rate_val = 0;
|
|
|
|
int id = dai->id - 1;
|
|
|
|
|
|
|
|
int i, cur_val, best_val, bclk_rate, best;
|
|
|
|
|
|
|
|
switch (dai->id) {
|
|
|
|
case 1:
|
|
|
|
aif1_reg = WM8994_AIF1_CONTROL_1;
|
2010-12-08 00:14:56 +07:00
|
|
|
aif2_reg = WM8994_AIF1_CONTROL_2;
|
2010-01-30 00:47:12 +07:00
|
|
|
bclk_reg = WM8994_AIF1_BCLK;
|
|
|
|
rate_reg = WM8994_AIF1_RATE;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
|
2010-08-23 16:54:43 +07:00
|
|
|
wm8994->lrclk_shared[0]) {
|
2010-01-30 00:47:12 +07:00
|
|
|
lrclk_reg = WM8994_AIF1DAC_LRCLK;
|
2010-08-23 16:54:43 +07:00
|
|
|
} else {
|
2010-01-30 00:47:12 +07:00
|
|
|
lrclk_reg = WM8994_AIF1ADC_LRCLK;
|
2010-08-23 16:54:43 +07:00
|
|
|
dev_dbg(codec->dev, "AIF1 using split LRCLK\n");
|
|
|
|
}
|
2010-01-30 00:47:12 +07:00
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
aif1_reg = WM8994_AIF2_CONTROL_1;
|
2010-12-08 00:14:56 +07:00
|
|
|
aif2_reg = WM8994_AIF2_CONTROL_2;
|
2010-01-30 00:47:12 +07:00
|
|
|
bclk_reg = WM8994_AIF2_BCLK;
|
|
|
|
rate_reg = WM8994_AIF2_RATE;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
|
2010-08-23 16:54:43 +07:00
|
|
|
wm8994->lrclk_shared[1]) {
|
2010-01-30 00:47:12 +07:00
|
|
|
lrclk_reg = WM8994_AIF2DAC_LRCLK;
|
2010-08-23 16:54:43 +07:00
|
|
|
} else {
|
2010-01-30 00:47:12 +07:00
|
|
|
lrclk_reg = WM8994_AIF2ADC_LRCLK;
|
2010-08-23 16:54:43 +07:00
|
|
|
dev_dbg(codec->dev, "AIF2 using split LRCLK\n");
|
|
|
|
}
|
2010-01-30 00:47:12 +07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
bclk_rate = params_rate(params) * 2;
|
|
|
|
switch (params_format(params)) {
|
|
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
|
|
bclk_rate *= 16;
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_FORMAT_S20_3LE:
|
|
|
|
bclk_rate *= 20;
|
|
|
|
aif1 |= 0x20;
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_FORMAT_S24_LE:
|
|
|
|
bclk_rate *= 24;
|
|
|
|
aif1 |= 0x40;
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_FORMAT_S32_LE:
|
|
|
|
bclk_rate *= 32;
|
|
|
|
aif1 |= 0x60;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Try to find an appropriate sample rate; look for an exact match. */
|
|
|
|
for (i = 0; i < ARRAY_SIZE(srs); i++)
|
|
|
|
if (srs[i].rate == params_rate(params))
|
|
|
|
break;
|
|
|
|
if (i == ARRAY_SIZE(srs))
|
|
|
|
return -EINVAL;
|
|
|
|
rate_val |= srs[i].val << WM8994_AIF1_SR_SHIFT;
|
|
|
|
|
|
|
|
dev_dbg(dai->dev, "Sample rate is %dHz\n", srs[i].rate);
|
|
|
|
dev_dbg(dai->dev, "AIF%dCLK is %dHz, target BCLK %dHz\n",
|
|
|
|
dai->id, wm8994->aifclk[id], bclk_rate);
|
|
|
|
|
2010-12-08 00:14:56 +07:00
|
|
|
if (params_channels(params) == 1 &&
|
|
|
|
(snd_soc_read(codec, aif1_reg) & 0x18) == 0x18)
|
|
|
|
aif2 |= WM8994_AIF1_MONO;
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
if (wm8994->aifclk[id] == 0) {
|
|
|
|
dev_err(dai->dev, "AIF%dCLK not configured\n", dai->id);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* AIFCLK/fs ratio; look for a close match in either direction */
|
|
|
|
best = 0;
|
|
|
|
best_val = abs((fs_ratios[0] * params_rate(params))
|
|
|
|
- wm8994->aifclk[id]);
|
|
|
|
for (i = 1; i < ARRAY_SIZE(fs_ratios); i++) {
|
|
|
|
cur_val = abs((fs_ratios[i] * params_rate(params))
|
|
|
|
- wm8994->aifclk[id]);
|
|
|
|
if (cur_val >= best_val)
|
|
|
|
continue;
|
|
|
|
best = i;
|
|
|
|
best_val = cur_val;
|
|
|
|
}
|
|
|
|
dev_dbg(dai->dev, "Selected AIF%dCLK/fs = %d\n",
|
|
|
|
dai->id, fs_ratios[best]);
|
|
|
|
rate_val |= best;
|
|
|
|
|
|
|
|
/* We may not get quite the right frequency if using
|
|
|
|
* approximate clocks so look for the closest match that is
|
|
|
|
* higher than the target (we need to ensure that there enough
|
|
|
|
* BCLKs to clock out the samples).
|
|
|
|
*/
|
|
|
|
best = 0;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) {
|
2010-02-02 16:53:19 +07:00
|
|
|
cur_val = (wm8994->aifclk[id] * 10 / bclk_divs[i]) - bclk_rate;
|
2010-01-30 00:47:12 +07:00
|
|
|
if (cur_val < 0) /* BCLK table is sorted */
|
|
|
|
break;
|
|
|
|
best = i;
|
|
|
|
}
|
2010-02-02 16:53:19 +07:00
|
|
|
bclk_rate = wm8994->aifclk[id] * 10 / bclk_divs[best];
|
2010-01-30 00:47:12 +07:00
|
|
|
dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n",
|
|
|
|
bclk_divs[best], bclk_rate);
|
|
|
|
bclk |= best << WM8994_AIF1_BCLK_DIV_SHIFT;
|
|
|
|
|
|
|
|
lrclk = bclk_rate / params_rate(params);
|
|
|
|
dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n",
|
|
|
|
lrclk, bclk_rate / lrclk);
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1);
|
2010-12-08 00:14:56 +07:00
|
|
|
snd_soc_update_bits(codec, aif2_reg, WM8994_AIF1_MONO, aif2);
|
2010-01-30 00:47:12 +07:00
|
|
|
snd_soc_update_bits(codec, bclk_reg, WM8994_AIF1_BCLK_DIV_MASK, bclk);
|
|
|
|
snd_soc_update_bits(codec, lrclk_reg, WM8994_AIF1DAC_RATE_MASK,
|
|
|
|
lrclk);
|
|
|
|
snd_soc_update_bits(codec, rate_reg, WM8994_AIF1_SR_MASK |
|
|
|
|
WM8994_AIF1CLK_RATE_MASK, rate_val);
|
|
|
|
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
switch (dai->id) {
|
|
|
|
case 1:
|
|
|
|
wm8994->dac_rates[0] = params_rate(params);
|
|
|
|
wm8994_set_retune_mobile(codec, 0);
|
|
|
|
wm8994_set_retune_mobile(codec, 1);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
wm8994->dac_rates[1] = params_rate(params);
|
|
|
|
wm8994_set_retune_mobile(codec, 2);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-11-26 22:21:07 +07:00
|
|
|
static int wm8994_aif3_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = dai->codec;
|
|
|
|
struct wm8994 *control = codec->control_data;
|
|
|
|
int aif1_reg;
|
|
|
|
int aif1 = 0;
|
|
|
|
|
|
|
|
switch (dai->id) {
|
|
|
|
case 3:
|
|
|
|
switch (control->type) {
|
2011-05-24 16:35:53 +07:00
|
|
|
case WM1811:
|
2010-11-26 22:21:07 +07:00
|
|
|
case WM8958:
|
|
|
|
aif1_reg = WM8958_AIF3_CONTROL_1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (params_format(params)) {
|
|
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_FORMAT_S20_3LE:
|
|
|
|
aif1 |= 0x20;
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_FORMAT_S24_LE:
|
|
|
|
aif1 |= 0x40;
|
|
|
|
break;
|
|
|
|
case SNDRV_PCM_FORMAT_S32_LE:
|
|
|
|
aif1 |= 0x60;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1);
|
|
|
|
}
|
|
|
|
|
2011-07-14 15:11:38 +07:00
|
|
|
static void wm8994_aif_shutdown(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = dai->codec;
|
|
|
|
int rate_reg = 0;
|
|
|
|
|
|
|
|
switch (dai->id) {
|
|
|
|
case 1:
|
|
|
|
rate_reg = WM8994_AIF1_RATE;
|
|
|
|
break;
|
|
|
|
case 2:
|
2011-10-04 21:07:18 +07:00
|
|
|
rate_reg = WM8994_AIF2_RATE;
|
2011-07-14 15:11:38 +07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the DAI is idle then configure the divider tree for the
|
|
|
|
* lowest output rate to save a little power if the clock is
|
|
|
|
* still active (eg, because it is system clock).
|
|
|
|
*/
|
|
|
|
if (rate_reg && !dai->playback_active && !dai->capture_active)
|
|
|
|
snd_soc_update_bits(codec, rate_reg,
|
|
|
|
WM8994_AIF1_SR_MASK |
|
|
|
|
WM8994_AIF1CLK_RATE_MASK, 0x9);
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
static int wm8994_aif_mute(struct snd_soc_dai *codec_dai, int mute)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = codec_dai->codec;
|
|
|
|
int mute_reg;
|
|
|
|
int reg;
|
|
|
|
|
|
|
|
switch (codec_dai->id) {
|
|
|
|
case 1:
|
|
|
|
mute_reg = WM8994_AIF1_DAC1_FILTERS_1;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
mute_reg = WM8994_AIF2_DAC_FILTERS_1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mute)
|
|
|
|
reg = WM8994_AIF1DAC1_MUTE;
|
|
|
|
else
|
|
|
|
reg = 0;
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, mute_reg, WM8994_AIF1DAC1_MUTE, reg);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-23 05:05:10 +07:00
|
|
|
static int wm8994_set_tristate(struct snd_soc_dai *codec_dai, int tristate)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = codec_dai->codec;
|
|
|
|
int reg, val, mask;
|
|
|
|
|
|
|
|
switch (codec_dai->id) {
|
|
|
|
case 1:
|
|
|
|
reg = WM8994_AIF1_MASTER_SLAVE;
|
|
|
|
mask = WM8994_AIF1_TRI;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
reg = WM8994_AIF2_MASTER_SLAVE;
|
|
|
|
mask = WM8994_AIF2_TRI;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
reg = WM8994_POWER_MANAGEMENT_6;
|
|
|
|
mask = WM8994_AIF3_TRI;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tristate)
|
|
|
|
val = mask;
|
|
|
|
else
|
|
|
|
val = 0;
|
|
|
|
|
2011-01-19 18:10:47 +07:00
|
|
|
return snd_soc_update_bits(codec, reg, mask, val);
|
2010-03-23 05:05:10 +07:00
|
|
|
}
|
|
|
|
|
2011-08-15 09:01:02 +07:00
|
|
|
static int wm8994_aif2_probe(struct snd_soc_dai *dai)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = dai->codec;
|
|
|
|
|
|
|
|
/* Disable the pulls on the AIF if we're using it to save power. */
|
|
|
|
snd_soc_update_bits(codec, WM8994_GPIO_3,
|
|
|
|
WM8994_GPN_PU | WM8994_GPN_PD, 0);
|
|
|
|
snd_soc_update_bits(codec, WM8994_GPIO_4,
|
|
|
|
WM8994_GPN_PU | WM8994_GPN_PD, 0);
|
|
|
|
snd_soc_update_bits(codec, WM8994_GPIO_5,
|
|
|
|
WM8994_GPN_PU | WM8994_GPN_PD, 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
#define WM8994_RATES SNDRV_PCM_RATE_8000_96000
|
|
|
|
|
|
|
|
#define WM8994_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
|
2010-09-01 05:56:34 +07:00
|
|
|
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
static struct snd_soc_dai_ops wm8994_aif1_dai_ops = {
|
|
|
|
.set_sysclk = wm8994_set_dai_sysclk,
|
|
|
|
.set_fmt = wm8994_set_dai_fmt,
|
|
|
|
.hw_params = wm8994_hw_params,
|
2011-07-14 15:11:38 +07:00
|
|
|
.shutdown = wm8994_aif_shutdown,
|
2010-01-30 00:47:12 +07:00
|
|
|
.digital_mute = wm8994_aif_mute,
|
|
|
|
.set_pll = wm8994_set_fll,
|
2010-03-23 05:05:10 +07:00
|
|
|
.set_tristate = wm8994_set_tristate,
|
2010-01-30 00:47:12 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct snd_soc_dai_ops wm8994_aif2_dai_ops = {
|
|
|
|
.set_sysclk = wm8994_set_dai_sysclk,
|
|
|
|
.set_fmt = wm8994_set_dai_fmt,
|
|
|
|
.hw_params = wm8994_hw_params,
|
2011-07-14 15:11:38 +07:00
|
|
|
.shutdown = wm8994_aif_shutdown,
|
2010-01-30 00:47:12 +07:00
|
|
|
.digital_mute = wm8994_aif_mute,
|
|
|
|
.set_pll = wm8994_set_fll,
|
2010-03-23 05:05:10 +07:00
|
|
|
.set_tristate = wm8994_set_tristate,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct snd_soc_dai_ops wm8994_aif3_dai_ops = {
|
2010-11-26 22:21:07 +07:00
|
|
|
.hw_params = wm8994_aif3_hw_params,
|
2010-03-23 05:05:10 +07:00
|
|
|
.set_tristate = wm8994_set_tristate,
|
2010-01-30 00:47:12 +07:00
|
|
|
};
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
static struct snd_soc_dai_driver wm8994_dai[] = {
|
2010-01-30 00:47:12 +07:00
|
|
|
{
|
2010-03-18 03:15:21 +07:00
|
|
|
.name = "wm8994-aif1",
|
2010-10-12 21:56:09 +07:00
|
|
|
.id = 1,
|
2010-01-30 00:47:12 +07:00
|
|
|
.playback = {
|
|
|
|
.stream_name = "AIF1 Playback",
|
2010-12-08 00:14:56 +07:00
|
|
|
.channels_min = 1,
|
2010-01-30 00:47:12 +07:00
|
|
|
.channels_max = 2,
|
|
|
|
.rates = WM8994_RATES,
|
|
|
|
.formats = WM8994_FORMATS,
|
|
|
|
},
|
|
|
|
.capture = {
|
|
|
|
.stream_name = "AIF1 Capture",
|
2010-12-08 00:14:56 +07:00
|
|
|
.channels_min = 1,
|
2010-01-30 00:47:12 +07:00
|
|
|
.channels_max = 2,
|
|
|
|
.rates = WM8994_RATES,
|
|
|
|
.formats = WM8994_FORMATS,
|
|
|
|
},
|
|
|
|
.ops = &wm8994_aif1_dai_ops,
|
|
|
|
},
|
|
|
|
{
|
2010-03-18 03:15:21 +07:00
|
|
|
.name = "wm8994-aif2",
|
2010-10-12 21:56:09 +07:00
|
|
|
.id = 2,
|
2010-01-30 00:47:12 +07:00
|
|
|
.playback = {
|
|
|
|
.stream_name = "AIF2 Playback",
|
2010-12-08 00:14:56 +07:00
|
|
|
.channels_min = 1,
|
2010-01-30 00:47:12 +07:00
|
|
|
.channels_max = 2,
|
|
|
|
.rates = WM8994_RATES,
|
|
|
|
.formats = WM8994_FORMATS,
|
|
|
|
},
|
|
|
|
.capture = {
|
|
|
|
.stream_name = "AIF2 Capture",
|
2010-12-08 00:14:56 +07:00
|
|
|
.channels_min = 1,
|
2010-01-30 00:47:12 +07:00
|
|
|
.channels_max = 2,
|
|
|
|
.rates = WM8994_RATES,
|
|
|
|
.formats = WM8994_FORMATS,
|
|
|
|
},
|
2011-08-15 09:01:02 +07:00
|
|
|
.probe = wm8994_aif2_probe,
|
2010-01-30 00:47:12 +07:00
|
|
|
.ops = &wm8994_aif2_dai_ops,
|
|
|
|
},
|
|
|
|
{
|
2010-03-18 03:15:21 +07:00
|
|
|
.name = "wm8994-aif3",
|
2010-10-12 21:56:09 +07:00
|
|
|
.id = 3,
|
2010-01-30 00:47:12 +07:00
|
|
|
.playback = {
|
|
|
|
.stream_name = "AIF3 Playback",
|
2010-12-08 00:14:56 +07:00
|
|
|
.channels_min = 1,
|
2010-01-30 00:47:12 +07:00
|
|
|
.channels_max = 2,
|
|
|
|
.rates = WM8994_RATES,
|
|
|
|
.formats = WM8994_FORMATS,
|
|
|
|
},
|
2010-03-24 18:58:34 +07:00
|
|
|
.capture = {
|
2010-01-30 00:47:12 +07:00
|
|
|
.stream_name = "AIF3 Capture",
|
2010-12-08 00:14:56 +07:00
|
|
|
.channels_min = 1,
|
2010-01-30 00:47:12 +07:00
|
|
|
.channels_max = 2,
|
|
|
|
.rates = WM8994_RATES,
|
|
|
|
.formats = WM8994_FORMATS,
|
|
|
|
},
|
2010-03-23 05:05:10 +07:00
|
|
|
.ops = &wm8994_aif3_dai_ops,
|
2010-01-30 00:47:12 +07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
2010-03-18 03:15:21 +07:00
|
|
|
static int wm8994_suspend(struct snd_soc_codec *codec, pm_message_t state)
|
2010-01-30 00:47:12 +07:00
|
|
|
{
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2011-05-11 19:34:53 +07:00
|
|
|
struct wm8994 *control = codec->control_data;
|
2010-01-30 00:47:12 +07:00
|
|
|
int i, ret;
|
|
|
|
|
2011-05-11 19:34:53 +07:00
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
|
|
|
snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, 0);
|
|
|
|
break;
|
2011-05-24 16:35:53 +07:00
|
|
|
case WM1811:
|
2011-05-11 19:34:53 +07:00
|
|
|
case WM8958:
|
|
|
|
snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
|
|
|
|
WM8958_MICD_ENA, 0);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) {
|
|
|
|
memcpy(&wm8994->fll_suspend[i], &wm8994->fll[i],
|
2011-03-10 02:31:01 +07:00
|
|
|
sizeof(struct wm8994_fll_config));
|
2010-03-18 03:15:21 +07:00
|
|
|
ret = _wm8994_set_fll(codec, i + 1, 0, 0, 0);
|
2010-01-30 00:47:12 +07:00
|
|
|
if (ret < 0)
|
|
|
|
dev_warn(codec->dev, "Failed to stop FLL%d: %d\n",
|
|
|
|
i + 1, ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
static int wm8994_resume(struct snd_soc_codec *codec)
|
2010-01-30 00:47:12 +07:00
|
|
|
{
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2011-05-11 19:34:53 +07:00
|
|
|
struct wm8994 *control = codec->control_data;
|
2010-01-30 00:47:12 +07:00
|
|
|
int i, ret;
|
2011-02-11 23:32:12 +07:00
|
|
|
unsigned int val, mask;
|
|
|
|
|
|
|
|
if (wm8994->revision < 4) {
|
|
|
|
/* force a HW read */
|
|
|
|
val = wm8994_reg_read(codec->control_data,
|
|
|
|
WM8994_POWER_MANAGEMENT_5);
|
|
|
|
|
|
|
|
/* modify the cache only */
|
|
|
|
codec->cache_only = 1;
|
|
|
|
mask = WM8994_DAC1R_ENA | WM8994_DAC1L_ENA |
|
|
|
|
WM8994_DAC2R_ENA | WM8994_DAC2L_ENA;
|
|
|
|
val &= mask;
|
|
|
|
snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5,
|
|
|
|
mask, val);
|
|
|
|
codec->cache_only = 0;
|
|
|
|
}
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
/* Restore the registers */
|
2010-11-27 00:23:41 +07:00
|
|
|
ret = snd_soc_cache_sync(codec);
|
|
|
|
if (ret != 0)
|
|
|
|
dev_err(codec->dev, "Failed to sync cache: %d\n", ret);
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(wm8994->fll); i++) {
|
2010-05-11 00:36:37 +07:00
|
|
|
if (!wm8994->fll_suspend[i].out)
|
|
|
|
continue;
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
ret = _wm8994_set_fll(codec, i + 1,
|
2010-01-30 00:47:12 +07:00
|
|
|
wm8994->fll_suspend[i].src,
|
|
|
|
wm8994->fll_suspend[i].in,
|
|
|
|
wm8994->fll_suspend[i].out);
|
|
|
|
if (ret < 0)
|
|
|
|
dev_warn(codec->dev, "Failed to restore FLL%d: %d\n",
|
|
|
|
i + 1, ret);
|
|
|
|
}
|
|
|
|
|
2011-05-11 19:34:53 +07:00
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
|
|
|
if (wm8994->micdet[0].jack || wm8994->micdet[1].jack)
|
|
|
|
snd_soc_update_bits(codec, WM8994_MICBIAS,
|
|
|
|
WM8994_MICD_ENA, WM8994_MICD_ENA);
|
|
|
|
break;
|
2011-05-24 16:35:53 +07:00
|
|
|
case WM1811:
|
2011-05-11 19:34:53 +07:00
|
|
|
case WM8958:
|
|
|
|
if (wm8994->jack_cb)
|
|
|
|
snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
|
|
|
|
WM8958_MICD_ENA, WM8958_MICD_ENA);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define wm8994_suspend NULL
|
|
|
|
#define wm8994_resume NULL
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static void wm8994_handle_retune_mobile_pdata(struct wm8994_priv *wm8994)
|
|
|
|
{
|
2010-03-18 03:15:21 +07:00
|
|
|
struct snd_soc_codec *codec = wm8994->codec;
|
2010-01-30 00:47:12 +07:00
|
|
|
struct wm8994_pdata *pdata = wm8994->pdata;
|
|
|
|
struct snd_kcontrol_new controls[] = {
|
|
|
|
SOC_ENUM_EXT("AIF1.1 EQ Mode",
|
|
|
|
wm8994->retune_mobile_enum,
|
|
|
|
wm8994_get_retune_mobile_enum,
|
|
|
|
wm8994_put_retune_mobile_enum),
|
|
|
|
SOC_ENUM_EXT("AIF1.2 EQ Mode",
|
|
|
|
wm8994->retune_mobile_enum,
|
|
|
|
wm8994_get_retune_mobile_enum,
|
|
|
|
wm8994_put_retune_mobile_enum),
|
|
|
|
SOC_ENUM_EXT("AIF2 EQ Mode",
|
|
|
|
wm8994->retune_mobile_enum,
|
|
|
|
wm8994_get_retune_mobile_enum,
|
|
|
|
wm8994_put_retune_mobile_enum),
|
|
|
|
};
|
|
|
|
int ret, i, j;
|
|
|
|
const char **t;
|
|
|
|
|
|
|
|
/* We need an array of texts for the enum API but the number
|
|
|
|
* of texts is likely to be less than the number of
|
|
|
|
* configurations due to the sample rate dependency of the
|
|
|
|
* configurations. */
|
|
|
|
wm8994->num_retune_mobile_texts = 0;
|
|
|
|
wm8994->retune_mobile_texts = NULL;
|
|
|
|
for (i = 0; i < pdata->num_retune_mobile_cfgs; i++) {
|
|
|
|
for (j = 0; j < wm8994->num_retune_mobile_texts; j++) {
|
|
|
|
if (strcmp(pdata->retune_mobile_cfgs[i].name,
|
|
|
|
wm8994->retune_mobile_texts[j]) == 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (j != wm8994->num_retune_mobile_texts)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* Expand the array... */
|
|
|
|
t = krealloc(wm8994->retune_mobile_texts,
|
|
|
|
sizeof(char *) *
|
|
|
|
(wm8994->num_retune_mobile_texts + 1),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (t == NULL)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* ...store the new entry... */
|
|
|
|
t[wm8994->num_retune_mobile_texts] =
|
|
|
|
pdata->retune_mobile_cfgs[i].name;
|
|
|
|
|
|
|
|
/* ...and remember the new version. */
|
|
|
|
wm8994->num_retune_mobile_texts++;
|
|
|
|
wm8994->retune_mobile_texts = t;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_dbg(codec->dev, "Allocated %d unique ReTune Mobile names\n",
|
|
|
|
wm8994->num_retune_mobile_texts);
|
|
|
|
|
|
|
|
wm8994->retune_mobile_enum.max = wm8994->num_retune_mobile_texts;
|
|
|
|
wm8994->retune_mobile_enum.texts = wm8994->retune_mobile_texts;
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
ret = snd_soc_add_controls(wm8994->codec, controls,
|
2010-01-30 00:47:12 +07:00
|
|
|
ARRAY_SIZE(controls));
|
|
|
|
if (ret != 0)
|
2010-03-18 03:15:21 +07:00
|
|
|
dev_err(wm8994->codec->dev,
|
2010-01-30 00:47:12 +07:00
|
|
|
"Failed to add ReTune Mobile controls: %d\n", ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void wm8994_handle_pdata(struct wm8994_priv *wm8994)
|
|
|
|
{
|
2010-03-18 03:15:21 +07:00
|
|
|
struct snd_soc_codec *codec = wm8994->codec;
|
2010-01-30 00:47:12 +07:00
|
|
|
struct wm8994_pdata *pdata = wm8994->pdata;
|
|
|
|
int ret, i;
|
|
|
|
|
|
|
|
if (!pdata)
|
|
|
|
return;
|
|
|
|
|
|
|
|
wm_hubs_handle_analogue_pdata(codec, pdata->lineout1_diff,
|
|
|
|
pdata->lineout2_diff,
|
|
|
|
pdata->lineout1fb,
|
|
|
|
pdata->lineout2fb,
|
|
|
|
pdata->jd_scthr,
|
|
|
|
pdata->jd_thr,
|
|
|
|
pdata->micbias1_lvl,
|
|
|
|
pdata->micbias2_lvl);
|
|
|
|
|
|
|
|
dev_dbg(codec->dev, "%d DRC configurations\n", pdata->num_drc_cfgs);
|
|
|
|
|
|
|
|
if (pdata->num_drc_cfgs) {
|
|
|
|
struct snd_kcontrol_new controls[] = {
|
|
|
|
SOC_ENUM_EXT("AIF1DRC1 Mode", wm8994->drc_enum,
|
|
|
|
wm8994_get_drc_enum, wm8994_put_drc_enum),
|
|
|
|
SOC_ENUM_EXT("AIF1DRC2 Mode", wm8994->drc_enum,
|
|
|
|
wm8994_get_drc_enum, wm8994_put_drc_enum),
|
|
|
|
SOC_ENUM_EXT("AIF2DRC Mode", wm8994->drc_enum,
|
|
|
|
wm8994_get_drc_enum, wm8994_put_drc_enum),
|
|
|
|
};
|
|
|
|
|
|
|
|
/* We need an array of texts for the enum API */
|
|
|
|
wm8994->drc_texts = kmalloc(sizeof(char *)
|
|
|
|
* pdata->num_drc_cfgs, GFP_KERNEL);
|
|
|
|
if (!wm8994->drc_texts) {
|
2010-03-18 03:15:21 +07:00
|
|
|
dev_err(wm8994->codec->dev,
|
2010-01-30 00:47:12 +07:00
|
|
|
"Failed to allocate %d DRC config texts\n",
|
|
|
|
pdata->num_drc_cfgs);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < pdata->num_drc_cfgs; i++)
|
|
|
|
wm8994->drc_texts[i] = pdata->drc_cfgs[i].name;
|
|
|
|
|
|
|
|
wm8994->drc_enum.max = pdata->num_drc_cfgs;
|
|
|
|
wm8994->drc_enum.texts = wm8994->drc_texts;
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
ret = snd_soc_add_controls(wm8994->codec, controls,
|
2010-01-30 00:47:12 +07:00
|
|
|
ARRAY_SIZE(controls));
|
|
|
|
if (ret != 0)
|
2010-03-18 03:15:21 +07:00
|
|
|
dev_err(wm8994->codec->dev,
|
2010-01-30 00:47:12 +07:00
|
|
|
"Failed to add DRC mode controls: %d\n", ret);
|
|
|
|
|
|
|
|
for (i = 0; i < WM8994_NUM_DRC; i++)
|
|
|
|
wm8994_set_drc(codec, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_dbg(codec->dev, "%d ReTune Mobile configurations\n",
|
|
|
|
pdata->num_retune_mobile_cfgs);
|
|
|
|
|
|
|
|
if (pdata->num_retune_mobile_cfgs)
|
|
|
|
wm8994_handle_retune_mobile_pdata(wm8994);
|
|
|
|
else
|
2010-03-18 03:15:21 +07:00
|
|
|
snd_soc_add_controls(wm8994->codec, wm8994_eq_controls,
|
2010-01-30 00:47:12 +07:00
|
|
|
ARRAY_SIZE(wm8994_eq_controls));
|
2011-02-22 08:11:59 +07:00
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pdata->micbias); i++) {
|
|
|
|
if (pdata->micbias[i]) {
|
|
|
|
snd_soc_write(codec, WM8958_MICBIAS1 + i,
|
|
|
|
pdata->micbias[i] & 0xffff);
|
|
|
|
}
|
|
|
|
}
|
2010-01-30 00:47:12 +07:00
|
|
|
}
|
|
|
|
|
2010-03-30 02:57:12 +07:00
|
|
|
/**
|
|
|
|
* wm8994_mic_detect - Enable microphone detection via the WM8994 IRQ
|
|
|
|
*
|
|
|
|
* @codec: WM8994 codec
|
|
|
|
* @jack: jack to report detection events on
|
|
|
|
* @micbias: microphone bias to detect on
|
|
|
|
* @det: value to report for presence detection
|
|
|
|
* @shrt: value to report for short detection
|
|
|
|
*
|
|
|
|
* Enable microphone detection via IRQ on the WM8994. If GPIOs are
|
|
|
|
* being used to bring out signals to the processor then only platform
|
2010-09-06 20:59:34 +07:00
|
|
|
* data configuration is needed for WM8994 and processor GPIOs should
|
2010-03-30 02:57:12 +07:00
|
|
|
* be configured using snd_soc_jack_add_gpios() instead.
|
|
|
|
*
|
|
|
|
* Configuration of detection levels is available via the micbias1_lvl
|
|
|
|
* and micbias2_lvl platform data members.
|
|
|
|
*/
|
|
|
|
int wm8994_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
|
|
|
|
int micbias, int det, int shrt)
|
|
|
|
{
|
2010-04-14 13:35:19 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-03-30 02:57:12 +07:00
|
|
|
struct wm8994_micdet *micdet;
|
2010-11-26 22:21:06 +07:00
|
|
|
struct wm8994 *control = codec->control_data;
|
2010-03-30 02:57:12 +07:00
|
|
|
int reg;
|
|
|
|
|
2010-11-26 22:21:06 +07:00
|
|
|
if (control->type != WM8994)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2010-03-30 02:57:12 +07:00
|
|
|
switch (micbias) {
|
|
|
|
case 1:
|
|
|
|
micdet = &wm8994->micdet[0];
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
micdet = &wm8994->micdet[1];
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_dbg(codec->dev, "Configuring microphone detection on %d: %x %x\n",
|
|
|
|
micbias, det, shrt);
|
|
|
|
|
|
|
|
/* Store the configuration */
|
|
|
|
micdet->jack = jack;
|
|
|
|
micdet->det = det;
|
|
|
|
micdet->shrt = shrt;
|
|
|
|
|
|
|
|
/* If either of the jacks is set up then enable detection */
|
|
|
|
if (wm8994->micdet[0].jack || wm8994->micdet[1].jack)
|
|
|
|
reg = WM8994_MICD_ENA;
|
|
|
|
else
|
|
|
|
reg = 0;
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, reg);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(wm8994_mic_detect);
|
|
|
|
|
|
|
|
static irqreturn_t wm8994_mic_irq(int irq, void *data)
|
|
|
|
{
|
|
|
|
struct wm8994_priv *priv = data;
|
2010-03-18 03:15:21 +07:00
|
|
|
struct snd_soc_codec *codec = priv->codec;
|
2010-03-30 02:57:12 +07:00
|
|
|
int reg;
|
|
|
|
int report;
|
|
|
|
|
2010-12-29 20:05:21 +07:00
|
|
|
#ifndef CONFIG_SND_SOC_WM8994_MODULE
|
2010-12-05 19:50:12 +07:00
|
|
|
trace_snd_soc_jack_irq(dev_name(codec->dev));
|
2010-12-29 20:05:21 +07:00
|
|
|
#endif
|
2010-12-05 19:50:12 +07:00
|
|
|
|
2010-03-30 02:57:12 +07:00
|
|
|
reg = snd_soc_read(codec, WM8994_INTERRUPT_RAW_STATUS_2);
|
|
|
|
if (reg < 0) {
|
|
|
|
dev_err(codec->dev, "Failed to read microphone status: %d\n",
|
|
|
|
reg);
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
dev_dbg(codec->dev, "Microphone status: %x\n", reg);
|
|
|
|
|
|
|
|
report = 0;
|
|
|
|
if (reg & WM8994_MIC1_DET_STS)
|
|
|
|
report |= priv->micdet[0].det;
|
|
|
|
if (reg & WM8994_MIC1_SHRT_STS)
|
|
|
|
report |= priv->micdet[0].shrt;
|
|
|
|
snd_soc_jack_report(priv->micdet[0].jack, report,
|
|
|
|
priv->micdet[0].det | priv->micdet[0].shrt);
|
|
|
|
|
|
|
|
report = 0;
|
|
|
|
if (reg & WM8994_MIC2_DET_STS)
|
|
|
|
report |= priv->micdet[1].det;
|
|
|
|
if (reg & WM8994_MIC2_SHRT_STS)
|
|
|
|
report |= priv->micdet[1].shrt;
|
|
|
|
snd_soc_jack_report(priv->micdet[1].jack, report,
|
|
|
|
priv->micdet[1].det | priv->micdet[1].shrt);
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2010-11-26 22:21:09 +07:00
|
|
|
/* Default microphone detection handler for WM8958 - the user can
|
|
|
|
* override this if they wish.
|
|
|
|
*/
|
|
|
|
static void wm8958_default_micdet(u16 status, void *data)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = data;
|
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
int report = 0;
|
|
|
|
|
|
|
|
/* If nothing present then clear our statuses */
|
2011-02-22 11:51:13 +07:00
|
|
|
if (!(status & WM8958_MICD_STS))
|
2010-11-26 22:21:09 +07:00
|
|
|
goto done;
|
|
|
|
|
2011-02-22 11:51:13 +07:00
|
|
|
report = SND_JACK_MICROPHONE;
|
2010-11-26 22:21:09 +07:00
|
|
|
|
|
|
|
/* Everything else is buttons; just assign slots */
|
2011-07-15 20:28:32 +07:00
|
|
|
if (status & 0x1c)
|
2010-11-26 22:21:09 +07:00
|
|
|
report |= SND_JACK_BTN_0;
|
|
|
|
|
|
|
|
done:
|
2011-02-22 11:41:25 +07:00
|
|
|
snd_soc_jack_report(wm8994->micdet[0].jack, report,
|
2011-02-22 11:51:13 +07:00
|
|
|
SND_JACK_BTN_0 | SND_JACK_MICROPHONE);
|
2010-11-26 22:21:09 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* wm8958_mic_detect - Enable microphone detection via the WM8958 IRQ
|
|
|
|
*
|
|
|
|
* @codec: WM8958 codec
|
|
|
|
* @jack: jack to report detection events on
|
|
|
|
*
|
|
|
|
* Enable microphone detection functionality for the WM8958. By
|
|
|
|
* default simple detection which supports the detection of up to 6
|
|
|
|
* buttons plus video and microphone functionality is supported.
|
|
|
|
*
|
|
|
|
* The WM8958 has an advanced jack detection facility which is able to
|
|
|
|
* support complex accessory detection, especially when used in
|
|
|
|
* conjunction with external circuitry. In order to provide maximum
|
|
|
|
* flexiblity a callback is provided which allows a completely custom
|
|
|
|
* detection algorithm.
|
|
|
|
*/
|
|
|
|
int wm8958_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack,
|
|
|
|
wm8958_micdet_cb cb, void *cb_data)
|
|
|
|
{
|
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
|
|
|
struct wm8994 *control = codec->control_data;
|
|
|
|
|
2011-05-24 16:35:53 +07:00
|
|
|
switch (control->type) {
|
|
|
|
case WM1811:
|
|
|
|
case WM8958:
|
|
|
|
break;
|
|
|
|
default:
|
2010-11-26 22:21:09 +07:00
|
|
|
return -EINVAL;
|
2011-05-24 16:35:53 +07:00
|
|
|
}
|
2010-11-26 22:21:09 +07:00
|
|
|
|
|
|
|
if (jack) {
|
|
|
|
if (!cb) {
|
|
|
|
dev_dbg(codec->dev, "Using default micdet callback\n");
|
|
|
|
cb = wm8958_default_micdet;
|
|
|
|
cb_data = codec;
|
|
|
|
}
|
|
|
|
|
|
|
|
wm8994->micdet[0].jack = jack;
|
|
|
|
wm8994->jack_cb = cb;
|
|
|
|
wm8994->jack_cb_data = cb_data;
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
|
|
|
|
WM8958_MICD_ENA, WM8958_MICD_ENA);
|
|
|
|
} else {
|
|
|
|
snd_soc_update_bits(codec, WM8958_MIC_DETECT_1,
|
|
|
|
WM8958_MICD_ENA, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(wm8958_mic_detect);
|
|
|
|
|
|
|
|
static irqreturn_t wm8958_mic_irq(int irq, void *data)
|
|
|
|
{
|
|
|
|
struct wm8994_priv *wm8994 = data;
|
|
|
|
struct snd_soc_codec *codec = wm8994->codec;
|
2011-08-19 16:05:05 +07:00
|
|
|
int reg, count;
|
2010-11-26 22:21:09 +07:00
|
|
|
|
2011-08-19 16:05:05 +07:00
|
|
|
/* We may occasionally read a detection without an impedence
|
|
|
|
* range being provided - if that happens loop again.
|
|
|
|
*/
|
|
|
|
count = 10;
|
|
|
|
do {
|
|
|
|
reg = snd_soc_read(codec, WM8958_MIC_DETECT_3);
|
|
|
|
if (reg < 0) {
|
|
|
|
dev_err(codec->dev,
|
|
|
|
"Failed to read mic detect status: %d\n",
|
|
|
|
reg);
|
|
|
|
return IRQ_NONE;
|
|
|
|
}
|
2010-11-26 22:21:09 +07:00
|
|
|
|
2011-08-19 16:05:05 +07:00
|
|
|
if (!(reg & WM8958_MICD_VALID)) {
|
|
|
|
dev_dbg(codec->dev, "Mic detect data not valid\n");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(reg & WM8958_MICD_STS) || (reg & WM8958_MICD_LVL_MASK))
|
|
|
|
break;
|
|
|
|
|
|
|
|
msleep(1);
|
|
|
|
} while (count--);
|
|
|
|
|
|
|
|
if (count == 0)
|
|
|
|
dev_warn(codec->dev, "No impedence range reported for jack\n");
|
2010-11-26 22:21:09 +07:00
|
|
|
|
2010-12-29 20:05:21 +07:00
|
|
|
#ifndef CONFIG_SND_SOC_WM8994_MODULE
|
2010-12-05 19:50:12 +07:00
|
|
|
trace_snd_soc_jack_irq(dev_name(codec->dev));
|
2010-12-29 20:05:21 +07:00
|
|
|
#endif
|
2010-12-05 19:50:12 +07:00
|
|
|
|
2010-11-26 22:21:09 +07:00
|
|
|
if (wm8994->jack_cb)
|
|
|
|
wm8994->jack_cb(reg, wm8994->jack_cb_data);
|
|
|
|
else
|
|
|
|
dev_warn(codec->dev, "Accessory detection with no callback\n");
|
|
|
|
|
|
|
|
out:
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2011-07-14 10:38:18 +07:00
|
|
|
static irqreturn_t wm8994_fifo_error(int irq, void *data)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = data;
|
|
|
|
|
|
|
|
dev_err(codec->dev, "FIFO error\n");
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2011-08-16 10:01:27 +07:00
|
|
|
static irqreturn_t wm8994_temp_warn(int irq, void *data)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = data;
|
|
|
|
|
|
|
|
dev_err(codec->dev, "Thermal warning\n");
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t wm8994_temp_shut(int irq, void *data)
|
|
|
|
{
|
|
|
|
struct snd_soc_codec *codec = data;
|
|
|
|
|
|
|
|
dev_crit(codec->dev, "Thermal shutdown\n");
|
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
static int wm8994_codec_probe(struct snd_soc_codec *codec)
|
2010-01-30 00:47:12 +07:00
|
|
|
{
|
2010-11-26 22:21:06 +07:00
|
|
|
struct wm8994 *control;
|
2010-01-30 00:47:12 +07:00
|
|
|
struct wm8994_priv *wm8994;
|
2010-11-05 20:53:46 +07:00
|
|
|
struct snd_soc_dapm_context *dapm = &codec->dapm;
|
2010-08-15 20:56:40 +07:00
|
|
|
int ret, i;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
codec->control_data = dev_get_drvdata(codec->dev->parent);
|
2010-11-26 22:21:06 +07:00
|
|
|
control = codec->control_data;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
wm8994 = kzalloc(sizeof(struct wm8994_priv), GFP_KERNEL);
|
2010-03-18 03:15:21 +07:00
|
|
|
if (wm8994 == NULL)
|
2010-01-30 00:47:12 +07:00
|
|
|
return -ENOMEM;
|
2010-04-14 13:35:19 +07:00
|
|
|
snd_soc_codec_set_drvdata(codec, wm8994);
|
2010-03-18 03:15:21 +07:00
|
|
|
|
|
|
|
wm8994->pdata = dev_get_platdata(codec->dev->parent);
|
|
|
|
wm8994->codec = codec;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
2011-07-12 17:47:59 +07:00
|
|
|
for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++)
|
|
|
|
init_completion(&wm8994->fll_locked[i]);
|
|
|
|
|
2011-02-18 11:05:44 +07:00
|
|
|
if (wm8994->pdata && wm8994->pdata->micdet_irq)
|
|
|
|
wm8994->micdet_irq = wm8994->pdata->micdet_irq;
|
|
|
|
else if (wm8994->pdata && wm8994->pdata->irq_base)
|
|
|
|
wm8994->micdet_irq = wm8994->pdata->irq_base +
|
|
|
|
WM8994_IRQ_MIC1_DET;
|
|
|
|
|
2010-11-27 00:23:43 +07:00
|
|
|
pm_runtime_enable(codec->dev);
|
|
|
|
pm_runtime_resume(codec->dev);
|
|
|
|
|
2010-11-27 00:23:41 +07:00
|
|
|
/* Read our current status back from the chip - we don't want to
|
|
|
|
* reset as this may interfere with the GPIO or LDO operation. */
|
|
|
|
for (i = 0; i < WM8994_CACHE_SIZE; i++) {
|
2011-01-13 19:20:37 +07:00
|
|
|
if (!wm8994_readable(codec, i) || wm8994_volatile(codec, i))
|
2010-11-27 00:23:41 +07:00
|
|
|
continue;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
2010-11-27 00:23:41 +07:00
|
|
|
ret = wm8994_reg_read(codec->control_data, i);
|
|
|
|
if (ret <= 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ret = snd_soc_cache_write(codec, i, ret);
|
|
|
|
if (ret != 0) {
|
|
|
|
dev_err(codec->dev,
|
|
|
|
"Failed to initialise cache for 0x%x: %d\n",
|
|
|
|
i, ret);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
}
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
/* Set revision-specific configuration */
|
2010-08-13 18:58:20 +07:00
|
|
|
wm8994->revision = snd_soc_read(codec, WM8994_CHIP_REVISION);
|
2010-11-26 22:21:06 +07:00
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
|
|
|
switch (wm8994->revision) {
|
|
|
|
case 2:
|
|
|
|
case 3:
|
2011-08-01 11:10:16 +07:00
|
|
|
wm8994->hubs.dcs_codes_l = -5;
|
|
|
|
wm8994->hubs.dcs_codes_r = -5;
|
2010-11-26 22:21:06 +07:00
|
|
|
wm8994->hubs.hp_startup_mode = 1;
|
|
|
|
wm8994->hubs.dcs_readback_mode = 1;
|
2011-06-08 05:23:52 +07:00
|
|
|
wm8994->hubs.series_startup = 1;
|
2010-11-26 22:21:06 +07:00
|
|
|
break;
|
|
|
|
default:
|
2011-08-01 11:02:17 +07:00
|
|
|
wm8994->hubs.dcs_readback_mode = 2;
|
2010-11-26 22:21:06 +07:00
|
|
|
break;
|
|
|
|
}
|
2011-08-10 20:19:19 +07:00
|
|
|
break;
|
2010-11-26 22:21:06 +07:00
|
|
|
|
|
|
|
case WM8958:
|
2010-03-29 23:09:45 +07:00
|
|
|
wm8994->hubs.dcs_readback_mode = 1;
|
2010-01-30 00:47:12 +07:00
|
|
|
break;
|
2010-11-26 22:21:06 +07:00
|
|
|
|
2011-05-24 16:35:53 +07:00
|
|
|
case WM1811:
|
|
|
|
wm8994->hubs.dcs_readback_mode = 2;
|
|
|
|
wm8994->hubs.no_series_update = 1;
|
|
|
|
|
|
|
|
switch (wm8994->revision) {
|
|
|
|
case 0:
|
|
|
|
case 1:
|
2011-10-18 01:38:52 +07:00
|
|
|
wm8994->hubs.dcs_codes_l = -9;
|
|
|
|
wm8994->hubs.dcs_codes_r = -5;
|
2011-05-24 16:35:53 +07:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_soc_update_bits(codec, WM8994_ANALOGUE_HP_1,
|
|
|
|
WM1811_HPOUT1_ATTN, WM1811_HPOUT1_ATTN);
|
|
|
|
break;
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-07-14 10:38:18 +07:00
|
|
|
wm8994_request_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR,
|
|
|
|
wm8994_fifo_error, "FIFO error", codec);
|
2011-11-04 22:53:48 +07:00
|
|
|
wm8994_request_irq(codec->control_data, WM8994_IRQ_TEMP_WARN,
|
2011-08-16 10:01:27 +07:00
|
|
|
wm8994_temp_warn, "Thermal warning", codec);
|
2011-11-04 22:53:48 +07:00
|
|
|
wm8994_request_irq(codec->control_data, WM8994_IRQ_TEMP_SHUT,
|
2011-08-16 10:01:27 +07:00
|
|
|
wm8994_temp_shut, "Thermal shutdown", codec);
|
2011-07-14 10:38:18 +07:00
|
|
|
|
2011-07-12 13:47:17 +07:00
|
|
|
ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_DCS_DONE,
|
|
|
|
wm_hubs_dcs_done, "DC servo done",
|
|
|
|
&wm8994->hubs);
|
|
|
|
if (ret == 0)
|
|
|
|
wm8994->hubs.dcs_done_irq = true;
|
|
|
|
|
2010-11-26 22:21:06 +07:00
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
2011-02-18 11:05:44 +07:00
|
|
|
if (wm8994->micdet_irq) {
|
|
|
|
ret = request_threaded_irq(wm8994->micdet_irq, NULL,
|
|
|
|
wm8994_mic_irq,
|
|
|
|
IRQF_TRIGGER_RISING,
|
|
|
|
"Mic1 detect",
|
|
|
|
wm8994);
|
|
|
|
if (ret != 0)
|
|
|
|
dev_warn(codec->dev,
|
|
|
|
"Failed to request Mic1 detect IRQ: %d\n",
|
|
|
|
ret);
|
|
|
|
}
|
2010-11-26 22:21:06 +07:00
|
|
|
|
|
|
|
ret = wm8994_request_irq(codec->control_data,
|
|
|
|
WM8994_IRQ_MIC1_SHRT,
|
|
|
|
wm8994_mic_irq, "Mic 1 short",
|
|
|
|
wm8994);
|
|
|
|
if (ret != 0)
|
|
|
|
dev_warn(codec->dev,
|
|
|
|
"Failed to request Mic1 short IRQ: %d\n",
|
|
|
|
ret);
|
|
|
|
|
|
|
|
ret = wm8994_request_irq(codec->control_data,
|
|
|
|
WM8994_IRQ_MIC2_DET,
|
|
|
|
wm8994_mic_irq, "Mic 2 detect",
|
|
|
|
wm8994);
|
|
|
|
if (ret != 0)
|
|
|
|
dev_warn(codec->dev,
|
|
|
|
"Failed to request Mic2 detect IRQ: %d\n",
|
|
|
|
ret);
|
|
|
|
|
|
|
|
ret = wm8994_request_irq(codec->control_data,
|
|
|
|
WM8994_IRQ_MIC2_SHRT,
|
|
|
|
wm8994_mic_irq, "Mic 2 short",
|
|
|
|
wm8994);
|
|
|
|
if (ret != 0)
|
|
|
|
dev_warn(codec->dev,
|
|
|
|
"Failed to request Mic2 short IRQ: %d\n",
|
|
|
|
ret);
|
|
|
|
break;
|
2010-11-26 22:21:09 +07:00
|
|
|
|
|
|
|
case WM8958:
|
2011-05-24 16:35:53 +07:00
|
|
|
case WM1811:
|
2011-02-18 11:05:44 +07:00
|
|
|
if (wm8994->micdet_irq) {
|
|
|
|
ret = request_threaded_irq(wm8994->micdet_irq, NULL,
|
|
|
|
wm8958_mic_irq,
|
|
|
|
IRQF_TRIGGER_RISING,
|
|
|
|
"Mic detect",
|
|
|
|
wm8994);
|
|
|
|
if (ret != 0)
|
|
|
|
dev_warn(codec->dev,
|
|
|
|
"Failed to request Mic detect IRQ: %d\n",
|
|
|
|
ret);
|
|
|
|
}
|
2010-11-26 22:21:06 +07:00
|
|
|
}
|
2010-03-30 02:57:12 +07:00
|
|
|
|
2011-07-12 17:47:59 +07:00
|
|
|
wm8994->fll_locked_irq = true;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) {
|
|
|
|
ret = wm8994_request_irq(codec->control_data,
|
|
|
|
WM8994_IRQ_FLL1_LOCK + i,
|
|
|
|
wm8994_fll_locked_irq, "FLL lock",
|
|
|
|
&wm8994->fll_locked[i]);
|
|
|
|
if (ret != 0)
|
|
|
|
wm8994->fll_locked_irq = false;
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
/* Remember if AIFnLRCLK is configured as a GPIO. This should be
|
|
|
|
* configured on init - if a system wants to do this dynamically
|
|
|
|
* at runtime we can deal with that then.
|
|
|
|
*/
|
|
|
|
ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_1);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(codec->dev, "Failed to read GPIO1 state: %d\n", ret);
|
2010-03-30 02:57:12 +07:00
|
|
|
goto err_irq;
|
2010-01-30 00:47:12 +07:00
|
|
|
}
|
|
|
|
if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) {
|
|
|
|
wm8994->lrclk_shared[0] = 1;
|
|
|
|
wm8994_dai[0].symmetric_rates = 1;
|
|
|
|
} else {
|
|
|
|
wm8994->lrclk_shared[0] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = wm8994_reg_read(codec->control_data, WM8994_GPIO_6);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(codec->dev, "Failed to read GPIO6 state: %d\n", ret);
|
2010-03-30 02:57:12 +07:00
|
|
|
goto err_irq;
|
2010-01-30 00:47:12 +07:00
|
|
|
}
|
|
|
|
if ((ret & WM8994_GPN_FN_MASK) != WM8994_GP_FN_PIN_SPECIFIC) {
|
|
|
|
wm8994->lrclk_shared[1] = 1;
|
|
|
|
wm8994_dai[1].symmetric_rates = 1;
|
|
|
|
} else {
|
|
|
|
wm8994->lrclk_shared[1] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
wm8994_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
|
|
|
|
|
|
|
|
/* Latch volume updates (right only; we always do left then right). */
|
2011-04-06 08:52:42 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_DAC1_LEFT_VOLUME,
|
|
|
|
WM8994_AIF1DAC1_VU, WM8994_AIF1DAC1_VU);
|
2010-01-30 00:47:12 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_DAC1_RIGHT_VOLUME,
|
|
|
|
WM8994_AIF1DAC1_VU, WM8994_AIF1DAC1_VU);
|
2011-04-06 08:52:42 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_DAC2_LEFT_VOLUME,
|
|
|
|
WM8994_AIF1DAC2_VU, WM8994_AIF1DAC2_VU);
|
2010-01-30 00:47:12 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_DAC2_RIGHT_VOLUME,
|
|
|
|
WM8994_AIF1DAC2_VU, WM8994_AIF1DAC2_VU);
|
2011-04-06 08:52:42 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF2_DAC_LEFT_VOLUME,
|
|
|
|
WM8994_AIF2DAC_VU, WM8994_AIF2DAC_VU);
|
2010-01-30 00:47:12 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF2_DAC_RIGHT_VOLUME,
|
|
|
|
WM8994_AIF2DAC_VU, WM8994_AIF2DAC_VU);
|
2011-04-06 08:52:42 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_ADC1_LEFT_VOLUME,
|
|
|
|
WM8994_AIF1ADC1_VU, WM8994_AIF1ADC1_VU);
|
2010-01-30 00:47:12 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_ADC1_RIGHT_VOLUME,
|
|
|
|
WM8994_AIF1ADC1_VU, WM8994_AIF1ADC1_VU);
|
2011-04-06 08:52:42 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_ADC2_LEFT_VOLUME,
|
|
|
|
WM8994_AIF1ADC2_VU, WM8994_AIF1ADC2_VU);
|
2010-01-30 00:47:12 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_ADC2_RIGHT_VOLUME,
|
|
|
|
WM8994_AIF1ADC2_VU, WM8994_AIF1ADC2_VU);
|
2011-04-06 08:52:42 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF2_ADC_LEFT_VOLUME,
|
|
|
|
WM8994_AIF2ADC_VU, WM8994_AIF1ADC2_VU);
|
2010-01-30 00:47:12 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_AIF2_ADC_RIGHT_VOLUME,
|
|
|
|
WM8994_AIF2ADC_VU, WM8994_AIF1ADC2_VU);
|
2011-04-06 08:52:42 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_DAC1_LEFT_VOLUME,
|
|
|
|
WM8994_DAC1_VU, WM8994_DAC1_VU);
|
2010-01-30 00:47:12 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_DAC1_RIGHT_VOLUME,
|
|
|
|
WM8994_DAC1_VU, WM8994_DAC1_VU);
|
2011-04-06 08:52:42 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_DAC2_LEFT_VOLUME,
|
|
|
|
WM8994_DAC2_VU, WM8994_DAC2_VU);
|
2010-01-30 00:47:12 +07:00
|
|
|
snd_soc_update_bits(codec, WM8994_DAC2_RIGHT_VOLUME,
|
|
|
|
WM8994_DAC2_VU, WM8994_DAC2_VU);
|
|
|
|
|
|
|
|
/* Set the low bit of the 3D stereo depth so TLV matches */
|
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_DAC1_FILTERS_2,
|
|
|
|
1 << WM8994_AIF1DAC1_3D_GAIN_SHIFT,
|
|
|
|
1 << WM8994_AIF1DAC1_3D_GAIN_SHIFT);
|
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_DAC2_FILTERS_2,
|
|
|
|
1 << WM8994_AIF1DAC2_3D_GAIN_SHIFT,
|
|
|
|
1 << WM8994_AIF1DAC2_3D_GAIN_SHIFT);
|
|
|
|
snd_soc_update_bits(codec, WM8994_AIF2_DAC_FILTERS_2,
|
|
|
|
1 << WM8994_AIF2DAC_3D_GAIN_SHIFT,
|
|
|
|
1 << WM8994_AIF2DAC_3D_GAIN_SHIFT);
|
|
|
|
|
2011-07-06 14:08:43 +07:00
|
|
|
/* Unconditionally enable AIF1 ADC TDM mode on chips which can
|
|
|
|
* use this; it only affects behaviour on idle TDM clock
|
|
|
|
* cycles. */
|
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
|
|
|
case WM8958:
|
|
|
|
snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1,
|
|
|
|
WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2010-07-20 16:13:14 +07:00
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
wm8994_update_class_w(codec);
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
wm8994_handle_pdata(wm8994);
|
2010-01-30 00:47:12 +07:00
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
wm_hubs_add_analogue_controls(codec);
|
|
|
|
snd_soc_add_controls(codec, wm8994_snd_controls,
|
|
|
|
ARRAY_SIZE(wm8994_snd_controls));
|
2010-11-05 20:53:46 +07:00
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_dapm_widgets,
|
2010-03-18 03:15:21 +07:00
|
|
|
ARRAY_SIZE(wm8994_dapm_widgets));
|
2010-11-26 22:21:07 +07:00
|
|
|
|
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_specific_dapm_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_specific_dapm_widgets));
|
2011-02-11 23:32:12 +07:00
|
|
|
if (wm8994->revision < 4) {
|
2011-02-11 23:32:11 +07:00
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_lateclk_revd_widgets));
|
2011-03-01 18:47:10 +07:00
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_adc_revd_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_adc_revd_widgets));
|
2011-02-11 23:32:12 +07:00
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_dac_revd_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_dac_revd_widgets));
|
|
|
|
} else {
|
2011-02-11 23:32:11 +07:00
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_lateclk_widgets));
|
2011-03-01 18:47:10 +07:00
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_adc_widgets));
|
2011-02-11 23:32:12 +07:00
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_dac_widgets));
|
|
|
|
}
|
2010-11-26 22:21:07 +07:00
|
|
|
break;
|
|
|
|
case WM8958:
|
|
|
|
snd_soc_add_controls(codec, wm8958_snd_controls,
|
|
|
|
ARRAY_SIZE(wm8958_snd_controls));
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8958_dapm_widgets,
|
|
|
|
ARRAY_SIZE(wm8958_dapm_widgets));
|
2011-03-12 01:00:19 +07:00
|
|
|
if (wm8994->revision < 1) {
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_lateclk_revd_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_lateclk_revd_widgets));
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_adc_revd_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_adc_revd_widgets));
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_dac_revd_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_dac_revd_widgets));
|
|
|
|
} else {
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_lateclk_widgets));
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_adc_widgets));
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_dac_widgets));
|
|
|
|
}
|
2010-11-26 22:21:07 +07:00
|
|
|
break;
|
2011-05-24 16:35:53 +07:00
|
|
|
|
|
|
|
case WM1811:
|
|
|
|
snd_soc_add_controls(codec, wm8958_snd_controls,
|
|
|
|
ARRAY_SIZE(wm8958_snd_controls));
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8958_dapm_widgets,
|
|
|
|
ARRAY_SIZE(wm8958_dapm_widgets));
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_lateclk_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_lateclk_widgets));
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_adc_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_adc_widgets));
|
|
|
|
snd_soc_dapm_new_controls(dapm, wm8994_dac_widgets,
|
|
|
|
ARRAY_SIZE(wm8994_dac_widgets));
|
|
|
|
break;
|
2010-11-26 22:21:07 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
wm_hubs_add_analogue_routes(codec, 0, 0);
|
2010-11-05 20:53:46 +07:00
|
|
|
snd_soc_dapm_add_routes(dapm, intercon, ARRAY_SIZE(intercon));
|
2010-01-30 00:47:12 +07:00
|
|
|
|
2010-11-26 22:21:07 +07:00
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
|
|
|
snd_soc_dapm_add_routes(dapm, wm8994_intercon,
|
|
|
|
ARRAY_SIZE(wm8994_intercon));
|
2011-02-03 23:27:35 +07:00
|
|
|
|
2011-02-11 23:32:11 +07:00
|
|
|
if (wm8994->revision < 4) {
|
2011-02-03 23:27:35 +07:00
|
|
|
snd_soc_dapm_add_routes(dapm, wm8994_revd_intercon,
|
|
|
|
ARRAY_SIZE(wm8994_revd_intercon));
|
2011-02-11 23:32:11 +07:00
|
|
|
snd_soc_dapm_add_routes(dapm, wm8994_lateclk_revd_intercon,
|
|
|
|
ARRAY_SIZE(wm8994_lateclk_revd_intercon));
|
|
|
|
} else {
|
|
|
|
snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon,
|
|
|
|
ARRAY_SIZE(wm8994_lateclk_intercon));
|
|
|
|
}
|
2010-11-26 22:21:07 +07:00
|
|
|
break;
|
|
|
|
case WM8958:
|
2011-03-12 01:00:19 +07:00
|
|
|
if (wm8994->revision < 1) {
|
|
|
|
snd_soc_dapm_add_routes(dapm, wm8994_revd_intercon,
|
|
|
|
ARRAY_SIZE(wm8994_revd_intercon));
|
|
|
|
snd_soc_dapm_add_routes(dapm, wm8994_lateclk_revd_intercon,
|
|
|
|
ARRAY_SIZE(wm8994_lateclk_revd_intercon));
|
|
|
|
} else {
|
|
|
|
snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon,
|
|
|
|
ARRAY_SIZE(wm8994_lateclk_intercon));
|
|
|
|
snd_soc_dapm_add_routes(dapm, wm8958_intercon,
|
|
|
|
ARRAY_SIZE(wm8958_intercon));
|
|
|
|
}
|
2011-03-10 02:31:01 +07:00
|
|
|
|
|
|
|
wm8958_dsp2_init(codec);
|
2010-11-26 22:21:07 +07:00
|
|
|
break;
|
2011-05-24 16:35:53 +07:00
|
|
|
case WM1811:
|
|
|
|
snd_soc_dapm_add_routes(dapm, wm8994_lateclk_intercon,
|
|
|
|
ARRAY_SIZE(wm8994_lateclk_intercon));
|
|
|
|
snd_soc_dapm_add_routes(dapm, wm8958_intercon,
|
|
|
|
ARRAY_SIZE(wm8958_intercon));
|
|
|
|
break;
|
2010-11-26 22:21:07 +07:00
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
return 0;
|
|
|
|
|
2010-03-30 02:57:12 +07:00
|
|
|
err_irq:
|
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_SHRT, wm8994);
|
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET, wm8994);
|
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994);
|
2011-02-18 11:05:44 +07:00
|
|
|
if (wm8994->micdet_irq)
|
|
|
|
free_irq(wm8994->micdet_irq, wm8994);
|
2011-07-12 17:47:59 +07:00
|
|
|
for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++)
|
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_FLL1_LOCK + i,
|
|
|
|
&wm8994->fll_locked[i]);
|
2011-07-12 13:47:17 +07:00
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_DCS_DONE,
|
|
|
|
&wm8994->hubs);
|
2011-07-14 10:38:18 +07:00
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, codec);
|
2011-08-16 10:01:27 +07:00
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_TEMP_SHUT, codec);
|
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_TEMP_WARN, codec);
|
2010-01-30 00:47:12 +07:00
|
|
|
err:
|
|
|
|
kfree(wm8994);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
static int wm8994_codec_remove(struct snd_soc_codec *codec)
|
2010-01-30 00:47:12 +07:00
|
|
|
{
|
2010-03-18 03:15:21 +07:00
|
|
|
struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec);
|
2010-11-26 22:21:06 +07:00
|
|
|
struct wm8994 *control = codec->control_data;
|
2011-07-12 17:47:59 +07:00
|
|
|
int i;
|
2010-01-30 00:47:12 +07:00
|
|
|
|
|
|
|
wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF);
|
2010-03-18 03:15:21 +07:00
|
|
|
|
2010-11-27 00:23:43 +07:00
|
|
|
pm_runtime_disable(codec->dev);
|
|
|
|
|
2011-07-12 17:47:59 +07:00
|
|
|
for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++)
|
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_FLL1_LOCK + i,
|
|
|
|
&wm8994->fll_locked[i]);
|
|
|
|
|
2011-07-12 13:47:17 +07:00
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_DCS_DONE,
|
|
|
|
&wm8994->hubs);
|
2011-07-14 10:38:18 +07:00
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, codec);
|
2011-08-16 10:01:27 +07:00
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_TEMP_SHUT, codec);
|
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_TEMP_WARN, codec);
|
2011-07-12 13:47:17 +07:00
|
|
|
|
2010-11-26 22:21:06 +07:00
|
|
|
switch (control->type) {
|
|
|
|
case WM8994:
|
2011-02-18 11:05:44 +07:00
|
|
|
if (wm8994->micdet_irq)
|
|
|
|
free_irq(wm8994->micdet_irq, wm8994);
|
2010-11-26 22:21:06 +07:00
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC2_DET,
|
|
|
|
wm8994);
|
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT,
|
|
|
|
wm8994);
|
|
|
|
wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_DET,
|
|
|
|
wm8994);
|
|
|
|
break;
|
2010-11-26 22:21:09 +07:00
|
|
|
|
2011-05-24 16:35:53 +07:00
|
|
|
case WM1811:
|
2010-11-26 22:21:09 +07:00
|
|
|
case WM8958:
|
2011-02-18 11:05:44 +07:00
|
|
|
if (wm8994->micdet_irq)
|
|
|
|
free_irq(wm8994->micdet_irq, wm8994);
|
2010-11-26 22:21:09 +07:00
|
|
|
break;
|
2010-11-26 22:21:06 +07:00
|
|
|
}
|
2011-03-12 01:09:04 +07:00
|
|
|
if (wm8994->mbc)
|
|
|
|
release_firmware(wm8994->mbc);
|
2011-03-17 05:57:47 +07:00
|
|
|
if (wm8994->mbc_vss)
|
|
|
|
release_firmware(wm8994->mbc_vss);
|
2011-03-18 03:23:43 +07:00
|
|
|
if (wm8994->enh_eq)
|
|
|
|
release_firmware(wm8994->enh_eq);
|
2010-11-23 14:58:39 +07:00
|
|
|
kfree(wm8994->retune_mobile_texts);
|
|
|
|
kfree(wm8994->drc_texts);
|
2010-01-30 00:47:12 +07:00
|
|
|
kfree(wm8994);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-03-18 03:15:21 +07:00
|
|
|
static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
|
|
|
|
.probe = wm8994_codec_probe,
|
|
|
|
.remove = wm8994_codec_remove,
|
|
|
|
.suspend = wm8994_suspend,
|
|
|
|
.resume = wm8994_resume,
|
2010-11-27 00:23:41 +07:00
|
|
|
.read = wm8994_read,
|
|
|
|
.write = wm8994_write,
|
2010-11-19 23:09:15 +07:00
|
|
|
.readable_register = wm8994_readable,
|
|
|
|
.volatile_register = wm8994_volatile,
|
2010-03-18 03:15:21 +07:00
|
|
|
.set_bias_level = wm8994_set_bias_level,
|
2010-11-27 00:23:41 +07:00
|
|
|
|
|
|
|
.reg_cache_size = WM8994_CACHE_SIZE,
|
|
|
|
.reg_cache_default = wm8994_reg_defaults,
|
|
|
|
.reg_word_size = 2,
|
2010-11-27 00:23:42 +07:00
|
|
|
.compress_type = SND_SOC_RBTREE_COMPRESSION,
|
2010-03-18 03:15:21 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static int __devinit wm8994_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994,
|
|
|
|
wm8994_dai, ARRAY_SIZE(wm8994_dai));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __devexit wm8994_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
snd_soc_unregister_codec(&pdev->dev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-01-30 00:47:12 +07:00
|
|
|
static struct platform_driver wm8994_codec_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "wm8994-codec",
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
},
|
2010-03-18 03:15:21 +07:00
|
|
|
.probe = wm8994_probe,
|
|
|
|
.remove = __devexit_p(wm8994_remove),
|
2010-01-30 00:47:12 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
static __init int wm8994_init(void)
|
|
|
|
{
|
|
|
|
return platform_driver_register(&wm8994_codec_driver);
|
|
|
|
}
|
|
|
|
module_init(wm8994_init);
|
|
|
|
|
|
|
|
static __exit void wm8994_exit(void)
|
|
|
|
{
|
|
|
|
platform_driver_unregister(&wm8994_codec_driver);
|
|
|
|
}
|
|
|
|
module_exit(wm8994_exit);
|
|
|
|
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("ASoC WM8994 driver");
|
|
|
|
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_ALIAS("platform:wm8994-codec");
|