* wm8903.c -- WM8903 ALSA SoC Audio driver
*
* Copyright 2008 Wolfson Microelectronics
+ * Copyright 2011 NVIDIA, Inc.
*
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
*
#include <linux/init.h>
#include <linux/completion.h>
#include <linux/delay.h>
+#include <linux/gpio.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <sound/soc.h>
#include <sound/initval.h>
#include <sound/wm8903.h>
+#include <trace/events/asoc.h>
#include "wm8903.h"
};
struct wm8903_priv {
-
- u16 reg_cache[ARRAY_SIZE(wm8903_reg_defaults)];
+ struct snd_soc_codec *codec;
int sysclk;
int irq;
int fs;
int deemph;
+ int dcs_pending;
+ int dcs_cache[4];
+
/* Reference count */
int class_w_users;
int mic_short;
int mic_last_report;
int mic_delay;
+
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gpio_chip;
+#endif
};
-static int wm8903_volatile_register(unsigned int reg)
+static int wm8903_volatile_register(struct snd_soc_codec *codec, unsigned int reg)
{
switch (reg) {
case WM8903_SW_RESET_AND_ID:
case WM8903_REVISION_NUMBER:
case WM8903_INTERRUPT_STATUS_1:
case WM8903_WRITE_SEQUENCER_4:
+ case WM8903_POWER_MANAGEMENT_3:
+ case WM8903_POWER_MANAGEMENT_2:
+ case WM8903_DC_SERVO_READBACK_1:
+ case WM8903_DC_SERVO_READBACK_2:
+ case WM8903_DC_SERVO_READBACK_3:
+ case WM8903_DC_SERVO_READBACK_4:
return 1;
default:
sizeof(wm8903_reg_defaults));
}
-#define WM8903_OUTPUT_SHORT 0x8
-#define WM8903_OUTPUT_OUT 0x4
-#define WM8903_OUTPUT_INT 0x2
-#define WM8903_OUTPUT_IN 0x1
-
static int wm8903_cp_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
return 0;
}
-/*
- * Event for headphone and line out amplifier power changes. Special
- * power up/down sequences are required in order to maximise pop/click
- * performance.
- */
-static int wm8903_output_event(struct snd_soc_dapm_widget *w,
- struct snd_kcontrol *kcontrol, int event)
+static int wm8903_dcs_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_codec *codec = w->codec;
- u16 val;
- u16 reg;
- u16 dcs_reg;
- u16 dcs_bit;
- int shift;
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
- switch (w->reg) {
- case WM8903_POWER_MANAGEMENT_2:
- reg = WM8903_ANALOGUE_HP_0;
- dcs_bit = 0 + w->shift;
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ wm8903->dcs_pending |= 1 << w->shift;
break;
- case WM8903_POWER_MANAGEMENT_3:
- reg = WM8903_ANALOGUE_LINEOUT_0;
- dcs_bit = 2 + w->shift;
+ case SND_SOC_DAPM_PRE_PMD:
+ snd_soc_update_bits(codec, WM8903_DC_SERVO_0,
+ 1 << w->shift, 0);
break;
- default:
- BUG();
- return -EINVAL; /* Spurious warning from some compilers */
}
- switch (w->shift) {
- case 0:
- shift = 0;
- break;
- case 1:
- shift = 4;
- break;
- default:
- BUG();
- return -EINVAL; /* Spurious warning from some compilers */
- }
+ return 0;
+}
- if (event & SND_SOC_DAPM_PRE_PMU) {
- val = snd_soc_read(codec, reg);
+#define WM8903_DCS_MODE_WRITE_STOP 0
+#define WM8903_DCS_MODE_START_STOP 2
- /* Short the output */
- val &= ~(WM8903_OUTPUT_SHORT << shift);
- snd_soc_write(codec, reg, val);
- }
+static void wm8903_seq_notifier(struct snd_soc_dapm_context *dapm,
+ enum snd_soc_dapm_type event, int subseq)
+{
+ struct snd_soc_codec *codec = container_of(dapm,
+ struct snd_soc_codec, dapm);
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int dcs_mode = WM8903_DCS_MODE_WRITE_STOP;
+ int i, val;
- if (event & SND_SOC_DAPM_POST_PMU) {
- val = snd_soc_read(codec, reg);
+ /* Complete any pending DC servo starts */
+ if (wm8903->dcs_pending) {
+ dev_dbg(codec->dev, "Starting DC servo for %x\n",
+ wm8903->dcs_pending);
- val |= (WM8903_OUTPUT_IN << shift);
- snd_soc_write(codec, reg, val);
+ /* If we've no cached values then we need to do startup */
+ for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) {
+ if (!(wm8903->dcs_pending & (1 << i)))
+ continue;
- val |= (WM8903_OUTPUT_INT << shift);
- snd_soc_write(codec, reg, val);
+ if (wm8903->dcs_cache[i]) {
+ dev_dbg(codec->dev,
+ "Restore DC servo %d value %x\n",
+ 3 - i, wm8903->dcs_cache[i]);
+
+ snd_soc_write(codec, WM8903_DC_SERVO_4 + i,
+ wm8903->dcs_cache[i] & 0xff);
+ } else {
+ dev_dbg(codec->dev,
+ "Calibrate DC servo %d\n", 3 - i);
+ dcs_mode = WM8903_DCS_MODE_START_STOP;
+ }
+ }
- /* Turn on the output ENA_OUTP */
- val |= (WM8903_OUTPUT_OUT << shift);
- snd_soc_write(codec, reg, val);
+ /* Don't trust the cache for analogue */
+ if (wm8903->class_w_users)
+ dcs_mode = WM8903_DCS_MODE_START_STOP;
- /* Enable the DC servo */
- dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0);
- dcs_reg |= dcs_bit;
- snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg);
+ snd_soc_update_bits(codec, WM8903_DC_SERVO_2,
+ WM8903_DCS_MODE_MASK, dcs_mode);
- /* Remove the short */
- val |= (WM8903_OUTPUT_SHORT << shift);
- snd_soc_write(codec, reg, val);
- }
+ snd_soc_update_bits(codec, WM8903_DC_SERVO_0,
+ WM8903_DCS_ENA_MASK, wm8903->dcs_pending);
- if (event & SND_SOC_DAPM_PRE_PMD) {
- val = snd_soc_read(codec, reg);
+ switch (dcs_mode) {
+ case WM8903_DCS_MODE_WRITE_STOP:
+ break;
- /* Short the output */
- val &= ~(WM8903_OUTPUT_SHORT << shift);
- snd_soc_write(codec, reg, val);
+ case WM8903_DCS_MODE_START_STOP:
+ msleep(270);
- /* Disable the DC servo */
- dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0);
- dcs_reg &= ~dcs_bit;
- snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg);
+ /* Cache the measured offsets for digital */
+ if (wm8903->class_w_users)
+ break;
- /* Then disable the intermediate and output stages */
- val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT |
- WM8903_OUTPUT_IN) << shift);
- snd_soc_write(codec, reg, val);
- }
+ for (i = 0; i < ARRAY_SIZE(wm8903->dcs_cache); i++) {
+ if (!(wm8903->dcs_pending & (1 << i)))
+ continue;
- return 0;
+ val = snd_soc_read(codec,
+ WM8903_DC_SERVO_READBACK_1 + i);
+ dev_dbg(codec->dev, "DC servo %d: %x\n",
+ 3 - i, val);
+ wm8903->dcs_cache[i] = val;
+ }
+ break;
+
+ default:
+ pr_warn("DCS mode %d delay not set\n", dcs_mode);
+ break;
+ }
+
+ wm8903->dcs_pending = 0;
+ }
}
/*
static const struct soc_enum rsidetone_enum =
SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text);
+static const char *aif_text[] = {
+ "Left", "Right"
+};
+
+static const struct soc_enum lcapture_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 7, 2, aif_text);
+
+static const struct soc_enum rcapture_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 6, 2, aif_text);
+
+static const struct soc_enum lplay_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 5, 2, aif_text);
+
+static const struct soc_enum rplay_enum =
+ SOC_ENUM_SINGLE(WM8903_AUDIO_INTERFACE_0, 4, 2, aif_text);
+
static const struct snd_kcontrol_new wm8903_snd_controls[] = {
/* Input PGAs - No TLV since the scale depends on PGA mode */
static const struct snd_kcontrol_new rsidetone_mux =
SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum);
+static const struct snd_kcontrol_new lcapture_mux =
+ SOC_DAPM_ENUM("Left Capture Mux", lcapture_enum);
+
+static const struct snd_kcontrol_new rcapture_mux =
+ SOC_DAPM_ENUM("Right Capture Mux", rcapture_enum);
+
+static const struct snd_kcontrol_new lplay_mux =
+ SOC_DAPM_ENUM("Left Playback Mux", lplay_enum);
+
+static const struct snd_kcontrol_new rplay_mux =
+ SOC_DAPM_ENUM("Right Playback Mux", rplay_enum);
+
static const struct snd_kcontrol_new left_output_mixer[] = {
SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0),
SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0),
SND_SOC_DAPM_PGA("Left Input PGA", WM8903_POWER_MANAGEMENT_0, 1, 0, NULL, 0),
SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0),
-SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0),
-SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0),
+SND_SOC_DAPM_ADC("ADCL", NULL, WM8903_POWER_MANAGEMENT_6, 1, 0),
+SND_SOC_DAPM_ADC("ADCR", NULL, WM8903_POWER_MANAGEMENT_6, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Capture Mux", SND_SOC_NOPM, 0, 0, &lcapture_mux),
+SND_SOC_DAPM_MUX("Right Capture Mux", SND_SOC_NOPM, 0, 0, &rcapture_mux),
+
+SND_SOC_DAPM_AIF_OUT("AIFTXL", "Left HiFi Capture", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_OUT("AIFTXR", "Right HiFi Capture", 0, SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux),
SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux),
-SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0),
-SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0),
+SND_SOC_DAPM_AIF_IN("AIFRXL", "Left Playback", 0, SND_SOC_NOPM, 0, 0),
+SND_SOC_DAPM_AIF_IN("AIFRXR", "Right Playback", 0, SND_SOC_NOPM, 0, 0),
+
+SND_SOC_DAPM_MUX("Left Playback Mux", SND_SOC_NOPM, 0, 0, &lplay_mux),
+SND_SOC_DAPM_MUX("Right Playback Mux", SND_SOC_NOPM, 0, 0, &rplay_mux),
+
+SND_SOC_DAPM_DAC("DACL", NULL, WM8903_POWER_MANAGEMENT_6, 3, 0),
+SND_SOC_DAPM_DAC("DACR", NULL, WM8903_POWER_MANAGEMENT_6, 2, 0),
SND_SOC_DAPM_MIXER("Left Output Mixer", WM8903_POWER_MANAGEMENT_1, 1, 0,
left_output_mixer, ARRAY_SIZE(left_output_mixer)),
SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0,
right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)),
-SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
- 1, 0, NULL, 0, wm8903_output_event,
- SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
- SND_SOC_DAPM_PRE_PMD),
-SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2,
- 0, 0, NULL, 0, wm8903_output_event,
- SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
- SND_SOC_DAPM_PRE_PMD),
-
-SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0,
- NULL, 0, wm8903_output_event,
- SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
- SND_SOC_DAPM_PRE_PMD),
-SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0,
- NULL, 0, wm8903_output_event,
- SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
- SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("Left Headphone Output PGA", 0, WM8903_ANALOGUE_HP_0,
+ 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("Right Headphone Output PGA", 0, WM8903_ANALOGUE_HP_0,
+ 0, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_S("Left Line Output PGA", 0, WM8903_ANALOGUE_LINEOUT_0, 4, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("Right Line Output PGA", 0, WM8903_ANALOGUE_LINEOUT_0, 0, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_PGA_S("HPL_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 7, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 6, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_ENA_DLY", 1, WM8903_ANALOGUE_HP_0, 5, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPR_RMV_SHORT", 4, WM8903_ANALOGUE_HP_0, 3, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPR_ENA_OUTP", 3, WM8903_ANALOGUE_HP_0, 2, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPR_ENA_DLY", 1, WM8903_ANALOGUE_HP_0, 1, 0, NULL, 0),
+
+SND_SOC_DAPM_PGA_S("LINEOUTL_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 7, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 6, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTL_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 5, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTR_RMV_SHORT", 4, WM8903_ANALOGUE_LINEOUT_0, 3, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_OUTP", 3, WM8903_ANALOGUE_LINEOUT_0, 2, 0,
+ NULL, 0),
+SND_SOC_DAPM_PGA_S("LINEOUTR_ENA_DLY", 1, WM8903_ANALOGUE_LINEOUT_0, 1, 0,
+ NULL, 0),
+
+SND_SOC_DAPM_SUPPLY("DCS Master", WM8903_DC_SERVO_0, 4, 0, NULL, 0),
+SND_SOC_DAPM_PGA_S("HPL_DCS", 3, SND_SOC_NOPM, 3, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("HPR_DCS", 3, SND_SOC_NOPM, 2, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("LINEOUTL_DCS", 3, SND_SOC_NOPM, 1, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+SND_SOC_DAPM_PGA_S("LINEOUTR_DCS", 3, SND_SOC_NOPM, 0, 0, wm8903_dcs_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0,
NULL, 0),
SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0,
wm8903_cp_event, SND_SOC_DAPM_POST_PMU),
SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0),
+SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8903_CLOCK_RATES_2, 2, 0, NULL, 0),
};
static const struct snd_soc_dapm_route intercon[] = {
+ { "CLK_DSP", NULL, "CLK_SYS" },
+ { "Mic Bias", NULL, "CLK_SYS" },
+ { "HPL_DCS", NULL, "CLK_SYS" },
+ { "HPR_DCS", NULL, "CLK_SYS" },
+ { "LINEOUTL_DCS", NULL, "CLK_SYS" },
+ { "LINEOUTR_DCS", NULL, "CLK_SYS" },
+
{ "Left Input Mux", "IN1L", "IN1L" },
{ "Left Input Mux", "IN2L", "IN2L" },
{ "Left Input Mux", "IN3L", "IN3L" },
{ "Left Input PGA", NULL, "Left Input Mode Mux" },
{ "Right Input PGA", NULL, "Right Input Mode Mux" },
+ { "Left Capture Mux", "Left", "ADCL" },
+ { "Left Capture Mux", "Right", "ADCR" },
+
+ { "Right Capture Mux", "Left", "ADCL" },
+ { "Right Capture Mux", "Right", "ADCR" },
+
+ { "AIFTXL", NULL, "Left Capture Mux" },
+ { "AIFTXR", NULL, "Right Capture Mux" },
+
{ "ADCL", NULL, "Left Input PGA" },
{ "ADCL", NULL, "CLK_DSP" },
{ "ADCR", NULL, "Right Input PGA" },
{ "ADCR", NULL, "CLK_DSP" },
+ { "Left Playback Mux", "Left", "AIFRXL" },
+ { "Left Playback Mux", "Right", "AIFRXR" },
+
+ { "Right Playback Mux", "Left", "AIFRXL" },
+ { "Right Playback Mux", "Right", "AIFRXR" },
+
{ "DACL Sidetone", "Left", "ADCL" },
{ "DACL Sidetone", "Right", "ADCR" },
{ "DACR Sidetone", "Left", "ADCL" },
{ "DACR Sidetone", "Right", "ADCR" },
+ { "DACL", NULL, "Left Playback Mux" },
{ "DACL", NULL, "DACL Sidetone" },
{ "DACL", NULL, "CLK_DSP" },
+
+ { "DACR", NULL, "Right Playback Mux" },
{ "DACR", NULL, "DACR Sidetone" },
{ "DACR", NULL, "CLK_DSP" },
{ "Left Speaker PGA", NULL, "Left Speaker Mixer" },
{ "Right Speaker PGA", NULL, "Right Speaker Mixer" },
- { "HPOUTL", NULL, "Left Headphone Output PGA" },
- { "HPOUTR", NULL, "Right Headphone Output PGA" },
+ { "HPL_ENA_DLY", NULL, "Left Headphone Output PGA" },
+ { "HPR_ENA_DLY", NULL, "Right Headphone Output PGA" },
+ { "LINEOUTL_ENA_DLY", NULL, "Left Line Output PGA" },
+ { "LINEOUTR_ENA_DLY", NULL, "Right Line Output PGA" },
+
+ { "HPL_DCS", NULL, "DCS Master" },
+ { "HPR_DCS", NULL, "DCS Master" },
+ { "LINEOUTL_DCS", NULL, "DCS Master" },
+ { "LINEOUTR_DCS", NULL, "DCS Master" },
+
+ { "HPL_DCS", NULL, "HPL_ENA_DLY" },
+ { "HPR_DCS", NULL, "HPR_ENA_DLY" },
+ { "LINEOUTL_DCS", NULL, "LINEOUTL_ENA_DLY" },
+ { "LINEOUTR_DCS", NULL, "LINEOUTR_ENA_DLY" },
+
+ { "HPL_ENA_OUTP", NULL, "HPL_DCS" },
+ { "HPR_ENA_OUTP", NULL, "HPR_DCS" },
+ { "LINEOUTL_ENA_OUTP", NULL, "LINEOUTL_DCS" },
+ { "LINEOUTR_ENA_OUTP", NULL, "LINEOUTR_DCS" },
- { "LINEOUTL", NULL, "Left Line Output PGA" },
- { "LINEOUTR", NULL, "Right Line Output PGA" },
+ { "HPL_RMV_SHORT", NULL, "HPL_ENA_OUTP" },
+ { "HPR_RMV_SHORT", NULL, "HPR_ENA_OUTP" },
+ { "LINEOUTL_RMV_SHORT", NULL, "LINEOUTL_ENA_OUTP" },
+ { "LINEOUTR_RMV_SHORT", NULL, "LINEOUTR_ENA_OUTP" },
+
+ { "HPOUTL", NULL, "HPL_RMV_SHORT" },
+ { "HPOUTR", NULL, "HPR_RMV_SHORT" },
+ { "LINEOUTL", NULL, "LINEOUTL_RMV_SHORT" },
+ { "LINEOUTR", NULL, "LINEOUTR_RMV_SHORT" },
{ "LOP", NULL, "Left Speaker PGA" },
{ "LON", NULL, "Left Speaker PGA" },
static int wm8903_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
- u16 reg, reg2;
+ u16 reg;
switch (level) {
case SND_SOC_BIAS_ON:
wm8903_run_sequence(codec, 0);
wm8903_sync_reg_cache(codec, codec->reg_cache);
- /* Enable low impedence charge pump output */
- reg = snd_soc_read(codec,
- WM8903_CONTROL_INTERFACE_TEST_1);
- snd_soc_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
- reg | WM8903_TEST_KEY);
- reg2 = snd_soc_read(codec, WM8903_CHARGE_PUMP_TEST_1);
- snd_soc_write(codec, WM8903_CHARGE_PUMP_TEST_1,
- reg2 | WM8903_CP_SW_KELVIN_MODE_MASK);
- snd_soc_write(codec, WM8903_CONTROL_INTERFACE_TEST_1,
- reg);
-
/* By default no bypass paths are enabled so
* enable Class W support.
*/
dev_dbg(codec->dev, "Enabling Class W\n");
- snd_soc_write(codec, WM8903_CLASS_W_0, reg |
- WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V);
+ snd_soc_update_bits(codec, WM8903_CLASS_W_0,
+ WM8903_CP_DYN_FREQ |
+ WM8903_CP_DYN_V,
+ WM8903_CP_DYN_FREQ |
+ WM8903_CP_DYN_V);
}
reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0);
break;
case SND_SOC_BIAS_OFF:
+ snd_soc_update_bits(codec, WM8903_CLOCK_RATES_2,
+ WM8903_CLK_SYS_ENA, WM8903_CLK_SYS_ENA);
wm8903_run_sequence(codec, 32);
- reg = snd_soc_read(codec, WM8903_CLOCK_RATES_2);
- reg &= ~WM8903_CLK_SYS_ENA;
- snd_soc_write(codec, WM8903_CLOCK_RATES_2, reg);
+ snd_soc_update_bits(codec, WM8903_CLOCK_RATES_2,
+ WM8903_CLK_SYS_ENA, 0);
break;
}
mic_report = wm8903->mic_last_report;
int_pol = snd_soc_read(codec, WM8903_INTERRUPT_POLARITY_1);
+#ifndef CONFIG_SND_SOC_WM8903_MODULE
+ if (int_val & (WM8903_MICSHRT_EINT | WM8903_MICDET_EINT))
+ trace_snd_soc_jack_irq(dev_name(codec->dev));
+#endif
+
if (int_val & WM8903_MICSHRT_EINT) {
dev_dbg(codec->dev, "Microphone short (pol=%x)\n", int_pol);
return 0;
}
+#ifdef CONFIG_GPIOLIB
+static inline struct wm8903_priv *gpio_to_wm8903(struct gpio_chip *chip)
+{
+ return container_of(chip, struct wm8903_priv, gpio_chip);
+}
+
+static int wm8903_gpio_request(struct gpio_chip *chip, unsigned offset)
+{
+ if (offset >= WM8903_NUM_GPIO)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int wm8903_gpio_direction_in(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+ unsigned int mask, val;
+
+ mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK;
+ val = (WM8903_GPn_FN_GPIO_INPUT << WM8903_GP1_FN_SHIFT) |
+ WM8903_GP1_DIR;
+
+ return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ mask, val);
+}
+
+static int wm8903_gpio_get(struct gpio_chip *chip, unsigned offset)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+ int reg;
+
+ reg = snd_soc_read(codec, WM8903_GPIO_CONTROL_1 + offset);
+
+ return (reg & WM8903_GP1_LVL_MASK) >> WM8903_GP1_LVL_SHIFT;
+}
+
+static int wm8903_gpio_direction_out(struct gpio_chip *chip,
+ unsigned offset, int value)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+ unsigned int mask, val;
+
+ mask = WM8903_GP1_FN_MASK | WM8903_GP1_DIR_MASK | WM8903_GP1_LVL_MASK;
+ val = (WM8903_GPn_FN_GPIO_OUTPUT << WM8903_GP1_FN_SHIFT) |
+ (value << WM8903_GP2_LVL_SHIFT);
+
+ return snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ mask, val);
+}
+
+static void wm8903_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
+{
+ struct wm8903_priv *wm8903 = gpio_to_wm8903(chip);
+ struct snd_soc_codec *codec = wm8903->codec;
+
+ snd_soc_update_bits(codec, WM8903_GPIO_CONTROL_1 + offset,
+ WM8903_GP1_LVL_MASK,
+ !!value << WM8903_GP1_LVL_SHIFT);
+}
+
+static struct gpio_chip wm8903_template_chip = {
+ .label = "wm8903",
+ .owner = THIS_MODULE,
+ .request = wm8903_gpio_request,
+ .direction_input = wm8903_gpio_direction_in,
+ .get = wm8903_gpio_get,
+ .direction_output = wm8903_gpio_direction_out,
+ .set = wm8903_gpio_set,
+ .can_sleep = 1,
+};
+
+static void wm8903_init_gpio(struct snd_soc_codec *codec)
+{
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev);
+ int ret;
+
+ wm8903->gpio_chip = wm8903_template_chip;
+ wm8903->gpio_chip.ngpio = WM8903_NUM_GPIO;
+ wm8903->gpio_chip.dev = codec->dev;
+
+ if (pdata && pdata->gpio_base)
+ wm8903->gpio_chip.base = pdata->gpio_base;
+ else
+ wm8903->gpio_chip.base = -1;
+
+ ret = gpiochip_add(&wm8903->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to add GPIOs: %d\n", ret);
+}
+
+static void wm8903_free_gpio(struct snd_soc_codec *codec)
+{
+ struct wm8903_priv *wm8903 = snd_soc_codec_get_drvdata(codec);
+ int ret;
+
+ ret = gpiochip_remove(&wm8903->gpio_chip);
+ if (ret != 0)
+ dev_err(codec->dev, "Failed to remove GPIOs: %d\n", ret);
+}
+#else
+static void wm8903_init_gpio(struct snd_soc_codec *codec)
+{
+}
+
+static void wm8903_free_gpio(struct snd_soc_codec *codec)
+{
+}
+#endif
+
static int wm8903_probe(struct snd_soc_codec *codec)
{
struct wm8903_platform_data *pdata = dev_get_platdata(codec->dev);
int trigger, irq_pol;
u16 val;
+ wm8903->codec = codec;
init_completion(&wm8903->wseq);
ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C);
}
val = snd_soc_read(codec, WM8903_REVISION_NUMBER);
- dev_info(codec->dev, "WM8903 revision %d\n",
- val & WM8903_CHIP_REV_MASK);
+ dev_info(codec->dev, "WM8903 revision %c\n",
+ (val & WM8903_CHIP_REV_MASK) + 'A');
wm8903_reset(codec);
/* Set up GPIOs and microphone detection */
if (pdata) {
for (i = 0; i < ARRAY_SIZE(pdata->gpio_cfg); i++) {
- if (!pdata->gpio_cfg[i])
+ if (pdata->gpio_cfg[i] == WM8903_GPIO_NO_CONFIG)
continue;
snd_soc_write(codec, WM8903_GPIO_CONTROL_1 + i,
snd_soc_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val);
/* Enable DAC soft mute by default */
- val = snd_soc_read(codec, WM8903_DAC_DIGITAL_1);
- val |= WM8903_DAC_MUTEMODE;
- snd_soc_write(codec, WM8903_DAC_DIGITAL_1, val);
+ snd_soc_update_bits(codec, WM8903_DAC_DIGITAL_1,
+ WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE,
+ WM8903_DAC_MUTEMODE | WM8903_DAC_MUTE);
snd_soc_add_controls(codec, wm8903_snd_controls,
ARRAY_SIZE(wm8903_snd_controls));
wm8903_add_widgets(codec);
+ wm8903_init_gpio(codec);
+
return ret;
}
/* power down chip */
static int wm8903_remove(struct snd_soc_codec *codec)
{
+ wm8903_free_gpio(codec);
wm8903_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
}
.reg_word_size = sizeof(u16),
.reg_cache_default = wm8903_reg_defaults,
.volatile_register = wm8903_volatile_register,
+ .seq_notifier = wm8903_seq_notifier,
};
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
static struct i2c_driver wm8903_i2c_driver = {
.driver = {
- .name = "wm8903-codec",
+ .name = "wm8903",
.owner = THIS_MODULE,
},
.probe = wm8903_i2c_probe,