diff options
Diffstat (limited to 'sound/soc/codecs/wm8960.c')
-rw-r--r-- | sound/soc/codecs/wm8960.c | 127 |
1 files changed, 84 insertions, 43 deletions
diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index 55112c1bba5e..0d167238a369 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -14,6 +14,7 @@ #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> @@ -45,6 +46,8 @@ #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); @@ -133,6 +136,7 @@ struct wm8960_priv { int freq_in; bool is_stream_in_use[2]; struct wm8960_data pdata; + ktime_t dsch_start; }; #define wm8960_reset(c) regmap_write(c, WM8960_RESET, 0) @@ -608,11 +612,7 @@ static const int bclk_divs[] = { * - lrclk = sysclk / dac_divs * - 10 * bclk = sysclk / 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. - * - * @wm8960_priv: wm8960 codec private data + * @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 @@ -629,7 +629,7 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, { int sysclk, bclk, lrclk; int i, j, k; - int diff, closest = mclk; + int diff; /* marker for no match */ *bclk_idx = -1; @@ -653,12 +653,6 @@ int wm8960_configure_sysclk(struct wm8960_priv *wm8960, int mclk, *bclk_idx = k; break; } - if (diff > 0 && closest > diff) { - *sysclk_idx = i; - *dac_idx = j; - *bclk_idx = k; - closest = diff; - } } if (k != ARRAY_SIZE(bclk_divs)) break; @@ -707,7 +701,13 @@ int wm8960_configure_pll(struct snd_soc_component *component, int freq_in, best_freq_out = -EINVAL; *sysclk_idx = *dac_idx = *bclk_idx = -1; - for (i = 0; i < ARRAY_SIZE(sysclk_divs); ++i) { + /* + * 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) { @@ -742,13 +742,20 @@ 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_read32(component, WM8960_IFACE1); + u16 iface1 = snd_soc_component_read(component, WM8960_IFACE1); int i, j, k; int ret; - if (!(iface1 & (1<<6))) { - dev_dbg(component->dev, - "Codec is slave mode, no need to configure clock\n"); + /* + * 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; } @@ -812,7 +819,7 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); - u16 iface = snd_soc_component_read32(component, WM8960_IFACE1) & 0xfff3; + u16 iface = snd_soc_component_read(component, WM8960_IFACE1) & 0xfff3; bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; int i; @@ -836,7 +843,7 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, iface |= 0x000c; break; } - /* fall through */ + fallthrough; default: dev_err(component->dev, "unsupported width %d\n", params_width(params)); @@ -860,8 +867,7 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, wm8960->is_stream_in_use[tx] = true; - if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_ON && - !wm8960->is_stream_in_use[!tx]) + if (!wm8960->is_stream_in_use[!tx]) return wm8960_configure_clocking(component); return 0; @@ -879,7 +885,7 @@ static int wm8960_hw_free(struct snd_pcm_substream *substream, 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_component *component = dai->component; @@ -894,8 +900,9 @@ static int wm8960_set_bias_level_out3(struct snd_soc_component *component, enum snd_soc_bias_level level) { struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); - u16 pm2 = snd_soc_component_read32(component, WM8960_POWER2); + u16 pm2 = snd_soc_component_read(component, WM8960_POWER2); int ret; + ktime_t tout; switch (level) { case SND_SOC_BIAS_ON: @@ -942,6 +949,11 @@ static int wm8960_set_bias_level_out3(struct snd_soc_component *component, case SND_SOC_BIAS_STANDBY: if (snd_soc_component_get_bias_level(component) == 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 */ @@ -971,9 +983,9 @@ static int wm8960_set_bias_level_out3(struct snd_soc_component *component, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN | WM8960_BUFIOEN); - /* Disable VMID and VREF, let them discharge */ + /* Disable VMID and VREF, mark discharge */ snd_soc_component_write(component, WM8960_POWER1, 0); - msleep(600); + wm8960->dsch_start = ktime_get(); break; } @@ -984,7 +996,7 @@ static int wm8960_set_bias_level_capless(struct snd_soc_component *component, enum snd_soc_bias_level level) { struct wm8960_priv *wm8960 = snd_soc_component_get_drvdata(component); - u16 pm2 = snd_soc_component_read32(component, WM8960_POWER2); + u16 pm2 = snd_soc_component_read(component, WM8960_POWER2); int reg, ret; switch (level) { @@ -1203,7 +1215,7 @@ static int wm8960_set_pll(struct snd_soc_component *component, if (!freq_in || !freq_out) return 0; - reg = snd_soc_component_read32(component, WM8960_PLL1) & ~0x3f; + reg = snd_soc_component_read(component, WM8960_PLL1) & ~0x3f; reg |= pll_div.pre_div << 4; reg |= pll_div.n; @@ -1246,23 +1258,23 @@ static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, switch (div_id) { case WM8960_SYSCLKDIV: - reg = snd_soc_component_read32(component, WM8960_CLOCK1) & 0x1f9; + 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_component_read32(component, WM8960_CLOCK1) & 0x1c7; + 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_component_read32(component, WM8960_PLL1) & 0x03f; + 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_component_read32(component, WM8960_CLOCK2) & 0x03f; + 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_component_read32(component, WM8960_ADDCTL1) & 0x1fd; + reg = snd_soc_component_read(component, WM8960_ADDCTL1) & 0x1fd; snd_soc_component_write(component, WM8960_ADDCTL1, reg | div); break; default: @@ -1316,11 +1328,12 @@ static int wm8960_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, static const struct snd_soc_dai_ops wm8960_dai_ops = { .hw_params = wm8960_hw_params, .hw_free = wm8960_hw_free, - .digital_mute = wm8960_mute, + .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 = { @@ -1338,7 +1351,7 @@ 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_probe(struct snd_soc_component *component) @@ -1365,7 +1378,6 @@ static const struct snd_soc_component_driver soc_component_dev_wm8960 = { .idle_bias_on = 1, .use_pmdown_time = 1, .endianness = 1, - .non_legacy_dai_naming = 1, }; static const struct regmap_config wm8960_regmap = { @@ -1390,10 +1402,15 @@ static void wm8960_set_pdata_from_of(struct i2c_client *i2c, 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, - const struct i2c_device_id *id) +static int wm8960_i2c_probe(struct i2c_client *i2c) { struct wm8960_data *pdata = dev_get_platdata(&i2c->dev); struct wm8960_priv *wm8960; @@ -1447,6 +1464,20 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, 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 = devm_snd_soc_register_component(&i2c->dev, @@ -1455,10 +1486,8 @@ static int wm8960_i2c_probe(struct i2c_client *i2c, return ret; } -static int wm8960_i2c_remove(struct i2c_client *client) -{ - return 0; -} +static void wm8960_i2c_remove(struct i2c_client *client) +{} static const struct i2c_device_id wm8960_i2c_id[] = { { "wm8960", 0 }, @@ -1466,18 +1495,30 @@ static const struct i2c_device_id wm8960_i2c_id[] = { }; 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", - .of_match_table = wm8960_of_match, + .of_match_table = of_match_ptr(wm8960_of_match), + .acpi_match_table = ACPI_PTR(wm8960_acpi_match), }, - .probe = wm8960_i2c_probe, + .probe_new = wm8960_i2c_probe, .remove = wm8960_i2c_remove, .id_table = wm8960_i2c_id, }; |