Commit 50760cad authored by Maciej S. Szmigiero's avatar Maciej S. Szmigiero Committed by Mark Brown

ASoC: fsl-asoc-card: add AC'97 support

Add AC'97 support to fsl-asoc-card using generic
ASoC AC'97 CODEC.

The SSI controller will silently enable any TX
AC'97 slots that have their bits set in SLOTREQ
received from CODEC and then will redirect some
of playback samples there.

That's why it is important to make sure that
any of CODEC playback slots that can pull samples
are set to slots 3/4 (standard PCM playback slots).
Currently, this applies to S/PDIF slots as they
were seen to pull samples sometimes even with
S/PDIF output being disabled.
Signed-off-by: default avatarMaciej Szmigiero <mail@maciej.szmigiero.name>
Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 6ff33f39
...@@ -13,13 +13,15 @@ So having this generic sound card allows all Freescale SoC users to benefit ...@@ -13,13 +13,15 @@ So having this generic sound card allows all Freescale SoC users to benefit
from the simplification of a new card support and the capability of the wide from the simplification of a new card support and the capability of the wide
sample rates support through ASRC. sample rates support through ASRC.
Note: The card is initially designed for those sound cards who use I2S and Note: The card is initially designed for those sound cards who use AC'97, I2S
PCM DAI formats. However, it'll be also possible to support those non and PCM DAI formats. However, it'll be also possible to support those non
I2S/PCM type sound cards, such as S/PDIF audio and HDMI audio, as long AC'97/I2S/PCM type sound cards, such as S/PDIF audio and HDMI audio, as
as the driver has been properly upgraded. long as the driver has been properly upgraded.
The compatible list for this generic sound card currently: The compatible list for this generic sound card currently:
"fsl,imx-audio-ac97"
"fsl,imx-audio-cs42888" "fsl,imx-audio-cs42888"
"fsl,imx-audio-wm8962" "fsl,imx-audio-wm8962"
......
...@@ -14,6 +14,9 @@ ...@@ -14,6 +14,9 @@
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_platform.h> #include <linux/of_platform.h>
#if IS_ENABLED(CONFIG_SND_AC97_CODEC)
#include <sound/ac97_codec.h>
#endif
#include <sound/pcm_params.h> #include <sound/pcm_params.h>
#include <sound/soc.h> #include <sound/soc.h>
...@@ -115,6 +118,11 @@ static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = { ...@@ -115,6 +118,11 @@ static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = {
SND_SOC_DAPM_MIC("DMIC", NULL), SND_SOC_DAPM_MIC("DMIC", NULL),
}; };
static bool fsl_asoc_card_is_ac97(struct fsl_asoc_card_priv *priv)
{
return priv->dai_fmt == SND_SOC_DAIFMT_AC97;
}
static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream, static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params) struct snd_pcm_hw_params *params)
{ {
...@@ -133,7 +141,9 @@ static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream, ...@@ -133,7 +141,9 @@ static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream,
* set_bias_level(), bypass the remaining settings in hw_params(). * set_bias_level(), bypass the remaining settings in hw_params().
* Note: (dai_fmt & CBM_CFM) includes CBM_CFM and CBM_CFS. * Note: (dai_fmt & CBM_CFM) includes CBM_CFM and CBM_CFS.
*/ */
if (priv->card.set_bias_level && priv->dai_fmt & SND_SOC_DAIFMT_CBM_CFM) if ((priv->card.set_bias_level &&
priv->dai_fmt & SND_SOC_DAIFMT_CBM_CFM) ||
fsl_asoc_card_is_ac97(priv))
return 0; return 0;
/* Specific configurations of DAIs starts from here */ /* Specific configurations of DAIs starts from here */
...@@ -300,7 +310,7 @@ static int fsl_asoc_card_audmux_init(struct device_node *np, ...@@ -300,7 +310,7 @@ static int fsl_asoc_card_audmux_init(struct device_node *np,
ext_port--; ext_port--;
/* /*
* Use asynchronous mode (6 wires) for all cases. * Use asynchronous mode (6 wires) for all cases except AC97.
* If only 4 wires are needed, just set SSI into * If only 4 wires are needed, just set SSI into
* synchronous mode and enable 4 PADs in IOMUX. * synchronous mode and enable 4 PADs in IOMUX.
*/ */
...@@ -346,16 +356,31 @@ static int fsl_asoc_card_audmux_init(struct device_node *np, ...@@ -346,16 +356,31 @@ static int fsl_asoc_card_audmux_init(struct device_node *np,
IMX_AUDMUX_V2_PTCR_TCLKDIR; IMX_AUDMUX_V2_PTCR_TCLKDIR;
break; break;
default: default:
if (!fsl_asoc_card_is_ac97(priv))
return -EINVAL; return -EINVAL;
} }
if (fsl_asoc_card_is_ac97(priv)) {
int_ptcr = IMX_AUDMUX_V2_PTCR_SYN |
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
IMX_AUDMUX_V2_PTCR_TCLKDIR;
ext_ptcr = IMX_AUDMUX_V2_PTCR_SYN |
IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
IMX_AUDMUX_V2_PTCR_TFSDIR;
}
/* Asynchronous mode can not be set along with RCLKDIR */ /* Asynchronous mode can not be set along with RCLKDIR */
if (!fsl_asoc_card_is_ac97(priv)) {
unsigned int pdcr =
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port);
ret = imx_audmux_v2_configure_port(int_port, 0, ret = imx_audmux_v2_configure_port(int_port, 0,
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); pdcr);
if (ret) { if (ret) {
dev_err(dev, "audmux internal port setup failed\n"); dev_err(dev, "audmux internal port setup failed\n");
return ret; return ret;
} }
}
ret = imx_audmux_v2_configure_port(int_port, int_ptcr, ret = imx_audmux_v2_configure_port(int_port, int_ptcr,
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port)); IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
...@@ -364,12 +389,17 @@ static int fsl_asoc_card_audmux_init(struct device_node *np, ...@@ -364,12 +389,17 @@ static int fsl_asoc_card_audmux_init(struct device_node *np,
return ret; return ret;
} }
if (!fsl_asoc_card_is_ac97(priv)) {
unsigned int pdcr =
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port);
ret = imx_audmux_v2_configure_port(ext_port, 0, ret = imx_audmux_v2_configure_port(ext_port, 0,
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); pdcr);
if (ret) { if (ret) {
dev_err(dev, "audmux external port setup failed\n"); dev_err(dev, "audmux external port setup failed\n");
return ret; return ret;
} }
}
ret = imx_audmux_v2_configure_port(ext_port, ext_ptcr, ret = imx_audmux_v2_configure_port(ext_port, ext_ptcr,
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port)); IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
...@@ -389,6 +419,23 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card) ...@@ -389,6 +419,23 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card)
struct device *dev = card->dev; struct device *dev = card->dev;
int ret; int ret;
if (fsl_asoc_card_is_ac97(priv)) {
#if IS_ENABLED(CONFIG_SND_AC97_CODEC)
struct snd_soc_codec *codec = card->rtd[0].codec;
struct snd_ac97 *ac97 = snd_soc_codec_get_drvdata(codec);
/*
* Use slots 3/4 for S/PDIF so SSI won't try to enable
* other slots and send some samples there
* due to SLOTREQ bits for S/PDIF received from codec
*/
snd_ac97_update_bits(ac97, AC97_EXTENDED_STATUS,
AC97_EA_SPSA_SLOT_MASK, AC97_EA_SPSA_3_4);
#endif
return 0;
}
ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id, ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
codec_priv->mclk_freq, SND_SOC_CLOCK_IN); codec_priv->mclk_freq, SND_SOC_CLOCK_IN);
if (ret) { if (ret) {
...@@ -407,7 +454,6 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) ...@@ -407,7 +454,6 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
struct platform_device *cpu_pdev; struct platform_device *cpu_pdev;
struct fsl_asoc_card_priv *priv; struct fsl_asoc_card_priv *priv;
struct i2c_client *codec_dev; struct i2c_client *codec_dev;
struct clk *codec_clk;
const char *codec_dai_name; const char *codec_dai_name;
u32 width; u32 width;
int ret; int ret;
...@@ -420,9 +466,8 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) ...@@ -420,9 +466,8 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
/* Give a chance to old DT binding */ /* Give a chance to old DT binding */
if (!cpu_np) if (!cpu_np)
cpu_np = of_parse_phandle(np, "ssi-controller", 0); cpu_np = of_parse_phandle(np, "ssi-controller", 0);
codec_np = of_parse_phandle(np, "audio-codec", 0); if (!cpu_np) {
if (!cpu_np || !codec_np) { dev_err(&pdev->dev, "CPU phandle missing or invalid\n");
dev_err(&pdev->dev, "phandle missing or invalid\n");
ret = -EINVAL; ret = -EINVAL;
goto fail; goto fail;
} }
...@@ -434,23 +479,25 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) ...@@ -434,23 +479,25 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
goto fail; goto fail;
} }
codec_np = of_parse_phandle(np, "audio-codec", 0);
if (codec_np)
codec_dev = of_find_i2c_device_by_node(codec_np); codec_dev = of_find_i2c_device_by_node(codec_np);
if (!codec_dev) { else
dev_err(&pdev->dev, "failed to find codec platform device\n"); codec_dev = NULL;
ret = -EINVAL;
goto fail;
}
asrc_np = of_parse_phandle(np, "audio-asrc", 0); asrc_np = of_parse_phandle(np, "audio-asrc", 0);
if (asrc_np) if (asrc_np)
asrc_pdev = of_find_device_by_node(asrc_np); asrc_pdev = of_find_device_by_node(asrc_np);
/* Get the MCLK rate only, and leave it controlled by CODEC drivers */ /* Get the MCLK rate only, and leave it controlled by CODEC drivers */
codec_clk = clk_get(&codec_dev->dev, NULL); if (codec_dev) {
struct clk *codec_clk = clk_get(&codec_dev->dev, NULL);
if (!IS_ERR(codec_clk)) { if (!IS_ERR(codec_clk)) {
priv->codec_priv.mclk_freq = clk_get_rate(codec_clk); priv->codec_priv.mclk_freq = clk_get_rate(codec_clk);
clk_put(codec_clk); clk_put(codec_clk);
} }
}
/* Default sample rate and format, will be updated in hw_params() */ /* Default sample rate and format, will be updated in hw_params() */
priv->sample_rate = 44100; priv->sample_rate = 44100;
...@@ -486,11 +533,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) ...@@ -486,11 +533,21 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
priv->codec_priv.fll_id = WM8960_SYSCLK_AUTO; priv->codec_priv.fll_id = WM8960_SYSCLK_AUTO;
priv->codec_priv.pll_id = WM8960_SYSCLK_AUTO; priv->codec_priv.pll_id = WM8960_SYSCLK_AUTO;
priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM; priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
} else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) {
codec_dai_name = "ac97-hifi";
priv->card.set_bias_level = NULL;
priv->dai_fmt = SND_SOC_DAIFMT_AC97;
} else { } else {
dev_err(&pdev->dev, "unknown Device Tree compatible\n"); dev_err(&pdev->dev, "unknown Device Tree compatible\n");
return -EINVAL; return -EINVAL;
} }
if (!fsl_asoc_card_is_ac97(priv) && !codec_dev) {
dev_err(&pdev->dev, "failed to find codec device\n");
ret = -EINVAL;
goto asrc_fail;
}
/* Common settings for corresponding Freescale CPU DAI driver */ /* Common settings for corresponding Freescale CPU DAI driver */
if (strstr(cpu_np->name, "ssi")) { if (strstr(cpu_np->name, "ssi")) {
/* Only SSI needs to configure AUDMUX */ /* Only SSI needs to configure AUDMUX */
...@@ -507,7 +564,9 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) ...@@ -507,7 +564,9 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1; priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1;
} }
sprintf(priv->name, "%s-audio", codec_dev->name); snprintf(priv->name, sizeof(priv->name), "%s-audio",
fsl_asoc_card_is_ac97(priv) ? "ac97" :
codec_dev->name);
/* Initialize sound card */ /* Initialize sound card */
priv->pdev = pdev; priv->pdev = pdev;
...@@ -531,8 +590,26 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) ...@@ -531,8 +590,26 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
/* Normal DAI Link */ /* Normal DAI Link */
priv->dai_link[0].cpu_of_node = cpu_np; priv->dai_link[0].cpu_of_node = cpu_np;
priv->dai_link[0].codec_of_node = codec_np;
priv->dai_link[0].codec_dai_name = codec_dai_name; priv->dai_link[0].codec_dai_name = codec_dai_name;
if (!fsl_asoc_card_is_ac97(priv))
priv->dai_link[0].codec_of_node = codec_np;
else {
u32 idx;
ret = of_property_read_u32(cpu_np, "cell-index", &idx);
if (ret) {
dev_err(&pdev->dev,
"cannot get CPU index property\n");
goto asrc_fail;
}
priv->dai_link[0].codec_name =
devm_kasprintf(&pdev->dev, GFP_KERNEL,
"ac97-codec.%u",
(unsigned int)idx);
}
priv->dai_link[0].platform_of_node = cpu_np; priv->dai_link[0].platform_of_node = cpu_np;
priv->dai_link[0].dai_fmt = priv->dai_fmt; priv->dai_link[0].dai_fmt = priv->dai_fmt;
priv->card.num_links = 1; priv->card.num_links = 1;
...@@ -543,6 +620,8 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) ...@@ -543,6 +620,8 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
priv->dai_link[1].platform_of_node = asrc_np; priv->dai_link[1].platform_of_node = asrc_np;
priv->dai_link[2].codec_dai_name = codec_dai_name; priv->dai_link[2].codec_dai_name = codec_dai_name;
priv->dai_link[2].codec_of_node = codec_np; priv->dai_link[2].codec_of_node = codec_np;
priv->dai_link[2].codec_name =
priv->dai_link[0].codec_name;
priv->dai_link[2].cpu_of_node = cpu_np; priv->dai_link[2].cpu_of_node = cpu_np;
priv->dai_link[2].dai_fmt = priv->dai_fmt; priv->dai_link[2].dai_fmt = priv->dai_fmt;
priv->card.num_links = 3; priv->card.num_links = 3;
...@@ -578,14 +657,15 @@ static int fsl_asoc_card_probe(struct platform_device *pdev) ...@@ -578,14 +657,15 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
asrc_fail: asrc_fail:
of_node_put(asrc_np); of_node_put(asrc_np);
fail:
of_node_put(codec_np); of_node_put(codec_np);
fail:
of_node_put(cpu_np); of_node_put(cpu_np);
return ret; return ret;
} }
static const struct of_device_id fsl_asoc_card_dt_ids[] = { static const struct of_device_id fsl_asoc_card_dt_ids[] = {
{ .compatible = "fsl,imx-audio-ac97", },
{ .compatible = "fsl,imx-audio-cs42888", }, { .compatible = "fsl,imx-audio-cs42888", },
{ .compatible = "fsl,imx-audio-sgtl5000", }, { .compatible = "fsl,imx-audio-sgtl5000", },
{ .compatible = "fsl,imx-audio-wm8962", }, { .compatible = "fsl,imx-audio-wm8962", },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment