blob: a6d9bd67418d86aa10bfa2ab1b5b8c9d9fb34dfa [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* mt8516-vesper.c -- MT8516-Vesper ALSA SoC machine driver
*
* Copyright (c) 2016 MediaTek Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/module.h>
#include <sound/soc.h>
#include <linux/of_device.h>
#include <linux/regulator/consumer.h>
struct adc_config {
struct device_node *component_of_node;
unsigned int tdm_mask;
};
struct vesper_config {
const char *dai_link_name;
struct adc_config *tdm_cfg;
int num_cfg;
};
static struct vesper_config *vesper_cfg;
static int num_vesper_cfg;
static int tdmin_capture_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
int i, j, k;
for (i = 0; i < rtd->num_codecs; i++)
if (rtd->codec_dais[i]->active)
return -EBUSY;
for (k = 0; k < num_vesper_cfg; k++)
if (!strcmp(rtd->dai_link->name, vesper_cfg[k].dai_link_name))
break;
if (k == num_vesper_cfg)
return 0;
for (i = 0; i < rtd->num_codecs; i++) {
for (j = 0; j < vesper_cfg[k].num_cfg; j++) {
if (rtd->codec_dais[i]->dev->of_node ==
vesper_cfg[k].tdm_cfg[j].component_of_node) {
snd_soc_dai_set_tdm_slot(rtd->codec_dais[i],
vesper_cfg[k].tdm_cfg[j].tdm_mask, 0, 8, 32);
break;
}
}
}
return 0;
}
static int tdmin_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
unsigned int rate = params_rate(params);
unsigned int mclk_rate = rate * 256;
int i;
/* codec mclk */
for (i = 0; i < rtd->num_codecs; i++)
snd_soc_dai_set_sysclk(rtd->codec_dais[i], 0, mclk_rate,
SND_SOC_CLOCK_IN);
return 0;
}
static struct snd_soc_ops tdmin_capture_ops = {
.startup = tdmin_capture_startup,
.hw_params = tdmin_hw_params,
};
/* No codec declared by default in the dai links. They are added
* dynamically depending on the dt
*/
/* Digital audio interface glue - connects codec <---> CPU */
static struct snd_soc_dai_link mt8516_vesper_dais[] = {
/* Front End DAI links */
{
.name = "TDM Capture",
.stream_name = "TDM_Capture",
.cpu_dai_name = "TDM_IN",
.dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_capture = 1,
.ops = &tdmin_capture_ops,
},
{
.name = "TDM Capture 6.1",
.stream_name = "TDM_Capture_6_1",
.cpu_dai_name = "TDM_IN",
.dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_capture = 1,
.ops = &tdmin_capture_ops,
},
{
.name = "DL1 Playback",
.stream_name = "DL1_Playback",
.cpu_dai_name = "DL1",
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.trigger = {
SND_SOC_DPCM_TRIGGER_POST,
SND_SOC_DPCM_TRIGGER_POST
},
.dynamic = 1,
.dpcm_playback = 1,
},
/* Backend End DAI links */
{
.name = "TDM IN BE",
.cpu_dai_name = "TDM_IN_IO",
.no_pcm = 1,
.codec_name = "snd-soc-dummy",
.codec_dai_name = "snd-soc-dummy-dai",
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.dpcm_capture = 1,
},
{
.name = "I2S BE",
.cpu_dai_name = "I2S",
.no_pcm = 1,
.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS,
.dpcm_playback = 1,
.dpcm_capture = 1,
},
};
static struct snd_soc_card mt8516_vesper_card = {
.name = "mt-snd-card",
.owner = THIS_MODULE,
.dai_link = mt8516_vesper_dais,
.num_links = ARRAY_SIZE(mt8516_vesper_dais),
};
static int set_card_codec_info(struct snd_soc_card *card)
{
struct snd_soc_dai_link_component *dai_link_codecs, *dlc;
struct device_node *dl_node, *c_node;
struct device *dev = card->dev;
struct of_phandle_args args;
struct adc_config *cur_cfg;
const char *dai_link_name;
const char *dai_format;
int cfg_idx, link_idx;
bool is_tdm_format;
int num_codecs;
u32 tdm_mask;
int ret, i;
/* Figure out how many vesper tdm configs are needed */
num_vesper_cfg = 0;
for_each_child_of_node(dev->of_node, dl_node) {
if (!of_property_read_string(dl_node, "dai-format",
&dai_format)) {
if (!strcmp(dai_format, "tdm"))
num_vesper_cfg++;
}
}
/* Allocate the number of vesper tdm configs that are needed */
vesper_cfg = devm_kcalloc(dev, num_vesper_cfg,
sizeof(*vesper_cfg), GFP_KERNEL);
if (!vesper_cfg)
return -ENOMEM;
cfg_idx = 0;
/* Loop over all the dai link sub nodes*/
for_each_child_of_node(dev->of_node, dl_node) {
if (of_property_read_string(dl_node, "dai-link-name",
&dai_link_name))
return -EINVAL;
/* Check wether the used format is tdm. If this is the case,
* the tdm mask information is stored to be used when the tdm
* is started and the tdm slots must be set.
*/
is_tdm_format = false;
if (!of_property_read_string(dl_node, "dai-format",
&dai_format)) {
if (!strcmp(dai_format, "tdm"))
is_tdm_format = true;
}
num_codecs = of_get_child_count(dl_node);
/* Allocate the snd_soc_dai_link_component array that will be
* used to dynamically add the list of codecs to the static
* snd_soc_dai_link array.
*/
dlc = dai_link_codecs = devm_kcalloc(dev, num_codecs,
sizeof(*dai_link_codecs),
GFP_KERNEL);
if (!dai_link_codecs)
return -ENOMEM;
if (is_tdm_format) {
/* Fill the vesper_cfg structure and allocate a number
* of tdm_cfg corresponding to the number of codecs.
*/
vesper_cfg[cfg_idx].num_cfg = num_codecs;
vesper_cfg[cfg_idx].dai_link_name = dai_link_name;
vesper_cfg[cfg_idx].tdm_cfg = devm_kcalloc(dev,
num_codecs, sizeof(struct adc_config),
GFP_KERNEL);
if (!vesper_cfg[cfg_idx].tdm_cfg)
return -ENOMEM;
}
link_idx = 0;
cur_cfg = vesper_cfg[cfg_idx].tdm_cfg;
/* Loop over all the codec sub nodes for this dai link */
for_each_child_of_node(dl_node, c_node) {
/* Retrieve the node and the dai_name that are used
* by the soundcard.
*/
ret = of_parse_phandle_with_args(c_node, "sound-dai",
"#sound-dai-cells", 0,
&args);
if (ret) {
if (ret != -EPROBE_DEFER)
dev_err(dev,
"can't parse dai %d\n", ret);
return ret;
}
dlc->of_node = args.np;
ret = snd_soc_get_dai_name(&args, &dlc->dai_name);
if (ret) {
of_node_put(c_node);
return ret;
}
if (is_tdm_format) {
/* Fill the tdm cfg for this codec */
if (of_property_read_u32(c_node, "tdm-mask",
&tdm_mask))
return -EINVAL;
cur_cfg->component_of_node = dlc->of_node;
cur_cfg->tdm_mask = tdm_mask;
cur_cfg++;
}
dlc++;
link_idx++;
}
/* Update the snd_soc_dai_link static array with the codecs
* we have just found.
*/
for (i = 0; i < card->num_links; i++) {
if (!strcmp(dai_link_name, card->dai_link[i].name)) {
card->dai_link[i].num_codecs = link_idx;
card->dai_link[i].codecs = dai_link_codecs;
break;
}
}
if (is_tdm_format)
cfg_idx++;
}
return 0;
}
static int mt8516_vesper_dev_probe(struct platform_device *pdev)
{
struct snd_soc_card *card = &mt8516_vesper_card;
struct device_node *platform_node;
struct device_node *codec_node;
int ret, i;
card->dev = &pdev->dev;
ret = set_card_codec_info(card);
if (ret) {
dev_err(&pdev->dev, "%s set_card_codec_info failed %d\n",
__func__, ret);
return ret;
}
platform_node = of_parse_phandle(pdev->dev.of_node,
"mediatek,platform", 0);
if (!platform_node) {
dev_err(&pdev->dev, "Property 'platform' missing or invalid\n");
return -EINVAL;
}
codec_node = of_parse_phandle(pdev->dev.of_node,
"mediatek,audio-codec", 0);
if (!codec_node) {
dev_err(&pdev->dev, "Property 'audio-codec' missing or invalid\n");
return -EINVAL;
}
for (i = 0; i < card->num_links; i++) {
if (mt8516_vesper_dais[i].platform_name)
continue;
mt8516_vesper_dais[i].platform_of_node = platform_node;
}
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n",
__func__, ret);
return ret;
}
return ret;
}
static const struct of_device_id mt8516_vesper_dt_match[] = {
{ .compatible = "mediatek,mt8516-soc-vesper", },
{ }
};
MODULE_DEVICE_TABLE(of, mt8516_vesper_dt_match);
static struct platform_driver mt8516_vesper_mach_driver = {
.driver = {
.name = "mt8516-soc-vesper",
.of_match_table = mt8516_vesper_dt_match,
#ifdef CONFIG_PM
.pm = &snd_soc_pm_ops,
#endif
},
.probe = mt8516_vesper_dev_probe,
};
module_platform_driver(mt8516_vesper_mach_driver);
/* Module information */
MODULE_DESCRIPTION("MT8516-Vesper ALSA SoC machine driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:mt8516-vesper");