[alsa-devel] [PATCH v2 0/4] wm8904: adapt driver for use with audio-graph-card
This series allows to use WM8904 codec as audio-graph-card component. It starts with rework of FLL handling in the codec's driver, and as an example includes (untested) rework for codec with similar FLL: WM8994.
Series based on tiwai/sound/for-next tree. You can also pull from: https://rere.qmqm.pl/git/linux branch: wm8904
(branch includes two fixes already sent to alsa-devel, but not merged yet).
Michał Mirosław (4): ASoC: wm_fll: extract common code for Wolfson FLLs ASoC: wm8904: use common FLL code ASoC: wm8904: automatically choose clock source [RFT] ASoC: wm8994: use common FLL code
sound/soc/atmel/atmel_wm8904.c | 11 +- sound/soc/codecs/Kconfig | 9 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8904.c | 516 +++++++++++--------------------- sound/soc/codecs/wm8904.h | 5 - sound/soc/codecs/wm8994.c | 281 +++++------------- sound/soc/codecs/wm8994.h | 4 +- sound/soc/codecs/wm_fll.c | 518 +++++++++++++++++++++++++++++++++ sound/soc/codecs/wm_fll.h | 60 ++++ 9 files changed, 849 insertions(+), 557 deletions(-) create mode 100644 sound/soc/codecs/wm_fll.c create mode 100644 sound/soc/codecs/wm_fll.h
A new implementation for FLLs as contained in WM8904, WM8994 and a few other Cirrus Logic (formerly Wolfson) codecs. Patches using this common code follow.
Signed-off-by: Michał Mirosław mirq-linux@rere.qmqm.pl --- sound/soc/codecs/Kconfig | 6 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm_fll.c | 518 ++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm_fll.h | 60 +++++ 4 files changed, 586 insertions(+)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 9f89a5346299..04086acf6d93 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -281,6 +281,12 @@ config SND_SOC_ARIZONA default m if SND_SOC_WM8997=m default m if SND_SOC_WM8998=m
+config SND_SOC_WM_FLL + tristate + +config SND_SOC_WM_FLL_EFS + bool + config SND_SOC_WM_HUBS tristate default y if SND_SOC_WM8993=y || SND_SOC_WM8994=y diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 5b4bb8cf4325..22704fbb7497 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -269,6 +269,7 @@ snd-soc-wm9090-objs := wm9090.o snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o +snd-soc-wm-fll-objs := wm_fll.o snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-zx-aud96p22-objs := zx_aud96p22.o # Amp @@ -549,6 +550,7 @@ obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o +obj-$(CONFIG_SND_SOC_WM_FLL) += snd-soc-wm-fll.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o obj-$(CONFIG_SND_SOC_ZX_AUD96P22) += snd-soc-zx-aud96p22.o
diff --git a/sound/soc/codecs/wm_fll.c b/sound/soc/codecs/wm_fll.c new file mode 100644 index 000000000000..0d8217287030 --- /dev/null +++ b/sound/soc/codecs/wm_fll.c @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * wm_fll.c -- WM89xx FLL support + * + * Copyright 2019 Michał Mirosław + * + * WM can generate its clock directly from MCLK, from + * internal FLL synchronizing to one of hw frame clocks + * or from FLL's VCO in free-running mode + */ + +#include <linux/bitfield.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/gcd.h> +#include <linux/kernel.h> + +#include "wm_fll.h" + +/* FLL Control 1 */ +#define WM_FLL_CONTROL_1 (hw->desc->ctl_offset + 0) +#define WM_FLL_FRACN_ENA BIT(2) +#define WM_FLL_OSC_ENA BIT(1) +#define WM_FLL_ENA BIT(0) + +/* FLL Control 2 */ +#define WM_FLL_CONTROL_2 (hw->desc->ctl_offset + 1) +#define WM_FLL_OUTDIV GENMASK(13, 8) +#define WM_FLL_CTRL_RATE GENMASK(6, 4) +#define WM_FLL_FRATIO GENMASK(2, 0) + +/* FLL Control 3 */ +#define WM_FLL_CONTROL_3 (hw->desc->ctl_offset + 2) +#define WM_FLL_K GENMASK(15, 0) + +/* FLL Control 4 */ +#define WM_FLL_CONTROL_4 (hw->desc->ctl_offset + 3) +#define WM_FLL_N GENMASK(14, 5) +#define WM_FLL_GAIN GENMASK(3, 0) + +/* FLL Control 5 */ +#define WM_FLL_CONTROL_5 (hw->desc->ctl_offset + 4) +#define WM_FLL_CLK_REF_DIV GENMASK(4, 3) +#define WM_FLL_CLK_REF_SRC GENMASK(1, 0) + +/* Interrupt Status */ +#define WM_INTERRUPT_STATUS (hw->desc->int_offset + 0) + +/* FLL NCO Test 0 (part of FLL Control 5 on some chips) */ +#define WM_FLL_NCO_TEST_0 (hw->desc->nco_reg0) +#define WM_FLL_FRC_NCO BIT(0) + +/* FLL NCO Test 1 (part of FLL Control 5 on some chips) */ +#define WM_FLL_NCO_TEST_1 (hw->desc->nco_reg1) +#define WM_FLL_FRC_NCO_VAL GENMASK(5, 0) + +/* FLL EFS 1 (chips with N+THETA/LAMBDA instead of N.K multiplier) */ +#define WM_FLL_EFS_1 (hw->desc->efs_offset + 0) +#define WM_FLL_LAMBDA GENMASK(15, 0) + +/* FLL EFS 2 (chips with N+THETA/LAMBDA instead of N.K multiplier) */ +#define WM_FLL_EFS_2 (hw->desc->efs_offset + 1) +#define WM_FLL_EFS_ENA BIT(0) + + +/* feature tests */ +#define WM_FLL_USES_EFS(hw) (IS_ENABLED(CONFIG_SND_SOC_WM_FLL_EFS) && hw->desc->efs_offset) + + +static bool wm_fll_in_free_running_mode(struct wm_fll_data *hw) +{ + unsigned int val; + + if (!hw->desc->nco_reg0) + return false; + if (regmap_read(hw->regmap, WM_FLL_NCO_TEST_0, &val) < 0) + return false; + + val >>= hw->desc->frc_nco_shift; + return FIELD_GET(WM_FLL_FRC_NCO, val); +} + +static int wm_fll_set_free_running_mode(struct wm_fll_data *hw, bool enable) +{ + unsigned int val, mask; + int err; + + if (!hw->desc->nco_reg0) + return enable ? -EINVAL : 0; + + if (enable) { + /* set osc freq (approx 96MHz) */ + val = FIELD_PREP(WM_FLL_FRC_NCO_VAL, 0x19); + mask = WM_FLL_FRC_NCO_VAL; + + val <<= hw->desc->frc_nco_val_shift; + mask <<= hw->desc->frc_nco_val_shift; + + err = regmap_update_bits(hw->regmap, WM_FLL_NCO_TEST_1, + mask, val); + if (err) + return err; + } + + /* set free-running mode */ + val = FIELD_PREP(WM_FLL_FRC_NCO, enable); + mask = WM_FLL_FRC_NCO; + + val <<= hw->desc->frc_nco_shift; + mask <<= hw->desc->frc_nco_shift; + + return regmap_update_bits(hw->regmap, WM_FLL_NCO_TEST_0, + mask, val); +} + +static int wm_fll_get_parent(struct wm_fll_data *hw) +{ + unsigned int val; + int err; + + /* free-running mode? */ + if (wm_fll_in_free_running_mode(hw)) + return FLL_REF_OSC; + + err = regmap_read(hw->regmap, WM_FLL_CONTROL_5, &val); + if (err < 0) + return err; + + val = FIELD_GET(WM_FLL_CLK_REF_SRC, val); + return hw->desc->clk_ref_map[val]; +} + +/** + * wm_fll_set_parent() - Change FLL clock source + * + * @hw: FLL hardware info + * @index: FLL source clock id + * + * Configures FLL for using @index clock as input. + * + * Return 0 if successful, error code if not. + */ +int wm_fll_set_parent(struct wm_fll_data *hw, enum wm_fll_ref_source index) +{ + unsigned int ref; + bool osc_en; + int err; + + osc_en = index == FLL_REF_OSC; + err = wm_fll_set_free_running_mode(hw, osc_en); + if (osc_en || err) + return err; + + err = -EINVAL; + for (ref = 0; ref < ARRAY_SIZE(hw->desc->clk_ref_map); ++ref) { + if (hw->desc->clk_ref_map[ref] != index) + continue; + + err = 0; + break; + } + if (err < 0) + return err; + + /* set FLL reference input */ + ref = FIELD_PREP(WM_FLL_CLK_REF_SRC, ref); + err = regmap_update_bits(hw->regmap, WM_FLL_CONTROL_5, + WM_FLL_CLK_REF_SRC, ref); + return err; +} +EXPORT_SYMBOL_GPL(wm_fll_set_parent); + +/** + * wm_fll_enable() - Enable FLL + * + * @hw: initialized FLL hardware info + * + * Requests source clock and starts the FLL. + * Waits for lock before returning. + * + * Return 0 if successful, error code if not. + */ +int wm_fll_enable(struct wm_fll_data *hw) +{ + unsigned int val; + int clk_src; + int err, retry; + + err = clk_src = wm_fll_get_parent(hw); + if (err < 0) + return err; + + if (clk_src == FLL_REF_MCLK) { + err = clk_prepare_enable(hw->mclk); + if (err < 0) + return err; + } + + err = regmap_update_bits(hw->regmap, WM_FLL_CONTROL_1, + WM_FLL_OSC_ENA, WM_FLL_OSC_ENA); + if (err < 0) + goto err_out; + + err = regmap_write(hw->regmap, WM_INTERRUPT_STATUS, + hw->desc->int_mask); + if (err < 0) + goto err_out; + + err = regmap_update_bits(hw->regmap, WM_FLL_CONTROL_1, + WM_FLL_ENA, WM_FLL_ENA); + if (err) + goto err_out; + + if (clk_src == FLL_REF_OSC) { + usleep_range(150, 250); + return 0; + } + + for (retry = 3; retry; --retry) { + msleep(1); + err = regmap_read(hw->regmap, WM_INTERRUPT_STATUS, &val); + if (err < 0) + goto err_out; + + if (val & hw->desc->int_mask) + break; + } + + /* it seems that FLL_LOCK might never be asserted */ + /* eg. WM8904's FLL doesn't, but works anyway */ + return 0; + +err_out: + wm_fll_disable(hw); + + if (clk_src == FLL_REF_MCLK) + clk_disable_unprepare(hw->mclk); + + return err; +} +EXPORT_SYMBOL_GPL(wm_fll_enable); + +/** + * wm_fll_disable() - Disable FLL + * + * @hw: initialized FLL hardware info + * + * Return 0 if successful, error code if not. + */ +int wm_fll_disable(struct wm_fll_data *hw) +{ + return regmap_update_bits(hw->regmap, WM_FLL_CONTROL_1, + WM_FLL_ENA|WM_FLL_OSC_ENA, + 0); +} +EXPORT_SYMBOL_GPL(wm_fll_disable); + +/** + * wm_fll_is_enabled() - Check whether FLL is enabled + * + * @hw: initialized FLL hardware info + * + * Returns 0 if disabled, 1 if enabled, or negative error code. + */ +int wm_fll_is_enabled(struct wm_fll_data *hw) +{ + unsigned int val; + int err; + + err = regmap_read(hw->regmap, WM_FLL_CONTROL_1, &val); + if (err < 0) + return err; + + return FIELD_GET(WM_FLL_ENA, val); +} +EXPORT_SYMBOL_GPL(wm_fll_is_enabled); + +static unsigned int wm_fll_apply_refdiv(unsigned long *parent_rate) +{ + unsigned int refdiv; + + /* FLL input divider; should ensure Fin <= 13.5MHz */ + + refdiv = DIV_ROUND_UP(*parent_rate, 13500000); + refdiv = order_base_2(refdiv); + if (refdiv > 3) + refdiv = 3; + + *parent_rate >>= refdiv; + + return refdiv; +} + +static unsigned int wm_fll_apply_fratio(unsigned long *parent_rate) +{ + unsigned int fratio; + + /* FLL comparator divider; efectively Fin multiplier */ + /* as tabularized in WM datasheet */ + + if (*parent_rate >= 256000) + fratio = *parent_rate < 1024000; + else if (*parent_rate >= 64000) + fratio = 2 + (*parent_rate < 128000); + else + fratio = 4; + + *parent_rate <<= fratio; + + return fratio; +} + +static unsigned int wm_fll_apply_outdiv_rev(unsigned long *rate) +{ + unsigned int div; + + /* Fvco -> Fout divider; target: 90 <= Fvco <= 100 MHz */ + + div = DIV_ROUND_UP(90000000, *rate); + if (div > 64) { + *rate = 90000000; + return 64; + } + + if (div < 4) + div = 4; + + *rate *= div; + return div; +} + +static unsigned long wm_fll_apply_frac(struct wm_fll_data *hw, + unsigned long rate_in, unsigned long *rate_out, + unsigned int *kdiv_out) +{ + unsigned long long freq; + unsigned long rate = *rate_out; + unsigned int kdiv = 0x10000; + + if (WM_FLL_USES_EFS(hw)) { + unsigned int cd, num; + + cd = gcd(rate, rate_in); + freq = rate / rate_in; + num = rate - freq * rate_in; + num /= cd; + kdiv = rate_in / cd; + + rate = freq * rate_in + num * rate_in / kdiv; + freq = (freq << 16) | num; + } else { + freq = (unsigned long long)rate << 16; + freq += rate_in / 2; + do_div(freq, rate_in); + + rate = (freq * rate_in) >> 16; + } + + *rate_out = rate; + *kdiv_out = kdiv; + return freq; +} + +/** + * wm_fll_set_rate() - Configures FLL for specified bitrate + * + * @hw: initialized FLL hardware info + * @rate: bitrate to configure for + * + * Return 0 if successful, error code if not. FLL must be disabled + * on entry. + */ +int wm_fll_set_rate(struct wm_fll_data *hw, unsigned long rate) +{ + unsigned long freq, mclk_rate; + unsigned int val, mask, refdiv, outdiv, fratio, kdiv; + int err; + + err = wm_fll_is_enabled(hw); + if (err < 0) + return err; + if (err > 0) + return -EBUSY; + + err = wm_fll_get_parent(hw); + if (err < 0) + return err; + + if (err != FLL_REF_OSC) { + unsigned long parent_rate; + + if (hw->mclk) + hw->freq_in = clk_get_rate(hw->mclk); + + parent_rate = mclk_rate = hw->freq_in; + refdiv = wm_fll_apply_refdiv(&parent_rate); + fratio = wm_fll_apply_fratio(&parent_rate); + outdiv = wm_fll_apply_outdiv_rev(&rate); + freq = wm_fll_apply_frac(hw, parent_rate, &rate, &kdiv); + } else { + unsigned long vco_rate = 96000000; + + mclk_rate = 0; + fratio = refdiv = 0; + rate = DIV_ROUND_CLOSEST(vco_rate, rate); + outdiv = clamp_t(unsigned long, rate, 4, 64); + freq = 0x177 << 16; + kdiv = 0; + + rate = vco_rate; + } + + /* configure */ + + dev_dbg(regmap_get_device(hw->regmap), + "configuring FLL for %luHz -> %luHz -> %luHz\n", + mclk_rate, rate, rate / outdiv); + dev_dbg(regmap_get_device(hw->regmap), + "FLL settings: N=%lu K=%lu/%u FRATIO=%u OUTDIV=%u REF_DIV=%u\n", + freq >> 16, freq & 0xFFFF, kdiv, fratio, outdiv, refdiv); + + val = FIELD_PREP(WM_FLL_CLK_REF_DIV, refdiv); + err = regmap_update_bits(hw->regmap, WM_FLL_CONTROL_5, + WM_FLL_CLK_REF_DIV, val); + if (err < 0) + return err; + + val = FIELD_PREP(WM_FLL_OUTDIV, outdiv - 1) | + FIELD_PREP(WM_FLL_FRATIO, fratio); + mask = WM_FLL_OUTDIV | WM_FLL_FRATIO; + err = regmap_update_bits(hw->regmap, WM_FLL_CONTROL_2, mask, val); + if (err < 0) + return err; + + err = regmap_write(hw->regmap, WM_FLL_CONTROL_3, (uint16_t)freq); + if (err < 0) + return err; + + if (WM_FLL_USES_EFS(hw)) { + val = FIELD_PREP(WM_FLL_EFS_ENA, !!(uint16_t)freq); + err = regmap_update_bits(hw->regmap, WM_FLL_EFS_2, + WM_FLL_EFS_ENA, val); + if (err < 0) + return err; + + val = FIELD_PREP(WM_FLL_LAMBDA, kdiv); + err = regmap_update_bits(hw->regmap, WM_FLL_EFS_1, + WM_FLL_LAMBDA, val); + } else { + val = FIELD_PREP(WM_FLL_FRACN_ENA, !!(uint16_t)freq); + err = regmap_update_bits(hw->regmap, WM_FLL_CONTROL_1, + WM_FLL_FRACN_ENA, val); + } + if (err < 0) + return err; + + val = FIELD_PREP(WM_FLL_N, freq >> 16); + err = regmap_update_bits(hw->regmap, WM_FLL_CONTROL_4, + WM_FLL_N, val); + return err; +} +EXPORT_SYMBOL_GPL(wm_fll_set_rate); + +/** + * wm_fll_init() - Initialize FLL + * + * @hw: FLL hardware info + * + * Checks and initializes FLL structure. + * Requires hw->desc and hw->regmap to be filled in by caller. + * + * Return 0 if successful, negative error code if not. + * A message is logged on error. + */ +int wm_fll_init(struct wm_fll_data *hw) +{ + if (!IS_ENABLED(CONFIG_SND_SOC_WM_FLL_EFS) && hw->desc->efs_offset) { + struct device *dev = regmap_get_device(hw->regmap); + + dev_err(dev, "FLL EFS support not compiled in\n"); + return -ENOSYS; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm_fll_init); + +/** + * wm_fll_init_with_clk() - Initialize FLL + * + * @hw: FLL hardware info + * + * Checks and initializes FLL described in @hw, and requests MCLK input clock. + * Requires hw->desc and hw->regmap to be filled in by caller. + * + * Return 0 if successful, negative error code if not. + * A message is logged on error. + */ +int wm_fll_init_with_clk(struct wm_fll_data *hw) +{ + struct device *dev = regmap_get_device(hw->regmap); + int err; + + err = wm_fll_init(hw); + if (err) + return err; + + hw->mclk = devm_clk_get(dev, "mclk"); + if (IS_ERR(hw->mclk)) { + err = PTR_ERR(hw->mclk); + dev_err(dev, "Failed to get MCLK for FLL @0x%x: %d\n", + hw->desc->ctl_offset, err); + return err; + } + + return 0; +} +EXPORT_SYMBOL_GPL(wm_fll_init_with_clk); diff --git a/sound/soc/codecs/wm_fll.h b/sound/soc/codecs/wm_fll.h new file mode 100644 index 000000000000..8519a8691397 --- /dev/null +++ b/sound/soc/codecs/wm_fll.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * wm_fll.h -- FLL support for Wolfson codecs + * + * Copyright 2019 Michał Mirosław + */ + +#ifndef _WM_FLL_H +#define _WM_FLL_H + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/regmap.h> + +enum wm_fll_ref_source +{ + FLL_REF_MCLK = 1, + FLL_REF_MCLK2, + FLL_REF_BCLK, + FLL_REF_FSCLK, + FLL_REF_OSC, +}; + +/** + * struct wm_fll_desc - FLL variant description + * + * @offset: FLL control register block offset + * @clk_ref_map: FLL_REF_* assignment for each of FLL.REF_SRC field value + */ +struct wm_fll_desc +{ + uint16_t ctl_offset; + uint16_t int_offset; + uint16_t int_mask; + uint16_t nco_reg0; + uint16_t nco_reg1; + uint8_t frc_nco_shift; + uint8_t frc_nco_val_shift; + uint16_t efs_offset; + uint8_t clk_ref_map[4]; +}; + +struct wm_fll_data +{ + const struct wm_fll_desc *desc; + struct regmap *regmap; + unsigned long freq_in; + + struct clk *mclk; +}; + +int wm_fll_init(struct wm_fll_data *hw); +int wm_fll_init_with_clk(struct wm_fll_data *hw); +int wm_fll_is_enabled(struct wm_fll_data *hw); +int wm_fll_enable(struct wm_fll_data *hw); +int wm_fll_disable(struct wm_fll_data *hw); +int wm_fll_set_parent(struct wm_fll_data *hw, enum wm_fll_ref_source index); +int wm_fll_set_rate(struct wm_fll_data *hw, unsigned long rate); + +#endif /* _WM_FLL_H */
Rework FLL handling to use common code introduced earlier.
Signed-off-by: Michał Mirosław mirq-linux@rere.qmqm.pl --- sound/soc/atmel/atmel_wm8904.c | 11 +- sound/soc/codecs/Kconfig | 1 + sound/soc/codecs/wm8904.c | 476 ++++++++++----------------------- sound/soc/codecs/wm8904.h | 5 - 4 files changed, 140 insertions(+), 353 deletions(-)
diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c index 776b27d3686e..b77ea2495efe 100644 --- a/sound/soc/atmel/atmel_wm8904.c +++ b/sound/soc/atmel/atmel_wm8904.c @@ -30,20 +30,11 @@ static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai = rtd->codec_dai; int ret;
- ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK, - 32768, params_rate(params) * 256); - if (ret < 0) { - pr_err("%s - failed to set wm8904 codec PLL.", __func__); - return ret; - } - /* * As here wm8904 use FLL output as its system clock - * so calling set_sysclk won't care freq parameter - * then we pass 0 */ ret = snd_soc_dai_set_sysclk(codec_dai, WM8904_CLK_FLL, - 0, SND_SOC_CLOCK_IN); + params_rate(params) * 256, SND_SOC_CLOCK_IN); if (ret < 0) { pr_err("%s -failed to set wm8904 SYSCLK\n", __func__); return ret; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 04086acf6d93..1a680023af7d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1331,6 +1331,7 @@ config SND_SOC_WM8903 config SND_SOC_WM8904 tristate "Wolfson Microelectronics WM8904 CODEC" depends on I2C + select SND_SOC_WM_FLL
config SND_SOC_WM8940 tristate diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index bcb3c9d5abf0..c9318fe34f91 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -24,6 +24,7 @@ #include <sound/tlv.h> #include <sound/wm8904.h>
+#include "wm_fll.h" #include "wm8904.h"
enum wm8904_type { @@ -66,12 +67,8 @@ struct wm8904_priv { int retune_mobile_cfg; struct soc_enum retune_mobile_enum;
- /* FLL setup */ - int fll_src; - int fll_fref; - int fll_fout; - /* Clocking configuration */ + struct wm_fll_data fll; unsigned int mclk_rate; int sysclk_src; unsigned int sysclk_rate; @@ -311,35 +308,111 @@ static bool wm8904_readable_register(struct device *dev, unsigned int reg) } }
-static int wm8904_configure_clocking(struct snd_soc_component *component) +static void wm8904_unprepare_sysclk(struct wm8904_priv *priv) { + switch (priv->sysclk_src) { + case WM8904_CLK_MCLK: + clk_disable_unprepare(priv->mclk); + break; + + case WM8904_CLK_FLL: + wm_fll_disable(&priv->fll); + break; + } +} + +static int wm8904_prepare_sysclk(struct wm8904_priv *priv) +{ + int err; + + switch (priv->sysclk_src) { + case WM8904_CLK_MCLK: + err = clk_set_rate(priv->mclk, priv->mclk_rate); + if (!err) + err = clk_prepare_enable(priv->mclk); + break; + + case WM8904_CLK_FLL: + err = wm_fll_enable(&priv->fll); + break; + + default: + err = -EINVAL; + break; + } + + return err; +} + +static void wm8904_disable_sysclk(struct wm8904_priv *priv) +{ + regmap_update_bits(priv->regmap, WM8904_CLOCK_RATES_2, + WM8904_CLK_SYS_ENA, 0); + wm8904_unprepare_sysclk(priv); +} + +static int wm8904_enable_sysclk(struct wm8904_priv *priv) +{ + int err; + + err = wm8904_prepare_sysclk(priv); + if (err < 0) + return err; + + err = regmap_update_bits(priv->regmap, WM8904_CLOCK_RATES_2, + WM8904_CLK_SYS_ENA_MASK, WM8904_CLK_SYS_ENA); + if (err < 0) + wm8904_unprepare_sysclk(priv); + + return err; +} + +static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int rate, int dir) +{ + struct snd_soc_component *component = dai->component; struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); - unsigned int clock0, clock2, rate; + unsigned int clock0, clock2; + int err; + + switch (clk_id) { + case WM8904_CLK_MCLK: + case WM8904_CLK_FLL: + break; + + default: + return -EINVAL; + } + + if (clk_id == wm8904->sysclk_src && rate == wm8904->mclk_rate) + return 0; + + dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, rate);
/* Gate the clock while we're updating to avoid misclocking */ clock2 = snd_soc_component_read32(component, WM8904_CLOCK_RATES_2); - snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2, - WM8904_SYSCLK_SRC, 0); + wm8904_disable_sysclk(wm8904); + + wm8904->sysclk_src = clk_id; + wm8904->mclk_rate = rate;
- /* This should be done on init() for bypass paths */ switch (wm8904->sysclk_src) { case WM8904_CLK_MCLK: - dev_dbg(component->dev, "Using %dHz MCLK\n", wm8904->mclk_rate); + dev_dbg(component->dev, "Using %dHz MCLK\n", rate);
clock2 &= ~WM8904_SYSCLK_SRC; - rate = wm8904->mclk_rate; - - /* Ensure the FLL is stopped */ - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, - WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0); break;
case WM8904_CLK_FLL: - dev_dbg(component->dev, "Using %dHz FLL clock\n", - wm8904->fll_fout); + err = wm_fll_set_rate(&wm8904->fll, rate); + if (err < 0) { + dev_err(component->dev, "Failed to set FLL rate: %d\n", err); + return err; + } + + dev_dbg(component->dev, "Using %dHz FLL clock\n", rate);
clock2 |= WM8904_SYSCLK_SRC; - rate = wm8904->fll_fout; break;
default: @@ -356,11 +429,18 @@ static int wm8904_configure_clocking(struct snd_soc_component *component) wm8904->sysclk_rate = rate; }
- snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_0, WM8904_MCLK_DIV, - clock0); - + snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_0, + WM8904_MCLK_DIV, clock0); snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2, - WM8904_CLK_SYS_ENA | WM8904_SYSCLK_SRC, clock2); + WM8904_SYSCLK_SRC, clock2); + + if (clock2 & WM8904_CLK_SYS_ENA) { + err = wm8904_enable_sysclk(wm8904); + if (err < 0) { + dev_err(component->dev, "Failed to reenable CLK_SYS: %d\n", err); + return err; + } + }
dev_dbg(component->dev, "CLK_SYS is %dHz\n", wm8904->sysclk_rate);
@@ -655,33 +735,21 @@ static int sysclk_event(struct snd_soc_dapm_widget *w, { struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); + int ret = 0;
switch (event) { case SND_SOC_DAPM_PRE_PMU: - /* If we're using the FLL then we only start it when - * required; we assume that the configuration has been - * done previously and all we need to do is kick it - * off. - */ - switch (wm8904->sysclk_src) { - case WM8904_CLK_FLL: - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, - WM8904_FLL_OSC_ENA, - WM8904_FLL_OSC_ENA); - - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, - WM8904_FLL_ENA, - WM8904_FLL_ENA); - break; - - default: - break; - } + ret = wm8904_prepare_sysclk(wm8904); + if (ret) + dev_err(component->dev, + "Failed to prepare SYSCLK: %d\n", ret); + else + dev_dbg(component->dev, "SYSCLK on\n"); break;
case SND_SOC_DAPM_POST_PMD: - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, - WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0); + wm8904_unprepare_sysclk(wm8904); + dev_dbg(component->dev, "SYSCLK off\n"); break; }
@@ -1289,7 +1357,7 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_component *component = dai->component; struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); - int ret, i, best, best_val, cur_val; + int i, best, best_val, cur_val; unsigned int aif1 = 0; unsigned int aif2 = 0; unsigned int aif3 = 0; @@ -1324,13 +1392,8 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream, return -EINVAL; }
- dev_dbg(component->dev, "Target BCLK is %dHz\n", wm8904->bclk);
- ret = wm8904_configure_clocking(component); - if (ret != 0) - return ret; - /* Select nearest CLK_SYS_RATE */ best = 0; best_val = abs((wm8904->sysclk_rate / clk_sys_rates[0].ratio) @@ -1382,8 +1445,8 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream, } } wm8904->bclk = (wm8904->sysclk_rate * 10) / bclk_divs[best].div; - dev_dbg(component->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n", - bclk_divs[best].div, wm8904->bclk); + dev_dbg(component->dev, "Selected BCLK_DIV of %d.%d for %dHz BCLK\n", + bclk_divs[best].div / 10, bclk_divs[best].div % 10, wm8904->bclk); aif2 |= bclk_divs[best].bclk_div;
/* LRCLK is a simple fraction of BCLK */ @@ -1410,34 +1473,6 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream, return 0; }
- -static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id, - unsigned int freq, int dir) -{ - struct snd_soc_component *component = dai->component; - struct wm8904_priv *priv = snd_soc_component_get_drvdata(component); - - switch (clk_id) { - case WM8904_CLK_MCLK: - priv->sysclk_src = clk_id; - priv->mclk_rate = freq; - break; - - case WM8904_CLK_FLL: - priv->sysclk_src = clk_id; - break; - - default: - return -EINVAL; - } - - dev_dbg(dai->dev, "Clock source is %d at %uHz\n", clk_id, freq); - - wm8904_configure_clocking(component); - - return 0; -} - static int wm8904_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_component *component = dai->component; @@ -1577,253 +1612,6 @@ static int wm8904_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, return 0; }
-struct _fll_div { - u16 fll_fratio; - u16 fll_outdiv; - u16 fll_clk_ref_div; - u16 n; - u16 k; -}; - -/* The size in bits of the FLL divide multiplied by 10 - * to allow rounding later */ -#define FIXED_FLL_SIZE ((1 << 16) * 10) - -static struct { - unsigned int min; - unsigned int max; - u16 fll_fratio; - int ratio; -} fll_fratios[] = { - { 0, 64000, 4, 16 }, - { 64000, 128000, 3, 8 }, - { 128000, 256000, 2, 4 }, - { 256000, 1000000, 1, 2 }, - { 1000000, 13500000, 0, 1 }, -}; - -static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, - unsigned int Fout) -{ - u64 Kpart; - unsigned int K, Ndiv, Nmod, target; - unsigned int div; - int i; - - /* Fref must be <=13.5MHz */ - div = 1; - fll_div->fll_clk_ref_div = 0; - while ((Fref / div) > 13500000) { - div *= 2; - fll_div->fll_clk_ref_div++; - - if (div > 8) { - pr_err("Can't scale %dMHz input down to <=13.5MHz\n", - Fref); - return -EINVAL; - } - } - - pr_debug("Fref=%u Fout=%u\n", Fref, Fout); - - /* Apply the division for our remaining calculations */ - Fref /= div; - - /* Fvco should be 90-100MHz; don't check the upper bound */ - div = 4; - while (Fout * div < 90000000) { - div++; - if (div > 64) { - pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", - Fout); - return -EINVAL; - } - } - target = Fout * div; - fll_div->fll_outdiv = div - 1; - - pr_debug("Fvco=%dHz\n", target); - - /* Find an appropriate FLL_FRATIO and factor it out of the target */ - for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { - if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { - fll_div->fll_fratio = fll_fratios[i].fll_fratio; - target /= fll_fratios[i].ratio; - break; - } - } - if (i == ARRAY_SIZE(fll_fratios)) { - pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); - return -EINVAL; - } - - /* Now, calculate N.K */ - Ndiv = target / Fref; - - fll_div->n = Ndiv; - Nmod = target % Fref; - 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, Fref); - - K = Kpart & 0xFFFFFFFF; - - if ((K % 10) >= 5) - K += 5; - - /* Move down to proper range now rounding is done */ - fll_div->k = K / 10; - - pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n", - fll_div->n, fll_div->k, - fll_div->fll_fratio, fll_div->fll_outdiv, - fll_div->fll_clk_ref_div); - - return 0; -} - -static int wm8904_set_fll(struct snd_soc_dai *dai, int fll_id, int source, - unsigned int Fref, unsigned int Fout) -{ - struct snd_soc_component *component = dai->component; - struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); - struct _fll_div fll_div; - int ret, val; - int clock2, fll1; - - /* Any change? */ - if (source == wm8904->fll_src && Fref == wm8904->fll_fref && - Fout == wm8904->fll_fout) - return 0; - - clock2 = snd_soc_component_read32(component, WM8904_CLOCK_RATES_2); - - if (Fout == 0) { - dev_dbg(component->dev, "FLL disabled\n"); - - wm8904->fll_fref = 0; - wm8904->fll_fout = 0; - - /* Gate SYSCLK to avoid glitches */ - snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2, - WM8904_CLK_SYS_ENA, 0); - - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, - WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0); - - goto out; - } - - /* Validate the FLL ID */ - switch (source) { - case WM8904_FLL_MCLK: - case WM8904_FLL_LRCLK: - case WM8904_FLL_BCLK: - ret = fll_factors(&fll_div, Fref, Fout); - if (ret != 0) - return ret; - break; - - case WM8904_FLL_FREE_RUNNING: - dev_dbg(component->dev, "Using free running FLL\n"); - /* Force 12MHz and output/4 for now */ - Fout = 12000000; - Fref = 12000000; - - memset(&fll_div, 0, sizeof(fll_div)); - fll_div.fll_outdiv = 3; - break; - - default: - dev_err(component->dev, "Unknown FLL ID %d\n", fll_id); - return -EINVAL; - } - - /* Save current state then disable the FLL and SYSCLK to avoid - * misclocking */ - fll1 = snd_soc_component_read32(component, WM8904_FLL_CONTROL_1); - snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2, - WM8904_CLK_SYS_ENA, 0); - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, - WM8904_FLL_OSC_ENA | WM8904_FLL_ENA, 0); - - /* Unlock forced oscilator control to switch it on/off */ - snd_soc_component_update_bits(component, WM8904_CONTROL_INTERFACE_TEST_1, - WM8904_USER_KEY, WM8904_USER_KEY); - - if (fll_id == WM8904_FLL_FREE_RUNNING) { - val = WM8904_FLL_FRC_NCO; - } else { - val = 0; - } - - snd_soc_component_update_bits(component, WM8904_FLL_NCO_TEST_1, WM8904_FLL_FRC_NCO, - val); - snd_soc_component_update_bits(component, WM8904_CONTROL_INTERFACE_TEST_1, - WM8904_USER_KEY, 0); - - switch (fll_id) { - case WM8904_FLL_MCLK: - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5, - WM8904_FLL_CLK_REF_SRC_MASK, 0); - break; - - case WM8904_FLL_LRCLK: - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5, - WM8904_FLL_CLK_REF_SRC_MASK, 1); - break; - - case WM8904_FLL_BCLK: - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5, - WM8904_FLL_CLK_REF_SRC_MASK, 2); - break; - } - - if (fll_div.k) - val = WM8904_FLL_FRACN_ENA; - else - val = 0; - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, - WM8904_FLL_FRACN_ENA, val); - - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_2, - WM8904_FLL_OUTDIV_MASK | WM8904_FLL_FRATIO_MASK, - (fll_div.fll_outdiv << WM8904_FLL_OUTDIV_SHIFT) | - (fll_div.fll_fratio << WM8904_FLL_FRATIO_SHIFT)); - - snd_soc_component_write(component, WM8904_FLL_CONTROL_3, fll_div.k); - - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_4, WM8904_FLL_N_MASK, - fll_div.n << WM8904_FLL_N_SHIFT); - - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_5, - WM8904_FLL_CLK_REF_DIV_MASK, - fll_div.fll_clk_ref_div - << WM8904_FLL_CLK_REF_DIV_SHIFT); - - dev_dbg(component->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout); - - wm8904->fll_fref = Fref; - wm8904->fll_fout = Fout; - wm8904->fll_src = source; - - /* Enable the FLL if it was previously active */ - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, - WM8904_FLL_OSC_ENA, fll1); - snd_soc_component_update_bits(component, WM8904_FLL_CONTROL_1, - WM8904_FLL_ENA, fll1); - -out: - /* Reenable SYSCLK if it was previously active */ - snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_2, - WM8904_CLK_SYS_ENA, clock2); - - return 0; -} - static int wm8904_digital_mute(struct snd_soc_dai *codec_dai, int mute) { struct snd_soc_component *component = codec_dai->component; @@ -1871,15 +1659,6 @@ static int wm8904_set_bias_level(struct snd_soc_component *component, return ret; }
- ret = clk_prepare_enable(wm8904->mclk); - if (ret) { - dev_err(component->dev, - "Failed to enable MCLK: %d\n", ret); - regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), - wm8904->supplies); - return ret; - } - regcache_cache_only(wm8904->regmap, false); regcache_sync(wm8904->regmap);
@@ -1922,7 +1701,6 @@ static int wm8904_set_bias_level(struct snd_soc_component *component,
regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies); - clk_disable_unprepare(wm8904->mclk); break; } return 0; @@ -1937,7 +1715,6 @@ static const struct snd_soc_dai_ops wm8904_dai_ops = { .set_sysclk = wm8904_set_sysclk, .set_fmt = wm8904_set_fmt, .set_tdm_slot = wm8904_set_tdm_slot, - .set_pll = wm8904_set_fll, .hw_params = wm8904_hw_params, .digital_mute = wm8904_digital_mute, }; @@ -2123,6 +1900,15 @@ static const struct regmap_config wm8904_regmap = { .num_reg_defaults = ARRAY_SIZE(wm8904_reg_defaults), };
+static const struct wm_fll_desc wm8904_fll_desc = { + .ctl_offset = WM8904_FLL_CONTROL_1, + .int_offset = WM8904_INTERRUPT_STATUS, + .int_mask = WM8904_FLL_LOCK_EINT_MASK, + .nco_reg0 = WM8904_FLL_NCO_TEST_0, + .nco_reg1 = WM8904_FLL_NCO_TEST_1, + .clk_ref_map = { FLL_REF_MCLK, FLL_REF_BCLK, FLL_REF_FSCLK, /* reserved */ 0 }, +}; + #ifdef CONFIG_OF static const struct of_device_id wm8904_of_match[] = { { @@ -2165,6 +1951,19 @@ static int wm8904_i2c_probe(struct i2c_client *i2c, return ret; }
+ wm8904->fll.regmap = wm8904->regmap; + wm8904->fll.desc = &wm8904_fll_desc; + ret = wm_fll_init_with_clk(&wm8904->fll); + if (ret) + return ret; + + ret = wm_fll_set_parent(&wm8904->fll, FLL_REF_MCLK); + if (ret < 0) { + dev_err(&i2c->dev, "Failed to select MCLK as FLL input: %d\n", + ret); + return ret; + } + if (i2c->dev.of_node) { const struct of_device_id *match;
@@ -2276,6 +2075,7 @@ static int wm8904_i2c_probe(struct i2c_client *i2c, WM8904_POBCTRL, 0);
/* Can leave the device powered off until we need it */ + wm8904_disable_sysclk(wm8904); regcache_cache_only(wm8904->regmap, true); regulator_bulk_disable(ARRAY_SIZE(wm8904->supplies), wm8904->supplies);
diff --git a/sound/soc/codecs/wm8904.h b/sound/soc/codecs/wm8904.h index c1bca52f9927..60af09e0bb15 100644 --- a/sound/soc/codecs/wm8904.h +++ b/sound/soc/codecs/wm8904.h @@ -13,11 +13,6 @@ #define WM8904_CLK_MCLK 1 #define WM8904_CLK_FLL 2
-#define WM8904_FLL_MCLK 1 -#define WM8904_FLL_BCLK 2 -#define WM8904_FLL_LRCLK 3 -#define WM8904_FLL_FREE_RUNNING 4 - /* * Register values. */
On Sun, Aug 25, 2019 at 02:17:35PM +0200, Michał Mirosław wrote:
Rework FLL handling to use common code introduced earlier.
Signed-off-by: Michał Mirosław mirq-linux@rere.qmqm.pl
Apologies for the slight delay in getting around to looking at this one, been quite busy and its a lot to go through.
sound/soc/atmel/atmel_wm8904.c | 11 +- sound/soc/codecs/Kconfig | 1 + sound/soc/codecs/wm8904.c | 476 ++++++++++----------------------- sound/soc/codecs/wm8904.h | 5 - 4 files changed, 140 insertions(+), 353 deletions(-)
diff --git a/sound/soc/atmel/atmel_wm8904.c b/sound/soc/atmel/atmel_wm8904.c index 776b27d3686e..b77ea2495efe 100644 --- a/sound/soc/atmel/atmel_wm8904.c +++ b/sound/soc/atmel/atmel_wm8904.c @@ -30,20 +30,11 @@ static int atmel_asoc_wm8904_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai = rtd->codec_dai; int ret;
- ret = snd_soc_dai_set_pll(codec_dai, WM8904_FLL_MCLK, WM8904_FLL_MCLK,
32768, params_rate(params) * 256);
- if (ret < 0) {
pr_err("%s - failed to set wm8904 codec PLL.", __func__);
return ret;
- }
As per my last comment it would be better to move the existing functionality of the driver over to the new library, then make actual functional changes in a separate patch. Clearly we have changed how the driver works here, since we no longer need to set the FLL.
This both makes review easier and proves that the new library approach can support the existing functionality of the driver.
+static int wm8904_prepare_sysclk(struct wm8904_priv *priv) +{
- int err;
- switch (priv->sysclk_src) {
- case WM8904_CLK_MCLK:
err = clk_set_rate(priv->mclk, priv->mclk_rate);
if (!err)
err = clk_prepare_enable(priv->mclk);
break;
- case WM8904_CLK_FLL:
err = wm_fll_enable(&priv->fll);
break;
Given the FLL can be sourced from the MCLK pin why is the mclk clock never enabled in the FLL case?
@@ -356,11 +429,18 @@ static int wm8904_configure_clocking(struct snd_soc_component *component) wm8904->sysclk_rate = rate; }
- snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_0, WM8904_MCLK_DIV,
clock0);
- snd_soc_component_update_bits(component, WM8904_CLOCK_RATES_0,
WM8904_MCLK_DIV, clock0);
Appreciate this is probably a good formatting change but with a large hard to review patch its better to keep unrelated changes out of it to easy review.
@@ -1382,8 +1445,8 @@ static int wm8904_hw_params(struct snd_pcm_substream *substream, } } wm8904->bclk = (wm8904->sysclk_rate * 10) / bclk_divs[best].div;
- dev_dbg(component->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n",
bclk_divs[best].div, wm8904->bclk);
- dev_dbg(component->dev, "Selected BCLK_DIV of %d.%d for %dHz BCLK\n",
bclk_divs[best].div / 10, bclk_divs[best].div % 10, wm8904->bclk);
This is a nice tidy up as well but would also be nice to not have it in this patch.
@@ -1937,7 +1715,6 @@ static const struct snd_soc_dai_ops wm8904_dai_ops = { .set_sysclk = wm8904_set_sysclk, .set_fmt = wm8904_set_fmt, .set_tdm_slot = wm8904_set_tdm_slot,
- .set_pll = wm8904_set_fll,
I am not keen on the way we are removing the ability to set the FLL source, there may be out of tree users using this and to my knowledge it is features that work at the moment so removing it seems like a step backwards.
+static const struct wm_fll_desc wm8904_fll_desc = {
- .ctl_offset = WM8904_FLL_CONTROL_1,
- .int_offset = WM8904_INTERRUPT_STATUS,
- .int_mask = WM8904_FLL_LOCK_EINT_MASK,
- .nco_reg0 = WM8904_FLL_NCO_TEST_0,
- .nco_reg1 = WM8904_FLL_NCO_TEST_1,
- .clk_ref_map = { FLL_REF_MCLK, FLL_REF_BCLK, FLL_REF_FSCLK, /* reserved */ 0 },
Minor nit, but would probably look nice to split this across a couple of lines and would keep us under the 80 char line limit.
.clk_ref_map = { .... },
@@ -2165,6 +1951,19 @@ static int wm8904_i2c_probe(struct i2c_client *i2c, /* Can leave the device powered off until we need it */
- wm8904_disable_sysclk(wm8904);
How come this is now enabled during probe?
I trimmed down the CC list, for the next version I would suggest using a similar list, this one was a little over sized.
Thanks, Charles
Choose clock source automatically if not provided. This will be the case with eg. audio-graph-card.
Signed-off-by: Michał Mirosław mirq-linux@rere.qmqm.pl --- sound/soc/codecs/wm8904.c | 42 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-)
diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index c9318fe34f91..946315d4cecf 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -367,15 +367,34 @@ static int wm8904_enable_sysclk(struct wm8904_priv *priv) return err; }
+static int wm8904_bump_fll_sysclk(unsigned int *rate); + static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int rate, int dir) { struct snd_soc_component *component = dai->component; struct wm8904_priv *wm8904 = snd_soc_component_get_drvdata(component); unsigned int clock0, clock2; - int err; + int err, do_div = false;
switch (clk_id) { + case 0: + if (rate == clk_round_rate(wm8904->mclk, rate)) { + clk_id = WM8904_CLK_MCLK; + } else if (rate * 2 == clk_round_rate(wm8904->mclk, rate * 2)) { + rate *= 2; + clk_id = WM8904_CLK_MCLK; + do_div = true; + } else { + clk_id = WM8904_CLK_FLL; + err = wm8904_bump_fll_sysclk(&rate); + if (err) { + dev_dbg(component->dev, "Can't match %u over FLL 1406250 Hz minimum\n", rate); + return err; + } + } + break; + case WM8904_CLK_MCLK: case WM8904_CLK_FLL: break; @@ -421,7 +440,9 @@ static int wm8904_set_sysclk(struct snd_soc_dai *dai, int clk_id, }
/* SYSCLK shouldn't be over 13.5MHz */ - if (rate > 13500000) { + if (rate > 13500000) + do_div = true; + if (do_div) { clock0 = WM8904_MCLK_DIV; wm8904->sysclk_rate = rate / 2; } else { @@ -1350,6 +1371,23 @@ static struct { { 480, 20 }, };
+static int wm8904_bump_fll_sysclk(unsigned int *rate) +{ + int i; + + /* bump SYSCLK rate if below minimal FLL output */ + + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + if (*rate * bclk_divs[i].div >= 1406250 * 10) + break; + } + + if (i == ARRAY_SIZE(bclk_divs)) + return -ERANGE; + + *rate = (*rate * bclk_divs[i].div) / 10; + return 0; +}
static int wm8904_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params,
Rework FLL handling to use common code. This uses polling for now to wait for FLL lock.
Signed-off-by: Michał Mirosław mirq-linux@rere.qmqm.pl --- sound/soc/codecs/Kconfig | 2 + sound/soc/codecs/wm8994.c | 281 +++++++++++--------------------------- sound/soc/codecs/wm8994.h | 4 +- 3 files changed, 84 insertions(+), 203 deletions(-)
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 1a680023af7d..1ff6290ce18d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -1382,6 +1382,8 @@ config SND_SOC_WM8993
config SND_SOC_WM8994 tristate + select SND_SOC_WM_FLL + select SND_SOC_WM_FLL_EFS
config SND_SOC_WM8995 tristate diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index c3d06e8bc54f..d0dbc352303b 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -2030,101 +2030,57 @@ static const struct snd_soc_dapm_route wm8958_intercon[] = { { "AIF3ADC Mux", "Mono PCM", "Mono PCM Out Mux" }, };
-/* 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 lambda; - u16 clk_ref_div; - u16 fll_fratio; +static const struct wm_fll_desc wm8994_fll_desc[2] = { + /* FLL1 */ + { + .ctl_offset = WM8994_FLL1_CONTROL_1, + .int_offset = WM8994_INTERRUPT_RAW_STATUS_2, + .int_mask = WM8994_IM_FLL1_LOCK_EINT_MASK, + .nco_reg0 = WM8994_FLL1_CONTROL_5, + .frc_nco_shift = 6, + .nco_reg1 = WM8994_FLL1_CONTROL_5, + .frc_nco_val_shift = 7, + .clk_ref_map = { FLL_REF_MCLK, FLL_REF_MCLK2, FLL_REF_FSCLK, FLL_REF_BCLK }, + }, + /* FLL2 */ + { + .ctl_offset = WM8994_FLL2_CONTROL_1, + .int_offset = WM8994_INTERRUPT_RAW_STATUS_2, + .int_mask = WM8994_IM_FLL2_LOCK_EINT_MASK, + .nco_reg0 = WM8994_FLL2_CONTROL_5, + .frc_nco_shift = 6, + .nco_reg1 = WM8994_FLL2_CONTROL_5, + .frc_nco_val_shift = 7, + .clk_ref_map = { FLL_REF_MCLK, FLL_REF_MCLK2, FLL_REF_FSCLK, FLL_REF_BCLK }, + }, };
-static int wm8994_get_fll_config(struct wm8994 *control, struct fll_div *fll, - int freq_in, int freq_out) -{ - u64 Kpart; - unsigned int K, Ndiv, Nmod, gcd_fll; - - 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; - } 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) { - fll->fll_fratio = 3; - freq_in *= 8; - } else { - fll->fll_fratio = 4; - freq_in *= 16; - } - 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); - - switch (control->type) { - case WM8994: - /* 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; - fll->lambda = 0; - - pr_debug("N=%x K=%x\n", fll->n, fll->k); - break; - - default: - gcd_fll = gcd(freq_out, freq_in); - - fll->k = (freq_out - (freq_in * fll->n)) / gcd_fll; - fll->lambda = freq_in / gcd_fll; - - } - - return 0; -} +static const struct wm_fll_desc wm8958_fll_desc[2] = { + /* FLL1 */ + { + .ctl_offset = WM8994_FLL1_CONTROL_1, + .int_offset = WM8994_INTERRUPT_RAW_STATUS_2, + .int_mask = WM8994_IM_FLL1_LOCK_EINT_MASK, + .nco_reg0 = WM8994_FLL1_CONTROL_5, + .frc_nco_shift = 6, + .nco_reg1 = WM8994_FLL1_CONTROL_5, + .frc_nco_val_shift = 7, + .efs_offset = WM8958_FLL1_EFS_1, + .clk_ref_map = { FLL_REF_MCLK, FLL_REF_MCLK2, FLL_REF_FSCLK, FLL_REF_BCLK }, + }, + /* FLL2 */ + { + .ctl_offset = WM8994_FLL2_CONTROL_1, + .int_offset = WM8994_INTERRUPT_RAW_STATUS_2, + .int_mask = WM8994_IM_FLL2_LOCK_EINT_MASK, + .nco_reg0 = WM8994_FLL2_CONTROL_5, + .frc_nco_shift = 6, + .nco_reg1 = WM8994_FLL2_CONTROL_5, + .frc_nco_val_shift = 7, + .efs_offset = WM8958_FLL1_EFS_1, + .clk_ref_map = { FLL_REF_MCLK, FLL_REF_MCLK2, FLL_REF_FSCLK, FLL_REF_BCLK }, + }, +};
static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src, unsigned int freq_in, unsigned int freq_out) @@ -2132,9 +2088,7 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src, struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); struct wm8994 *control = wm8994->wm8994; int reg_offset, ret; - struct fll_div fll; u16 reg, clk1, aif_reg, aif_src; - unsigned long timeout; bool was_enabled;
switch (id) { @@ -2152,9 +2106,6 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src, return -EINVAL; }
- reg = snd_soc_component_read32(component, WM8994_FLL1_CONTROL_1 + reg_offset); - was_enabled = reg & WM8994_FLL1_ENA; - switch (src) { case 0: /* Allow no source specification when stopping */ @@ -2166,10 +2117,12 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src, case WM8994_FLL_SRC_MCLK2: case WM8994_FLL_SRC_LRCLK: case WM8994_FLL_SRC_BCLK: + src = wm8994_fll_desc[0].clk_ref_map[src - WM8994_FLL_SRC_MCLK1]; break; case WM8994_FLL_SRC_INTERNAL: freq_in = 12000000; freq_out = 12000000; + src = FLL_REF_OSC; break; default: return -EINVAL; @@ -2180,18 +2133,6 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int 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(control, &fll, freq_in, freq_out); - else - ret = wm8994_get_fll_config(control, &fll, wm8994->fll[id].in, - wm8994->fll[id].out); - if (ret < 0) - return ret; - /* Make sure that we're not providing SYSCLK right now */ clk1 = snd_soc_component_read32(component, WM8994_CLOCKING_1); if (clk1 & WM8994_SYSCLK_SRC) @@ -2207,9 +2148,11 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src, return -EBUSY; }
- /* We always need to disable the FLL while reconfiguring */ - snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_1 + reg_offset, - WM8994_FLL1_ENA, 0); + was_enabled = wm_fll_is_enabled(&wm8994->fll_hw[id]) > 0; + + ret = wm_fll_disable(&wm8994->fll_hw[id]); + if (ret) + return ret;
if (wm8994->fll_byp && src == WM8994_FLL_SRC_BCLK && freq_in == freq_out && freq_out) { @@ -2217,46 +2160,21 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src, snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_5 + reg_offset, WM8958_FLL1_BYP, WM8958_FLL1_BYP); goto out; + } else if (wm8994->fll_byp) { + snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_5 + reg_offset, + WM8958_FLL1_BYP, 0); }
- reg = (fll.outdiv << WM8994_FLL1_OUTDIV_SHIFT) | - (fll.fll_fratio << WM8994_FLL1_FRATIO_SHIFT); - snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_2 + reg_offset, - WM8994_FLL1_OUTDIV_MASK | - WM8994_FLL1_FRATIO_MASK, reg); - - snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_3 + reg_offset, - WM8994_FLL1_K_MASK, fll.k); - - snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_4 + reg_offset, - WM8994_FLL1_N_MASK, - fll.n << WM8994_FLL1_N_SHIFT); - - if (fll.lambda) { - snd_soc_component_update_bits(component, WM8958_FLL1_EFS_1 + reg_offset, - WM8958_FLL1_LAMBDA_MASK, - fll.lambda); - snd_soc_component_update_bits(component, WM8958_FLL1_EFS_2 + reg_offset, - WM8958_FLL1_EFS_ENA, WM8958_FLL1_EFS_ENA); - } else { - snd_soc_component_update_bits(component, WM8958_FLL1_EFS_2 + reg_offset, - WM8958_FLL1_EFS_ENA, 0); - } - - snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_5 + reg_offset, - WM8994_FLL1_FRC_NCO | WM8958_FLL1_BYP | - WM8994_FLL1_REFCLK_DIV_MASK | - WM8994_FLL1_REFCLK_SRC_MASK, - ((src == WM8994_FLL_SRC_INTERNAL) - << WM8994_FLL1_FRC_NCO_SHIFT) | - (fll.clk_ref_div << WM8994_FLL1_REFCLK_DIV_SHIFT) | - (src - 1)); - - /* Clear any pending completion from a previous failure */ - try_wait_for_completion(&wm8994->fll_locked[id]); - - /* Enable (with fractional mode if required) */ if (freq_out) { + wm8994->fll_hw[id].freq_in = freq_in; + ret = wm_fll_set_parent(&wm8994->fll_hw[id], src); + if (!ret) + ret = wm_fll_set_rate(&wm8994->fll_hw[id], freq_out); + if (!ret) + ret = wm_fll_enable(&wm8994->fll_hw[id]); + if (ret < 0) + return ret; + /* Enable VMID if we need it */ if (!was_enabled) { active_reference(component); @@ -2273,27 +2191,6 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src, break; } } - - reg = WM8994_FLL1_ENA; - - if (fll.k) - reg |= WM8994_FLL1_FRAC; - if (src == WM8994_FLL_SRC_INTERNAL) - reg |= WM8994_FLL1_OSC_ENA; - - snd_soc_component_update_bits(component, WM8994_FLL1_CONTROL_1 + reg_offset, - WM8994_FLL1_ENA | WM8994_FLL1_OSC_ENA | - WM8994_FLL1_FRAC, reg); - - if (wm8994->fll_locked_irq) { - timeout = wait_for_completion_timeout(&wm8994->fll_locked[id], - msecs_to_jiffies(10)); - if (timeout == 0) - dev_warn(component->dev, - "Timed out waiting for FLL lock\n"); - } else { - msleep(5); - } } else { if (was_enabled) { switch (control->type) { @@ -2350,15 +2247,6 @@ static int _wm8994_set_fll(struct snd_soc_component *component, int id, int src, return 0; }
-static irqreturn_t wm8994_fll_locked_irq(int irq, void *data) -{ - struct completion *completion = data; - - complete(completion); - - return IRQ_HANDLED; -} - static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 };
static int wm8994_set_fll(struct snd_soc_dai *dai, int id, int src, @@ -3992,6 +3880,18 @@ static int wm8994_component_probe(struct snd_soc_component *component)
snd_soc_component_init_regmap(component, control->regmap);
+ for (i = 0; i < ARRAY_SIZE(wm8994->fll_hw); ++i) { + wm8994->fll_hw[i].regmap = control->regmap; + if (control->type == WM8994) + wm8994->fll_hw[i].desc = wm8994_fll_desc; + else + wm8994->fll_hw[i].desc = wm8958_fll_desc; + + ret = wm_fll_init(&wm8994->fll_hw[i]); + if (ret) + return ret; + } + wm8994->hubs.component = component;
mutex_init(&wm8994->accdet_lock); @@ -4013,9 +3913,6 @@ static int wm8994_component_probe(struct snd_soc_component *component)
INIT_DELAYED_WORK(&wm8994->mic_complete_work, wm8958_mic_work);
- for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) - init_completion(&wm8994->fll_locked[i]); - wm8994->micdet_irq = control->pdata.micdet_irq;
/* By default use idle_bias_off, will override for WM8994 */ @@ -4166,16 +4063,6 @@ static int wm8994_component_probe(struct snd_soc_component *component) break; }
- wm8994->fll_locked_irq = true; - for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) { - ret = wm8994_request_irq(wm8994->wm8994, - WM8994_IRQ_FLL1_LOCK + i, - wm8994_fll_locked_irq, "FLL lock", - &wm8994->fll_locked[i]); - if (ret != 0) - wm8994->fll_locked_irq = false; - } - /* Make sure we can read from the GPIOs if they're inputs */ pm_runtime_get_sync(component->dev);
@@ -4377,9 +4264,6 @@ static int wm8994_component_probe(struct snd_soc_component *component) wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_MIC1_SHRT, wm8994); if (wm8994->micdet_irq) free_irq(wm8994->micdet_irq, wm8994); - for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) - wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_FLL1_LOCK + i, - &wm8994->fll_locked[i]); wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_DCS_DONE, &wm8994->hubs); wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_FIFOS_ERR, component); @@ -4393,11 +4277,6 @@ static void wm8994_component_remove(struct snd_soc_component *component) { struct wm8994_priv *wm8994 = snd_soc_component_get_drvdata(component); struct wm8994 *control = wm8994->wm8994; - int i; - - for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) - wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_FLL1_LOCK + i, - &wm8994->fll_locked[i]);
wm8994_free_irq(wm8994->wm8994, WM8994_IRQ_DCS_DONE, &wm8994->hubs); diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 1d6f2abe1c11..9c61d95c9053 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -13,6 +13,7 @@ #include <linux/mutex.h>
#include "wm_hubs.h" +#include "wm_fll.h"
/* Sources for AIF1/2 SYSCLK - use with set_dai_sysclk() */ #define WM8994_SYSCLK_MCLK1 1 @@ -80,8 +81,7 @@ struct wm8994_priv { int aifdiv[2]; int channels[2]; struct wm8994_fll_config fll[2], fll_suspend[2]; - struct completion fll_locked[2]; - bool fll_locked_irq; + struct wm_fll_data fll_hw[2]; bool fll_byp; bool clk_has_run;
On Sun, Aug 25, 2019 at 02:17:30PM +0200, Michał Mirosław wrote:
This series allows to use WM8904 codec as audio-graph-card component. It starts with rework of FLL handling in the codec's driver, and as an example includes (untested) rework for codec with similar FLL: WM8994.
Please make some effort to focus your CC list on only relevant people, many upstream developers get a lot of e-mail and cutting down on that helps everyone stay more productive, too many can also set off anti-spam software. You've sent this to a lot of people and I'm struggling to figure out why most of them are on the list.
participants (3)
-
Charles Keepax
-
Mark Brown
-
Michał Mirosław