Files
linux-nv-oot/sound/soc/tegra/tegra_asoc_machine.c
Jon Hunter f926a58dd1 ASoC: tegra: Fix build for Linux v6.5
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>
2023-10-07 13:04:11 -07:00

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",
&params->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",
&params->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");