diff options
Diffstat (limited to 'sound/soc/codecs/wm8960.c')
| -rw-r--r-- | sound/soc/codecs/wm8960.c | 926 |
1 files changed, 708 insertions, 218 deletions
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 0a4ffdd1d2a7..384e8e703446 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * wm8960.c -- WM8960 ALSA SoC Audio driver * * Copyright 2007-11 Wolfson Microelectronics, plc * * Author: Liam Girdwood - * - * 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> @@ -15,7 +12,9 @@ #include <linux/init.h> #include <linux/delay.h> #include <linux/pm.h> +#include <linux/clk.h> #include <linux/i2c.h> +#include <linux/acpi.h> #include <linux/slab.h> #include <sound/core.h> #include <sound/pcm.h> @@ -47,6 +46,11 @@ #define WM8960_DISOP 0x40 #define WM8960_DRES_MASK 0x30 +#define WM8960_DSCH_TOUT 600 /* discharge timeout, ms */ + +static bool is_pll_freq_available(unsigned int source, unsigned int target); +static int wm8960_set_pll(struct snd_soc_component *component, + unsigned int freq_in, unsigned int freq_out); /* * wm8960 register cache * We can't read the WM8960 register space when we are @@ -116,18 +120,36 @@ static bool wm8960_volatile(struct device *dev, unsigned int reg) } } +#define WM8960_NUM_SUPPLIES 5 +static const char *wm8960_supply_names[WM8960_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD", + "AVDD", + "SPKVDD1", + "SPKVDD2", +}; + struct wm8960_priv { + struct clk *mclk; struct regmap *regmap; - int (*set_bias_level)(struct snd_soc_codec *, + int (*set_bias_level)(struct snd_soc_component *, enum snd_soc_bias_level level); struct snd_soc_dapm_widget *lout1; struct snd_soc_dapm_widget *rout1; struct snd_soc_dapm_widget *out3; bool deemph; - int playback_fs; + int lrclk; + int bclk; + int sysclk; + int clk_id; + int freq_in; + bool is_stream_in_use[2]; + struct wm8960_data pdata; + ktime_t dsch_start; + struct regulator_bulk_data supplies[WM8960_NUM_SUPPLIES]; }; -#define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) +#define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0) /* enumerated controls */ static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", @@ -136,6 +158,14 @@ static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"}; static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; +static const char *wm8960_adc_data_output_sel[] = { + "Left Data = Left ADC; Right Data = Right ADC", + "Left Data = Left ADC; Right Data = Left ADC", + "Left Data = Right ADC; Right Data = Right ADC", + "Left Data = Right ADC; Right Data = Left ADC", +}; +static const char *wm8960_dmonomix[] = {"Stereo", "Mono"}; +static const char *wm8960_dacslope[] = {"Normal", "Sloping"}; static const struct soc_enum wm8960_enum[] = { SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), @@ -144,13 +174,16 @@ static const struct soc_enum wm8960_enum[] = { SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), + SOC_ENUM_SINGLE(WM8960_ADDCTL1, 2, 4, wm8960_adc_data_output_sel), + SOC_ENUM_SINGLE(WM8960_ADDCTL1, 4, 2, wm8960_dmonomix), + SOC_ENUM_SINGLE(WM8960_DACCTL2, 1, 2, wm8960_dacslope), }; static const int deemph_settings[] = { 0, 32000, 44100, 48000 }; -static int wm8960_set_deemph(struct snd_soc_codec *codec) +static int wm8960_set_deemph(struct snd_soc_component *component) { - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); int val, i, best; /* If we're using deemphasis select the nearest available sample @@ -159,8 +192,8 @@ static int wm8960_set_deemph(struct snd_soc_codec *codec) if (wm8960->deemph) { best = 1; for (i = 2; i < ARRAY_SIZE(deemph_settings); i++) { - if (abs(deemph_settings[i] - wm8960->playback_fs) < - abs(deemph_settings[best] - wm8960->playback_fs)) + if (abs(deemph_settings[i] - wm8960->lrclk) < + abs(deemph_settings[best] - wm8960->lrclk)) best = i; } @@ -169,59 +202,68 @@ static int wm8960_set_deemph(struct snd_soc_codec *codec) val = 0; } - dev_dbg(codec->dev, "Set deemphasis %d\n", val); + dev_dbg(component->dev, "Set deemphasis %d\n", val); - return snd_soc_update_bits(codec, WM8960_DACCTL1, + return snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x6, val); } static int wm8960_get_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); - ucontrol->value.enumerated.item[0] = wm8960->deemph; + ucontrol->value.integer.value[0] = wm8960->deemph; return 0; } static int wm8960_put_deemph(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - int deemph = ucontrol->value.enumerated.item[0]; + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + unsigned int deemph = ucontrol->value.integer.value[0]; if (deemph > 1) return -EINVAL; wm8960->deemph = deemph; - return wm8960_set_deemph(codec); + return wm8960_set_deemph(component); } -static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0); -static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); -static const DECLARE_TLV_DB_SCALE(boost_tlv, -1200, 300, 1); +static const DECLARE_TLV_DB_SCALE(lineinboost_tlv, -1500, 300, 1); +static const SNDRV_CTL_TLVD_DECLARE_DB_RANGE(micboost_tlv, + 0, 1, TLV_DB_SCALE_ITEM(0, 1300, 0), + 2, 3, TLV_DB_SCALE_ITEM(2000, 900, 0), +); static const struct snd_kcontrol_new wm8960_snd_controls[] = { SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, - 0, 63, 0, adc_tlv), + 0, 63, 0, inpga_tlv), SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, 6, 1, 0), SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, - 7, 1, 0), + 7, 1, 1), -SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT3 Volume", - WM8960_INBMIX1, 4, 7, 0, boost_tlv), -SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT2 Volume", - WM8960_INBMIX1, 1, 7, 0, boost_tlv), SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT3 Volume", - WM8960_INBMIX2, 4, 7, 0, boost_tlv), + WM8960_INBMIX1, 4, 7, 0, lineinboost_tlv), SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT2 Volume", - WM8960_INBMIX2, 1, 7, 0, boost_tlv), + WM8960_INBMIX1, 1, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT3 Volume", + WM8960_INBMIX2, 4, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT2 Volume", + WM8960_INBMIX2, 1, 7, 0, lineinboost_tlv), +SOC_SINGLE_TLV("Right Input Boost Mixer RINPUT1 Volume", + WM8960_RINPATH, 4, 3, 0, micboost_tlv), +SOC_SINGLE_TLV("Left Input Boost Mixer LINPUT1 Volume", + WM8960_LINPATH, 4, 3, 0, micboost_tlv), SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, 0, 255, 0, dac_tlv), @@ -242,7 +284,7 @@ SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), SOC_ENUM("ADC Polarity", wm8960_enum[0]), SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), -SOC_ENUM("DAC Polarity", wm8960_enum[2]), +SOC_ENUM("DAC Polarity", wm8960_enum[1]), SOC_SINGLE_BOOL_EXT("DAC Deemphasis Switch", 0, wm8960_get_deemph, wm8960_put_deemph), @@ -263,8 +305,8 @@ SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), -SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH, - 0, 127, 0), +SOC_DOUBLE_R_TLV("ADC PCM Capture Volume", WM8960_LADC, WM8960_RADC, + 0, 255, 0, adc_tlv), SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", WM8960_BYPASS1, 4, 7, 1, bypass_tlv), @@ -274,6 +316,10 @@ SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", WM8960_BYPASS2, 4, 7, 1, bypass_tlv), SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), + +SOC_ENUM("ADC Data Output Select", wm8960_enum[6]), +SOC_ENUM("DAC Mono Mix", wm8960_enum[7]), +SOC_ENUM("DAC Filter Characteristics", wm8960_enum[8]), }; static const struct snd_kcontrol_new wm8960_lin_boost[] = { @@ -380,8 +426,8 @@ static const struct snd_soc_dapm_route audio_paths[] = { { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, - { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", }, - { "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */ + { "Left Input Mixer", "Boost Switch", "Left Boost Mixer" }, + { "Left Input Mixer", "Boost Switch", "LINPUT1" }, /* Really Boost Switch */ { "Left Input Mixer", NULL, "LINPUT2" }, { "Left Input Mixer", NULL, "LINPUT3" }, @@ -389,20 +435,20 @@ static const struct snd_soc_dapm_route audio_paths[] = { { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, - { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", }, - { "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */ + { "Right Input Mixer", "Boost Switch", "Right Boost Mixer" }, + { "Right Input Mixer", "Boost Switch", "RINPUT1" }, /* Really Boost Switch */ { "Right Input Mixer", NULL, "RINPUT2" }, - { "Right Input Mixer", NULL, "LINPUT3" }, + { "Right Input Mixer", NULL, "RINPUT3" }, { "Left ADC", NULL, "Left Input Mixer" }, { "Right ADC", NULL, "Right Input Mixer" }, { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, - { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} , + { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer" }, { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, - { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , + { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" }, { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, { "LOUT1 PGA", NULL, "Left Output Mixer" }, @@ -438,11 +484,11 @@ static const struct snd_soc_dapm_route audio_paths_capless[] = { { "OUT3 VMID", NULL, "Right Output Mixer" }, }; -static int wm8960_add_widgets(struct snd_soc_codec *codec) +static int wm8960_add_widgets(struct snd_soc_component *component) { - struct wm8960_data *pdata = codec->dev->platform_data; - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - struct snd_soc_dapm_context *dapm = &codec->dapm; + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + struct wm8960_data *pdata = &wm8960->pdata; + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); struct snd_soc_dapm_widget *w; snd_soc_dapm_new_controls(dapm, wm8960_dapm_widgets, @@ -472,8 +518,8 @@ static int wm8960_add_widgets(struct snd_soc_codec *codec) * list each time to find the desired power state do so now * and save the result. */ - list_for_each_entry(w, &codec->card->widgets, list) { - if (w->dapm != &codec->dapm) + list_for_each_entry(w, &component->card->widgets, list) { + if (w->dapm != dapm) continue; if (strcmp(w->name, "LOUT1 PGA") == 0) wm8960->lout1 = w; @@ -489,15 +535,15 @@ static int wm8960_add_widgets(struct snd_soc_codec *codec) static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { - struct snd_soc_codec *codec = codec_dai->codec; + struct snd_soc_component *component = codec_dai->component; u16 iface = 0; /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBM_CFM: + case SND_SOC_DAIFMT_CBP_CFP: iface |= 0x0040; break; - case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBC_CFC: break; default: return -EINVAL; @@ -541,7 +587,7 @@ static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, } /* set iface */ - snd_soc_write(codec, WM8960_IFACE1, iface); + snd_soc_component_write(component, WM8960_IFACE1, iface); return 0; } @@ -555,138 +601,428 @@ static struct { { 22050, 2 }, { 24000, 2 }, { 16000, 3 }, - { 11250, 4 }, + { 11025, 4 }, { 12000, 4 }, { 8000, 5 }, }; +/* -1 for reserved value */ +static const int sysclk_divs[] = { 1, -1, 2, -1 }; + +/* Multiply 256 for internal 256 div */ +static const int dac_divs[] = { 256, 384, 512, 768, 1024, 1408, 1536 }; + +/* Multiply 10 to eliminate decimials */ +static const int bclk_divs[] = { + 10, 15, 20, 30, 40, 55, 60, 80, 110, + 120, 160, 220, 240, 320, 320, 320 +}; + +/** + * wm8960_configure_sysclk - checks if there is a sysclk frequency available + * The sysclk must be chosen such that: + * - sysclk = MCLK / sysclk_divs + * - lrclk = sysclk / dac_divs + * - 10 * bclk = sysclk / bclk_divs + * + * @wm8960: codec private data + * @mclk: MCLK used to derive sysclk + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * -1, in case no sysclk frequency available found + * >=0, in case we could derive bclk and lrclk from sysclk using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + int sysclk, bclk, lrclk; + int i, j, k; + int diff; + + /* marker for no match */ + *bclk_idx = -1; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + + /* check if the sysclk frequency is available. */ + for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + if (sysclk_divs[i] == -1) + continue; + sysclk = mclk / sysclk_divs[i]; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + if (sysclk != dac_divs[j] * lrclk) + continue; + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + break; + } + } + if (k != ARRAY_SIZE(bclk_divs)) + break; + } + if (j != ARRAY_SIZE(dac_divs)) + break; + } + return *bclk_idx; +} + +/** + * wm8960_configure_pll - checks if there is a PLL out frequency available + * The PLL out frequency must be chosen such that: + * - sysclk = lrclk * dac_divs + * - freq_out = sysclk * sysclk_divs + * - 10 * sysclk = bclk * bclk_divs + * + * If we cannot find an exact match for (sysclk, lrclk, bclk) + * triplet, we relax the bclk such that bclk is chosen as the + * closest available frequency greater than expected bclk. + * + * @component: component structure + * @freq_in: input frequency used to derive freq out via PLL + * @sysclk_idx: sysclk_divs index for found sysclk + * @dac_idx: dac_divs index for found lrclk + * @bclk_idx: bclk_divs index for found bclk + * + * Returns: + * < 0, in case no PLL frequency out available was found + * >=0, in case we could derive bclk, lrclk, sysclk from PLL out using + * (@sysclk_idx, @dac_idx, @bclk_idx) dividers + */ +static +int wm8960_configure_pll(struct snd_soc_component *component, int freq_in, + int *sysclk_idx, int *dac_idx, int *bclk_idx) +{ + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + int sysclk, bclk, lrclk, freq_out; + int diff, closest, best_freq_out; + int i, j, k; + + bclk = wm8960->bclk; + lrclk = wm8960->lrclk; + closest = freq_in; + + best_freq_out = -EINVAL; + *sysclk_idx = *dac_idx = *bclk_idx = -1; + + /* + * From Datasheet, the PLL performs best when f2 is between + * 90MHz and 100MHz, the desired sysclk output is 11.2896MHz + * or 12.288MHz, then sysclkdiv = 2 is the best choice. + * So search sysclk_divs from 2 to 1 other than from 1 to 2. + */ + for (i = ARRAY_SIZE(sysclk_divs) - 1; i >= 0; --i) { + if (sysclk_divs[i] == -1) + continue; + for (j = 0; j < ARRAY_SIZE(dac_divs); ++j) { + sysclk = lrclk * dac_divs[j]; + freq_out = sysclk * sysclk_divs[i]; + + for (k = 0; k < ARRAY_SIZE(bclk_divs); ++k) { + if (!is_pll_freq_available(freq_in, freq_out)) + continue; + + diff = sysclk - bclk * bclk_divs[k] / 10; + if (diff == 0) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + return freq_out; + } + if (diff > 0 && closest > diff) { + *sysclk_idx = i; + *dac_idx = j; + *bclk_idx = k; + closest = diff; + best_freq_out = freq_out; + } + } + } + } + + return best_freq_out; +} +static int wm8960_configure_clocking(struct snd_soc_component *component) +{ + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + int freq_out, freq_in; + u16 iface1 = snd_soc_component_read(component, WM8960_IFACE1); + int i, j, k; + int ret; + + /* + * For Slave mode clocking should still be configured, + * so this if statement should be removed, but some platform + * may not work if the sysclk is not configured, to avoid such + * compatible issue, just add '!wm8960->sysclk' condition in + * this if statement. + */ + if (!(iface1 & (1 << 6)) && !wm8960->sysclk) { + dev_warn(component->dev, + "slave mode, but proceeding with no clock configuration\n"); + return 0; + } + + if (wm8960->clk_id != WM8960_SYSCLK_MCLK && !wm8960->freq_in) { + dev_err(component->dev, "No MCLK configured\n"); + return -EINVAL; + } + + freq_in = wm8960->freq_in; + /* + * If it's sysclk auto mode, check if the MCLK can provide sysclk or + * not. If MCLK can provide sysclk, using MCLK to provide sysclk + * directly. Otherwise, auto select a available pll out frequency + * and set PLL. + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO) { + /* disable the PLL and using MCLK to provide sysclk */ + wm8960_set_pll(component, 0, 0); + freq_out = freq_in; + } else if (wm8960->sysclk) { + freq_out = wm8960->sysclk; + } else { + dev_err(component->dev, "No SYSCLK configured\n"); + return -EINVAL; + } + + if (wm8960->clk_id != WM8960_SYSCLK_PLL) { + ret = wm8960_configure_sysclk(wm8960, freq_out, &i, &j, &k); + if (ret >= 0) { + goto configure_clock; + } else if (wm8960->clk_id != WM8960_SYSCLK_AUTO) { + dev_err(component->dev, "failed to configure clock\n"); + return -EINVAL; + } + } + + freq_out = wm8960_configure_pll(component, freq_in, &i, &j, &k); + if (freq_out < 0) { + dev_err(component->dev, "failed to configure clock via PLL\n"); + return freq_out; + } + wm8960_set_pll(component, freq_in, freq_out); + +configure_clock: + /* configure sysclk clock */ + snd_soc_component_update_bits(component, WM8960_CLOCK1, 3 << 1, i << 1); + + /* configure frame clock */ + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x7 << 3, j << 3); + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x7 << 6, j << 6); + + /* configure bit clock */ + snd_soc_component_update_bits(component, WM8960_CLOCK2, 0xf, k); + + return 0; +} + static int wm8960_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 wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; - snd_pcm_format_t format = params_format(params); + struct snd_soc_component *component = dai->component; + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + u16 iface = snd_soc_component_read(component, WM8960_IFACE1) & 0xfff3; + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; int i; + wm8960->bclk = snd_soc_params_to_bclk(params); + if (params_channels(params) == 1) + wm8960->bclk *= 2; + /* bit size */ - switch (format) { - case SNDRV_PCM_FORMAT_S16_LE: - case SNDRV_PCM_FORMAT_S16_BE: + switch (params_width(params)) { + case 16: break; - case SNDRV_PCM_FORMAT_S20_3LE: - case SNDRV_PCM_FORMAT_S20_3BE: + case 20: iface |= 0x0004; break; - case SNDRV_PCM_FORMAT_S24_LE: - case SNDRV_PCM_FORMAT_S24_BE: + case 24: iface |= 0x0008; break; + case 32: + /* right justify mode does not support 32 word length */ + if ((iface & 0x3) != 0) { + iface |= 0x000c; + break; + } + fallthrough; default: - dev_err(codec->dev, "unsupported format %i\n", format); + dev_err(component->dev, "unsupported width %d\n", + params_width(params)); return -EINVAL; } + wm8960->lrclk = params_rate(params); /* Update filters for the new rate */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - wm8960->playback_fs = params_rate(params); - wm8960_set_deemph(codec); + if (tx) { + wm8960_set_deemph(component); } else { for (i = 0; i < ARRAY_SIZE(alc_rates); i++) if (alc_rates[i].rate == params_rate(params)) - snd_soc_update_bits(codec, + snd_soc_component_update_bits(component, WM8960_ADDCTL3, 0x7, alc_rates[i].val); } /* set iface */ - snd_soc_write(codec, WM8960_IFACE1, iface); + snd_soc_component_write(component, WM8960_IFACE1, iface); + + wm8960->is_stream_in_use[tx] = true; + + if (!wm8960->is_stream_in_use[!tx]) + return wm8960_configure_clocking(component); + + return 0; +} + +static int wm8960_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; + + wm8960->is_stream_in_use[tx] = false; + return 0; } -static int wm8960_mute(struct snd_soc_dai *dai, int mute) +static int wm8960_mute(struct snd_soc_dai *dai, int mute, int direction) { - struct snd_soc_codec *codec = dai->codec; + struct snd_soc_component *component = dai->component; if (mute) - snd_soc_update_bits(codec, WM8960_DACCTL1, 0x8, 0x8); + snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x8, 0x8); else - snd_soc_update_bits(codec, WM8960_DACCTL1, 0x8, 0); + snd_soc_component_update_bits(component, WM8960_DACCTL1, 0x8, 0); return 0; } -static int wm8960_set_bias_level_out3(struct snd_soc_codec *codec, +static int wm8960_set_bias_level_out3(struct snd_soc_component *component, enum snd_soc_bias_level level) { - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + u16 pm2 = snd_soc_component_read(component, WM8960_POWER2); + int ret; + ktime_t tout; switch (level) { case SND_SOC_BIAS_ON: break; case SND_SOC_BIAS_PREPARE: - /* Set VMID to 2x50k */ - snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x80); + switch (snd_soc_dapm_get_bias_level(dapm)) { + case SND_SOC_BIAS_STANDBY: + if (!IS_ERR(wm8960->mclk)) { + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(component->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + + ret = wm8960_configure_clocking(component); + if (ret) + return ret; + + /* Set VMID to 2x50k */ + snd_soc_component_update_bits(component, WM8960_POWER1, 0x180, 0x80); + break; + + case SND_SOC_BIAS_ON: + /* + * If it's sysclk auto mode, and the pll is enabled, + * disable the pll + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1)) + wm8960_set_pll(component, 0, 0); + + if (!IS_ERR(wm8960->mclk)) + clk_disable_unprepare(wm8960->mclk); + break; + + default: + break; + } + break; case SND_SOC_BIAS_STANDBY: - if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + if (snd_soc_dapm_get_bias_level(dapm) == SND_SOC_BIAS_OFF) { + /* ensure discharge is complete */ + tout = WM8960_DSCH_TOUT - ktime_ms_delta(ktime_get(), wm8960->dsch_start); + if (tout > 0) + msleep(tout); + regcache_sync(wm8960->regmap); /* Enable anti-pop features */ - snd_soc_write(codec, WM8960_APOP1, + snd_soc_component_write(component, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN | WM8960_BUFIOEN); /* Enable & ramp VMID at 2x50k */ - snd_soc_update_bits(codec, WM8960_POWER1, 0x80, 0x80); + snd_soc_component_update_bits(component, WM8960_POWER1, 0x80, 0x80); msleep(100); /* Enable VREF */ - snd_soc_update_bits(codec, WM8960_POWER1, WM8960_VREF, + snd_soc_component_update_bits(component, WM8960_POWER1, WM8960_VREF, WM8960_VREF); /* Disable anti-pop features */ - snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN); + snd_soc_component_write(component, WM8960_APOP1, WM8960_BUFIOEN); } /* Set VMID to 2x250k */ - snd_soc_update_bits(codec, WM8960_POWER1, 0x180, 0x100); + snd_soc_component_update_bits(component, WM8960_POWER1, 0x180, 0x100); break; case SND_SOC_BIAS_OFF: /* Enable anti-pop features */ - snd_soc_write(codec, WM8960_APOP1, + snd_soc_component_write(component, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN | WM8960_BUFIOEN); - /* Disable VMID and VREF, let them discharge */ - snd_soc_write(codec, WM8960_POWER1, 0); - msleep(600); + /* Disable VMID and VREF, mark discharge */ + snd_soc_component_write(component, WM8960_POWER1, 0); + wm8960->dsch_start = ktime_get(); break; } - codec->dapm.bias_level = level; - return 0; } -static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, +static int wm8960_set_bias_level_capless(struct snd_soc_component *component, enum snd_soc_bias_level level) { - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - int reg; + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + struct snd_soc_dapm_context *dapm = snd_soc_component_to_dapm(component); + u16 pm2 = snd_soc_component_read(component, WM8960_POWER2); + int reg, ret; switch (level) { case SND_SOC_BIAS_ON: break; case SND_SOC_BIAS_PREPARE: - switch (codec->dapm.bias_level) { + switch (snd_soc_dapm_get_bias_level(dapm)) { case SND_SOC_BIAS_STANDBY: /* Enable anti pop mode */ - snd_soc_update_bits(codec, WM8960_APOP1, + snd_soc_component_update_bits(component, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN, WM8960_POBCTRL | WM8960_SOFT_ST | @@ -700,35 +1036,60 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, reg |= WM8960_PWR2_ROUT1; if (wm8960->out3 && wm8960->out3->power) reg |= WM8960_PWR2_OUT3; - snd_soc_update_bits(codec, WM8960_POWER2, + snd_soc_component_update_bits(component, WM8960_POWER2, WM8960_PWR2_LOUT1 | WM8960_PWR2_ROUT1 | WM8960_PWR2_OUT3, reg); /* Enable VMID at 2*50k */ - snd_soc_update_bits(codec, WM8960_POWER1, + snd_soc_component_update_bits(component, WM8960_POWER1, WM8960_VMID_MASK, 0x80); /* Ramp */ msleep(100); /* Enable VREF */ - snd_soc_update_bits(codec, WM8960_POWER1, + snd_soc_component_update_bits(component, WM8960_POWER1, WM8960_VREF, WM8960_VREF); msleep(100); + + if (!IS_ERR(wm8960->mclk)) { + ret = clk_prepare_enable(wm8960->mclk); + if (ret) { + dev_err(component->dev, + "Failed to enable MCLK: %d\n", + ret); + return ret; + } + } + + ret = wm8960_configure_clocking(component); + if (ret) + return ret; + break; case SND_SOC_BIAS_ON: + /* + * If it's sysclk auto mode, and the pll is enabled, + * disable the pll + */ + if (wm8960->clk_id == WM8960_SYSCLK_AUTO && (pm2 & 0x1)) + wm8960_set_pll(component, 0, 0); + + if (!IS_ERR(wm8960->mclk)) + clk_disable_unprepare(wm8960->mclk); + /* Enable anti-pop mode */ - snd_soc_update_bits(codec, WM8960_APOP1, + snd_soc_component_update_bits(component, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN); /* Disable VMID and VREF */ - snd_soc_update_bits(codec, WM8960_POWER1, + snd_soc_component_update_bits(component, WM8960_POWER1, WM8960_VREF | WM8960_VMID_MASK, 0); break; @@ -741,15 +1102,15 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_STANDBY: - switch (codec->dapm.bias_level) { + switch (snd_soc_dapm_get_bias_level(dapm)) { case SND_SOC_BIAS_PREPARE: /* Disable HP discharge */ - snd_soc_update_bits(codec, WM8960_APOP2, + snd_soc_component_update_bits(component, WM8960_APOP2, WM8960_DISOP | WM8960_DRES_MASK, 0); /* Disable anti-pop features */ - snd_soc_update_bits(codec, WM8960_APOP1, + snd_soc_component_update_bits(component, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN, WM8960_POBCTRL | WM8960_SOFT_ST | @@ -765,8 +1126,6 @@ static int wm8960_set_bias_level_capless(struct snd_soc_codec *codec, break; } - codec->dapm.bias_level = level; - return 0; } @@ -777,6 +1136,28 @@ struct _pll_div { u32 k:24; }; +static bool is_pll_freq_available(unsigned int source, unsigned int target) +{ + unsigned int Ndiv; + + if (source == 0 || target == 0) + return false; + + /* Scale up target to PLL operating frequency */ + target *= 4; + Ndiv = target / source; + + if (Ndiv < 6) { + source >>= 1; + Ndiv = target / source; + } + + if ((Ndiv < 6) || (Ndiv > 12)) + return false; + + return true; +} + /* The size in bits of the pll divide multiplied by 10 * to allow rounding later */ #define FIXED_PLL_SIZE ((1 << 24) * 10) @@ -828,10 +1209,9 @@ static int pll_factors(unsigned int source, unsigned int target, return 0; } -static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, - int source, unsigned int freq_in, unsigned int freq_out) +static int wm8960_set_pll(struct snd_soc_component *component, + unsigned int freq_in, unsigned int freq_out) { - struct snd_soc_codec *codec = codec_dai->codec; u16 reg; static struct _pll_div pll_div; int ret; @@ -844,59 +1224,73 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, /* Disable the PLL: even if we are changing the frequency the * PLL needs to be disabled while we do so. */ - snd_soc_update_bits(codec, WM8960_CLOCK1, 0x1, 0); - snd_soc_update_bits(codec, WM8960_POWER2, 0x1, 0); + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x1, 0); + snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0); if (!freq_in || !freq_out) return 0; - reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f; + reg = snd_soc_component_read(component, WM8960_PLL1) & ~0x3f; reg |= pll_div.pre_div << 4; reg |= pll_div.n; if (pll_div.k) { reg |= 0x20; - snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); - snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); - snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); + snd_soc_component_write(component, WM8960_PLL2, (pll_div.k >> 16) & 0xff); + snd_soc_component_write(component, WM8960_PLL3, (pll_div.k >> 8) & 0xff); + snd_soc_component_write(component, WM8960_PLL4, pll_div.k & 0xff); } - snd_soc_write(codec, WM8960_PLL1, reg); + snd_soc_component_write(component, WM8960_PLL1, reg); /* Turn it on */ - snd_soc_update_bits(codec, WM8960_POWER2, 0x1, 0x1); + snd_soc_component_update_bits(component, WM8960_POWER2, 0x1, 0x1); msleep(250); - snd_soc_update_bits(codec, WM8960_CLOCK1, 0x1, 0x1); + snd_soc_component_update_bits(component, WM8960_CLOCK1, 0x1, 0x1); return 0; } +static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_component *component = codec_dai->component; + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + + wm8960->freq_in = freq_in; + + if (pll_id == WM8960_SYSCLK_AUTO) + return 0; + + return wm8960_set_pll(component, freq_in, freq_out); +} + static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, int div_id, int div) { - struct snd_soc_codec *codec = codec_dai->codec; + struct snd_soc_component *component = codec_dai->component; u16 reg; switch (div_id) { case WM8960_SYSCLKDIV: - reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9; - snd_soc_write(codec, WM8960_CLOCK1, reg | div); + reg = snd_soc_component_read(component, WM8960_CLOCK1) & 0x1f9; + snd_soc_component_write(component, WM8960_CLOCK1, reg | div); break; case WM8960_DACDIV: - reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7; - snd_soc_write(codec, WM8960_CLOCK1, reg | div); + reg = snd_soc_component_read(component, WM8960_CLOCK1) & 0x1c7; + snd_soc_component_write(component, WM8960_CLOCK1, reg | div); break; case WM8960_OPCLKDIV: - reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f; - snd_soc_write(codec, WM8960_PLL1, reg | div); + reg = snd_soc_component_read(component, WM8960_PLL1) & 0x03f; + snd_soc_component_write(component, WM8960_PLL1, reg | div); break; case WM8960_DCLKDIV: - reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f; - snd_soc_write(codec, WM8960_CLOCK2, reg | div); + reg = snd_soc_component_read(component, WM8960_CLOCK2) & 0x03f; + snd_soc_component_write(component, WM8960_CLOCK2, reg | div); break; case WM8960_TOCLKSEL: - reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd; - snd_soc_write(codec, WM8960_ADDCTL1, reg | div); + reg = snd_soc_component_read(component, WM8960_ADDCTL1) & 0x1fd; + snd_soc_component_write(component, WM8960_ADDCTL1, reg | div); break; default: return -EINVAL; @@ -905,26 +1299,56 @@ static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, return 0; } -static int wm8960_set_bias_level(struct snd_soc_codec *codec, +static int wm8960_set_bias_level(struct snd_soc_component *component, enum snd_soc_bias_level level) { - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); - return wm8960->set_bias_level(codec, level); + return wm8960->set_bias_level(component, level); +} + +static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_component *component = dai->component; + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + + switch (clk_id) { + case WM8960_SYSCLK_MCLK: + snd_soc_component_update_bits(component, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_MCLK); + break; + case WM8960_SYSCLK_PLL: + snd_soc_component_update_bits(component, WM8960_CLOCK1, + 0x1, WM8960_SYSCLK_PLL); + break; + case WM8960_SYSCLK_AUTO: + break; + default: + return -EINVAL; + } + + wm8960->sysclk = freq; + wm8960->clk_id = clk_id; + + return 0; } #define WM8960_RATES SNDRV_PCM_RATE_8000_48000 #define WM8960_FORMATS \ (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ - SNDRV_PCM_FMTBIT_S24_LE) + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) static const struct snd_soc_dai_ops wm8960_dai_ops = { .hw_params = wm8960_hw_params, - .digital_mute = wm8960_mute, + .hw_free = wm8960_hw_free, + .mute_stream = wm8960_mute, .set_fmt = wm8960_set_dai_fmt, .set_clkdiv = wm8960_set_dai_clkdiv, .set_pll = wm8960_set_dai_pll, + .set_sysclk = wm8960_set_dai_sysclk, + .no_capture_mute = 1, }; static struct snd_soc_dai_driver wm8960_dai = { @@ -942,88 +1366,33 @@ static struct snd_soc_dai_driver wm8960_dai = { .rates = WM8960_RATES, .formats = WM8960_FORMATS,}, .ops = &wm8960_dai_ops, - .symmetric_rates = 1, + .symmetric_rate = 1, }; -static int wm8960_suspend(struct snd_soc_codec *codec) +static int wm8960_probe(struct snd_soc_component *component) { - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - - wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} + struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); + struct wm8960_data *pdata = &wm8960->pdata; -static int wm8960_resume(struct snd_soc_codec *codec) -{ - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - - wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY); - return 0; -} - -static int wm8960_probe(struct snd_soc_codec *codec) -{ - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - struct wm8960_data *pdata = dev_get_platdata(codec->dev); - int ret; - - wm8960->set_bias_level = wm8960_set_bias_level_out3; - - if (!pdata) { - dev_warn(codec->dev, "No platform data supplied\n"); - } else { - if (pdata->capless) - wm8960->set_bias_level = wm8960_set_bias_level_capless; - } - - ret = snd_soc_codec_set_cache_io(codec, 7, 9, SND_SOC_REGMAP); - if (ret < 0) { - dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); - return ret; - } - - ret = wm8960_reset(codec); - if (ret < 0) { - dev_err(codec->dev, "Failed to issue reset\n"); - return ret; - } - - wm8960->set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (pdata->capless) + wm8960->set_bias_level = wm8960_set_bias_level_capless; + else + wm8960->set_bias_level = wm8960_set_bias_level_out3; - /* Latch the update bits */ - snd_soc_update_bits(codec, WM8960_LINVOL, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_RINVOL, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_LADC, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_RADC, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_LDAC, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_RDAC, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_LOUT1, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_ROUT1, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_LOUT2, 0x100, 0x100); - snd_soc_update_bits(codec, WM8960_ROUT2, 0x100, 0x100); - - snd_soc_add_codec_controls(codec, wm8960_snd_controls, + snd_soc_add_component_controls(component, wm8960_snd_controls, ARRAY_SIZE(wm8960_snd_controls)); - wm8960_add_widgets(codec); + wm8960_add_widgets(component); return 0; } -/* power down chip */ -static int wm8960_remove(struct snd_soc_codec *codec) -{ - struct wm8960_priv *wm8960 = snd_soc_codec_get_drvdata(codec); - - wm8960->set_bias_level(codec, SND_SOC_BIAS_OFF); - return 0; -} - -static struct snd_soc_codec_driver soc_codec_dev_wm8960 = { - .probe = wm8960_probe, - .remove = wm8960_remove, - .suspend = wm8960_suspend, - .resume = wm8960_resume, - .set_bias_level = wm8960_set_bias_level, +static const struct snd_soc_component_driver soc_component_dev_wm8960 = { + .probe = wm8960_probe, + .set_bias_level = wm8960_set_bias_level, + .suspend_bias_off = 1, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, }; static const struct regmap_config wm8960_regmap = { @@ -1033,61 +1402,182 @@ static const struct regmap_config wm8960_regmap = { .reg_defaults = wm8960_reg_defaults, .num_reg_defaults = ARRAY_SIZE(wm8960_reg_defaults), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = wm8960_volatile, }; -static int wm8960_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +static void wm8960_set_pdata_from_of(struct i2c_client *i2c, + struct wm8960_data *pdata) +{ + const struct device_node *np = i2c->dev.of_node; + + if (of_property_read_bool(np, "wlf,capless")) + pdata->capless = true; + + if (of_property_read_bool(np, "wlf,shared-lrclk")) + pdata->shared_lrclk = true; + + of_property_read_u32_array(np, "wlf,gpio-cfg", pdata->gpio_cfg, + ARRAY_SIZE(pdata->gpio_cfg)); + + of_property_read_u32_array(np, "wlf,hp-cfg", pdata->hp_cfg, + ARRAY_SIZE(pdata->hp_cfg)); +} + +static int wm8960_i2c_probe(struct i2c_client *i2c) { struct wm8960_data *pdata = dev_get_platdata(&i2c->dev); struct wm8960_priv *wm8960; + unsigned int i; int ret; + u8 val; wm8960 = devm_kzalloc(&i2c->dev, sizeof(struct wm8960_priv), GFP_KERNEL); if (wm8960 == NULL) return -ENOMEM; + wm8960->mclk = devm_clk_get(&i2c->dev, "mclk"); + if (IS_ERR(wm8960->mclk)) { + if (PTR_ERR(wm8960->mclk) == -EPROBE_DEFER) + return -EPROBE_DEFER; + } else { + ret = clk_get_rate(wm8960->mclk); + if (ret >= 0) { + wm8960->freq_in = ret; + } else { + dev_err(&i2c->dev, "Failed to read MCLK rate: %d\n", + ret); + } + } + + for (i = 0; i < ARRAY_SIZE(wm8960->supplies); i++) + wm8960->supplies[i].supply = wm8960_supply_names[i]; + + ret = devm_regulator_bulk_get(&i2c->dev, ARRAY_SIZE(wm8960->supplies), + wm8960->supplies); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8960->supplies), + wm8960->supplies); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to enable supplies: %d\n", ret); + return ret; + } + wm8960->regmap = devm_regmap_init_i2c(i2c, &wm8960_regmap); - if (IS_ERR(wm8960->regmap)) - return PTR_ERR(wm8960->regmap); + if (IS_ERR(wm8960->regmap)) { + ret = PTR_ERR(wm8960->regmap); + goto bulk_disable; + } + + if (pdata) + memcpy(&wm8960->pdata, pdata, sizeof(struct wm8960_data)); + else if (i2c->dev.of_node) + wm8960_set_pdata_from_of(i2c, &wm8960->pdata); + + ret = i2c_master_recv(i2c, &val, sizeof(val)); + if (ret >= 0) { + dev_err(&i2c->dev, "Not wm8960, wm8960 reg can not read by i2c\n"); + ret = -EINVAL; + goto bulk_disable; + } - if (pdata && pdata->shared_lrclk) { + ret = wm8960_reset(wm8960->regmap); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to issue reset\n"); + goto bulk_disable; + } + + if (wm8960->pdata.shared_lrclk) { ret = regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2, 0x4, 0x4); if (ret != 0) { dev_err(&i2c->dev, "Failed to enable LRCM: %d\n", ret); - return ret; + goto bulk_disable; } } + /* Latch the update bits */ + regmap_update_bits(wm8960->regmap, WM8960_LINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RINVOL, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RADC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_RDAC, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT1, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_LOUT2, 0x100, 0x100); + regmap_update_bits(wm8960->regmap, WM8960_ROUT2, 0x100, 0x100); + + /* ADCLRC pin configured as GPIO. */ + regmap_update_bits(wm8960->regmap, WM8960_IFACE2, 1 << 6, + wm8960->pdata.gpio_cfg[0] << 6); + regmap_update_bits(wm8960->regmap, WM8960_ADDCTL4, 0xF << 4, + wm8960->pdata.gpio_cfg[1] << 4); + + /* Enable headphone jack detect */ + regmap_update_bits(wm8960->regmap, WM8960_ADDCTL4, 3 << 2, + wm8960->pdata.hp_cfg[0] << 2); + regmap_update_bits(wm8960->regmap, WM8960_ADDCTL2, 3 << 5, + wm8960->pdata.hp_cfg[1] << 5); + regmap_update_bits(wm8960->regmap, WM8960_ADDCTL1, 3, + wm8960->pdata.hp_cfg[2]); + i2c_set_clientdata(i2c, wm8960); - ret = snd_soc_register_codec(&i2c->dev, - &soc_codec_dev_wm8960, &wm8960_dai, 1); + ret = devm_snd_soc_register_component(&i2c->dev, + &soc_component_dev_wm8960, &wm8960_dai, 1); + if (ret) + goto bulk_disable; + + return 0; +bulk_disable: + regulator_bulk_disable(ARRAY_SIZE(wm8960->supplies), wm8960->supplies); return ret; } -static int wm8960_i2c_remove(struct i2c_client *client) +static void wm8960_i2c_remove(struct i2c_client *client) { - snd_soc_unregister_codec(&client->dev); - return 0; + struct wm8960_priv *wm8960 = i2c_get_clientdata(client); + + regulator_bulk_disable(ARRAY_SIZE(wm8960->supplies), wm8960->supplies); } static const struct i2c_device_id wm8960_i2c_id[] = { - { "wm8960", 0 }, + { "wm8960" }, { } }; MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); +#if defined(CONFIG_OF) +static const struct of_device_id wm8960_of_match[] = { + { .compatible = "wlf,wm8960", }, + { } +}; +MODULE_DEVICE_TABLE(of, wm8960_of_match); +#endif + +#if defined(CONFIG_ACPI) +static const struct acpi_device_id wm8960_acpi_match[] = { + { "1AEC8960", 0 }, /* Wolfson PCI ID + part ID */ + { "10138960", 0 }, /* Cirrus Logic PCI ID + part ID */ + { }, +}; +MODULE_DEVICE_TABLE(acpi, wm8960_acpi_match); +#endif + static struct i2c_driver wm8960_i2c_driver = { .driver = { .name = "wm8960", - .owner = THIS_MODULE, + .of_match_table = of_match_ptr(wm8960_of_match), + .acpi_match_table = ACPI_PTR(wm8960_acpi_match), }, .probe = wm8960_i2c_probe, .remove = wm8960_i2c_remove, |
