summaryrefslogtreecommitdiff
path: root/sound/soc/fsl/fsl_spdif.c
diff options
context:
space:
mode:
authorViorel Suman <viorel.suman@nxp.com>2018-08-16 14:33:07 +0300
committerDong Aisheng <aisheng.dong@nxp.com>2019-11-25 15:54:21 +0800
commit960f3f4848db6e2b0b83407e053dd0fed920e60a (patch)
treeb3bec7cd0e3a1fd9a3f14b6f52e63f8511f8ef01 /sound/soc/fsl/fsl_spdif.c
parent1b4843721915e8bc76bdfd7148cd382c43b0f046 (diff)
MLK-19154-3: ASoC: fsl_spdif: Add support for PLL switch at runtime.
iMX8 platforms typically have 2 AUDIO PLLs being configured to handle 8k and 11k audio rates. The patch implements the functionality to select at runtime the appropriate AUDIO PLL as function of audio file rate. Signed-off-by: Viorel Suman <viorel.suman@nxp.com> (cherry picked from commit 3a29374cfbe0bfaf1785fa66163ffd3b9e30aca3)
Diffstat (limited to 'sound/soc/fsl/fsl_spdif.c')
-rw-r--r--sound/soc/fsl/fsl_spdif.c303
1 files changed, 199 insertions, 104 deletions
diff --git a/sound/soc/fsl/fsl_spdif.c b/sound/soc/fsl/fsl_spdif.c
index 59214b148e28..eedf20e03a3f 100644
--- a/sound/soc/fsl/fsl_spdif.c
+++ b/sound/soc/fsl/fsl_spdif.c
@@ -11,6 +11,7 @@
#include <linux/bitrev.h>
#include <linux/clk.h>
+#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
@@ -120,6 +121,9 @@ struct fsl_spdif_priv {
struct snd_dmaengine_dai_dma_data dma_params_rx;
/* regcache for SRPC */
u32 regcache_srpc;
+ struct clk *pll8k_clk;
+ struct clk *pll11k_clk;
+ u8 streams;
};
static struct fsl_spdif_soc_data fsl_spdif_vf610 = {
@@ -602,6 +606,9 @@ static int fsl_spdif_hw_params(struct snd_pcm_substream *substream,
ret = spdif_set_rx_clksrc(spdif_priv, SPDIF_DEFAULT_GAINSEL, 1);
}
+ if (!ret)
+ spdif_priv->streams |= BIT(substream->stream);
+
return ret;
}
@@ -635,14 +642,197 @@ static int fsl_spdif_trigger(struct snd_pcm_substream *substream,
return 0;
}
+static int fsl_spdif_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct fsl_spdif_priv *spdif_priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+ spdif_priv->streams &= ~BIT(substream->stream);
+
+ return 0;
+}
+
+static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
+ struct clk *clk, u64 savesub,
+ enum spdif_txrate index, bool round)
+{
+ static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
+ bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk);
+ u64 rate_actual, sub;
+ u32 arate;
+ u16 sysclk_dfmin, sysclk_dfmax, sysclk_df;
+ u8 txclk_df;
+
+ /* The sysclk has an extra divisor [2, 512] */
+ sysclk_dfmin = is_sysclk ? 2 : 1;
+ sysclk_dfmax = is_sysclk ? 512 : 1;
+
+ for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) {
+ for (txclk_df = 1; txclk_df <= 128; txclk_df++) {
+
+ rate_actual = clk_get_rate(clk);
+
+ arate = rate_actual / 64;
+ arate /= txclk_df * sysclk_df;
+
+ if (arate == rate[index]) {
+ /* We are lucky */
+ savesub = 0;
+ spdif_priv->txclk_df[index] = txclk_df;
+ spdif_priv->sysclk_df[index] = sysclk_df;
+ spdif_priv->txrate[index] = arate;
+ goto out;
+ } else if (arate / rate[index] == 1) {
+ /* A little bigger than expect */
+ sub = (u64)(arate - rate[index]) * 100000;
+ do_div(sub, rate[index]);
+ if (sub >= savesub)
+ continue;
+ savesub = sub;
+ spdif_priv->txclk_df[index] = txclk_df;
+ spdif_priv->sysclk_df[index] = sysclk_df;
+ spdif_priv->txrate[index] = arate;
+ } else if (rate[index] / arate == 1) {
+ /* A little smaller than expect */
+ sub = (u64)(rate[index] - arate) * 100000;
+ do_div(sub, rate[index]);
+ if (sub >= savesub)
+ continue;
+ savesub = sub;
+ spdif_priv->txclk_df[index] = txclk_df;
+ spdif_priv->sysclk_df[index] = sysclk_df;
+ spdif_priv->txrate[index] = arate;
+ }
+ }
+ }
+
+out:
+ return savesub;
+}
+
+static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv,
+ enum spdif_txrate index)
+{
+ static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
+ struct platform_device *pdev = spdif_priv->pdev;
+ struct device *dev = &pdev->dev;
+ u64 savesub = 100000, ret;
+ struct clk *clk;
+ int i;
+
+ for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
+ clk = spdif_priv->txclk[i];
+ if (IS_ERR(clk)) {
+ dev_err(dev, "no rxtx%d clock in devicetree\n", i);
+ return PTR_ERR(clk);
+ }
+ if (!clk_get_rate(clk))
+ continue;
+
+ ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index,
+ i == STC_TXCLK_SPDIF_ROOT);
+ if (savesub == ret)
+ continue;
+
+ savesub = ret;
+ spdif_priv->txclk_src[index] = i;
+
+ /* To quick catch a divisor, we allow a 0.1% deviation */
+ if (savesub < 100)
+ break;
+ }
+
+ dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n",
+ spdif_priv->txclk_src[index], rate[index]);
+ dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n",
+ spdif_priv->txclk_df[index], rate[index]);
+ if (clk_is_match(spdif_priv->txclk[spdif_priv->txclk_src[index]], spdif_priv->sysclk))
+ dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n",
+ spdif_priv->sysclk_df[index], rate[index]);
+ dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n",
+ rate[index], spdif_priv->txrate[index]);
+
+ return 0;
+}
+
+static int fsl_spdif_set_dai_sysclk(struct snd_soc_dai *cpu_dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct fsl_spdif_priv *data = snd_soc_dai_get_drvdata(cpu_dai);
+ struct platform_device *pdev = data->pdev;
+ struct device *dev = &pdev->dev;
+ struct clk *clk, *p, *pll = 0, *npll = 0;
+ u64 ratio = freq;
+ int ret, i;
+
+ if (dir != SND_SOC_CLOCK_OUT || freq == 0 || clk_id != STC_TXCLK_SPDIF_ROOT)
+ return 0;
+
+ if (data->pll8k_clk == NULL || data->pll11k_clk == NULL)
+ return 0;
+
+ clk = data->txclk[clk_id];
+ if (IS_ERR_OR_NULL(clk)) {
+ dev_err(dev, "no rxtx%d clock in devicetree\n", clk_id);
+ return PTR_ERR(clk);
+ }
+
+ p = clk;
+ while (p && data->pll8k_clk && data->pll11k_clk) {
+ struct clk *pp = clk_get_parent(p);
+
+ if (clk_is_match(pp, data->pll8k_clk) ||
+ clk_is_match(pp, data->pll11k_clk)) {
+ pll = pp;
+ break;
+ }
+ p = pp;
+ }
+
+ if (pll) {
+ npll = (do_div(ratio, 8000) ? data->pll11k_clk : data->pll8k_clk);
+ if (!clk_is_match(pll, npll)) {
+ if (!data->streams) {
+ ret = clk_set_parent(p, npll);
+ if (ret < 0)
+ dev_warn(cpu_dai->dev,
+ "failed to set parent %s: %d\n",
+ __clk_get_name(npll), ret);
+ } else {
+ dev_err(cpu_dai->dev,
+ "PLL %s is in use by a running stream.\n",
+ __clk_get_name(pll));
+ return -EINVAL;
+ }
+ }
+ }
+
+ ret = clk_set_rate(clk, freq);
+ if (ret < 0) {
+ dev_err(cpu_dai->dev, "failed to set clock rate (%u): %d\n",
+ freq, ret);
+ return ret;
+ }
+
+ for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
+ ret = fsl_spdif_probe_txclk(data, i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static const struct snd_soc_dai_ops fsl_spdif_dai_ops = {
.startup = fsl_spdif_startup,
+ .set_sysclk = fsl_spdif_set_dai_sysclk,
.hw_params = fsl_spdif_hw_params,
.trigger = fsl_spdif_trigger,
.shutdown = fsl_spdif_shutdown,
+ .hw_free = fsl_spdif_hw_free,
};
-
/*
* FSL SPDIF IEC958 controller(mixer) functions
*
@@ -1174,109 +1364,6 @@ static const struct regmap_config fsl_spdif_regmap_config = {
.cache_type = REGCACHE_FLAT,
};
-static u32 fsl_spdif_txclk_caldiv(struct fsl_spdif_priv *spdif_priv,
- struct clk *clk, u64 savesub,
- enum spdif_txrate index, bool round)
-{
- static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
- bool is_sysclk = clk_is_match(clk, spdif_priv->sysclk);
- u64 rate_ideal, rate_actual, sub;
- u32 arate;
- u16 sysclk_dfmin, sysclk_dfmax, sysclk_df;
- u8 txclk_df;
-
- /* The sysclk has an extra divisor [2, 512] */
- sysclk_dfmin = is_sysclk ? 2 : 1;
- sysclk_dfmax = is_sysclk ? 512 : 1;
-
- for (sysclk_df = sysclk_dfmin; sysclk_df <= sysclk_dfmax; sysclk_df++) {
- for (txclk_df = 1; txclk_df <= 128; txclk_df++) {
-
- rate_actual = clk_get_rate(clk);
-
- arate = rate_actual / 64;
- arate /= txclk_df * sysclk_df;
-
- if (arate == rate[index]) {
- /* We are lucky */
- savesub = 0;
- spdif_priv->txclk_df[index] = txclk_df;
- spdif_priv->sysclk_df[index] = sysclk_df;
- spdif_priv->txrate[index] = arate;
- goto out;
- } else if (arate / rate[index] == 1) {
- /* A little bigger than expect */
- sub = (u64)(arate - rate[index]) * 100000;
- do_div(sub, rate[index]);
- if (sub >= savesub)
- continue;
- savesub = sub;
- spdif_priv->txclk_df[index] = txclk_df;
- spdif_priv->sysclk_df[index] = sysclk_df;
- spdif_priv->txrate[index] = arate;
- } else if (rate[index] / arate == 1) {
- /* A little smaller than expect */
- sub = (u64)(rate[index] - arate) * 100000;
- do_div(sub, rate[index]);
- if (sub >= savesub)
- continue;
- savesub = sub;
- spdif_priv->txclk_df[index] = txclk_df;
- spdif_priv->sysclk_df[index] = sysclk_df;
- spdif_priv->txrate[index] = arate;
- }
- }
- }
-
-out:
- return savesub;
-}
-
-static int fsl_spdif_probe_txclk(struct fsl_spdif_priv *spdif_priv,
- enum spdif_txrate index)
-{
- static const u32 rate[] = { 32000, 44100, 48000, 96000, 192000 };
- struct platform_device *pdev = spdif_priv->pdev;
- struct device *dev = &pdev->dev;
- u64 savesub = 100000, ret;
- struct clk *clk;
- int i;
-
- for (i = 0; i < STC_TXCLK_SRC_MAX; i++) {
- clk = spdif_priv->txclk[i];
- if (IS_ERR(clk)) {
- dev_err(dev, "no rxtx%d clock in devicetree\n", i);
- return PTR_ERR(clk);
- }
- if (!clk_get_rate(clk))
- continue;
-
- ret = fsl_spdif_txclk_caldiv(spdif_priv, clk, savesub, index,
- i == STC_TXCLK_SPDIF_ROOT);
- if (savesub == ret)
- continue;
-
- savesub = ret;
- spdif_priv->txclk_src[index] = i;
-
- /* To quick catch a divisor, we allow a 0.1% deviation */
- if (savesub < 100)
- break;
- }
-
- dev_dbg(&pdev->dev, "use rxtx%d as tx clock source for %dHz sample rate\n",
- spdif_priv->txclk_src[index], rate[index]);
- dev_dbg(&pdev->dev, "use txclk df %d for %dHz sample rate\n",
- spdif_priv->txclk_df[index], rate[index]);
- if (clk_is_match(spdif_priv->txclk[spdif_priv->txclk_src[index]], spdif_priv->sysclk))
- dev_dbg(&pdev->dev, "use sysclk df %d for %dHz sample rate\n",
- spdif_priv->sysclk_df[index], rate[index]);
- dev_dbg(&pdev->dev, "the best rate for %dHz sample rate is %dHz\n",
- rate[index], spdif_priv->txrate[index]);
-
- return 0;
-}
-
static const struct of_device_id fsl_spdif_dt_ids[] = {
{ .compatible = "fsl,imx8mm-spdif", .data = &fsl_spdif_imx8mm, },
{ .compatible = "fsl,imx8qm-spdif", .data = &fsl_spdif_imx8qm, },
@@ -1394,6 +1481,14 @@ static int fsl_spdif_probe(struct platform_device *pdev)
}
spdif_priv->rxclk_src = DEFAULT_RXCLK_SRC;
+ spdif_priv->pll8k_clk = devm_clk_get(&pdev->dev, "pll8k");
+ if (IS_ERR(spdif_priv->pll8k_clk))
+ spdif_priv->pll8k_clk = NULL;
+
+ spdif_priv->pll11k_clk = devm_clk_get(&pdev->dev, "pll11k");
+ if (IS_ERR(spdif_priv->pll11k_clk))
+ spdif_priv->pll11k_clk = NULL;
+
for (i = 0; i < SPDIF_TXRATE_MAX; i++) {
ret = fsl_spdif_probe_txclk(spdif_priv, i);
if (ret)