u16 mute;
};
+struct wm8350_jack_data {
+ struct snd_soc_jack *jack;
+ int report;
+};
+
struct wm8350_data {
struct snd_soc_codec codec;
struct wm8350_output out1;
struct wm8350_output out2;
+ struct wm8350_jack_data hpl;
+ struct wm8350_jack_data hpr;
struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
+ int fll_freq_out;
+ int fll_freq_in;
};
static unsigned int wm8350_codec_cache_read(struct snd_soc_codec *codec,
static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" };
static const char *wm8350_dacmutem[] = { "Normal", "Soft" };
static const char *wm8350_dacmutes[] = { "Fast", "Slow" };
-static const char *wm8350_dacfilter[] = { "Normal", "Sloping" };
static const char *wm8350_adcfilter[] = { "None", "High Pass" };
static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" };
static const char *wm8350_lr[] = { "Left", "Right" };
SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol),
SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem),
SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes),
- SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 12, 2, wm8350_dacfilter),
SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter),
SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp),
SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol),
0, 255, 0, dac_pcm_tlv),
SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]),
SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]),
- SOC_ENUM("Playback PCM Filter", wm8350_enum[4]),
- SOC_ENUM("Capture PCM Filter", wm8350_enum[5]),
- SOC_ENUM("Capture PCM HP Filter", wm8350_enum[6]),
- SOC_ENUM("Capture ADC Inversion", wm8350_enum[7]),
+ SOC_ENUM("Capture PCM Filter", wm8350_enum[4]),
+ SOC_ENUM("Capture PCM HP Filter", wm8350_enum[5]),
+ SOC_ENUM("Capture ADC Inversion", wm8350_enum[6]),
SOC_WM8350_DOUBLE_R_TLV("Capture PCM Volume",
WM8350_ADC_DIGITAL_VOLUME_L,
WM8350_ADC_DIGITAL_VOLUME_R,
SOC_DAPM_SINGLE_TLV("L3 Capture Volume",
WM8350_INPUT_MIXER_VOLUME_L, 9, 7, 0, out_mix_tlv),
SOC_DAPM_SINGLE("PGA Capture Switch",
- WM8350_LEFT_INPUT_VOLUME, 14, 1, 0),
+ WM8350_LEFT_INPUT_VOLUME, 14, 1, 1),
};
/* Right Input Mixer */
SOC_DAPM_SINGLE_TLV("L3 Capture Volume",
WM8350_INPUT_MIXER_VOLUME_R, 13, 7, 0, out_mix_tlv),
SOC_DAPM_SINGLE("PGA Capture Switch",
- WM8350_RIGHT_INPUT_VOLUME, 14, 1, 0),
+ WM8350_RIGHT_INPUT_VOLUME, 14, 1, 1),
};
/* Left Mic Mixer */
/* Out4 Capture Mux */
static const struct snd_kcontrol_new wm8350_out4_capture_controls =
-SOC_DAPM_ENUM("Route", wm8350_enum[8]);
+SOC_DAPM_ENUM("Route", wm8350_enum[7]);
static const struct snd_soc_dapm_widget wm8350_dapm_widgets[] = {
{"Beep", NULL, "IN3R PGA"},
};
-static int wm8350_add_controls(struct snd_soc_codec *codec)
-{
- int err, i;
-
- for (i = 0; i < ARRAY_SIZE(wm8350_snd_controls); i++) {
- err = snd_ctl_add(codec->card,
- snd_soc_cnew(&wm8350_snd_controls[i],
- codec, NULL));
- if (err < 0)
- return err;
- }
-
- return 0;
-}
-
static int wm8350_add_widgets(struct snd_soc_codec *codec)
{
int ret;
return ret;
}
- return snd_soc_dapm_new_widgets(codec);
+ return 0;
}
static int wm8350_set_dai_sysclk(struct snd_soc_dai *codec_dai,
iface |= 0x3 << 8;
break;
case SND_SOC_DAIFMT_DSP_B:
- iface |= 0x3 << 8; /* lg not sure which mode */
+ iface |= 0x3 << 8 | WM8350_AIF_LRCLK_INV;
break;
default:
return -EINVAL;
* required for LRC in master mode. The DACs or ADCs need a
* valid audio path i.e. pin -> ADC or DAC -> pin before
* the LRC will be enabled in master mode. */
- if (!master && cmd != SNDRV_PCM_TRIGGER_START)
+ if (!master || cmd != SNDRV_PCM_TRIGGER_START)
return 0;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
struct snd_soc_dai *codec_dai)
{
struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8350 *wm8350 = codec->control_data;
u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) &
~WM8350_AIF_WL_MASK;
}
wm8350_codec_write(codec, WM8350_AI_FORMATING, iface);
+
+ /* The sloping stopband filter is recommended for use with
+ * lower sample rates to improve performance.
+ */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ if (params_rate(params) < 24000)
+ wm8350_set_bits(wm8350, WM8350_DAC_MUTE_VOLUME,
+ WM8350_DAC_SB_FILT);
+ else
+ wm8350_clear_bits(wm8350, WM8350_DAC_MUTE_VOLUME,
+ WM8350_DAC_SB_FILT);
+ }
+
return 0;
}
}
static int wm8350_set_fll(struct snd_soc_dai *codec_dai,
- int pll_id, unsigned int freq_in,
+ int pll_id, int source, unsigned int freq_in,
unsigned int freq_out)
{
struct snd_soc_codec *codec = codec_dai->codec;
struct wm8350 *wm8350 = codec->control_data;
+ struct wm8350_data *priv = codec->private_data;
struct _fll_div fll_div;
int ret = 0;
u16 fll_1, fll_4;
+ if (freq_in == priv->fll_freq_in && freq_out == priv->fll_freq_out)
+ return 0;
+
/* power down FLL - we need to do this for reconfiguration */
wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4,
WM8350_FLL_ENA | WM8350_FLL_OSC_ENA);
if (ret < 0)
return ret;
dev_dbg(wm8350->dev,
- "FLL in %d FLL out %d N 0x%x K 0x%x div %d ratio %d",
+ "FLL in %u FLL out %u N 0x%x K 0x%x div %d ratio %d",
freq_in, freq_out, fll_div.n, fll_div.k, fll_div.div,
fll_div.ratio);
wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_OSC_ENA);
wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_FLL_ENA);
+ priv->fll_freq_out = freq_out;
+ priv->fll_freq_in = freq_in;
+
return 0;
}
static int wm8350_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
static int wm8350_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
return 0;
}
+static irqreturn_t wm8350_hp_jack_handler(int irq, void *data)
+{
+ struct wm8350_data *priv = data;
+ struct wm8350 *wm8350 = priv->codec.control_data;
+ u16 reg;
+ int report;
+ int mask;
+ struct wm8350_jack_data *jack = NULL;
+
+ switch (irq - wm8350->irq_base) {
+ case WM8350_IRQ_CODEC_JCK_DET_L:
+ jack = &priv->hpl;
+ mask = WM8350_JACK_L_LVL;
+ break;
+
+ case WM8350_IRQ_CODEC_JCK_DET_R:
+ jack = &priv->hpr;
+ mask = WM8350_JACK_R_LVL;
+ break;
+
+ default:
+ BUG();
+ }
+
+ if (!jack->jack) {
+ dev_warn(wm8350->dev, "Jack interrupt called with no jack\n");
+ return IRQ_NONE;
+ }
+
+ /* Debounce */
+ msleep(200);
+
+ reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS);
+ if (reg & mask)
+ report = jack->report;
+ else
+ report = 0;
+
+ snd_soc_jack_report(jack->jack, report, jack->report);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * wm8350_hp_jack_detect - Enable headphone jack detection.
+ *
+ * @codec: WM8350 codec
+ * @which: left or right jack detect signal
+ * @jack: jack to report detection events on
+ * @report: value to report
+ *
+ * Enables the headphone jack detection of the WM8350.
+ */
+int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which,
+ struct snd_soc_jack *jack, int report)
+{
+ struct wm8350_data *priv = codec->private_data;
+ struct wm8350 *wm8350 = codec->control_data;
+ int irq;
+ int ena;
+
+ switch (which) {
+ case WM8350_JDL:
+ priv->hpl.jack = jack;
+ priv->hpl.report = report;
+ irq = WM8350_IRQ_CODEC_JCK_DET_L;
+ ena = WM8350_JDL_ENA;
+ break;
+
+ case WM8350_JDR:
+ priv->hpr.jack = jack;
+ priv->hpr.report = report;
+ irq = WM8350_IRQ_CODEC_JCK_DET_R;
+ ena = WM8350_JDR_ENA;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
+ wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena);
+
+ /* Sync status */
+ wm8350_hp_jack_handler(irq + wm8350->irq_base, priv);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_hp_jack_detect);
+
static struct snd_soc_codec *wm8350_codec;
static int wm8350_probe(struct platform_device *pdev)
BUG_ON(!wm8350_codec);
- socdev->codec = wm8350_codec;
- codec = socdev->codec;
+ socdev->card->codec = wm8350_codec;
+ codec = socdev->card->codec;
wm8350 = codec->control_data;
priv = codec->private_data;
wm8350_set_bits(wm8350, WM8350_ROUT2_VOLUME,
WM8350_OUT2_VU | WM8350_OUT2R_MUTE);
+ /* Make sure jack detect is disabled to start off with */
+ wm8350_clear_bits(wm8350, WM8350_JACK_DETECT,
+ WM8350_JDL_ENA | WM8350_JDR_ENA);
+
+ wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L,
+ wm8350_hp_jack_handler, 0, "Left jack detect",
+ priv);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R,
+ wm8350_hp_jack_handler, 0, "Right jack detect",
+ priv);
+
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
dev_err(&pdev->dev, "failed to create pcms\n");
return ret;
}
- wm8350_add_controls(codec);
+ snd_soc_add_controls(codec, wm8350_snd_controls,
+ ARRAY_SIZE(wm8350_snd_controls));
wm8350_add_widgets(codec);
wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- ret = snd_soc_init_card(socdev);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to register card\n");
- goto card_err;
- }
-
return 0;
-
-card_err:
- snd_soc_free_pcms(socdev);
- snd_soc_dapm_free(socdev);
- return ret;
}
static int wm8350_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct wm8350 *wm8350 = codec->control_data;
+ struct wm8350_data *priv = codec->private_data;
int ret;
+ wm8350_clear_bits(wm8350, WM8350_JACK_DETECT,
+ WM8350_JDL_ENA | WM8350_JDR_ENA);
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
+
+ wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L, priv);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R, priv);
+
+ priv->hpl.jack = NULL;
+ priv->hpr.jack = NULL;
+
/* cancel any work waiting to be queued. */
ret = cancel_delayed_work(&codec->delayed_work);
SNDRV_PCM_FMTBIT_S20_3LE |\
SNDRV_PCM_FMTBIT_S24_LE)
+static struct snd_soc_dai_ops wm8350_dai_ops = {
+ .hw_params = wm8350_pcm_hw_params,
+ .digital_mute = wm8350_mute,
+ .trigger = wm8350_pcm_trigger,
+ .set_fmt = wm8350_set_dai_fmt,
+ .set_sysclk = wm8350_set_dai_sysclk,
+ .set_pll = wm8350_set_fll,
+ .set_clkdiv = wm8350_set_clkdiv,
+};
+
struct snd_soc_dai wm8350_dai = {
.name = "WM8350",
.playback = {
.rates = WM8350_RATES,
.formats = WM8350_FORMATS,
},
- .ops = {
- .hw_params = wm8350_pcm_hw_params,
- .digital_mute = wm8350_mute,
- .trigger = wm8350_pcm_trigger,
- .set_fmt = wm8350_set_dai_fmt,
- .set_sysclk = wm8350_set_dai_sysclk,
- .set_pll = wm8350_set_fll,
- .set_clkdiv = wm8350_set_clkdiv,
- },
+ .ops = &wm8350_dai_ops,
};
EXPORT_SYMBOL_GPL(wm8350_dai);
};
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8350);
-static int wm8350_codec_probe(struct platform_device *pdev)
+static __devinit int wm8350_codec_probe(struct platform_device *pdev)
{
struct wm8350 *wm8350 = platform_get_drvdata(pdev);
struct wm8350_data *priv;