forked from thesofproject/linux
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ASoC: fsl: Add Audio Mixer machine driver
This patch implements Audio Mixer machine driver for NXP iMX8 SOCs. It connects together Audio Mixer and related SAI instances. Signed-off-by: Viorel Suman <viorel.suman@nxp.com> Signed-off-by: Mark Brown <broonie@kernel.org>
- Loading branch information
Showing
3 changed files
with
338 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,327 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* | ||
* Copyright 2017 NXP | ||
* | ||
* The code contained herein is licensed under the GNU General Public | ||
* License. You may obtain a copy of the GNU General Public License | ||
* Version 2 or later at the following locations: | ||
* | ||
* http://www.opensource.org/licenses/gpl-license.html | ||
* http://www.gnu.org/copyleft/gpl.html | ||
*/ | ||
|
||
#include <linux/module.h> | ||
#include <linux/of_platform.h> | ||
#include <linux/clk.h> | ||
#include <sound/soc.h> | ||
#include <sound/soc-dapm.h> | ||
#include <linux/pm_runtime.h> | ||
#include "fsl_sai.h" | ||
#include "fsl_audmix.h" | ||
|
||
struct imx_audmix { | ||
struct platform_device *pdev; | ||
struct snd_soc_card card; | ||
struct platform_device *audmix_pdev; | ||
struct platform_device *out_pdev; | ||
struct clk *cpu_mclk; | ||
int num_dai; | ||
struct snd_soc_dai_link *dai; | ||
int num_dai_conf; | ||
struct snd_soc_codec_conf *dai_conf; | ||
int num_dapm_routes; | ||
struct snd_soc_dapm_route *dapm_routes; | ||
}; | ||
|
||
static const u32 imx_audmix_rates[] = { | ||
8000, 12000, 16000, 24000, 32000, 48000, 64000, 96000, | ||
}; | ||
|
||
static const struct snd_pcm_hw_constraint_list imx_audmix_rate_constraints = { | ||
.count = ARRAY_SIZE(imx_audmix_rates), | ||
.list = imx_audmix_rates, | ||
}; | ||
|
||
static int imx_audmix_fe_startup(struct snd_pcm_substream *substream) | ||
{ | ||
struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
struct imx_audmix *priv = snd_soc_card_get_drvdata(rtd->card); | ||
struct snd_pcm_runtime *runtime = substream->runtime; | ||
struct device *dev = rtd->card->dev; | ||
unsigned long clk_rate = clk_get_rate(priv->cpu_mclk); | ||
int ret; | ||
|
||
if (clk_rate % 24576000 == 0) { | ||
ret = snd_pcm_hw_constraint_list(runtime, 0, | ||
SNDRV_PCM_HW_PARAM_RATE, | ||
&imx_audmix_rate_constraints); | ||
if (ret < 0) | ||
return ret; | ||
} else { | ||
dev_warn(dev, "mclk may be not supported %lu\n", clk_rate); | ||
} | ||
|
||
ret = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, | ||
1, 8); | ||
if (ret < 0) | ||
return ret; | ||
|
||
return snd_pcm_hw_constraint_mask64(runtime, SNDRV_PCM_HW_PARAM_FORMAT, | ||
FSL_AUDMIX_FORMATS); | ||
} | ||
|
||
static int imx_audmix_fe_hw_params(struct snd_pcm_substream *substream, | ||
struct snd_pcm_hw_params *params) | ||
{ | ||
struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
struct device *dev = rtd->card->dev; | ||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; | ||
unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF; | ||
u32 channels = params_channels(params); | ||
int ret, dir; | ||
|
||
/* For playback the AUDMIX is slave, and for record is master */ | ||
fmt |= tx ? SND_SOC_DAIFMT_CBS_CFS : SND_SOC_DAIFMT_CBM_CFM; | ||
dir = tx ? SND_SOC_CLOCK_OUT : SND_SOC_CLOCK_IN; | ||
|
||
/* set DAI configuration */ | ||
ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); | ||
if (ret) { | ||
dev_err(dev, "failed to set cpu dai fmt: %d\n", ret); | ||
return ret; | ||
} | ||
|
||
ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, FSL_SAI_CLK_MAST1, 0, dir); | ||
if (ret) { | ||
dev_err(dev, "failed to set cpu sysclk: %d\n", ret); | ||
return ret; | ||
} | ||
|
||
/* | ||
* Per datasheet, AUDMIX expects 8 slots and 32 bits | ||
* for every slot in TDM mode. | ||
*/ | ||
ret = snd_soc_dai_set_tdm_slot(rtd->cpu_dai, BIT(channels) - 1, | ||
BIT(channels) - 1, 8, 32); | ||
if (ret) | ||
dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); | ||
|
||
return ret; | ||
} | ||
|
||
static int imx_audmix_be_hw_params(struct snd_pcm_substream *substream, | ||
struct snd_pcm_hw_params *params) | ||
{ | ||
struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
struct device *dev = rtd->card->dev; | ||
bool tx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; | ||
unsigned int fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF; | ||
int ret; | ||
|
||
if (!tx) | ||
return 0; | ||
|
||
/* For playback the AUDMIX is slave */ | ||
fmt |= SND_SOC_DAIFMT_CBM_CFM; | ||
|
||
/* set AUDMIX DAI configuration */ | ||
ret = snd_soc_dai_set_fmt(rtd->cpu_dai, fmt); | ||
if (ret) | ||
dev_err(dev, "failed to set AUDMIX DAI fmt: %d\n", ret); | ||
|
||
return ret; | ||
} | ||
|
||
static struct snd_soc_ops imx_audmix_fe_ops = { | ||
.startup = imx_audmix_fe_startup, | ||
.hw_params = imx_audmix_fe_hw_params, | ||
}; | ||
|
||
static struct snd_soc_ops imx_audmix_be_ops = { | ||
.hw_params = imx_audmix_be_hw_params, | ||
}; | ||
|
||
static int imx_audmix_probe(struct platform_device *pdev) | ||
{ | ||
struct device_node *np = pdev->dev.of_node; | ||
struct device_node *audmix_np = NULL, *out_cpu_np = NULL; | ||
struct platform_device *audmix_pdev = NULL; | ||
struct platform_device *cpu_pdev; | ||
struct of_phandle_args args; | ||
struct imx_audmix *priv; | ||
int i, num_dai, ret; | ||
const char *fe_name_pref = "HiFi-AUDMIX-FE-"; | ||
char *be_name, *be_pb, *be_cp, *dai_name, *capture_dai_name; | ||
|
||
if (pdev->dev.parent) { | ||
audmix_np = pdev->dev.parent->of_node; | ||
} else { | ||
dev_err(&pdev->dev, "Missing parent device.\n"); | ||
return -EINVAL; | ||
} | ||
|
||
if (!audmix_np) { | ||
dev_err(&pdev->dev, "Missign DT node for parent device.\n"); | ||
return -EINVAL; | ||
} | ||
|
||
audmix_pdev = of_find_device_by_node(audmix_np); | ||
if (!audmix_pdev) { | ||
dev_err(&pdev->dev, "Missing AUDMIX platform device for %s\n", | ||
np->full_name); | ||
return -EINVAL; | ||
} | ||
|
||
num_dai = of_count_phandle_with_args(audmix_np, "dais", NULL); | ||
if (num_dai != FSL_AUDMIX_MAX_DAIS) { | ||
dev_err(&pdev->dev, "Need 2 dais to be provided for %s\n", | ||
audmix_np->full_name); | ||
return -EINVAL; | ||
} | ||
|
||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); | ||
if (!priv) | ||
return -ENOMEM; | ||
|
||
priv->num_dai = 2 * num_dai; | ||
priv->dai = devm_kzalloc(&pdev->dev, priv->num_dai * | ||
sizeof(struct snd_soc_dai_link), GFP_KERNEL); | ||
if (!priv->dai) | ||
return -ENOMEM; | ||
|
||
priv->num_dai_conf = num_dai; | ||
priv->dai_conf = devm_kzalloc(&pdev->dev, priv->num_dai_conf * | ||
sizeof(struct snd_soc_codec_conf), | ||
GFP_KERNEL); | ||
if (!priv->dai_conf) | ||
return -ENOMEM; | ||
|
||
priv->num_dapm_routes = 3 * num_dai; | ||
priv->dapm_routes = devm_kzalloc(&pdev->dev, priv->num_dapm_routes * | ||
sizeof(struct snd_soc_dapm_route), | ||
GFP_KERNEL); | ||
if (!priv->dapm_routes) | ||
return -ENOMEM; | ||
|
||
for (i = 0; i < num_dai; i++) { | ||
ret = of_parse_phandle_with_args(audmix_np, "dais", NULL, i, | ||
&args); | ||
if (ret < 0) { | ||
dev_err(&pdev->dev, "of_parse_phandle_with_args failed\n"); | ||
return ret; | ||
} | ||
|
||
cpu_pdev = of_find_device_by_node(args.np); | ||
if (!cpu_pdev) { | ||
dev_err(&pdev->dev, "failed to find SAI platform device\n"); | ||
return -EINVAL; | ||
} | ||
|
||
dai_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s%s", | ||
fe_name_pref, args.np->full_name + 1); | ||
|
||
dev_info(pdev->dev.parent, "DAI FE name:%s\n", dai_name); | ||
|
||
if (i == 0) { | ||
out_cpu_np = args.np; | ||
capture_dai_name = | ||
devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s", | ||
dai_name, "CPU-Capture"); | ||
} | ||
|
||
priv->dai[i].name = dai_name; | ||
priv->dai[i].stream_name = "HiFi-AUDMIX-FE"; | ||
priv->dai[i].codec_dai_name = "snd-soc-dummy-dai"; | ||
priv->dai[i].codec_name = "snd-soc-dummy"; | ||
priv->dai[i].cpu_of_node = args.np; | ||
priv->dai[i].cpu_dai_name = dev_name(&cpu_pdev->dev); | ||
priv->dai[i].platform_of_node = args.np; | ||
priv->dai[i].dynamic = 1; | ||
priv->dai[i].dpcm_playback = 1; | ||
priv->dai[i].dpcm_capture = (i == 0 ? 1 : 0); | ||
priv->dai[i].ignore_pmdown_time = 1; | ||
priv->dai[i].ops = &imx_audmix_fe_ops; | ||
|
||
/* Add AUDMIX Backend */ | ||
be_name = devm_kasprintf(&pdev->dev, GFP_KERNEL, | ||
"audmix-%d", i); | ||
be_pb = devm_kasprintf(&pdev->dev, GFP_KERNEL, | ||
"AUDMIX-Playback-%d", i); | ||
be_cp = devm_kasprintf(&pdev->dev, GFP_KERNEL, | ||
"AUDMIX-Capture-%d", i); | ||
|
||
priv->dai[num_dai + i].name = be_name; | ||
priv->dai[num_dai + i].codec_dai_name = "snd-soc-dummy-dai"; | ||
priv->dai[num_dai + i].codec_name = "snd-soc-dummy"; | ||
priv->dai[num_dai + i].cpu_of_node = audmix_np; | ||
priv->dai[num_dai + i].cpu_dai_name = be_name; | ||
priv->dai[num_dai + i].platform_name = "snd-soc-dummy"; | ||
priv->dai[num_dai + i].no_pcm = 1; | ||
priv->dai[num_dai + i].dpcm_playback = 1; | ||
priv->dai[num_dai + i].dpcm_capture = 1; | ||
priv->dai[num_dai + i].ignore_pmdown_time = 1; | ||
priv->dai[num_dai + i].ops = &imx_audmix_be_ops; | ||
|
||
priv->dai_conf[i].of_node = args.np; | ||
priv->dai_conf[i].name_prefix = dai_name; | ||
|
||
priv->dapm_routes[i].source = | ||
devm_kasprintf(&pdev->dev, GFP_KERNEL, "%s %s", | ||
dai_name, "CPU-Playback"); | ||
priv->dapm_routes[i].sink = be_pb; | ||
priv->dapm_routes[num_dai + i].source = be_pb; | ||
priv->dapm_routes[num_dai + i].sink = be_cp; | ||
priv->dapm_routes[2 * num_dai + i].source = be_cp; | ||
priv->dapm_routes[2 * num_dai + i].sink = capture_dai_name; | ||
} | ||
|
||
cpu_pdev = of_find_device_by_node(out_cpu_np); | ||
if (!cpu_pdev) { | ||
dev_err(&pdev->dev, "failed to find SAI platform device\n"); | ||
return -EINVAL; | ||
} | ||
priv->cpu_mclk = devm_clk_get(&cpu_pdev->dev, "mclk1"); | ||
if (IS_ERR(priv->cpu_mclk)) { | ||
ret = PTR_ERR(priv->cpu_mclk); | ||
dev_err(&cpu_pdev->dev, "failed to get DAI mclk1: %d\n", ret); | ||
return -EINVAL; | ||
} | ||
|
||
priv->audmix_pdev = audmix_pdev; | ||
priv->out_pdev = cpu_pdev; | ||
|
||
priv->card.dai_link = priv->dai; | ||
priv->card.num_links = priv->num_dai; | ||
priv->card.codec_conf = priv->dai_conf; | ||
priv->card.num_configs = priv->num_dai_conf; | ||
priv->card.dapm_routes = priv->dapm_routes; | ||
priv->card.num_dapm_routes = priv->num_dapm_routes; | ||
priv->card.dev = pdev->dev.parent; | ||
priv->card.owner = THIS_MODULE; | ||
priv->card.name = "imx-audmix"; | ||
|
||
platform_set_drvdata(pdev, &priv->card); | ||
snd_soc_card_set_drvdata(&priv->card, priv); | ||
|
||
ret = devm_snd_soc_register_card(pdev->dev.parent, &priv->card); | ||
if (ret) { | ||
dev_err(&pdev->dev, "snd_soc_register_card failed\n"); | ||
return ret; | ||
} | ||
|
||
return ret; | ||
} | ||
|
||
static struct platform_driver imx_audmix_driver = { | ||
.probe = imx_audmix_probe, | ||
.driver = { | ||
.name = "imx-audmix", | ||
.pm = &snd_soc_pm_ops, | ||
}, | ||
}; | ||
module_platform_driver(imx_audmix_driver); | ||
|
||
MODULE_DESCRIPTION("NXP AUDMIX ASoC machine driver"); | ||
MODULE_AUTHOR("Viorel Suman <viorel.suman@nxp.com>"); | ||
MODULE_ALIAS("platform:imx-audmix"); | ||
MODULE_LICENSE("GPL v2"); |