ASoC: fsl: Add generic DAI driver
On i.MX8 platforms that have a DSP the DAI handling is taken care of by two entities: * Application Processor (AP), which runs Linux * DSP, which runs a firmware (typically Sound Open Firmware) The DSP has access to DAI IP registers, but it cannot easily handle resources like: * clock * power domain management * pinctrl. For this reason we introduce a generic FSL DAI driver which will take care of the resources above. Signed-off-by: Daniel Baluta <daniel.baluta@nxp.com>5.4-rM2-2.2.x-imx-squashed
parent
78570725ce
commit
925ad97abd
|
@ -65,6 +65,14 @@ config SND_SOC_FSL_ESAI
|
|||
This option is only useful for out-of-tree drivers since
|
||||
in-tree drivers select it automatically.
|
||||
|
||||
config SND_SOC_FSL_DAI
|
||||
tristate "Generic FSL DAI support for Sound Open Firmware"
|
||||
help
|
||||
Say Y if you want to enable generic FSL DAI support to be used
|
||||
with Sound Open Firmware. This module takes care of enabling
|
||||
clocks, power domain, pinctrl for FSL DAIs. The rest of DAI
|
||||
control is taken care of by SOF firmware.
|
||||
|
||||
config SND_SOC_FSL_MICFIL
|
||||
tristate "Pulse Density Modulation Microphone Interface (MICFIL) module support"
|
||||
select REGMAP_MMIO
|
||||
|
|
|
@ -20,6 +20,7 @@ snd-soc-fsl-ssi-y := fsl_ssi.o
|
|||
snd-soc-fsl-ssi-$(CONFIG_DEBUG_FS) += fsl_ssi_dbg.o
|
||||
snd-soc-fsl-spdif-objs := fsl_spdif.o
|
||||
snd-soc-fsl-esai-objs := fsl_esai.o
|
||||
snd-soc-fsl-dai-objs := fsl_dai.o
|
||||
snd-soc-fsl-micfil-objs := fsl_micfil.o
|
||||
snd-soc-fsl-utils-objs := fsl_utils.o
|
||||
snd-soc-fsl-dma-objs := fsl_dma.o
|
||||
|
@ -31,6 +32,7 @@ obj-$(CONFIG_SND_SOC_FSL_SAI) += snd-soc-fsl-sai.o
|
|||
obj-$(CONFIG_SND_SOC_FSL_SSI) += snd-soc-fsl-ssi.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_SPDIF) += snd-soc-fsl-spdif.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_ESAI) += snd-soc-fsl-esai.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_DAI) += snd-soc-fsl-dai.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_MICFIL) += snd-soc-fsl-micfil.o
|
||||
obj-$(CONFIG_SND_SOC_FSL_UTILS) += snd-soc-fsl-utils.o
|
||||
obj-$(CONFIG_SND_SOC_POWERPC_DMA) += snd-soc-fsl-dma.o
|
||||
|
|
|
@ -0,0 +1,276 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
//
|
||||
// Freescale Generic DAI driver for DSP
|
||||
//
|
||||
// Copyright 2019 NXP
|
||||
// Author: Daniel Baluta <daniel.baluta@nxp.com>
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/pm_domain.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include <sound/soc.h>
|
||||
|
||||
enum fsl_dai_type {
|
||||
FSL_DAI_TYPE_NONE,
|
||||
FSL_DAI_TYPE_SAI,
|
||||
FSL_DAI_TYPE_ESAI,
|
||||
};
|
||||
|
||||
#define FSL_DAI_ESAI_CLK_NUM 4
|
||||
static const char *esai_clks[FSL_DAI_ESAI_CLK_NUM] = {
|
||||
"core",
|
||||
"extal",
|
||||
"fsys",
|
||||
"spba",
|
||||
};
|
||||
|
||||
#define FSL_DAI_SAI_CLK_NUM 5
|
||||
static const char *sai_clks[FSL_DAI_SAI_CLK_NUM] = {
|
||||
"bus",
|
||||
"mclk0",
|
||||
"mclk1",
|
||||
"mclk2",
|
||||
"mclk3",
|
||||
};
|
||||
|
||||
struct fsl_dai {
|
||||
struct platform_device *pdev;
|
||||
|
||||
/* DAI clocks */
|
||||
struct clk **clks;
|
||||
const char **clk_names;
|
||||
int num_clks;
|
||||
|
||||
/* Power Domain handling */
|
||||
int num_domains;
|
||||
struct device **pd_dev;
|
||||
struct device_link **link;
|
||||
|
||||
/* DAIS */
|
||||
struct snd_soc_dai_driver *dai_drv;
|
||||
int num_drv;
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver fsl_esai_dai = {
|
||||
.name = "esai0",
|
||||
};
|
||||
|
||||
static struct snd_soc_dai_driver fsl_sai_dai = {
|
||||
.name = "sai1",
|
||||
};
|
||||
|
||||
static const struct snd_soc_component_driver fsl_dai_component = {
|
||||
.name = "fsl-dai",
|
||||
};
|
||||
|
||||
static int fsl_dai_init_clocks(struct fsl_dai *dai_priv)
|
||||
{
|
||||
struct device *dev = &dai_priv->pdev->dev;
|
||||
int i;
|
||||
|
||||
dai_priv->clks = devm_kcalloc(dev, dai_priv->num_clks,
|
||||
sizeof(*dai_priv->clks), GFP_KERNEL);
|
||||
if (!dai_priv->clks)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < dai_priv->num_clks; i++) {
|
||||
dai_priv->clks[i] = devm_clk_get(dev, dai_priv->clk_names[i]);
|
||||
if (IS_ERR(dai_priv->clks[i])) {
|
||||
dev_dbg(dev, "Failed to get clk %s\n",
|
||||
dai_priv->clk_names[i]);
|
||||
dai_priv->clks[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fsl_get_dai_type(struct fsl_dai *dai_priv)
|
||||
{
|
||||
struct device_node *np = dai_priv->pdev->dev.of_node;
|
||||
|
||||
if (of_device_is_compatible(np, "fsl,esai-dai"))
|
||||
return FSL_DAI_TYPE_ESAI;
|
||||
|
||||
if (of_device_is_compatible(np, "fsl,sai-dai"))
|
||||
return FSL_DAI_TYPE_SAI;
|
||||
|
||||
return FSL_DAI_TYPE_NONE;
|
||||
}
|
||||
|
||||
static int fsl_dai_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct fsl_dai *priv;
|
||||
int dai_type;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->pdev = pdev;
|
||||
|
||||
dev_set_drvdata(&pdev->dev, priv);
|
||||
|
||||
dai_type = fsl_get_dai_type(priv);
|
||||
switch (dai_type) {
|
||||
case FSL_DAI_TYPE_ESAI:
|
||||
priv->clk_names = esai_clks;
|
||||
priv->num_clks = FSL_DAI_ESAI_CLK_NUM;
|
||||
priv->dai_drv = &fsl_esai_dai;
|
||||
priv->num_drv = 1;
|
||||
break;
|
||||
case FSL_DAI_TYPE_SAI:
|
||||
priv->clk_names = sai_clks;
|
||||
priv->num_clks = FSL_DAI_SAI_CLK_NUM;
|
||||
priv->dai_drv = &fsl_sai_dai;
|
||||
priv->num_drv = 1;
|
||||
break;
|
||||
default:
|
||||
dev_err(&pdev->dev, "Invalid DAI type %d\n", dai_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = fsl_dai_init_clocks(priv);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Error at init clocks\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
priv->num_domains = of_count_phandle_with_args(np, "power-domains",
|
||||
"#power-domain-cells");
|
||||
if (priv->num_domains < 0) {
|
||||
dev_err(&pdev->dev, "no power-domains property in %pOF\n", np);
|
||||
return priv->num_domains;
|
||||
}
|
||||
|
||||
priv->pd_dev = devm_kmalloc_array(&pdev->dev, priv->num_domains,
|
||||
sizeof(*priv->pd_dev), GFP_KERNEL);
|
||||
if (!priv->pd_dev)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->link = devm_kmalloc_array(&pdev->dev, priv->num_domains,
|
||||
sizeof(*priv->link), GFP_KERNEL);
|
||||
if (!priv->link)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < priv->num_domains; i++) {
|
||||
priv->pd_dev[i] = dev_pm_domain_attach_by_id(&pdev->dev, i);
|
||||
if (IS_ERR(priv->pd_dev[i])) {
|
||||
ret = PTR_ERR(priv->pd_dev[i]);
|
||||
goto unroll_pm;
|
||||
}
|
||||
|
||||
priv->link[i] = device_link_add(&pdev->dev, priv->pd_dev[i],
|
||||
DL_FLAG_STATELESS |
|
||||
DL_FLAG_PM_RUNTIME |
|
||||
DL_FLAG_RPM_ACTIVE);
|
||||
if (!priv->link[i]) {
|
||||
ret = -EINVAL;
|
||||
dev_pm_domain_detach(priv->pd_dev[i], false);
|
||||
goto unroll_pm;
|
||||
}
|
||||
}
|
||||
|
||||
pm_runtime_enable(&pdev->dev);
|
||||
|
||||
ret = devm_snd_soc_register_component(&pdev->dev, &fsl_dai_component,
|
||||
priv->dai_drv, priv->num_drv);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Failed to register DAI ret = %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
|
||||
unroll_pm:
|
||||
while (--i >= 0) {
|
||||
device_link_del(priv->link[i]);
|
||||
dev_pm_domain_detach(priv->pd_dev[i], false);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fsl_dai_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct fsl_dai *priv = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
pm_runtime_disable(&priv->pdev->dev);
|
||||
|
||||
for (i = 0; i < priv->num_domains; i++) {
|
||||
device_link_del(priv->link[i]);
|
||||
dev_pm_domain_detach(priv->pd_dev[i], false);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id fsl_dai_dt_ids[] = {
|
||||
{ .compatible = "fsl,esai-dai", },
|
||||
{ .compatible = "fsl,sai-dai", },
|
||||
{}
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, fsl_dai_dt_ids);
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
static int fsl_dai_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct fsl_dai *priv = dev_get_drvdata(dev);
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < priv->num_clks; i++) {
|
||||
ret = clk_prepare_enable(priv->clks[i]);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "Failed to enable clk %s\n",
|
||||
priv->clk_names[i]);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
out:
|
||||
while (--i >= 0)
|
||||
clk_disable_unprepare(priv->clks[i]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fsl_dai_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct fsl_dai *priv = dev_get_drvdata(dev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < priv->num_clks; i++)
|
||||
clk_disable_unprepare(priv->clks[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif /* CONFIG_PM */
|
||||
|
||||
static const struct dev_pm_ops fsl_dai_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(fsl_dai_runtime_suspend,
|
||||
fsl_dai_runtime_resume, NULL)
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume)
|
||||
};
|
||||
|
||||
static struct platform_driver fsl_dai_driver = {
|
||||
.probe = fsl_dai_probe,
|
||||
.remove = fsl_dai_remove,
|
||||
.driver = {
|
||||
.name = "fsl-dai",
|
||||
.pm = &fsl_dai_pm_ops,
|
||||
.of_match_table = fsl_dai_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(fsl_dai_driver);
|
||||
|
||||
MODULE_ALIAS("platform:fsl-dai");
|
||||
|
||||
MODULE_AUTHOR("Daniel Baluta <daniel.baluta@nxp.com>");
|
||||
MODULE_DESCRIPTION("FSL Generic DAI driver for DSP");
|
||||
MODULE_LICENSE("GPL v2");
|
Loading…
Reference in New Issue