mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
In Linux v6.5, the arguments to the function snd_soc_of_get_dai_name() were updated and this is causing the Tegra ASoC machine driver build to fail. Add a new test to the conftest script to check the arguments for snd_soc_of_get_dai_name() and use the definition generated by the conftest script accordingly. Bug 4221847 Change-Id: I32772da12ab59eb09e2f9d8bc66471d859201f8e Signed-off-by: Jon Hunter <jonathanh@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2990531 Tested-by: mobile promotions <svcmobile_promotions@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
923 lines
22 KiB
C
923 lines
22 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* tegra_asoc_machine.c - Tegra DAI links parser
|
|
*
|
|
* Copyright (c) 2014-2023 NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
*/
|
|
|
|
#include <nvidia/conftest.h>
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <sound/simple_card_utils.h>
|
|
#include <sound/jack.h>
|
|
#include <sound/soc.h>
|
|
#include "tegra_asoc_machine.h"
|
|
|
|
#define PREFIX "nvidia-audio-card,"
|
|
#define CELL "#sound-dai-cells"
|
|
#define DAI "sound-dai"
|
|
|
|
/* DT also uses similar values to specify link type */
|
|
enum dai_link_type {
|
|
PCM_LINK,
|
|
COMPR_LINK,
|
|
C2C_LINK,
|
|
};
|
|
|
|
struct snd_soc_pcm_stream link_params = {
|
|
.formats = SNDRV_PCM_FMTBIT_S8 |
|
|
SNDRV_PCM_FMTBIT_S16_LE |
|
|
SNDRV_PCM_FMTBIT_S24_LE |
|
|
SNDRV_PCM_FMTBIT_S32_LE,
|
|
.rate_min = 8000,
|
|
.rate_max = 192000,
|
|
.channels_min = 1,
|
|
.channels_max = 16,
|
|
};
|
|
|
|
/* find if DAI link or its cpu/codec DAI nodes are disabled */
|
|
static bool of_dai_link_is_available(struct device_node *link_node)
|
|
{
|
|
struct device_node *child, *dai_node;
|
|
|
|
if (!of_device_is_available(link_node))
|
|
return false;
|
|
|
|
for_each_child_of_node(link_node, child) {
|
|
/* check for "cpu" and "codec" nodes only */
|
|
if (of_node_cmp(child->name, "cpu") &&
|
|
of_node_cmp(child->name, "codec"))
|
|
continue;
|
|
|
|
/*
|
|
* Skip a codec subnode if DAI property is missing. For a
|
|
* link with multiple codecs, at least one codec needs to
|
|
* have DAI property (which is ensured while counting the
|
|
* number of links that DT exposes). Other codec subnodes
|
|
* can be empty and populated in override file.
|
|
*/
|
|
if (!of_property_read_bool(child, DAI) &&
|
|
!of_node_cmp(child->name, "codec"))
|
|
continue;
|
|
|
|
dai_node = of_parse_phandle(child, DAI, 0);
|
|
if (!dai_node) {
|
|
of_node_put(child);
|
|
return false;
|
|
}
|
|
|
|
if (!of_device_is_available(dai_node)) {
|
|
of_node_put(dai_node);
|
|
of_node_put(child);
|
|
return false;
|
|
}
|
|
|
|
of_node_put(dai_node);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* find number of child nodes with given name and containing DAI property */
|
|
static int of_get_child_count_with_name(struct device_node *node,
|
|
const char *name)
|
|
{
|
|
struct device_node *child;
|
|
int num = 0;
|
|
|
|
for_each_child_of_node(node, child)
|
|
if (!of_node_cmp(child->name, name) &&
|
|
of_property_read_bool(child, DAI))
|
|
num++;
|
|
|
|
return num;
|
|
}
|
|
|
|
static int get_num_dai_links(struct platform_device *pdev,
|
|
unsigned int *num_links)
|
|
{
|
|
struct device_node *top = pdev->dev.of_node;
|
|
struct device_node *link_node;
|
|
unsigned int link_count = 0;
|
|
|
|
link_node = of_get_child_by_name(top, PREFIX "dai-link");
|
|
if (!link_node) {
|
|
dev_err(&pdev->dev, "no dai links found\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
do {
|
|
if (!of_dai_link_is_available(link_node)) {
|
|
link_node = of_get_next_child(top, link_node);
|
|
continue;
|
|
}
|
|
|
|
link_count++;
|
|
link_node = of_get_next_child(top, link_node);
|
|
} while (link_node);
|
|
|
|
*num_links = link_count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int allocate_link_dais(struct platform_device *pdev,
|
|
struct snd_soc_dai_link *dai_links)
|
|
{
|
|
struct device_node *top = pdev->dev.of_node;
|
|
struct device_node *link_node;
|
|
unsigned int link_count = 0, num_codecs;
|
|
|
|
link_node = of_get_child_by_name(top, PREFIX "dai-link");
|
|
if (!link_node) {
|
|
dev_err(&pdev->dev, "no dai links found\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
do {
|
|
if (!of_dai_link_is_available(link_node)) {
|
|
link_node = of_get_next_child(top, link_node);
|
|
continue;
|
|
}
|
|
|
|
dai_links[link_count].cpus = devm_kzalloc(&pdev->dev,
|
|
sizeof(*dai_links[link_count].cpus),
|
|
GFP_KERNEL);
|
|
if (!dai_links[link_count].cpus)
|
|
return -ENOMEM;
|
|
|
|
num_codecs = of_get_child_count_with_name(link_node,
|
|
"codec");
|
|
if (!num_codecs) {
|
|
of_node_put(link_node);
|
|
dev_err(&pdev->dev,
|
|
"no codec subnode or sound-dai property\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dai_links[link_count].codecs =
|
|
devm_kzalloc(&pdev->dev,
|
|
sizeof(*dai_links[link_count].codecs) * num_codecs,
|
|
GFP_KERNEL);
|
|
if (!dai_links[link_count].codecs)
|
|
return -ENOMEM;
|
|
|
|
dai_links[link_count].platforms = devm_kzalloc(&pdev->dev,
|
|
sizeof(*dai_links[link_count].platforms),
|
|
GFP_KERNEL);
|
|
if (!dai_links[link_count].platforms)
|
|
return -ENOMEM;
|
|
|
|
dai_links[link_count].num_cpus = 1;
|
|
dai_links[link_count].num_codecs = num_codecs;
|
|
dai_links[link_count].num_platforms = 1;
|
|
|
|
link_count++;
|
|
|
|
link_node = of_get_next_child(top, link_node);
|
|
} while (link_node);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_num_codec_confs(struct platform_device *pdev, int *num_confs)
|
|
{
|
|
struct device_node *top = pdev->dev.of_node;
|
|
struct device_node *link_node, *codec;
|
|
unsigned int conf_count = 0, num_codecs;
|
|
|
|
link_node = of_get_child_by_name(top, PREFIX "dai-link");
|
|
if (!link_node) {
|
|
dev_err(&pdev->dev, "no dai links found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
do {
|
|
if (!of_dai_link_is_available(link_node)) {
|
|
link_node = of_get_next_child(top, link_node);
|
|
continue;
|
|
}
|
|
|
|
num_codecs = of_get_child_count_with_name(link_node,
|
|
"codec");
|
|
if (!num_codecs) {
|
|
of_node_put(link_node);
|
|
dev_err(&pdev->dev, "missing codec subnode\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
for_each_child_of_node(link_node, codec) {
|
|
if (of_node_cmp(codec->name, "codec"))
|
|
continue;
|
|
|
|
if (of_property_read_bool(codec, "prefix"))
|
|
conf_count++;
|
|
}
|
|
|
|
link_node = of_get_next_child(top, link_node);
|
|
} while (link_node);
|
|
|
|
*num_confs = conf_count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void parse_mclk_fs(struct snd_soc_card *card)
|
|
{
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
struct platform_device *pdev = to_platform_device(card->dev);
|
|
|
|
if (of_property_read_u32(pdev->dev.of_node, PREFIX "mclk-fs",
|
|
&machine->audio_clock.mclk_fs))
|
|
dev_dbg(&pdev->dev, "'%smclk-fs' property is missing\n",
|
|
PREFIX);
|
|
}
|
|
|
|
static int parse_dai(struct device_node *node,
|
|
struct snd_soc_dai_link_component *dlc)
|
|
{
|
|
struct of_phandle_args args;
|
|
int ret;
|
|
|
|
ret = of_parse_phandle_with_args(node, DAI, CELL, 0, &args);
|
|
if (ret)
|
|
return ret;
|
|
|
|
#if defined(NV_SND_SOC_OF_GET_DAI_NAME_HAS_INDEX_ARG)
|
|
ret = snd_soc_of_get_dai_name(node, &dlc->dai_name, 0);
|
|
#else
|
|
ret = snd_soc_of_get_dai_name(node, &dlc->dai_name);
|
|
#endif
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
dlc->of_node = args.np;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_dt_codec_confs(struct snd_soc_card *card)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(card->dev);
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
struct device_node *top = pdev->dev.of_node;
|
|
struct device_node *link_node;
|
|
struct snd_soc_codec_conf *codec_confs;
|
|
unsigned int num_confs, i = 0;
|
|
int err;
|
|
|
|
err = get_num_codec_confs(pdev, &machine->asoc->num_confs);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
num_confs = machine->asoc->num_confs;
|
|
if (!num_confs)
|
|
return 0;
|
|
|
|
machine->asoc->codec_confs = devm_kcalloc(&pdev->dev,
|
|
num_confs,
|
|
sizeof(*codec_confs),
|
|
GFP_KERNEL);
|
|
if (!machine->asoc->codec_confs)
|
|
return -ENOMEM;
|
|
codec_confs = machine->asoc->codec_confs;
|
|
|
|
link_node = of_get_child_by_name(top, PREFIX "dai-link");
|
|
if (!link_node) {
|
|
dev_err(&pdev->dev, "DAI links not found in DT\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
do {
|
|
struct of_phandle_args args;
|
|
struct device_node *codec;
|
|
|
|
if (!of_dai_link_is_available(link_node)) {
|
|
link_node = of_get_next_child(top, link_node);
|
|
continue;
|
|
}
|
|
|
|
for_each_child_of_node(link_node, codec) {
|
|
if (of_node_cmp(codec->name, "codec"))
|
|
continue;
|
|
|
|
if (!of_property_read_bool(codec, "prefix"))
|
|
continue;
|
|
|
|
err = of_parse_phandle_with_args(codec, DAI, CELL, 0,
|
|
&args);
|
|
if (err < 0) {
|
|
of_node_put(codec);
|
|
of_node_put(link_node);
|
|
return err;
|
|
}
|
|
|
|
codec_confs[i].dlc.of_node = args.np;
|
|
codec_confs[i].dlc.name = NULL;
|
|
|
|
of_property_read_string(codec, "prefix",
|
|
&codec_confs[i].name_prefix);
|
|
|
|
i++;
|
|
}
|
|
|
|
link_node = of_get_next_child(top, link_node);
|
|
} while (link_node);
|
|
|
|
card->num_configs = num_confs;
|
|
card->codec_conf = codec_confs;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_dai_link_params(struct platform_device *pdev,
|
|
struct device_node *link_node,
|
|
struct snd_soc_dai_link *dai_link)
|
|
{
|
|
struct snd_soc_pcm_stream *params;
|
|
char *str;
|
|
|
|
params = devm_kzalloc(&pdev->dev, sizeof(*params), GFP_KERNEL);
|
|
if (!params)
|
|
return -ENOMEM;
|
|
|
|
/* Copy default settings */
|
|
memcpy(params, &link_params, sizeof(*params));
|
|
|
|
if (!of_property_read_u32(link_node, "srate",
|
|
¶ms->rate_min)) {
|
|
if (params->rate_min < link_params.rate_min ||
|
|
params->rate_min > link_params.rate_max) {
|
|
dev_err(&pdev->dev,
|
|
"Unsupported rate %d for DAI link (%pOF)\n",
|
|
params->rate_min, link_node);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
params->rate_max = params->rate_min;
|
|
}
|
|
|
|
if (!of_property_read_u32(link_node, "num-channel",
|
|
¶ms->channels_min)) {
|
|
if (params->channels_min < link_params.channels_min ||
|
|
params->channels_min > link_params.channels_max) {
|
|
dev_err(&pdev->dev,
|
|
"Unsupported channel %d for DAI link (%pOF)\n",
|
|
params->channels_min, link_node);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
params->channels_max = params->channels_min;
|
|
}
|
|
|
|
if (!of_property_read_string(link_node, "bit-format",
|
|
(const char **)&str)) {
|
|
if (!strcmp("s8", str)) {
|
|
params->formats = SNDRV_PCM_FMTBIT_S8;
|
|
} else if (!strcmp("s16_le", str)) {
|
|
params->formats = SNDRV_PCM_FMTBIT_S16_LE;
|
|
} else if (!strcmp("s24_le", str)) {
|
|
params->formats = SNDRV_PCM_FMTBIT_S24_LE;
|
|
} else if (!strcmp("s32_le", str)) {
|
|
params->formats = SNDRV_PCM_FMTBIT_S32_LE;
|
|
} else {
|
|
dev_err(&pdev->dev,
|
|
"Unsupported format %s for DAI link (%pOF)\n",
|
|
str, link_node);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
if (!(params->formats & link_params.formats)) {
|
|
dev_err(&pdev->dev,
|
|
"Unsupported format %s for DAI link (%pOF)\n",
|
|
str, link_node);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
}
|
|
|
|
#if defined(NV_SND_SOC_DAI_LINK_STRUCT_HAS_C2C_PARAMS_ARG) /* Linux v6.4 */
|
|
dai_link->c2c_params = params;
|
|
#else
|
|
dai_link->params = params;
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_dt_dai_links(struct snd_soc_card *card,
|
|
struct snd_soc_ops *pcm_ops,
|
|
struct snd_soc_compr_ops *compr_ops)
|
|
{
|
|
struct platform_device *pdev = to_platform_device(card->dev);
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
struct device_node *top = pdev->dev.of_node;
|
|
struct device_node *link_node;
|
|
struct snd_soc_dai_link *dai_links;
|
|
unsigned int num_links, link_count = 0;
|
|
int ret;
|
|
|
|
ret = get_num_dai_links(pdev, &machine->asoc->num_links);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
num_links = machine->asoc->num_links;
|
|
if (!num_links)
|
|
return -EINVAL;
|
|
|
|
dai_links = devm_kcalloc(&pdev->dev, num_links, sizeof(*dai_links),
|
|
GFP_KERNEL);
|
|
if (!dai_links)
|
|
return -ENOMEM;
|
|
|
|
ret = allocate_link_dais(pdev, dai_links);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
machine->asoc->dai_links = dai_links;
|
|
|
|
link_node = of_get_child_by_name(top, PREFIX "dai-link");
|
|
if (!link_node) {
|
|
dev_err(&pdev->dev, "DAI links not found in DT\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
do {
|
|
struct device_node *codec = NULL, *cpu = NULL;
|
|
struct snd_soc_dai_link *dai_link;
|
|
int link_type = 0, codec_count = 0;
|
|
char *link_addr;
|
|
|
|
if (!of_dai_link_is_available(link_node)) {
|
|
link_node = of_get_next_child(top, link_node);
|
|
continue;
|
|
}
|
|
|
|
dev_dbg(&pdev->dev, "parsing (%pOF)\n", link_node);
|
|
|
|
dai_link = &dai_links[link_count];
|
|
cpu = of_get_child_by_name(link_node, "cpu");
|
|
if (!cpu) {
|
|
dev_err(&pdev->dev, "cpu subnode is missing");
|
|
ret = -ENOENT;
|
|
goto cleanup;
|
|
}
|
|
|
|
/* parse CPU DAI */
|
|
ret = parse_dai(cpu, dai_link->cpus);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
|
|
for_each_child_of_node(link_node, codec) {
|
|
/* loop over codecs only */
|
|
if (of_node_cmp(codec->name, "codec"))
|
|
continue;
|
|
|
|
if (!of_property_read_bool(codec, DAI)) {
|
|
dev_dbg(&pdev->dev,
|
|
"sound-dai prop missing for (%pOF)\n",
|
|
codec);
|
|
codec_count++;
|
|
continue;
|
|
}
|
|
|
|
/* parse CODEC DAI */
|
|
ret = parse_dai(codec, &dai_link->codecs[codec_count]);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
|
|
codec_count++;
|
|
}
|
|
|
|
/* set DAI link name */
|
|
if (of_property_read_string(link_node,
|
|
"link-name",
|
|
&dai_link->name)) {
|
|
ret = asoc_simple_set_dailink_name(
|
|
&pdev->dev, dai_link, "%s-%d",
|
|
"tegra-dlink", link_count);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
}
|
|
|
|
asoc_simple_parse_daifmt(&pdev->dev, link_node, codec,
|
|
NULL, &dai_link->dai_fmt);
|
|
|
|
asoc_simple_canonicalize_platform(dai_link->platforms,
|
|
dai_link->cpus);
|
|
|
|
of_property_read_u32(link_node, "link-type",
|
|
&link_type);
|
|
switch (link_type) {
|
|
case PCM_LINK:
|
|
dai_link->ops = pcm_ops;
|
|
break;
|
|
case COMPR_LINK:
|
|
dai_link->compr_ops = compr_ops;
|
|
break;
|
|
case C2C_LINK:
|
|
/* Parse DT provided link params */
|
|
ret = parse_dai_link_params(pdev, link_node,
|
|
dai_link);
|
|
if (ret < 0)
|
|
goto cleanup;
|
|
|
|
break;
|
|
default:
|
|
dev_err(&pdev->dev, "DAI link type invalid\n");
|
|
ret = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Assign DAI link ID based on DT link address.
|
|
* This is done to use consistent PCM/Compress device
|
|
* IDs irrespective of parsing order of DT DAI links.
|
|
*/
|
|
link_addr = strrchr(link_node->full_name, '@');
|
|
if (!link_addr) {
|
|
dev_err(&pdev->dev,
|
|
"Invalid link node (%pOF)\n",
|
|
link_node);
|
|
ret = -EINVAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
ret = kstrtos32(++link_addr, 10, &dai_link->id);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev,
|
|
"Failed to get link node (%pOF) ID\n",
|
|
link_node);
|
|
goto cleanup;
|
|
}
|
|
|
|
link_count++;
|
|
|
|
cleanup:
|
|
of_node_put(cpu);
|
|
if (ret < 0) {
|
|
of_node_put(codec);
|
|
of_node_put(link_node);
|
|
return ret;
|
|
}
|
|
|
|
link_node = of_get_next_child(top, link_node);
|
|
} while (link_node);
|
|
|
|
card->num_links = num_links;
|
|
card->dai_link = dai_links;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int parse_card_info(struct snd_soc_card *card, struct snd_soc_ops *pcm_ops,
|
|
struct snd_soc_compr_ops *compr_ops)
|
|
{
|
|
struct tegra_machine *machine = snd_soc_card_get_drvdata(card);
|
|
struct device_node *node = card->dev->of_node;
|
|
int ret;
|
|
|
|
ret = asoc_simple_parse_card_name(card, PREFIX);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
/* parse machine DAPM widgets */
|
|
if (of_property_read_bool(node, PREFIX "widgets")) {
|
|
ret = snd_soc_of_parse_audio_simple_widgets(card,
|
|
PREFIX "widgets");
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
/*str
|
|
* Below property of routing map is required only when there
|
|
* are DAPM input/output widgets available for external codec,
|
|
* which require them to be connected to machine source/sink
|
|
* DAPM widgets.
|
|
*/
|
|
if (of_property_read_bool(node, PREFIX "routing")) {
|
|
ret = snd_soc_of_parse_audio_routing(card, PREFIX "routing");
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
parse_mclk_fs(card);
|
|
|
|
if (of_property_read_bool(node, "fixed-pll")) {
|
|
machine->audio_clock.fixed_pll = true;
|
|
dev_info(card->dev, "PLL configuration is fixed from DT\n");
|
|
}
|
|
|
|
ret = parse_dt_dai_links(card, pcm_ops, compr_ops);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = parse_dt_codec_confs(card);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(parse_card_info);
|
|
|
|
struct tegra_machine_control_data {
|
|
struct snd_soc_pcm_runtime *rtd;
|
|
unsigned int frame_mode;
|
|
unsigned int master_mode;
|
|
};
|
|
|
|
static int tegra_machine_codec_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
|
|
unsigned int frame_mode,
|
|
unsigned int master_mode)
|
|
{
|
|
unsigned int fmt = rtd->dai_link->dai_fmt;
|
|
|
|
if (frame_mode) {
|
|
fmt &= ~SND_SOC_DAIFMT_FORMAT_MASK;
|
|
fmt |= frame_mode;
|
|
}
|
|
|
|
if (master_mode) {
|
|
fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
|
|
master_mode <<= ffs(SND_SOC_DAIFMT_MASTER_MASK) - 1;
|
|
|
|
if (master_mode == SND_SOC_DAIFMT_CBM_CFM)
|
|
fmt |= SND_SOC_DAIFMT_CBM_CFM;
|
|
else
|
|
fmt |= SND_SOC_DAIFMT_CBS_CFS;
|
|
}
|
|
|
|
return snd_soc_runtime_set_dai_fmt(rtd, fmt);
|
|
}
|
|
|
|
/*
|
|
* The order of the below must not be changed as this
|
|
* aligns with the SND_SOC_DAIFMT_XXX definitions in
|
|
* include/sound/soc-dai.h.
|
|
*/
|
|
static const char * const tegra_machine_frame_mode_text[] = {
|
|
"None",
|
|
"i2s",
|
|
"right-j",
|
|
"left-j",
|
|
"dsp-a",
|
|
"dsp-b",
|
|
};
|
|
|
|
static int tegra_machine_codec_get_frame_mode(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tegra_machine_control_data *data = kcontrol->private_data;
|
|
|
|
ucontrol->value.integer.value[0] = data->frame_mode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_machine_codec_put_frame_mode(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tegra_machine_control_data *data = kcontrol->private_data;
|
|
int err;
|
|
|
|
err = tegra_machine_codec_set_dai_fmt(data->rtd,
|
|
ucontrol->value.integer.value[0],
|
|
data->master_mode);
|
|
if (err)
|
|
return err;
|
|
|
|
data->frame_mode = ucontrol->value.integer.value[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char * const tegra_machine_master_mode_text[] = {
|
|
"None",
|
|
"cbm-cfm",
|
|
"cbs-cfs",
|
|
};
|
|
|
|
static int tegra_machine_codec_get_master_mode(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tegra_machine_control_data *data = kcontrol->private_data;
|
|
|
|
ucontrol->value.integer.value[0] = data->master_mode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_machine_codec_put_master_mode(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct tegra_machine_control_data *data = kcontrol->private_data;
|
|
int err;
|
|
|
|
err = tegra_machine_codec_set_dai_fmt(data->rtd,
|
|
data->frame_mode,
|
|
ucontrol->value.integer.value[0]);
|
|
if (err)
|
|
return err;
|
|
|
|
data->master_mode = ucontrol->value.integer.value[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct soc_enum tegra_machine_codec_frame_mode =
|
|
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(tegra_machine_frame_mode_text),
|
|
tegra_machine_frame_mode_text);
|
|
|
|
static const struct soc_enum tegra_machine_codec_master_mode =
|
|
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(tegra_machine_master_mode_text),
|
|
tegra_machine_master_mode_text);
|
|
|
|
static int tegra_machine_add_ctl(struct snd_soc_card *card,
|
|
struct snd_kcontrol_new *knew,
|
|
void *private_data,
|
|
const unsigned char *name)
|
|
{
|
|
struct snd_kcontrol *kctl;
|
|
int ret;
|
|
|
|
kctl = snd_ctl_new1(knew, private_data);
|
|
if (!kctl)
|
|
return -ENOMEM;
|
|
|
|
ret = snd_ctl_add(card->snd_card, kctl);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_machine_add_frame_mode_ctl(struct snd_soc_card *card,
|
|
struct snd_soc_pcm_runtime *rtd, const unsigned char *name,
|
|
struct tegra_machine_control_data *data)
|
|
{
|
|
struct snd_kcontrol_new knew = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = name,
|
|
.info = snd_soc_info_enum_double,
|
|
.index = 0,
|
|
.get = tegra_machine_codec_get_frame_mode,
|
|
.put = tegra_machine_codec_put_frame_mode,
|
|
.private_value =
|
|
(unsigned long)&tegra_machine_codec_frame_mode,
|
|
};
|
|
|
|
return tegra_machine_add_ctl(card, &knew, data, name);
|
|
}
|
|
|
|
static int tegra_machine_add_master_mode_ctl(struct snd_soc_card *card,
|
|
struct snd_soc_pcm_runtime *rtd, const unsigned char *name,
|
|
struct tegra_machine_control_data *data)
|
|
{
|
|
struct snd_kcontrol_new knew = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = name,
|
|
.info = snd_soc_info_enum_double,
|
|
.index = 0,
|
|
.get = tegra_machine_codec_get_master_mode,
|
|
.put = tegra_machine_codec_put_master_mode,
|
|
.private_value =
|
|
(unsigned long)&tegra_machine_codec_master_mode,
|
|
};
|
|
|
|
return tegra_machine_add_ctl(card, &knew, data, name);
|
|
}
|
|
|
|
int tegra_machine_add_i2s_codec_controls(struct snd_soc_card *card)
|
|
{
|
|
struct tegra_machine_control_data *data;
|
|
struct snd_soc_pcm_runtime *rtd;
|
|
struct device_node *np;
|
|
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
unsigned int id;
|
|
int ret;
|
|
|
|
list_for_each_entry(rtd, &card->rtd_list, list) {
|
|
np = rtd->dai_link->cpus->of_node;
|
|
|
|
if (!np)
|
|
continue;
|
|
|
|
data = devm_kzalloc(card->dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
data->rtd = rtd;
|
|
data->frame_mode = 0;
|
|
data->master_mode = 0;
|
|
|
|
if (of_property_read_u32(np, "nvidia,ahub-i2s-id", &id) < 0)
|
|
continue;
|
|
|
|
snprintf(name, sizeof(name), "I2S%d codec frame mode", id+1);
|
|
|
|
ret = tegra_machine_add_frame_mode_ctl(card, rtd, name, data);
|
|
if (ret)
|
|
dev_warn(card->dev, "Failed to add control: %s!\n",
|
|
name);
|
|
|
|
snprintf(name, sizeof(name), "I2S%d codec master mode", id+1);
|
|
|
|
ret = tegra_machine_add_master_mode_ctl(card, rtd, name, data);
|
|
if (ret) {
|
|
dev_warn(card->dev, "Failed to add control: %s!\n",
|
|
name);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(tegra_machine_add_i2s_codec_controls);
|
|
|
|
/*
|
|
* The order of the following definitions should align with
|
|
* the 'snd_jack_types' enum as defined in include/sound/jack.h.
|
|
*/
|
|
static const char * const tegra_machine_jack_state_text[] = {
|
|
"None",
|
|
"HP",
|
|
"MIC",
|
|
"HS",
|
|
};
|
|
|
|
static const struct soc_enum tegra_machine_jack_state =
|
|
SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(tegra_machine_jack_state_text),
|
|
tegra_machine_jack_state_text);
|
|
|
|
static int tegra_machine_codec_get_jack_state(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_jack *jack = kcontrol->private_data;
|
|
|
|
ucontrol->value.integer.value[0] = jack->status;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_machine_codec_put_jack_state(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_jack *jack = kcontrol->private_data;
|
|
|
|
snd_soc_jack_report(jack, ucontrol->value.integer.value[0],
|
|
SND_JACK_HEADSET);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int tegra_machine_add_codec_jack_control(struct snd_soc_card *card,
|
|
struct snd_soc_pcm_runtime *rtd,
|
|
struct snd_soc_jack *jack)
|
|
{
|
|
char name[SNDRV_CTL_ELEM_ID_NAME_MAXLEN];
|
|
struct snd_kcontrol_new knew = {
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
.name = name,
|
|
.info = snd_soc_info_enum_double,
|
|
.index = 0,
|
|
.get = tegra_machine_codec_get_jack_state,
|
|
.put = tegra_machine_codec_put_jack_state,
|
|
.private_value = (unsigned long)&tegra_machine_jack_state,
|
|
};
|
|
|
|
if (rtd->dais[rtd->dai_link->num_cpus]->component->name_prefix)
|
|
snprintf(name, sizeof(name), "%s Jack-state",
|
|
rtd->dais[rtd->dai_link->num_cpus]->component->name_prefix);
|
|
else
|
|
snprintf(name, sizeof(name), "Jack-state");
|
|
|
|
return tegra_machine_add_ctl(card, &knew, jack, name);
|
|
}
|
|
EXPORT_SYMBOL_GPL(tegra_machine_add_codec_jack_control);
|
|
|
|
void release_asoc_phandles(struct tegra_machine *machine)
|
|
{
|
|
unsigned int i;
|
|
|
|
if (machine->asoc->dai_links) {
|
|
for (i = 0; i < machine->asoc->num_links; i++) {
|
|
of_node_put(machine->asoc->dai_links[i].cpus->of_node);
|
|
of_node_put(machine->asoc->dai_links[i].codecs->of_node);
|
|
}
|
|
}
|
|
if (machine->asoc->codec_confs) {
|
|
for (i = 0; i < machine->asoc->num_confs; i++)
|
|
of_node_put(machine->asoc->codec_confs[i].dlc.of_node);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL_GPL(release_asoc_phandles);
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DESCRIPTION("Tegra ASoC Machine Utility Code");
|
|
MODULE_LICENSE("GPL v2");
|