2018-07-02 14:24:18 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
|
|
|
//
|
|
|
|
// soc-core.c -- ALSA SoC Audio Layer
|
|
|
|
//
|
|
|
|
// Copyright 2005 Wolfson Microelectronics PLC.
|
|
|
|
// Copyright 2005 Openedhand Ltd.
|
|
|
|
// Copyright (C) 2010 Slimlogic Ltd.
|
|
|
|
// Copyright (C) 2010 Texas Instruments Inc.
|
|
|
|
//
|
|
|
|
// Author: Liam Girdwood <lrg@slimlogic.co.uk>
|
|
|
|
// with code, comments and ideas from :-
|
|
|
|
// Richard Purdie <richard@openedhand.com>
|
|
|
|
//
|
|
|
|
// TODO:
|
|
|
|
// o Add hw rules to enforce rates, etc.
|
|
|
|
// o More testing with other codecs/machines.
|
|
|
|
// o Add more codecs and platforms to ensure good API coverage.
|
|
|
|
// o Support TDM on PCM and I2S
|
2006-10-07 00:31:09 +08:00
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/moduleparam.h>
|
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/pm.h>
|
|
|
|
#include <linux/bitops.h>
|
2008-10-14 08:42:14 +08:00
|
|
|
#include <linux/debugfs.h>
|
2006-10-07 00:31:09 +08:00
|
|
|
#include <linux/platform_device.h>
|
2013-08-19 23:05:55 +08:00
|
|
|
#include <linux/pinctrl/consumer.h>
|
2011-09-20 18:41:54 +08:00
|
|
|
#include <linux/ctype.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2011-12-13 06:55:34 +08:00
|
|
|
#include <linux/of.h>
|
2017-05-18 09:39:25 +08:00
|
|
|
#include <linux/of_graph.h>
|
2017-01-14 16:13:02 +08:00
|
|
|
#include <linux/dmi.h>
|
2021-03-03 19:55:26 +08:00
|
|
|
#include <linux/acpi.h>
|
2006-10-07 00:31:09 +08:00
|
|
|
#include <sound/core.h>
|
|
|
|
#include <sound/pcm.h>
|
|
|
|
#include <sound/pcm_params.h>
|
|
|
|
#include <sound/soc.h>
|
ASoC: dpcm: Add Dynamic PCM core operations.
The Dynamic PCM core allows digital audio data to be dynamically
routed between different ALSA PCMs and DAI links on SoC CPUs with
on chip DSP devices. e.g. audio data could be played on pcm:0,0 and
routed to any (or all) SoC DAI links.
Dynamic PCM introduces the concept of Front End (FE) PCMs and Back
End (BE) PCMs. The FE PCMs are normal ALSA PCM devices except that
they can dynamically route digital audio data to any supported BE
PCM. A BE PCM has no ALSA device, but represents a DAI link and it's
substream and audio HW parameters.
e.g. pcm:0,0 routing digital data to 2 external codecs.
FE pcm:0,0 ----> BE (McBSP.0) ----> CODEC 0
+--> BE (McPDM.0) ----> CODEC 1
e.g. pcm:0,0 and pcm:0,1 routing digital data to 1 external codec.
FE pcm:0,0 ---
+--> BE (McBSP.0) ----> CODEC
FE pcm:0,1 ---
The digital audio routing is controlled by the usual ALSA method
of mixer kcontrols. Dynamic PCM uses a DAPM graph to work out the
routing based upon the mixer settings and configures the BE PCMs
based on routing and the FE HW params.
DPCM is designed so that most ASoC component drivers will need no
modification at all. It's intended that existing CODEC, DAI and
platform drivers can be used in DPCM based audio devices without
any changes. However, there will be some cases where minor changes
are required (e.g. for very tightly coupled HW) and there are
helpers to support this too.
Somethimes the HW params of a FE and BE do not match or are
incompatible, so in these cases the machine driver can reconfigure
any hw_params and make any DSP perform sample rate / format conversion.
This patch adds the core DPCM code and contains :-
o The FE and BE PCM operations.
o FE and BE DAI link support.
o FE and BE PCM creation.
o BE support API.
o BE and FE link management.
Signed-off-by: Liam Girdwood <lrg@ti.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
2012-04-25 19:12:49 +08:00
|
|
|
#include <sound/soc-dpcm.h>
|
2015-05-30 02:06:14 +08:00
|
|
|
#include <sound/soc-topology.h>
|
2020-05-25 08:57:14 +08:00
|
|
|
#include <sound/soc-link.h>
|
2006-10-07 00:31:09 +08:00
|
|
|
#include <sound/initval.h>
|
|
|
|
|
2010-11-04 06:05:58 +08:00
|
|
|
#define CREATE_TRACE_POINTS
|
|
|
|
#include <trace/events/asoc.h>
|
|
|
|
|
2008-11-28 21:29:45 +08:00
|
|
|
static DEFINE_MUTEX(client_mutex);
|
2013-03-12 09:27:21 +08:00
|
|
|
static LIST_HEAD(component_list);
|
2018-09-12 17:15:00 +08:00
|
|
|
static LIST_HEAD(unbind_card_list);
|
2008-11-28 21:29:45 +08:00
|
|
|
|
2018-09-21 13:23:01 +08:00
|
|
|
#define for_each_component(component) \
|
|
|
|
list_for_each_entry(component, &component_list, list)
|
|
|
|
|
2019-06-06 12:07:42 +08:00
|
|
|
/*
|
|
|
|
* This is used if driver don't need to have CPU/Codec/Platform
|
|
|
|
* dai_link. see soc.h
|
|
|
|
*/
|
|
|
|
struct snd_soc_dai_link_component null_dailink_component[0];
|
|
|
|
EXPORT_SYMBOL_GPL(null_dailink_component);
|
|
|
|
|
2006-10-07 00:31:09 +08:00
|
|
|
/*
|
|
|
|
* This is a timeout to do a DAPM powerdown after a stream is closed().
|
|
|
|
* It can be used to eliminate pops between different playback streams, e.g.
|
|
|
|
* between two audio tracks.
|
|
|
|
*/
|
|
|
|
static int pmdown_time = 5000;
|
|
|
|
module_param(pmdown_time, int, 0);
|
|
|
|
MODULE_PARM_DESC(pmdown_time, "DAPM stream powerdown time (msecs)");
|
|
|
|
|
2010-02-12 19:37:24 +08:00
|
|
|
static ssize_t pmdown_time_show(struct device *dev,
|
|
|
|
struct device_attribute *attr, char *buf)
|
|
|
|
{
|
2012-01-07 09:12:45 +08:00
|
|
|
struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev);
|
2010-02-12 19:37:24 +08:00
|
|
|
|
2022-08-02 01:01:06 +08:00
|
|
|
return sysfs_emit(buf, "%ld\n", rtd->pmdown_time);
|
2010-02-12 19:37:24 +08:00
|
|
|
}
|
|
|
|
|
2021-05-14 16:11:00 +08:00
|
|
|
static ssize_t pmdown_time_store(struct device *dev,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
const char *buf, size_t count)
|
2010-02-12 19:37:24 +08:00
|
|
|
{
|
2012-01-07 09:12:45 +08:00
|
|
|
struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev);
|
2010-10-28 11:11:17 +08:00
|
|
|
int ret;
|
2010-02-12 19:37:24 +08:00
|
|
|
|
2013-07-19 15:24:59 +08:00
|
|
|
ret = kstrtol(buf, 10, &rtd->pmdown_time);
|
2010-10-28 11:11:17 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2010-02-12 19:37:24 +08:00
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
2021-05-14 16:11:00 +08:00
|
|
|
static DEVICE_ATTR_RW(pmdown_time);
|
2010-02-12 19:37:24 +08:00
|
|
|
|
2015-01-31 03:16:37 +08:00
|
|
|
static struct attribute *soc_dev_attrs[] = {
|
|
|
|
&dev_attr_pmdown_time.attr,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
static umode_t soc_dev_attr_is_visible(struct kobject *kobj,
|
|
|
|
struct attribute *attr, int idx)
|
|
|
|
{
|
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
|
|
struct snd_soc_pcm_runtime *rtd = dev_get_drvdata(dev);
|
|
|
|
|
2019-09-12 12:42:30 +08:00
|
|
|
if (!rtd)
|
|
|
|
return 0;
|
|
|
|
|
2015-01-31 03:16:37 +08:00
|
|
|
if (attr == &dev_attr_pmdown_time.attr)
|
|
|
|
return attr->mode; /* always visible */
|
2022-09-20 14:32:16 +08:00
|
|
|
return rtd->dai_link->num_codecs ? attr->mode : 0; /* enabled only with codec */
|
2015-01-31 03:16:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct attribute_group soc_dapm_dev_group = {
|
|
|
|
.attrs = soc_dapm_dev_attrs,
|
|
|
|
.is_visible = soc_dev_attr_is_visible,
|
|
|
|
};
|
|
|
|
|
2018-03-09 20:46:27 +08:00
|
|
|
static const struct attribute_group soc_dev_group = {
|
2015-01-31 03:16:37 +08:00
|
|
|
.attrs = soc_dev_attrs,
|
|
|
|
.is_visible = soc_dev_attr_is_visible,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group *soc_dev_attr_groups[] = {
|
|
|
|
&soc_dapm_dev_group,
|
2018-03-09 20:46:27 +08:00
|
|
|
&soc_dev_group,
|
2015-01-31 03:16:37 +08:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
2009-11-04 05:56:13 +08:00
|
|
|
#ifdef CONFIG_DEBUG_FS
|
2019-12-11 12:32:20 +08:00
|
|
|
struct dentry *snd_soc_debugfs_root;
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_debugfs_root);
|
|
|
|
|
2014-08-19 21:51:18 +08:00
|
|
|
static void soc_init_component_debugfs(struct snd_soc_component *component)
|
2014-06-26 22:22:50 +08:00
|
|
|
{
|
2015-04-09 16:52:38 +08:00
|
|
|
if (!component->card->debugfs_card_root)
|
|
|
|
return;
|
|
|
|
|
2014-08-19 21:51:18 +08:00
|
|
|
if (component->debugfs_prefix) {
|
|
|
|
char *name;
|
2014-06-26 22:22:50 +08:00
|
|
|
|
2014-08-19 21:51:18 +08:00
|
|
|
name = kasprintf(GFP_KERNEL, "%s:%s",
|
|
|
|
component->debugfs_prefix, component->name);
|
|
|
|
if (name) {
|
|
|
|
component->debugfs_root = debugfs_create_dir(name,
|
|
|
|
component->card->debugfs_card_root);
|
|
|
|
kfree(name);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
component->debugfs_root = debugfs_create_dir(component->name,
|
|
|
|
component->card->debugfs_card_root);
|
|
|
|
}
|
2014-06-26 22:22:50 +08:00
|
|
|
|
2014-08-19 21:51:18 +08:00
|
|
|
snd_soc_dapm_debugfs_init(snd_soc_component_get_dapm(component),
|
|
|
|
component->debugfs_root);
|
2014-06-26 22:22:50 +08:00
|
|
|
}
|
|
|
|
|
2014-08-19 21:51:18 +08:00
|
|
|
static void soc_cleanup_component_debugfs(struct snd_soc_component *component)
|
2009-11-04 05:56:13 +08:00
|
|
|
{
|
2019-08-07 09:30:13 +08:00
|
|
|
if (!component->debugfs_root)
|
|
|
|
return;
|
2014-08-19 21:51:18 +08:00
|
|
|
debugfs_remove_recursive(component->debugfs_root);
|
2019-08-07 09:30:13 +08:00
|
|
|
component->debugfs_root = NULL;
|
2014-08-19 21:51:18 +08:00
|
|
|
}
|
2010-11-06 02:35:20 +08:00
|
|
|
|
2018-02-14 22:48:07 +08:00
|
|
|
static int dai_list_show(struct seq_file *m, void *v)
|
2010-09-16 01:19:07 +08:00
|
|
|
{
|
2014-03-10 00:41:47 +08:00
|
|
|
struct snd_soc_component *component;
|
2010-09-16 01:19:07 +08:00
|
|
|
struct snd_soc_dai *dai;
|
|
|
|
|
2015-03-08 02:34:03 +08:00
|
|
|
mutex_lock(&client_mutex);
|
|
|
|
|
2018-09-21 13:23:01 +08:00
|
|
|
for_each_component(component)
|
2018-09-21 13:23:17 +08:00
|
|
|
for_each_component_dais(component, dai)
|
2018-01-18 13:31:26 +08:00
|
|
|
seq_printf(m, "%s\n", dai->name);
|
2010-09-16 01:19:07 +08:00
|
|
|
|
2015-03-08 02:34:03 +08:00
|
|
|
mutex_unlock(&client_mutex);
|
|
|
|
|
2018-01-18 13:31:26 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2018-02-14 22:48:07 +08:00
|
|
|
DEFINE_SHOW_ATTRIBUTE(dai_list);
|
2010-09-16 01:19:07 +08:00
|
|
|
|
2018-05-08 11:21:00 +08:00
|
|
|
static int component_list_show(struct seq_file *m, void *v)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *component;
|
|
|
|
|
|
|
|
mutex_lock(&client_mutex);
|
|
|
|
|
2018-09-21 13:23:01 +08:00
|
|
|
for_each_component(component)
|
2018-05-08 11:21:00 +08:00
|
|
|
seq_printf(m, "%s\n", component->name);
|
|
|
|
|
|
|
|
mutex_unlock(&client_mutex);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
DEFINE_SHOW_ATTRIBUTE(component_list);
|
|
|
|
|
2010-11-06 02:35:19 +08:00
|
|
|
static void soc_init_card_debugfs(struct snd_soc_card *card)
|
|
|
|
{
|
|
|
|
card->debugfs_card_root = debugfs_create_dir(card->name,
|
2011-01-11 06:25:21 +08:00
|
|
|
snd_soc_debugfs_root);
|
2010-11-06 02:35:21 +08:00
|
|
|
|
2019-07-31 21:17:15 +08:00
|
|
|
debugfs_create_u32("dapm_pop_time", 0644, card->debugfs_card_root,
|
|
|
|
&card->pop_time);
|
2019-08-07 09:31:14 +08:00
|
|
|
|
|
|
|
snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root);
|
2010-11-06 02:35:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void soc_cleanup_card_debugfs(struct snd_soc_card *card)
|
|
|
|
{
|
|
|
|
debugfs_remove_recursive(card->debugfs_card_root);
|
2019-05-27 15:51:34 +08:00
|
|
|
card->debugfs_card_root = NULL;
|
2010-11-06 02:35:19 +08:00
|
|
|
}
|
|
|
|
|
2015-04-09 16:52:38 +08:00
|
|
|
static void snd_soc_debugfs_init(void)
|
|
|
|
{
|
|
|
|
snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL);
|
|
|
|
|
2019-07-31 21:17:15 +08:00
|
|
|
debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL,
|
|
|
|
&dai_list_fops);
|
2018-05-08 11:21:00 +08:00
|
|
|
|
2019-07-31 21:17:15 +08:00
|
|
|
debugfs_create_file("components", 0444, snd_soc_debugfs_root, NULL,
|
|
|
|
&component_list_fops);
|
2015-04-09 16:52:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void snd_soc_debugfs_exit(void)
|
|
|
|
{
|
|
|
|
debugfs_remove_recursive(snd_soc_debugfs_root);
|
|
|
|
}
|
|
|
|
|
2009-11-04 05:56:13 +08:00
|
|
|
#else
|
|
|
|
|
2021-10-18 10:05:34 +08:00
|
|
|
static inline void soc_init_component_debugfs(struct snd_soc_component *component) { }
|
|
|
|
static inline void soc_cleanup_component_debugfs(struct snd_soc_component *component) { }
|
|
|
|
static inline void soc_init_card_debugfs(struct snd_soc_card *card) { }
|
|
|
|
static inline void soc_cleanup_card_debugfs(struct snd_soc_card *card) { }
|
|
|
|
static inline void snd_soc_debugfs_init(void) { }
|
|
|
|
static inline void snd_soc_debugfs_exit(void) { }
|
2015-04-09 16:52:38 +08:00
|
|
|
|
2009-11-04 05:56:13 +08:00
|
|
|
#endif
|
|
|
|
|
ASoC: soc-core.c: enable multi Component
Current ASoC Card is using dlc (snd_soc_dai_link_component) to find
target DAI / Component to be used.
Current dlc has below 3 items to identify DAI / Component
(a) name for Component
(b) of_node for Component
(c) dai_name for DAI
(a) or (b) is used to identify target Component, and (c) is used
to identify DAI.
One of the biggest issue on it today is dlc needs "name matching"
for "dai_name" (c).
It was not a big deal when we were using platform_device, because we
could specify nessesary "dai_name" via its platform_data.
But we need to find DAI name pointer from whole registered datas and/or
each related driver somehow in case of DT, because we can't specify it.
Therefore, Card driver parses DT and assumes the DAI, and find its name
pointer. How to assume is based on each Component and/or Card.
Next biggest issue is Component node (a)/(b).
Basically, Component is registered when CPU/Codec driver was
probed() (X). Here, 1 Component is possible to have some DAIs.
int xxx_probe(struct platform_device *pdev)
{
...
(X) ret = devm_snd_soc_register_component(pdev->dev,
&component_driver,
&dai_driver, dai_driver_num);
...
}
The image of each data will be like below.
One note here is "driver" is included for later explanation.
+-driver------+
|+-component-+|
|| dai0||
|| dai1||
|| ...||
|+-----------+|
+-------------+
The point here is 1 driver has 1 Component, because basically driver
calles snd_soc_register_component() (= X) once.
Here is the very basic CPU/Codec connection image.
HW image SW image
+-- Board ------------+ +-card--------------------------+
|+-----+ +------+| |+-driver------+ +-driver------+|
|| CPU | <--> |CodecA|| ||+-component-+| |+-component-+||
|+-----+ +------+| ||| dai|<=>|dai |||
+---------------------+ ||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It will be very complex if it has multi DAIs.
Here is intuitive easy to understandable HW / SW example.
HW image SW image
+-- Board ---------------+ +-card--------------------------+
|+--------+ +------+| |+-driver------+ +-driver------+|
|| CPU ch0| <--> |CodecA|| ||+-component-+| |+-component-+||
|| | +------+| ||| ch0 dai|<=>|dai |||
|| | +------+| ||| || |+-----------+||
|| ch1| <--> |CodecB|| ||| || +-------------+|
|+--------+ +------+| ||| || +-driver------+|
+------------------------+ ||| || |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It will be handled as multi interface as "one Card".
card0,0: CPU-ch0 - CodecA
card0,1: CPU-ch1 - CodecB
^
But, here is the HW image example which will be more complex
+-- Basic Board ---------+
|+--------+ +------+|
|| CPU ch0| <--> |CodecA||
|| ch1| <-+ +------+|
|+--------+ | |
+-------------|----------+
+-- expansion board -----+
| | +------+|
| +->|CodecB||
| +------+|
+------------------------+
We intuitively think we want to handle these as "2 Sound Cards".
card0,0: CPU-ch0 - CodecA
card1,0: CPU-ch1 - CodecB
^
But below image which we can register today doesn't allow it,
because the same Component will be connected to both Card0/1,
but it will be rejected by (Z).
+-driver------+
|+-component-+|
+-card0-------------------------+
||| || +-driver------+|
||| || |+-component-+||
||| ch0 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
+-------------------------------+
|| ||
+-card1-------------------------+
||| || +-driver------+|
||| || |+-component-+||
||| ch1 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
+-------------------------------+
|+-----------+|
+-------------+
static int soc_probe_component()
{
...
if (component->card) {
(Z) if (component->card != card) {
dev_err(component->dev, ...);
return -ENODEV;
}
return 0;
}
...
}
So, how about to call snd_soc_register_component() (= X) multiple times
on probe() to avoid buplicated component->card limitation, to be like
below ?
+-driver------+
+-card0-------------------------+
|| | +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||+-----------+| |+-----------+||
|| | +-------------+|
+-------------------------------+
| |
+-card1-------------------------+
|| | +-driver------+|
||+-component-+| |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|| | +-------------+|
+-------------------------------+
+-------------+
Yes, looks good. But unfortunately it doesn't help us for now.
Let's see soc_component_to_node() and snd_soc_is_matching_component()
static struct device_node
*soc_component_to_node(struct snd_soc_component *component)
{
...
(A) of_node = component->dev->of_node;
...
}
static int snd_soc_is_matching_component(...)
{
...
(B) if (dlc->of_node && component_of_node != dlc->of_node)
...
}
dlc checkes "of_node" to identify target component (B),
but this "of_node" came from component->dev (A) which is added
by snd_soc_register_component() (X) on probe().
This means we can have different "component->card", but have same
"component->dev" in this case.
Even though we calls snd_soc_register_component() (= X) multiple times,
all Components have same driver's dev, thus it is impossible to
identified the Component.
And if it was impossible to identify Component, it is impossible to
identify DAI on current implementation.
So, how to handle above complex HW image today is 2 patterns.
One is handles it as "1 big sound card".
The SW image is like below.
SW image
+-card--------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
||| || +-driver------+|
||| || |+-component-+||
||| ch1 dai|<->|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
But the problem is not intuitive.
We want to handle it as "2 Cards".
2nd pattern is like below.
SW image
+-card0-------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
+-card1-------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It handles as "2 Cards", but CPU part needs to be probed as 2 drivers.
It is also not intuitive.
To solve this issue, we need to have multi Component support.
In current implementation, we need to identify Component first
to identify DAI, and it is using name matching to identify DAI.
But how about to be enable to directly identify DAI by unique way
instead of name matching ? In such case, we can directly identify DAI,
then it can identify Component from DAI.
For example Simple-Card / Audio-Graph-Card case, it is specifying DAI
via its node.
Simple-Card
sound-dai = <&cpu-sound>;
Audio-Graph-Card
dais = <&cpu-sound>;
If each CPU/Codec driver keeps this property when probing,
we can identify DAI directly from Card.
Being able to identify DAI directly means being able to identify its
Component as well even though Component has same dev (= B).
This patch adds new "dai_node" for it.
To keeping compatibility, it checks "dai_node" first if it has,
otherwise, use existing method (name matching).
Link: https://lore.kernel.org/r/87fskz5yrr.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87fs5wo94v.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2023-07-10 09:20:00 +08:00
|
|
|
static int snd_soc_is_match_dai_args(struct of_phandle_args *args1,
|
|
|
|
struct of_phandle_args *args2)
|
|
|
|
{
|
|
|
|
if (!args1 || !args2)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (args1->np != args2->np)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < args1->args_count; i++)
|
|
|
|
if (args1->args[i] != args2->args[i])
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2023-06-21 10:18:17 +08:00
|
|
|
static inline int snd_soc_dlc_component_is_empty(struct snd_soc_dai_link_component *dlc)
|
|
|
|
{
|
ASoC: soc-core.c: enable multi Component
Current ASoC Card is using dlc (snd_soc_dai_link_component) to find
target DAI / Component to be used.
Current dlc has below 3 items to identify DAI / Component
(a) name for Component
(b) of_node for Component
(c) dai_name for DAI
(a) or (b) is used to identify target Component, and (c) is used
to identify DAI.
One of the biggest issue on it today is dlc needs "name matching"
for "dai_name" (c).
It was not a big deal when we were using platform_device, because we
could specify nessesary "dai_name" via its platform_data.
But we need to find DAI name pointer from whole registered datas and/or
each related driver somehow in case of DT, because we can't specify it.
Therefore, Card driver parses DT and assumes the DAI, and find its name
pointer. How to assume is based on each Component and/or Card.
Next biggest issue is Component node (a)/(b).
Basically, Component is registered when CPU/Codec driver was
probed() (X). Here, 1 Component is possible to have some DAIs.
int xxx_probe(struct platform_device *pdev)
{
...
(X) ret = devm_snd_soc_register_component(pdev->dev,
&component_driver,
&dai_driver, dai_driver_num);
...
}
The image of each data will be like below.
One note here is "driver" is included for later explanation.
+-driver------+
|+-component-+|
|| dai0||
|| dai1||
|| ...||
|+-----------+|
+-------------+
The point here is 1 driver has 1 Component, because basically driver
calles snd_soc_register_component() (= X) once.
Here is the very basic CPU/Codec connection image.
HW image SW image
+-- Board ------------+ +-card--------------------------+
|+-----+ +------+| |+-driver------+ +-driver------+|
|| CPU | <--> |CodecA|| ||+-component-+| |+-component-+||
|+-----+ +------+| ||| dai|<=>|dai |||
+---------------------+ ||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It will be very complex if it has multi DAIs.
Here is intuitive easy to understandable HW / SW example.
HW image SW image
+-- Board ---------------+ +-card--------------------------+
|+--------+ +------+| |+-driver------+ +-driver------+|
|| CPU ch0| <--> |CodecA|| ||+-component-+| |+-component-+||
|| | +------+| ||| ch0 dai|<=>|dai |||
|| | +------+| ||| || |+-----------+||
|| ch1| <--> |CodecB|| ||| || +-------------+|
|+--------+ +------+| ||| || +-driver------+|
+------------------------+ ||| || |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It will be handled as multi interface as "one Card".
card0,0: CPU-ch0 - CodecA
card0,1: CPU-ch1 - CodecB
^
But, here is the HW image example which will be more complex
+-- Basic Board ---------+
|+--------+ +------+|
|| CPU ch0| <--> |CodecA||
|| ch1| <-+ +------+|
|+--------+ | |
+-------------|----------+
+-- expansion board -----+
| | +------+|
| +->|CodecB||
| +------+|
+------------------------+
We intuitively think we want to handle these as "2 Sound Cards".
card0,0: CPU-ch0 - CodecA
card1,0: CPU-ch1 - CodecB
^
But below image which we can register today doesn't allow it,
because the same Component will be connected to both Card0/1,
but it will be rejected by (Z).
+-driver------+
|+-component-+|
+-card0-------------------------+
||| || +-driver------+|
||| || |+-component-+||
||| ch0 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
+-------------------------------+
|| ||
+-card1-------------------------+
||| || +-driver------+|
||| || |+-component-+||
||| ch1 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
+-------------------------------+
|+-----------+|
+-------------+
static int soc_probe_component()
{
...
if (component->card) {
(Z) if (component->card != card) {
dev_err(component->dev, ...);
return -ENODEV;
}
return 0;
}
...
}
So, how about to call snd_soc_register_component() (= X) multiple times
on probe() to avoid buplicated component->card limitation, to be like
below ?
+-driver------+
+-card0-------------------------+
|| | +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||+-----------+| |+-----------+||
|| | +-------------+|
+-------------------------------+
| |
+-card1-------------------------+
|| | +-driver------+|
||+-component-+| |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|| | +-------------+|
+-------------------------------+
+-------------+
Yes, looks good. But unfortunately it doesn't help us for now.
Let's see soc_component_to_node() and snd_soc_is_matching_component()
static struct device_node
*soc_component_to_node(struct snd_soc_component *component)
{
...
(A) of_node = component->dev->of_node;
...
}
static int snd_soc_is_matching_component(...)
{
...
(B) if (dlc->of_node && component_of_node != dlc->of_node)
...
}
dlc checkes "of_node" to identify target component (B),
but this "of_node" came from component->dev (A) which is added
by snd_soc_register_component() (X) on probe().
This means we can have different "component->card", but have same
"component->dev" in this case.
Even though we calls snd_soc_register_component() (= X) multiple times,
all Components have same driver's dev, thus it is impossible to
identified the Component.
And if it was impossible to identify Component, it is impossible to
identify DAI on current implementation.
So, how to handle above complex HW image today is 2 patterns.
One is handles it as "1 big sound card".
The SW image is like below.
SW image
+-card--------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
||| || +-driver------+|
||| || |+-component-+||
||| ch1 dai|<->|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
But the problem is not intuitive.
We want to handle it as "2 Cards".
2nd pattern is like below.
SW image
+-card0-------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
+-card1-------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It handles as "2 Cards", but CPU part needs to be probed as 2 drivers.
It is also not intuitive.
To solve this issue, we need to have multi Component support.
In current implementation, we need to identify Component first
to identify DAI, and it is using name matching to identify DAI.
But how about to be enable to directly identify DAI by unique way
instead of name matching ? In such case, we can directly identify DAI,
then it can identify Component from DAI.
For example Simple-Card / Audio-Graph-Card case, it is specifying DAI
via its node.
Simple-Card
sound-dai = <&cpu-sound>;
Audio-Graph-Card
dais = <&cpu-sound>;
If each CPU/Codec driver keeps this property when probing,
we can identify DAI directly from Card.
Being able to identify DAI directly means being able to identify its
Component as well even though Component has same dev (= B).
This patch adds new "dai_node" for it.
To keeping compatibility, it checks "dai_node" first if it has,
otherwise, use existing method (name matching).
Link: https://lore.kernel.org/r/87fskz5yrr.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87fs5wo94v.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2023-07-10 09:20:00 +08:00
|
|
|
return !(dlc->dai_args || dlc->name || dlc->of_node);
|
2023-06-21 10:18:17 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline int snd_soc_dlc_component_is_invalid(struct snd_soc_dai_link_component *dlc)
|
|
|
|
{
|
|
|
|
return (dlc->name && dlc->of_node);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline int snd_soc_dlc_dai_is_empty(struct snd_soc_dai_link_component *dlc)
|
|
|
|
{
|
ASoC: soc-core.c: enable multi Component
Current ASoC Card is using dlc (snd_soc_dai_link_component) to find
target DAI / Component to be used.
Current dlc has below 3 items to identify DAI / Component
(a) name for Component
(b) of_node for Component
(c) dai_name for DAI
(a) or (b) is used to identify target Component, and (c) is used
to identify DAI.
One of the biggest issue on it today is dlc needs "name matching"
for "dai_name" (c).
It was not a big deal when we were using platform_device, because we
could specify nessesary "dai_name" via its platform_data.
But we need to find DAI name pointer from whole registered datas and/or
each related driver somehow in case of DT, because we can't specify it.
Therefore, Card driver parses DT and assumes the DAI, and find its name
pointer. How to assume is based on each Component and/or Card.
Next biggest issue is Component node (a)/(b).
Basically, Component is registered when CPU/Codec driver was
probed() (X). Here, 1 Component is possible to have some DAIs.
int xxx_probe(struct platform_device *pdev)
{
...
(X) ret = devm_snd_soc_register_component(pdev->dev,
&component_driver,
&dai_driver, dai_driver_num);
...
}
The image of each data will be like below.
One note here is "driver" is included for later explanation.
+-driver------+
|+-component-+|
|| dai0||
|| dai1||
|| ...||
|+-----------+|
+-------------+
The point here is 1 driver has 1 Component, because basically driver
calles snd_soc_register_component() (= X) once.
Here is the very basic CPU/Codec connection image.
HW image SW image
+-- Board ------------+ +-card--------------------------+
|+-----+ +------+| |+-driver------+ +-driver------+|
|| CPU | <--> |CodecA|| ||+-component-+| |+-component-+||
|+-----+ +------+| ||| dai|<=>|dai |||
+---------------------+ ||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It will be very complex if it has multi DAIs.
Here is intuitive easy to understandable HW / SW example.
HW image SW image
+-- Board ---------------+ +-card--------------------------+
|+--------+ +------+| |+-driver------+ +-driver------+|
|| CPU ch0| <--> |CodecA|| ||+-component-+| |+-component-+||
|| | +------+| ||| ch0 dai|<=>|dai |||
|| | +------+| ||| || |+-----------+||
|| ch1| <--> |CodecB|| ||| || +-------------+|
|+--------+ +------+| ||| || +-driver------+|
+------------------------+ ||| || |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It will be handled as multi interface as "one Card".
card0,0: CPU-ch0 - CodecA
card0,1: CPU-ch1 - CodecB
^
But, here is the HW image example which will be more complex
+-- Basic Board ---------+
|+--------+ +------+|
|| CPU ch0| <--> |CodecA||
|| ch1| <-+ +------+|
|+--------+ | |
+-------------|----------+
+-- expansion board -----+
| | +------+|
| +->|CodecB||
| +------+|
+------------------------+
We intuitively think we want to handle these as "2 Sound Cards".
card0,0: CPU-ch0 - CodecA
card1,0: CPU-ch1 - CodecB
^
But below image which we can register today doesn't allow it,
because the same Component will be connected to both Card0/1,
but it will be rejected by (Z).
+-driver------+
|+-component-+|
+-card0-------------------------+
||| || +-driver------+|
||| || |+-component-+||
||| ch0 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
+-------------------------------+
|| ||
+-card1-------------------------+
||| || +-driver------+|
||| || |+-component-+||
||| ch1 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
+-------------------------------+
|+-----------+|
+-------------+
static int soc_probe_component()
{
...
if (component->card) {
(Z) if (component->card != card) {
dev_err(component->dev, ...);
return -ENODEV;
}
return 0;
}
...
}
So, how about to call snd_soc_register_component() (= X) multiple times
on probe() to avoid buplicated component->card limitation, to be like
below ?
+-driver------+
+-card0-------------------------+
|| | +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||+-----------+| |+-----------+||
|| | +-------------+|
+-------------------------------+
| |
+-card1-------------------------+
|| | +-driver------+|
||+-component-+| |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|| | +-------------+|
+-------------------------------+
+-------------+
Yes, looks good. But unfortunately it doesn't help us for now.
Let's see soc_component_to_node() and snd_soc_is_matching_component()
static struct device_node
*soc_component_to_node(struct snd_soc_component *component)
{
...
(A) of_node = component->dev->of_node;
...
}
static int snd_soc_is_matching_component(...)
{
...
(B) if (dlc->of_node && component_of_node != dlc->of_node)
...
}
dlc checkes "of_node" to identify target component (B),
but this "of_node" came from component->dev (A) which is added
by snd_soc_register_component() (X) on probe().
This means we can have different "component->card", but have same
"component->dev" in this case.
Even though we calls snd_soc_register_component() (= X) multiple times,
all Components have same driver's dev, thus it is impossible to
identified the Component.
And if it was impossible to identify Component, it is impossible to
identify DAI on current implementation.
So, how to handle above complex HW image today is 2 patterns.
One is handles it as "1 big sound card".
The SW image is like below.
SW image
+-card--------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
||| || +-driver------+|
||| || |+-component-+||
||| ch1 dai|<->|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
But the problem is not intuitive.
We want to handle it as "2 Cards".
2nd pattern is like below.
SW image
+-card0-------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
+-card1-------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It handles as "2 Cards", but CPU part needs to be probed as 2 drivers.
It is also not intuitive.
To solve this issue, we need to have multi Component support.
In current implementation, we need to identify Component first
to identify DAI, and it is using name matching to identify DAI.
But how about to be enable to directly identify DAI by unique way
instead of name matching ? In such case, we can directly identify DAI,
then it can identify Component from DAI.
For example Simple-Card / Audio-Graph-Card case, it is specifying DAI
via its node.
Simple-Card
sound-dai = <&cpu-sound>;
Audio-Graph-Card
dais = <&cpu-sound>;
If each CPU/Codec driver keeps this property when probing,
we can identify DAI directly from Card.
Being able to identify DAI directly means being able to identify its
Component as well even though Component has same dev (= B).
This patch adds new "dai_node" for it.
To keeping compatibility, it checks "dai_node" first if it has,
otherwise, use existing method (name matching).
Link: https://lore.kernel.org/r/87fskz5yrr.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87fs5wo94v.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2023-07-10 09:20:00 +08:00
|
|
|
return !(dlc->dai_args || dlc->dai_name);
|
2023-06-21 10:18:17 +08:00
|
|
|
}
|
|
|
|
|
2023-07-10 09:19:53 +08:00
|
|
|
static int snd_soc_is_matching_dai(const struct snd_soc_dai_link_component *dlc,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
|
|
|
if (!dlc)
|
|
|
|
return 0;
|
|
|
|
|
ASoC: soc-core.c: enable multi Component
Current ASoC Card is using dlc (snd_soc_dai_link_component) to find
target DAI / Component to be used.
Current dlc has below 3 items to identify DAI / Component
(a) name for Component
(b) of_node for Component
(c) dai_name for DAI
(a) or (b) is used to identify target Component, and (c) is used
to identify DAI.
One of the biggest issue on it today is dlc needs "name matching"
for "dai_name" (c).
It was not a big deal when we were using platform_device, because we
could specify nessesary "dai_name" via its platform_data.
But we need to find DAI name pointer from whole registered datas and/or
each related driver somehow in case of DT, because we can't specify it.
Therefore, Card driver parses DT and assumes the DAI, and find its name
pointer. How to assume is based on each Component and/or Card.
Next biggest issue is Component node (a)/(b).
Basically, Component is registered when CPU/Codec driver was
probed() (X). Here, 1 Component is possible to have some DAIs.
int xxx_probe(struct platform_device *pdev)
{
...
(X) ret = devm_snd_soc_register_component(pdev->dev,
&component_driver,
&dai_driver, dai_driver_num);
...
}
The image of each data will be like below.
One note here is "driver" is included for later explanation.
+-driver------+
|+-component-+|
|| dai0||
|| dai1||
|| ...||
|+-----------+|
+-------------+
The point here is 1 driver has 1 Component, because basically driver
calles snd_soc_register_component() (= X) once.
Here is the very basic CPU/Codec connection image.
HW image SW image
+-- Board ------------+ +-card--------------------------+
|+-----+ +------+| |+-driver------+ +-driver------+|
|| CPU | <--> |CodecA|| ||+-component-+| |+-component-+||
|+-----+ +------+| ||| dai|<=>|dai |||
+---------------------+ ||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It will be very complex if it has multi DAIs.
Here is intuitive easy to understandable HW / SW example.
HW image SW image
+-- Board ---------------+ +-card--------------------------+
|+--------+ +------+| |+-driver------+ +-driver------+|
|| CPU ch0| <--> |CodecA|| ||+-component-+| |+-component-+||
|| | +------+| ||| ch0 dai|<=>|dai |||
|| | +------+| ||| || |+-----------+||
|| ch1| <--> |CodecB|| ||| || +-------------+|
|+--------+ +------+| ||| || +-driver------+|
+------------------------+ ||| || |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It will be handled as multi interface as "one Card".
card0,0: CPU-ch0 - CodecA
card0,1: CPU-ch1 - CodecB
^
But, here is the HW image example which will be more complex
+-- Basic Board ---------+
|+--------+ +------+|
|| CPU ch0| <--> |CodecA||
|| ch1| <-+ +------+|
|+--------+ | |
+-------------|----------+
+-- expansion board -----+
| | +------+|
| +->|CodecB||
| +------+|
+------------------------+
We intuitively think we want to handle these as "2 Sound Cards".
card0,0: CPU-ch0 - CodecA
card1,0: CPU-ch1 - CodecB
^
But below image which we can register today doesn't allow it,
because the same Component will be connected to both Card0/1,
but it will be rejected by (Z).
+-driver------+
|+-component-+|
+-card0-------------------------+
||| || +-driver------+|
||| || |+-component-+||
||| ch0 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
+-------------------------------+
|| ||
+-card1-------------------------+
||| || +-driver------+|
||| || |+-component-+||
||| ch1 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
+-------------------------------+
|+-----------+|
+-------------+
static int soc_probe_component()
{
...
if (component->card) {
(Z) if (component->card != card) {
dev_err(component->dev, ...);
return -ENODEV;
}
return 0;
}
...
}
So, how about to call snd_soc_register_component() (= X) multiple times
on probe() to avoid buplicated component->card limitation, to be like
below ?
+-driver------+
+-card0-------------------------+
|| | +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||+-----------+| |+-----------+||
|| | +-------------+|
+-------------------------------+
| |
+-card1-------------------------+
|| | +-driver------+|
||+-component-+| |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|| | +-------------+|
+-------------------------------+
+-------------+
Yes, looks good. But unfortunately it doesn't help us for now.
Let's see soc_component_to_node() and snd_soc_is_matching_component()
static struct device_node
*soc_component_to_node(struct snd_soc_component *component)
{
...
(A) of_node = component->dev->of_node;
...
}
static int snd_soc_is_matching_component(...)
{
...
(B) if (dlc->of_node && component_of_node != dlc->of_node)
...
}
dlc checkes "of_node" to identify target component (B),
but this "of_node" came from component->dev (A) which is added
by snd_soc_register_component() (X) on probe().
This means we can have different "component->card", but have same
"component->dev" in this case.
Even though we calls snd_soc_register_component() (= X) multiple times,
all Components have same driver's dev, thus it is impossible to
identified the Component.
And if it was impossible to identify Component, it is impossible to
identify DAI on current implementation.
So, how to handle above complex HW image today is 2 patterns.
One is handles it as "1 big sound card".
The SW image is like below.
SW image
+-card--------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
||| || +-driver------+|
||| || |+-component-+||
||| ch1 dai|<->|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
But the problem is not intuitive.
We want to handle it as "2 Cards".
2nd pattern is like below.
SW image
+-card0-------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
+-card1-------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It handles as "2 Cards", but CPU part needs to be probed as 2 drivers.
It is also not intuitive.
To solve this issue, we need to have multi Component support.
In current implementation, we need to identify Component first
to identify DAI, and it is using name matching to identify DAI.
But how about to be enable to directly identify DAI by unique way
instead of name matching ? In such case, we can directly identify DAI,
then it can identify Component from DAI.
For example Simple-Card / Audio-Graph-Card case, it is specifying DAI
via its node.
Simple-Card
sound-dai = <&cpu-sound>;
Audio-Graph-Card
dais = <&cpu-sound>;
If each CPU/Codec driver keeps this property when probing,
we can identify DAI directly from Card.
Being able to identify DAI directly means being able to identify its
Component as well even though Component has same dev (= B).
This patch adds new "dai_node" for it.
To keeping compatibility, it checks "dai_node" first if it has,
otherwise, use existing method (name matching).
Link: https://lore.kernel.org/r/87fskz5yrr.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87fs5wo94v.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2023-07-10 09:20:00 +08:00
|
|
|
if (dlc->dai_args)
|
|
|
|
return snd_soc_is_match_dai_args(dai->driver->dai_args, dlc->dai_args);
|
|
|
|
|
2023-07-10 09:19:53 +08:00
|
|
|
if (!dlc->dai_name)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
/* see snd_soc_dai_name_get() */
|
|
|
|
|
|
|
|
if (strcmp(dlc->dai_name, dai->name) == 0)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (dai->driver->name &&
|
|
|
|
strcmp(dai->driver->name, dlc->dai_name) == 0)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (dai->component->name &&
|
|
|
|
strcmp(dlc->dai_name, dai->component->name) == 0)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *snd_soc_dai_name_get(struct snd_soc_dai *dai)
|
|
|
|
{
|
|
|
|
/* see snd_soc_is_matching_dai() */
|
|
|
|
if (dai->name)
|
|
|
|
return dai->name;
|
|
|
|
|
|
|
|
if (dai->driver->name)
|
|
|
|
return dai->driver->name;
|
|
|
|
|
|
|
|
if (dai->component->name)
|
|
|
|
return dai->component->name;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_dai_name_get);
|
|
|
|
|
2020-01-10 10:35:54 +08:00
|
|
|
static int snd_soc_rtd_add_component(struct snd_soc_pcm_runtime *rtd,
|
|
|
|
struct snd_soc_component *component)
|
2017-08-08 14:17:47 +08:00
|
|
|
{
|
ASoC: soc-core: add for_each_rtd_components() and replace
ALSA SoC has for_each_rtdcom() which is link list for
rtd-component which is called as rtdcom. The relationship image is like below
rtdcom rtdcom rtdcom
component component component
rtd->component_list -> list -> list -> list ...
Here, the pointer get via normal link list is rtdcom,
Thus, current for_each loop is like below, and need to get
component via rtdcom->component
for_each_rtdcom(rtd, rtdcom) {
component = rtdcom->component;
...
}
but usually, user want to get pointer from for_each_xxx is component
directly, like below.
for_each_rtd_component(rtd, rtdcom, component) {
...
}
This patch expands list_for_each_entry manually, and enable to get
component directly from for_each macro.
Because of it, the macro becoming difficult to read,
but macro itself becoming useful.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/878spm64m4.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-10-15 11:59:31 +08:00
|
|
|
struct snd_soc_component *comp;
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
int i;
|
2017-08-08 14:17:47 +08:00
|
|
|
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
for_each_rtd_components(rtd, i, comp) {
|
2017-08-08 14:17:47 +08:00
|
|
|
/* already connected */
|
ASoC: soc-core: add for_each_rtd_components() and replace
ALSA SoC has for_each_rtdcom() which is link list for
rtd-component which is called as rtdcom. The relationship image is like below
rtdcom rtdcom rtdcom
component component component
rtd->component_list -> list -> list -> list ...
Here, the pointer get via normal link list is rtdcom,
Thus, current for_each loop is like below, and need to get
component via rtdcom->component
for_each_rtdcom(rtd, rtdcom) {
component = rtdcom->component;
...
}
but usually, user want to get pointer from for_each_xxx is component
directly, like below.
for_each_rtd_component(rtd, rtdcom, component) {
...
}
This patch expands list_for_each_entry manually, and enable to get
component directly from for_each macro.
Because of it, the macro becoming difficult to read,
but macro itself becoming useful.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/878spm64m4.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-10-15 11:59:31 +08:00
|
|
|
if (comp == component)
|
2017-08-08 14:17:47 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
/* see for_each_rtd_components */
|
|
|
|
rtd->components[rtd->num_components] = component;
|
|
|
|
rtd->num_components++;
|
2017-08-08 14:17:47 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct snd_soc_component *snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd,
|
|
|
|
const char *driver_name)
|
|
|
|
{
|
ASoC: soc-core: add for_each_rtd_components() and replace
ALSA SoC has for_each_rtdcom() which is link list for
rtd-component which is called as rtdcom. The relationship image is like below
rtdcom rtdcom rtdcom
component component component
rtd->component_list -> list -> list -> list ...
Here, the pointer get via normal link list is rtdcom,
Thus, current for_each loop is like below, and need to get
component via rtdcom->component
for_each_rtdcom(rtd, rtdcom) {
component = rtdcom->component;
...
}
but usually, user want to get pointer from for_each_xxx is component
directly, like below.
for_each_rtd_component(rtd, rtdcom, component) {
...
}
This patch expands list_for_each_entry manually, and enable to get
component directly from for_each macro.
Because of it, the macro becoming difficult to read,
but macro itself becoming useful.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/878spm64m4.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-10-15 11:59:31 +08:00
|
|
|
struct snd_soc_component *component;
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
int i;
|
2017-08-08 14:17:47 +08:00
|
|
|
|
2018-01-23 08:41:24 +08:00
|
|
|
if (!driver_name)
|
|
|
|
return NULL;
|
|
|
|
|
2019-08-20 13:05:02 +08:00
|
|
|
/*
|
|
|
|
* NOTE
|
|
|
|
*
|
|
|
|
* snd_soc_rtdcom_lookup() will find component from rtd by using
|
|
|
|
* specified driver name.
|
|
|
|
* But, if many components which have same driver name are connected
|
|
|
|
* to 1 rtd, this function will return 1st found component.
|
|
|
|
*/
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
for_each_rtd_components(rtd, i, component) {
|
ASoC: soc-core: add for_each_rtd_components() and replace
ALSA SoC has for_each_rtdcom() which is link list for
rtd-component which is called as rtdcom. The relationship image is like below
rtdcom rtdcom rtdcom
component component component
rtd->component_list -> list -> list -> list ...
Here, the pointer get via normal link list is rtdcom,
Thus, current for_each loop is like below, and need to get
component via rtdcom->component
for_each_rtdcom(rtd, rtdcom) {
component = rtdcom->component;
...
}
but usually, user want to get pointer from for_each_xxx is component
directly, like below.
for_each_rtd_component(rtd, rtdcom, component) {
...
}
This patch expands list_for_each_entry manually, and enable to get
component directly from for_each macro.
Because of it, the macro becoming difficult to read,
but macro itself becoming useful.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/878spm64m4.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-10-15 11:59:31 +08:00
|
|
|
const char *component_name = component->driver->name;
|
2018-01-23 08:41:24 +08:00
|
|
|
|
|
|
|
if (!component_name)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if ((component_name == driver_name) ||
|
|
|
|
strcmp(component_name, driver_name) == 0)
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
return component;
|
2017-08-08 14:17:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
2018-01-18 09:13:54 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_rtdcom_lookup);
|
2017-08-08 14:17:47 +08:00
|
|
|
|
2020-06-12 15:37:48 +08:00
|
|
|
struct snd_soc_component
|
2019-11-06 15:05:05 +08:00
|
|
|
*snd_soc_lookup_component_nolocked(struct device *dev, const char *driver_name)
|
2019-11-05 14:46:30 +08:00
|
|
|
{
|
|
|
|
struct snd_soc_component *component;
|
2019-11-05 14:46:35 +08:00
|
|
|
struct snd_soc_component *found_component;
|
2019-11-05 14:46:30 +08:00
|
|
|
|
2019-11-05 14:46:35 +08:00
|
|
|
found_component = NULL;
|
2019-11-05 14:46:30 +08:00
|
|
|
for_each_component(component) {
|
2019-11-05 14:46:35 +08:00
|
|
|
if ((dev == component->dev) &&
|
|
|
|
(!driver_name ||
|
|
|
|
(driver_name == component->driver->name) ||
|
|
|
|
(strcmp(component->driver->name, driver_name) == 0))) {
|
|
|
|
found_component = component;
|
|
|
|
break;
|
|
|
|
}
|
2019-11-05 14:46:30 +08:00
|
|
|
}
|
|
|
|
|
2019-11-05 14:46:35 +08:00
|
|
|
return found_component;
|
2019-11-05 14:46:30 +08:00
|
|
|
}
|
2020-06-12 15:37:48 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_lookup_component_nolocked);
|
2019-11-06 15:05:05 +08:00
|
|
|
|
|
|
|
struct snd_soc_component *snd_soc_lookup_component(struct device *dev,
|
|
|
|
const char *driver_name)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *component;
|
|
|
|
|
|
|
|
mutex_lock(&client_mutex);
|
|
|
|
component = snd_soc_lookup_component_nolocked(dev, driver_name);
|
|
|
|
mutex_unlock(&client_mutex);
|
|
|
|
|
|
|
|
return component;
|
|
|
|
}
|
2019-11-05 14:46:30 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_lookup_component);
|
|
|
|
|
2019-12-10 08:34:01 +08:00
|
|
|
struct snd_soc_pcm_runtime
|
|
|
|
*snd_soc_get_pcm_runtime(struct snd_soc_card *card,
|
2019-12-10 08:34:08 +08:00
|
|
|
struct snd_soc_dai_link *dai_link)
|
2019-12-10 08:34:01 +08:00
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
|
|
|
|
|
|
|
for_each_card_rtds(card, rtd) {
|
2019-12-10 08:34:08 +08:00
|
|
|
if (rtd->dai_link == dai_link)
|
2019-12-10 08:34:01 +08:00
|
|
|
return rtd;
|
|
|
|
}
|
2019-12-10 08:34:08 +08:00
|
|
|
dev_dbg(card->dev, "ASoC: failed to find rtd %s\n", dai_link->name);
|
2019-12-10 08:34:01 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_get_pcm_runtime);
|
|
|
|
|
2020-01-10 10:36:17 +08:00
|
|
|
/*
|
|
|
|
* Power down the audio subsystem pmdown_time msecs after close is called.
|
|
|
|
* This is to ensure there are no pops or clicks in between any music tracks
|
|
|
|
* due to DAPM power cycling.
|
|
|
|
*/
|
|
|
|
void snd_soc_close_delayed_work(struct snd_soc_pcm_runtime *rtd)
|
|
|
|
{
|
2020-03-30 09:47:37 +08:00
|
|
|
struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
|
2020-02-17 16:28:15 +08:00
|
|
|
int playback = SNDRV_PCM_STREAM_PLAYBACK;
|
2020-01-10 10:36:17 +08:00
|
|
|
|
2023-04-06 08:16:10 +08:00
|
|
|
snd_soc_dpcm_mutex_lock(rtd);
|
2020-01-10 10:36:17 +08:00
|
|
|
|
|
|
|
dev_dbg(rtd->dev,
|
|
|
|
"ASoC: pop wq checking: %s status: %s waiting: %s\n",
|
|
|
|
codec_dai->driver->playback.stream_name,
|
2020-05-15 08:46:51 +08:00
|
|
|
snd_soc_dai_stream_active(codec_dai, playback) ?
|
|
|
|
"active" : "inactive",
|
2020-01-10 10:36:17 +08:00
|
|
|
rtd->pop_wait ? "yes" : "no");
|
|
|
|
|
|
|
|
/* are we waiting on this codec DAI stream */
|
|
|
|
if (rtd->pop_wait == 1) {
|
|
|
|
rtd->pop_wait = 0;
|
2020-02-17 16:28:15 +08:00
|
|
|
snd_soc_dapm_stream_event(rtd, playback,
|
2020-01-10 10:36:17 +08:00
|
|
|
SND_SOC_DAPM_STREAM_STOP);
|
|
|
|
}
|
|
|
|
|
2023-04-06 08:16:10 +08:00
|
|
|
snd_soc_dpcm_mutex_unlock(rtd);
|
2020-01-10 10:36:17 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_close_delayed_work);
|
|
|
|
|
2019-09-12 12:40:08 +08:00
|
|
|
static void soc_release_rtd_dev(struct device *dev)
|
|
|
|
{
|
|
|
|
/* "dev" means "rtd->dev" */
|
|
|
|
kfree(dev);
|
|
|
|
}
|
|
|
|
|
2019-09-12 12:38:22 +08:00
|
|
|
static void soc_free_pcm_runtime(struct snd_soc_pcm_runtime *rtd)
|
|
|
|
{
|
2019-09-12 12:40:08 +08:00
|
|
|
if (!rtd)
|
|
|
|
return;
|
|
|
|
|
2019-09-12 12:38:50 +08:00
|
|
|
list_del(&rtd->list);
|
2019-09-12 12:41:01 +08:00
|
|
|
|
2019-11-28 09:13:58 +08:00
|
|
|
if (delayed_work_pending(&rtd->delayed_work))
|
|
|
|
flush_delayed_work(&rtd->delayed_work);
|
ASoC: soc-pcm: remove soc_pcm_private_free()
soc-topology adds extra dai_link by using snd_soc_add_dai_link(),
and removes it by snd_soc_romove_dai_link().
This snd_soc_add/remove_dai_link() and/or its related
functions are unbalanced before, and now, these are balance-uped.
But, it finds the random operation issue, and it is reported by
Pierre-Louis.
When card was released, topology will call snd_soc_remove_dai_link()
via (A).
static void soc_cleanup_card_resources(struct snd_soc_card *card)
{
struct snd_soc_dai_link *link, *_link;
/* This should be called before snd_card_free() */
(A) soc_remove_link_components(card);
/* free the ALSA card at first; this syncs with pending operations */
if (card->snd_card) {
(B) snd_card_free(card->snd_card);
card->snd_card = NULL;
}
/* remove and free each DAI */
(X) soc_remove_link_dais(card);
for_each_card_links_safe(card, link, _link)
(C) snd_soc_remove_dai_link(card, link);
...
}
At (A), topology calls snd_soc_remove_dai_link().
Then topology rtd, and its related all data are freed.
Next, (B) is called, and then, pcm->private_free = soc_pcm_private_free()
is called.
static void soc_pcm_private_free(struct snd_pcm *pcm)
{
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
/* need to sync the delayed work before releasing resources */
flush_delayed_work(&rtd->delayed_work);
snd_soc_pcm_component_free(rtd);
}
Here, it gets rtd via pcm->private_data.
But, topology related rtd are already freed at (A).
Normal sound card has no damage, becase it frees rtd at (C).
These are finalizing rtd related data.
Thus, these should be called when rtd was freed, not sound card
was freed. It is very natural and understandable.
In other words, pcm->private_free = soc_pcm_private_free()
is no longer needed.
Extra issue is that there is zero chance to call
soc_remove_dai() for topology related dai at (X).
Because (A) removes rtd connection from card too, and,
(X) is based on card connected rtd.
This means, (X) need to be called before (C) (= for normal sound)
and (A) (= for topology).
Now, I want to focus this patch which is the reason why
snd_card_free() = (B) is located there.
commit 4efda5f2130da033aeedc5b3205569893b910de2
("ASoC: Fix use-after-free at card unregistration")
Original snd_card_free() was called last of this function.
But moved to top to avoid use-after-free issue.
The issue was happen at soc_pcm_free() which was pcm->private_free,
today it is updated/renamed to soc_pcm_private_free().
In other words, (B) need to be called before (C) (= for normal sound)
and (A) (= for topology), because it needs (not yet freed) rtd.
But, (A) need to be called before (B),
because it needs card->snd_card pointer.
If we call flush_delayed_work() and snd_soc_pcm_component_free()
(= same as soc_pcm_private_free()) when rtd was freed (= (C), (A)),
there is no reason to call snd_card_free() at top of this function.
It can be called end of this function, again.
But, in such case, it will likely break unbind again, as Takashi-san
reported. When unbind is performed in a busy state, the code may
release still-in-use resources.
At least we need to call snd_card_disconnect_sync() at the first place.
The final code will be...
static void soc_cleanup_card_resources(struct snd_soc_card *card)
{
struct snd_soc_dai_link *link, *_link;
if (card->snd_card)
(Z) snd_card_disconnect_sync(card->snd_card);
(X) soc_remove_link_dais(card);
(A) soc_remove_link_components(card);
for_each_card_links_safe(card, link, _link)
(C) snd_soc_remove_dai_link(card, link);
...
if (card->snd_card) {
(B) snd_card_free(card->snd_card);
card->snd_card = NULL;
}
}
To avoid release still-in-use resources,
call snd_card_disconnect_sync() at (Z).
(X) is needed for both non-topology and topology.
topology removes rtd via (A), and
non topology removes rtd via (C).
snd_card_free() is no longer related to use-after-free issue.
Thus, locating (B) is no problem.
Fixes: df95a16d2a9626 ("ASoC: soc-core: fix RIP warning on card removal")
Fixes: bc7a9091e5b927 ("ASoC: soc-core: add soc_unbind_dai_link()")
Reported-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Tested-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/87o8xax88g.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-18 09:51:11 +08:00
|
|
|
snd_soc_pcm_component_free(rtd);
|
|
|
|
|
2019-09-12 12:41:01 +08:00
|
|
|
/*
|
|
|
|
* we don't need to call kfree() for rtd->dev
|
|
|
|
* see
|
|
|
|
* soc_release_rtd_dev()
|
2019-09-12 12:42:30 +08:00
|
|
|
*
|
|
|
|
* We don't need rtd->dev NULL check, because
|
|
|
|
* it is alloced *before* rtd.
|
|
|
|
* see
|
|
|
|
* soc_new_pcm_runtime()
|
2021-03-30 13:26:28 +08:00
|
|
|
*
|
|
|
|
* We don't need to mind freeing for rtd,
|
|
|
|
* because it was created from dev (= rtd->dev)
|
|
|
|
* see
|
|
|
|
* soc_new_pcm_runtime()
|
|
|
|
*
|
|
|
|
* rtd = devm_kzalloc(dev, ...);
|
|
|
|
* rtd->dev = dev
|
2019-09-12 12:41:01 +08:00
|
|
|
*/
|
2019-09-12 12:42:30 +08:00
|
|
|
device_unregister(rtd->dev);
|
2019-09-12 12:38:22 +08:00
|
|
|
}
|
|
|
|
|
2019-12-04 01:30:07 +08:00
|
|
|
static void close_delayed_work(struct work_struct *work) {
|
|
|
|
struct snd_soc_pcm_runtime *rtd =
|
|
|
|
container_of(work, struct snd_soc_pcm_runtime,
|
|
|
|
delayed_work.work);
|
|
|
|
|
|
|
|
if (rtd->close_delayed_work_func)
|
|
|
|
rtd->close_delayed_work_func(rtd);
|
|
|
|
}
|
|
|
|
|
2015-11-18 15:34:11 +08:00
|
|
|
static struct snd_soc_pcm_runtime *soc_new_pcm_runtime(
|
|
|
|
struct snd_soc_card *card, struct snd_soc_dai_link *dai_link)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
struct snd_soc_component *component;
|
2019-09-12 12:42:30 +08:00
|
|
|
struct device *dev;
|
2019-09-12 12:40:08 +08:00
|
|
|
int ret;
|
2020-02-17 16:28:25 +08:00
|
|
|
int stream;
|
2015-11-18 15:34:11 +08:00
|
|
|
|
2019-09-12 12:42:30 +08:00
|
|
|
/*
|
|
|
|
* for rtd->dev
|
|
|
|
*/
|
|
|
|
dev = kzalloc(sizeof(struct device), GFP_KERNEL);
|
|
|
|
if (!dev)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
dev->parent = card->dev;
|
|
|
|
dev->release = soc_release_rtd_dev;
|
|
|
|
|
|
|
|
dev_set_name(dev, "%s", dai_link->name);
|
|
|
|
|
|
|
|
ret = device_register(dev);
|
|
|
|
if (ret < 0) {
|
|
|
|
put_device(dev); /* soc_release_rtd_dev */
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-09-12 12:40:08 +08:00
|
|
|
/*
|
|
|
|
* for rtd
|
|
|
|
*/
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
rtd = devm_kzalloc(dev,
|
|
|
|
sizeof(*rtd) +
|
2022-11-08 12:24:56 +08:00
|
|
|
sizeof(component) * (dai_link->num_cpus +
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
dai_link->num_codecs +
|
|
|
|
dai_link->num_platforms),
|
|
|
|
GFP_KERNEL);
|
2021-03-30 13:26:38 +08:00
|
|
|
if (!rtd) {
|
|
|
|
device_unregister(dev);
|
|
|
|
return NULL;
|
|
|
|
}
|
2015-11-18 15:34:11 +08:00
|
|
|
|
2019-09-12 12:42:30 +08:00
|
|
|
rtd->dev = dev;
|
2019-12-04 23:14:54 +08:00
|
|
|
INIT_LIST_HEAD(&rtd->list);
|
2020-02-17 16:28:25 +08:00
|
|
|
for_each_pcm_streams(stream) {
|
|
|
|
INIT_LIST_HEAD(&rtd->dpcm[stream].be_clients);
|
|
|
|
INIT_LIST_HEAD(&rtd->dpcm[stream].fe_clients);
|
|
|
|
}
|
2019-09-12 12:42:30 +08:00
|
|
|
dev_set_drvdata(dev, rtd);
|
2019-12-04 01:30:07 +08:00
|
|
|
INIT_DELAYED_WORK(&rtd->delayed_work, close_delayed_work);
|
2019-09-12 12:42:30 +08:00
|
|
|
|
2019-09-12 12:40:08 +08:00
|
|
|
/*
|
2020-03-16 14:36:58 +08:00
|
|
|
* for rtd->dais
|
2019-09-12 12:40:08 +08:00
|
|
|
*/
|
2020-03-16 14:36:58 +08:00
|
|
|
rtd->dais = devm_kcalloc(dev, dai_link->num_cpus + dai_link->num_codecs,
|
treewide: kzalloc() -> kcalloc()
The kzalloc() function has a 2-factor argument form, kcalloc(). This
patch replaces cases of:
kzalloc(a * b, gfp)
with:
kcalloc(a * b, gfp)
as well as handling cases of:
kzalloc(a * b * c, gfp)
with:
kzalloc(array3_size(a, b, c), gfp)
as it's slightly less ugly than:
kzalloc_array(array_size(a, b), c, gfp)
This does, however, attempt to ignore constant size factors like:
kzalloc(4 * 1024, gfp)
though any constants defined via macros get caught up in the conversion.
Any factors with a sizeof() of "unsigned char", "char", and "u8" were
dropped, since they're redundant.
The Coccinelle script used for this was:
// Fix redundant parens around sizeof().
@@
type TYPE;
expression THING, E;
@@
(
kzalloc(
- (sizeof(TYPE)) * E
+ sizeof(TYPE) * E
, ...)
|
kzalloc(
- (sizeof(THING)) * E
+ sizeof(THING) * E
, ...)
)
// Drop single-byte sizes and redundant parens.
@@
expression COUNT;
typedef u8;
typedef __u8;
@@
(
kzalloc(
- sizeof(u8) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(__u8) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(char) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(unsigned char) * (COUNT)
+ COUNT
, ...)
|
kzalloc(
- sizeof(u8) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(__u8) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(char) * COUNT
+ COUNT
, ...)
|
kzalloc(
- sizeof(unsigned char) * COUNT
+ COUNT
, ...)
)
// 2-factor product with sizeof(type/expression) and identifier or constant.
@@
type TYPE;
expression THING;
identifier COUNT_ID;
constant COUNT_CONST;
@@
(
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (COUNT_ID)
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * COUNT_ID
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (COUNT_CONST)
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * COUNT_CONST
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (COUNT_ID)
+ COUNT_ID, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * COUNT_ID
+ COUNT_ID, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (COUNT_CONST)
+ COUNT_CONST, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * COUNT_CONST
+ COUNT_CONST, sizeof(THING)
, ...)
)
// 2-factor product, only identifiers.
@@
identifier SIZE, COUNT;
@@
- kzalloc
+ kcalloc
(
- SIZE * COUNT
+ COUNT, SIZE
, ...)
// 3-factor product with 1 sizeof(type) or sizeof(expression), with
// redundant parens removed.
@@
expression THING;
identifier STRIDE, COUNT;
type TYPE;
@@
(
kzalloc(
- sizeof(TYPE) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(TYPE) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
kzalloc(
- sizeof(THING) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
kzalloc(
- sizeof(THING) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
)
// 3-factor product with 2 sizeof(variable), with redundant parens removed.
@@
expression THING1, THING2;
identifier COUNT;
type TYPE1, TYPE2;
@@
(
kzalloc(
- sizeof(TYPE1) * sizeof(TYPE2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
kzalloc(
- sizeof(THING1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(THING1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
|
kzalloc(
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
)
// 3-factor product, only identifiers, with redundant parens removed.
@@
identifier STRIDE, SIZE, COUNT;
@@
(
kzalloc(
- (COUNT) * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- (COUNT) * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
kzalloc(
- COUNT * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
)
// Any remaining multi-factor products, first at least 3-factor products,
// when they're not all constants...
@@
expression E1, E2, E3;
constant C1, C2, C3;
@@
(
kzalloc(C1 * C2 * C3, ...)
|
kzalloc(
- (E1) * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- (E1) * (E2) * E3
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- (E1) * (E2) * (E3)
+ array3_size(E1, E2, E3)
, ...)
|
kzalloc(
- E1 * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
)
// And then all remaining 2 factors products when they're not all constants,
// keeping sizeof() as the second factor argument.
@@
expression THING, E1, E2;
type TYPE;
constant C1, C2, C3;
@@
(
kzalloc(sizeof(THING) * C2, ...)
|
kzalloc(sizeof(TYPE) * C2, ...)
|
kzalloc(C1 * C2 * C3, ...)
|
kzalloc(C1 * C2, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * (E2)
+ E2, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(TYPE) * E2
+ E2, sizeof(TYPE)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * (E2)
+ E2, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- sizeof(THING) * E2
+ E2, sizeof(THING)
, ...)
|
- kzalloc
+ kcalloc
(
- (E1) * E2
+ E1, E2
, ...)
|
- kzalloc
+ kcalloc
(
- (E1) * (E2)
+ E1, E2
, ...)
|
- kzalloc
+ kcalloc
(
- E1 * E2
+ E1, E2
, ...)
)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-06-13 05:03:40 +08:00
|
|
|
sizeof(struct snd_soc_dai *),
|
2015-11-18 15:34:11 +08:00
|
|
|
GFP_KERNEL);
|
2020-03-16 14:36:58 +08:00
|
|
|
if (!rtd->dais)
|
2019-09-12 12:40:08 +08:00
|
|
|
goto free_rtd;
|
|
|
|
|
2020-02-25 21:39:12 +08:00
|
|
|
/*
|
2020-03-16 14:36:58 +08:00
|
|
|
* dais = [][][][][][][][][][][][][][][][][][]
|
|
|
|
* ^cpu_dais ^codec_dais
|
|
|
|
* |--- num_cpus ---|--- num_codecs --|
|
2020-03-30 09:48:27 +08:00
|
|
|
* see
|
|
|
|
* asoc_rtd_to_cpu()
|
|
|
|
* asoc_rtd_to_codec()
|
2020-02-25 21:39:12 +08:00
|
|
|
*/
|
2020-03-30 09:48:14 +08:00
|
|
|
rtd->card = card;
|
|
|
|
rtd->dai_link = dai_link;
|
|
|
|
rtd->num = card->num_rtd++;
|
2022-09-09 09:19:18 +08:00
|
|
|
rtd->pmdown_time = pmdown_time; /* default power off timeout */
|
2019-09-12 12:39:32 +08:00
|
|
|
|
2019-08-07 09:30:31 +08:00
|
|
|
/* see for_each_card_rtds */
|
2015-11-18 15:34:11 +08:00
|
|
|
list_add_tail(&rtd->list, &card->rtd_list);
|
2019-09-12 12:38:34 +08:00
|
|
|
|
2020-07-30 20:07:14 +08:00
|
|
|
ret = device_add_groups(dev, soc_dev_attr_groups);
|
|
|
|
if (ret < 0)
|
|
|
|
goto free_rtd;
|
|
|
|
|
2019-09-12 12:38:34 +08:00
|
|
|
return rtd;
|
2019-09-12 12:40:08 +08:00
|
|
|
|
|
|
|
free_rtd:
|
|
|
|
soc_free_pcm_runtime(rtd);
|
|
|
|
return NULL;
|
2015-11-18 15:34:11 +08:00
|
|
|
}
|
|
|
|
|
2019-01-21 08:32:37 +08:00
|
|
|
static void snd_soc_flush_all_delayed_work(struct snd_soc_card *card)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
|
|
|
|
|
|
|
for_each_card_rtds(card, rtd)
|
|
|
|
flush_delayed_work(&rtd->delayed_work);
|
|
|
|
}
|
|
|
|
|
2011-01-26 22:59:27 +08:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
2020-11-27 08:07:35 +08:00
|
|
|
static void soc_playback_digital_mute(struct snd_soc_card *card, int mute)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
|
|
|
struct snd_soc_dai *dai;
|
|
|
|
int playback = SNDRV_PCM_STREAM_PLAYBACK;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for_each_card_rtds(card, rtd) {
|
|
|
|
|
|
|
|
if (rtd->dai_link->ignore_suspend)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for_each_rtd_dais(rtd, i, dai) {
|
|
|
|
if (snd_soc_dai_stream_active(dai, playback))
|
|
|
|
snd_soc_dai_digital_mute(dai, mute, playback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-27 08:07:40 +08:00
|
|
|
static void soc_dapm_suspend_resume(struct snd_soc_card *card, int event)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
|
|
|
int stream;
|
|
|
|
|
|
|
|
for_each_card_rtds(card, rtd) {
|
|
|
|
|
|
|
|
if (rtd->dai_link->ignore_suspend)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for_each_pcm_streams(stream)
|
|
|
|
snd_soc_dapm_stream_event(rtd, stream, event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-10-07 00:31:09 +08:00
|
|
|
/* powers down audio subsystem for suspend */
|
2011-01-26 22:59:27 +08:00
|
|
|
int snd_soc_suspend(struct device *dev)
|
2006-10-07 00:31:09 +08:00
|
|
|
{
|
2011-01-26 22:59:27 +08:00
|
|
|
struct snd_soc_card *card = dev_get_drvdata(dev);
|
2016-11-30 14:22:36 +08:00
|
|
|
struct snd_soc_component *component;
|
2015-11-18 15:34:11 +08:00
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
|
|
|
int i;
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2014-08-19 21:51:30 +08:00
|
|
|
/* If the card is not initialized yet there is nothing to do */
|
2023-01-31 10:01:29 +08:00
|
|
|
if (!snd_soc_card_is_instantiated(card))
|
2009-06-03 23:44:49 +08:00
|
|
|
return 0;
|
|
|
|
|
2018-10-18 19:18:28 +08:00
|
|
|
/*
|
|
|
|
* Due to the resume being scheduled into a workqueue we could
|
|
|
|
* suspend before that's finished - wait for it to complete.
|
2008-06-13 23:24:05 +08:00
|
|
|
*/
|
2021-05-23 17:09:19 +08:00
|
|
|
snd_power_wait(card->snd_card);
|
2008-06-13 23:24:05 +08:00
|
|
|
|
|
|
|
/* we're going to block userspace touching us until resume completes */
|
2010-03-18 04:15:21 +08:00
|
|
|
snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D3hot);
|
2008-06-13 23:24:05 +08:00
|
|
|
|
2010-12-03 00:24:24 +08:00
|
|
|
/* mute any active DACs */
|
2020-11-27 08:07:35 +08:00
|
|
|
soc_playback_digital_mute(card, 1);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2008-01-10 21:39:01 +08:00
|
|
|
/* suspend all pcms */
|
2018-09-18 09:29:35 +08:00
|
|
|
for_each_card_rtds(card, rtd) {
|
2015-11-18 15:34:11 +08:00
|
|
|
if (rtd->dai_link->ignore_suspend)
|
2010-05-09 20:25:43 +08:00
|
|
|
continue;
|
|
|
|
|
2015-11-18 15:34:11 +08:00
|
|
|
snd_pcm_suspend_all(rtd->pcm);
|
2010-05-09 20:25:43 +08:00
|
|
|
}
|
2008-01-10 21:39:01 +08:00
|
|
|
|
2020-05-28 09:48:39 +08:00
|
|
|
snd_soc_card_suspend_pre(card);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2015-03-31 03:04:50 +08:00
|
|
|
/* close any waiting streams */
|
2019-01-21 08:32:37 +08:00
|
|
|
snd_soc_flush_all_delayed_work(card);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2020-11-27 08:07:40 +08:00
|
|
|
soc_dapm_suspend_resume(card, SND_SOC_DAPM_STREAM_SUSPEND);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2014-10-25 23:42:01 +08:00
|
|
|
/* Recheck all endpoints too, their state is affected by suspend */
|
|
|
|
dapm_mark_endpoints_dirty(card);
|
2012-09-01 08:38:32 +08:00
|
|
|
snd_soc_dapm_sync(&card->dapm);
|
|
|
|
|
2016-11-30 14:23:13 +08:00
|
|
|
/* suspend all COMPONENTs */
|
2020-01-10 10:36:02 +08:00
|
|
|
for_each_card_rtds(card, rtd) {
|
|
|
|
|
|
|
|
if (rtd->dai_link->ignore_suspend)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for_each_rtd_components(rtd, i, component) {
|
|
|
|
struct snd_soc_dapm_context *dapm =
|
2018-10-18 19:18:28 +08:00
|
|
|
snd_soc_component_get_dapm(component);
|
2016-11-30 14:22:36 +08:00
|
|
|
|
2020-01-10 10:36:02 +08:00
|
|
|
/*
|
|
|
|
* ignore if component was already suspended
|
|
|
|
*/
|
|
|
|
if (snd_soc_component_is_suspended(component))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If there are paths active then the COMPONENT will be
|
|
|
|
* held with bias _ON and should not be suspended.
|
|
|
|
*/
|
2015-07-06 21:38:11 +08:00
|
|
|
switch (snd_soc_dapm_get_bias_level(dapm)) {
|
2010-03-18 04:15:21 +08:00
|
|
|
case SND_SOC_BIAS_STANDBY:
|
2012-01-31 23:49:10 +08:00
|
|
|
/*
|
2016-11-30 14:23:13 +08:00
|
|
|
* If the COMPONENT is capable of idle
|
2012-01-31 23:49:10 +08:00
|
|
|
* bias off then being in STANDBY
|
|
|
|
* means it's doing something,
|
|
|
|
* otherwise fall through.
|
|
|
|
*/
|
2015-07-06 21:38:11 +08:00
|
|
|
if (dapm->idle_bias_off) {
|
2016-11-30 14:23:13 +08:00
|
|
|
dev_dbg(component->dev,
|
2013-05-05 04:21:38 +08:00
|
|
|
"ASoC: idle_bias_off CODEC on over suspend\n");
|
2012-01-31 23:49:10 +08:00
|
|
|
break;
|
|
|
|
}
|
2020-08-24 06:36:59 +08:00
|
|
|
fallthrough;
|
2014-09-05 01:44:07 +08:00
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
case SND_SOC_BIAS_OFF:
|
2019-07-26 12:50:34 +08:00
|
|
|
snd_soc_component_suspend(component);
|
2016-11-30 14:23:13 +08:00
|
|
|
if (component->regmap)
|
|
|
|
regcache_mark_dirty(component->regmap);
|
ASoC: Add pinctrl PM to components of active DAIs
It's quite popular that more drivers are using pinctrl PM, for example:
(Documentation/devicetree/bindings/arm/primecell.txt). Just like what
runtime PM does, it would deactivate and activate pin group depending
on whether it's being used or not.
And this pinctrl PM might be also beneficial to cpu dai drivers because
they might have actual pinctrl so as to sleep their pins and wake them
up as needed.
To achieve this goal, this patch sets pins to the default state during
resume or startup; While during suspend and shutdown, it would set pins
to the sleep state.
As pinctrl PM would return zero if there is no such pinctrl sleep state
settings, this patch would not break current ASoC subsystem directly.
[ However, there is still an exception that the patch can not handle,
that is, when cpu dai driver does not have pinctrl property but another
device has it. (The AUDMUX <-> SSI on Freescale i.MX6 series for example.
SSI as a cpu dai doesn't contain pinctrl property while AUDMUX, an Audio
Multiplexer, has it). In this case, this kind of cpu dai driver needs to
find a way to obtain the pinctrl property as its own, by moving property
from AUDMUX to SSI, or creating a pins link/dependency between these two
devices, or using a more decent way after we figure it out. ]
Signed-off-by: Nicolin Chen <b42378@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2013-11-04 14:57:31 +08:00
|
|
|
/* deactivate pins to sleep state */
|
2016-11-30 14:23:13 +08:00
|
|
|
pinctrl_pm_select_sleep_state(component->dev);
|
2010-03-18 04:15:21 +08:00
|
|
|
break;
|
|
|
|
default:
|
2016-11-30 14:23:13 +08:00
|
|
|
dev_dbg(component->dev,
|
|
|
|
"ASoC: COMPONENT is on over suspend\n");
|
2010-03-18 04:15:21 +08:00
|
|
|
break;
|
|
|
|
}
|
2010-05-08 04:11:40 +08:00
|
|
|
}
|
|
|
|
}
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2020-05-28 09:48:48 +08:00
|
|
|
snd_soc_card_suspend_post(card);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2011-01-26 22:59:27 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_suspend);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2018-10-18 19:18:28 +08:00
|
|
|
/*
|
|
|
|
* deferred resume work, so resume can complete before we finished
|
2008-06-13 23:24:05 +08:00
|
|
|
* setting our codec back up, which can be very slow on I2C
|
|
|
|
*/
|
|
|
|
static void soc_resume_deferred(struct work_struct *work)
|
2006-10-07 00:31:09 +08:00
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
struct snd_soc_card *card =
|
2018-10-18 19:18:28 +08:00
|
|
|
container_of(work, struct snd_soc_card,
|
|
|
|
deferred_resume_work);
|
2016-11-30 14:22:36 +08:00
|
|
|
struct snd_soc_component *component;
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2018-10-18 19:18:28 +08:00
|
|
|
/*
|
|
|
|
* our power state is still SNDRV_CTL_POWER_D3hot from suspend time,
|
2008-06-13 23:24:05 +08:00
|
|
|
* so userspace apps are blocked from touching us
|
|
|
|
*/
|
|
|
|
|
2012-11-19 22:47:09 +08:00
|
|
|
dev_dbg(card->dev, "ASoC: starting resume work\n");
|
2008-06-13 23:24:05 +08:00
|
|
|
|
2010-05-08 03:24:05 +08:00
|
|
|
/* Bring us up into D2 so that DAPM starts enabling things */
|
2010-03-18 04:15:21 +08:00
|
|
|
snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D2);
|
2010-05-08 03:24:05 +08:00
|
|
|
|
2020-05-28 09:48:55 +08:00
|
|
|
snd_soc_card_resume_pre(card);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2018-09-18 09:29:55 +08:00
|
|
|
for_each_card_components(card, component) {
|
2019-07-26 12:51:13 +08:00
|
|
|
if (snd_soc_component_is_suspended(component))
|
2019-07-26 12:51:08 +08:00
|
|
|
snd_soc_component_resume(component);
|
2010-05-08 04:11:40 +08:00
|
|
|
}
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2020-11-27 08:07:40 +08:00
|
|
|
soc_dapm_suspend_resume(card, SND_SOC_DAPM_STREAM_RESUME);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2008-05-19 18:32:25 +08:00
|
|
|
/* unmute any active DACs */
|
2020-11-27 08:07:35 +08:00
|
|
|
soc_playback_digital_mute(card, 0);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2020-05-28 09:49:02 +08:00
|
|
|
snd_soc_card_resume_post(card);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2012-11-19 22:47:09 +08:00
|
|
|
dev_dbg(card->dev, "ASoC: resume work completed\n");
|
2008-06-13 23:24:05 +08:00
|
|
|
|
2014-10-25 23:42:01 +08:00
|
|
|
/* Recheck all endpoints too, their state is affected by suspend */
|
|
|
|
dapm_mark_endpoints_dirty(card);
|
2012-09-01 08:38:32 +08:00
|
|
|
snd_soc_dapm_sync(&card->dapm);
|
2015-11-23 23:52:31 +08:00
|
|
|
|
|
|
|
/* userspace can access us now we are back as we were before */
|
|
|
|
snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D0);
|
2008-06-13 23:24:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* powers up audio subsystem after a suspend */
|
2011-01-26 22:59:27 +08:00
|
|
|
int snd_soc_resume(struct device *dev)
|
2008-06-13 23:24:05 +08:00
|
|
|
{
|
2011-01-26 22:59:27 +08:00
|
|
|
struct snd_soc_card *card = dev_get_drvdata(dev);
|
2020-01-10 10:36:13 +08:00
|
|
|
struct snd_soc_component *component;
|
2010-02-22 19:27:13 +08:00
|
|
|
|
2014-08-19 21:51:30 +08:00
|
|
|
/* If the card is not initialized yet there is nothing to do */
|
2023-01-31 10:01:29 +08:00
|
|
|
if (!snd_soc_card_is_instantiated(card))
|
2011-11-23 22:37:00 +08:00
|
|
|
return 0;
|
|
|
|
|
ASoC: Add pinctrl PM to components of active DAIs
It's quite popular that more drivers are using pinctrl PM, for example:
(Documentation/devicetree/bindings/arm/primecell.txt). Just like what
runtime PM does, it would deactivate and activate pin group depending
on whether it's being used or not.
And this pinctrl PM might be also beneficial to cpu dai drivers because
they might have actual pinctrl so as to sleep their pins and wake them
up as needed.
To achieve this goal, this patch sets pins to the default state during
resume or startup; While during suspend and shutdown, it would set pins
to the sleep state.
As pinctrl PM would return zero if there is no such pinctrl sleep state
settings, this patch would not break current ASoC subsystem directly.
[ However, there is still an exception that the patch can not handle,
that is, when cpu dai driver does not have pinctrl property but another
device has it. (The AUDMUX <-> SSI on Freescale i.MX6 series for example.
SSI as a cpu dai doesn't contain pinctrl property while AUDMUX, an Audio
Multiplexer, has it). In this case, this kind of cpu dai driver needs to
find a way to obtain the pinctrl property as its own, by moving property
from AUDMUX to SSI, or creating a pins link/dependency between these two
devices, or using a more decent way after we figure it out. ]
Signed-off-by: Nicolin Chen <b42378@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2013-11-04 14:57:31 +08:00
|
|
|
/* activate pins from sleep state */
|
2020-01-10 10:36:13 +08:00
|
|
|
for_each_card_components(card, component)
|
2020-05-15 08:46:51 +08:00
|
|
|
if (snd_soc_component_active(component))
|
2020-01-10 10:36:13 +08:00
|
|
|
pinctrl_pm_select_default_state(component->dev);
|
ASoC: Add pinctrl PM to components of active DAIs
It's quite popular that more drivers are using pinctrl PM, for example:
(Documentation/devicetree/bindings/arm/primecell.txt). Just like what
runtime PM does, it would deactivate and activate pin group depending
on whether it's being used or not.
And this pinctrl PM might be also beneficial to cpu dai drivers because
they might have actual pinctrl so as to sleep their pins and wake them
up as needed.
To achieve this goal, this patch sets pins to the default state during
resume or startup; While during suspend and shutdown, it would set pins
to the sleep state.
As pinctrl PM would return zero if there is no such pinctrl sleep state
settings, this patch would not break current ASoC subsystem directly.
[ However, there is still an exception that the patch can not handle,
that is, when cpu dai driver does not have pinctrl property but another
device has it. (The AUDMUX <-> SSI on Freescale i.MX6 series for example.
SSI as a cpu dai doesn't contain pinctrl property while AUDMUX, an Audio
Multiplexer, has it). In this case, this kind of cpu dai driver needs to
find a way to obtain the pinctrl property as its own, by moving property
from AUDMUX to SSI, or creating a pins link/dependency between these two
devices, or using a more decent way after we figure it out. ]
Signed-off-by: Nicolin Chen <b42378@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2013-11-04 14:57:31 +08:00
|
|
|
|
2020-01-20 09:05:07 +08:00
|
|
|
dev_dbg(dev, "ASoC: Scheduling resume work\n");
|
|
|
|
if (!schedule_work(&card->deferred_resume_work))
|
|
|
|
dev_err(dev, "ASoC: resume work item may be lost\n");
|
2008-06-13 23:24:05 +08:00
|
|
|
|
2006-10-07 00:31:09 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2011-01-26 22:59:27 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_resume);
|
2019-08-07 09:31:24 +08:00
|
|
|
|
|
|
|
static void soc_resume_init(struct snd_soc_card *card)
|
|
|
|
{
|
|
|
|
/* deferred resume work */
|
|
|
|
INIT_WORK(&card->deferred_resume_work, soc_resume_deferred);
|
|
|
|
}
|
2006-10-07 00:31:09 +08:00
|
|
|
#else
|
2011-01-26 22:59:27 +08:00
|
|
|
#define snd_soc_suspend NULL
|
|
|
|
#define snd_soc_resume NULL
|
2021-10-18 10:05:34 +08:00
|
|
|
static inline void soc_resume_init(struct snd_soc_card *card) { }
|
2006-10-07 00:31:09 +08:00
|
|
|
#endif
|
|
|
|
|
2019-05-13 15:06:59 +08:00
|
|
|
static struct device_node
|
|
|
|
*soc_component_to_node(struct snd_soc_component *component)
|
2014-03-21 23:27:25 +08:00
|
|
|
{
|
2019-05-13 15:06:59 +08:00
|
|
|
struct device_node *of_node;
|
2014-03-21 23:27:25 +08:00
|
|
|
|
2019-05-13 15:06:59 +08:00
|
|
|
of_node = component->dev->of_node;
|
|
|
|
if (!of_node && component->dev->parent)
|
|
|
|
of_node = component->dev->parent->of_node;
|
2014-03-21 23:27:25 +08:00
|
|
|
|
2019-05-13 15:06:59 +08:00
|
|
|
return of_node;
|
2014-03-21 23:27:25 +08:00
|
|
|
}
|
|
|
|
|
2023-07-10 09:20:17 +08:00
|
|
|
struct of_phandle_args *snd_soc_copy_dai_args(struct device *dev, struct of_phandle_args *args)
|
|
|
|
{
|
|
|
|
struct of_phandle_args *ret = devm_kzalloc(dev, sizeof(*ret), GFP_KERNEL);
|
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
*ret = *args;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_copy_dai_args);
|
|
|
|
|
2018-09-11 14:51:45 +08:00
|
|
|
static int snd_soc_is_matching_component(
|
|
|
|
const struct snd_soc_dai_link_component *dlc,
|
|
|
|
struct snd_soc_component *component)
|
|
|
|
{
|
|
|
|
struct device_node *component_of_node;
|
|
|
|
|
2019-06-20 08:49:17 +08:00
|
|
|
if (!dlc)
|
|
|
|
return 0;
|
|
|
|
|
ASoC: soc-core.c: enable multi Component
Current ASoC Card is using dlc (snd_soc_dai_link_component) to find
target DAI / Component to be used.
Current dlc has below 3 items to identify DAI / Component
(a) name for Component
(b) of_node for Component
(c) dai_name for DAI
(a) or (b) is used to identify target Component, and (c) is used
to identify DAI.
One of the biggest issue on it today is dlc needs "name matching"
for "dai_name" (c).
It was not a big deal when we were using platform_device, because we
could specify nessesary "dai_name" via its platform_data.
But we need to find DAI name pointer from whole registered datas and/or
each related driver somehow in case of DT, because we can't specify it.
Therefore, Card driver parses DT and assumes the DAI, and find its name
pointer. How to assume is based on each Component and/or Card.
Next biggest issue is Component node (a)/(b).
Basically, Component is registered when CPU/Codec driver was
probed() (X). Here, 1 Component is possible to have some DAIs.
int xxx_probe(struct platform_device *pdev)
{
...
(X) ret = devm_snd_soc_register_component(pdev->dev,
&component_driver,
&dai_driver, dai_driver_num);
...
}
The image of each data will be like below.
One note here is "driver" is included for later explanation.
+-driver------+
|+-component-+|
|| dai0||
|| dai1||
|| ...||
|+-----------+|
+-------------+
The point here is 1 driver has 1 Component, because basically driver
calles snd_soc_register_component() (= X) once.
Here is the very basic CPU/Codec connection image.
HW image SW image
+-- Board ------------+ +-card--------------------------+
|+-----+ +------+| |+-driver------+ +-driver------+|
|| CPU | <--> |CodecA|| ||+-component-+| |+-component-+||
|+-----+ +------+| ||| dai|<=>|dai |||
+---------------------+ ||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It will be very complex if it has multi DAIs.
Here is intuitive easy to understandable HW / SW example.
HW image SW image
+-- Board ---------------+ +-card--------------------------+
|+--------+ +------+| |+-driver------+ +-driver------+|
|| CPU ch0| <--> |CodecA|| ||+-component-+| |+-component-+||
|| | +------+| ||| ch0 dai|<=>|dai |||
|| | +------+| ||| || |+-----------+||
|| ch1| <--> |CodecB|| ||| || +-------------+|
|+--------+ +------+| ||| || +-driver------+|
+------------------------+ ||| || |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It will be handled as multi interface as "one Card".
card0,0: CPU-ch0 - CodecA
card0,1: CPU-ch1 - CodecB
^
But, here is the HW image example which will be more complex
+-- Basic Board ---------+
|+--------+ +------+|
|| CPU ch0| <--> |CodecA||
|| ch1| <-+ +------+|
|+--------+ | |
+-------------|----------+
+-- expansion board -----+
| | +------+|
| +->|CodecB||
| +------+|
+------------------------+
We intuitively think we want to handle these as "2 Sound Cards".
card0,0: CPU-ch0 - CodecA
card1,0: CPU-ch1 - CodecB
^
But below image which we can register today doesn't allow it,
because the same Component will be connected to both Card0/1,
but it will be rejected by (Z).
+-driver------+
|+-component-+|
+-card0-------------------------+
||| || +-driver------+|
||| || |+-component-+||
||| ch0 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
+-------------------------------+
|| ||
+-card1-------------------------+
||| || +-driver------+|
||| || |+-component-+||
||| ch1 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
+-------------------------------+
|+-----------+|
+-------------+
static int soc_probe_component()
{
...
if (component->card) {
(Z) if (component->card != card) {
dev_err(component->dev, ...);
return -ENODEV;
}
return 0;
}
...
}
So, how about to call snd_soc_register_component() (= X) multiple times
on probe() to avoid buplicated component->card limitation, to be like
below ?
+-driver------+
+-card0-------------------------+
|| | +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||+-----------+| |+-----------+||
|| | +-------------+|
+-------------------------------+
| |
+-card1-------------------------+
|| | +-driver------+|
||+-component-+| |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|| | +-------------+|
+-------------------------------+
+-------------+
Yes, looks good. But unfortunately it doesn't help us for now.
Let's see soc_component_to_node() and snd_soc_is_matching_component()
static struct device_node
*soc_component_to_node(struct snd_soc_component *component)
{
...
(A) of_node = component->dev->of_node;
...
}
static int snd_soc_is_matching_component(...)
{
...
(B) if (dlc->of_node && component_of_node != dlc->of_node)
...
}
dlc checkes "of_node" to identify target component (B),
but this "of_node" came from component->dev (A) which is added
by snd_soc_register_component() (X) on probe().
This means we can have different "component->card", but have same
"component->dev" in this case.
Even though we calls snd_soc_register_component() (= X) multiple times,
all Components have same driver's dev, thus it is impossible to
identified the Component.
And if it was impossible to identify Component, it is impossible to
identify DAI on current implementation.
So, how to handle above complex HW image today is 2 patterns.
One is handles it as "1 big sound card".
The SW image is like below.
SW image
+-card--------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||| || |+-----------+||
||| || +-------------+|
||| || +-driver------+|
||| || |+-component-+||
||| ch1 dai|<->|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
But the problem is not intuitive.
We want to handle it as "2 Cards".
2nd pattern is like below.
SW image
+-card0-------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch0 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
+-card1-------------------------+
|+-driver------+ +-driver------+|
||+-component-+| |+-component-+||
||| ch1 dai|<=>|dai |||
||+-----------+| |+-----------+||
|+-------------+ +-------------+|
+-------------------------------+
It handles as "2 Cards", but CPU part needs to be probed as 2 drivers.
It is also not intuitive.
To solve this issue, we need to have multi Component support.
In current implementation, we need to identify Component first
to identify DAI, and it is using name matching to identify DAI.
But how about to be enable to directly identify DAI by unique way
instead of name matching ? In such case, we can directly identify DAI,
then it can identify Component from DAI.
For example Simple-Card / Audio-Graph-Card case, it is specifying DAI
via its node.
Simple-Card
sound-dai = <&cpu-sound>;
Audio-Graph-Card
dais = <&cpu-sound>;
If each CPU/Codec driver keeps this property when probing,
we can identify DAI directly from Card.
Being able to identify DAI directly means being able to identify its
Component as well even though Component has same dev (= B).
This patch adds new "dai_node" for it.
To keeping compatibility, it checks "dai_node" first if it has,
otherwise, use existing method (name matching).
Link: https://lore.kernel.org/r/87fskz5yrr.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87fs5wo94v.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2023-07-10 09:20:00 +08:00
|
|
|
if (dlc->dai_args) {
|
|
|
|
struct snd_soc_dai *dai;
|
|
|
|
|
|
|
|
for_each_component_dais(component, dai)
|
|
|
|
if (snd_soc_is_matching_dai(dlc, dai))
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-06-20 08:49:17 +08:00
|
|
|
component_of_node = soc_component_to_node(component);
|
2018-09-11 14:51:45 +08:00
|
|
|
|
|
|
|
if (dlc->of_node && component_of_node != dlc->of_node)
|
|
|
|
return 0;
|
|
|
|
if (dlc->name && strcmp(component->name, dlc->name))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-08-19 21:51:22 +08:00
|
|
|
static struct snd_soc_component *soc_find_component(
|
2019-06-20 08:49:27 +08:00
|
|
|
const struct snd_soc_dai_link_component *dlc)
|
2014-03-21 23:27:25 +08:00
|
|
|
{
|
2014-08-19 21:51:22 +08:00
|
|
|
struct snd_soc_component *component;
|
2014-03-21 23:27:25 +08:00
|
|
|
|
2015-03-08 02:34:03 +08:00
|
|
|
lockdep_assert_held(&client_mutex);
|
|
|
|
|
2019-06-26 09:40:59 +08:00
|
|
|
/*
|
|
|
|
* NOTE
|
|
|
|
*
|
|
|
|
* It returns *1st* found component, but some driver
|
|
|
|
* has few components by same of_node/name
|
|
|
|
* ex)
|
|
|
|
* CPU component and generic DMAEngine component
|
|
|
|
*/
|
2019-06-20 08:49:27 +08:00
|
|
|
for_each_component(component)
|
|
|
|
if (snd_soc_is_matching_component(dlc, component))
|
2014-08-19 21:51:22 +08:00
|
|
|
return component;
|
2014-03-21 23:27:25 +08:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2016-04-22 12:25:33 +08:00
|
|
|
/**
|
|
|
|
* snd_soc_find_dai - Find a registered DAI
|
|
|
|
*
|
2017-08-22 15:57:21 +08:00
|
|
|
* @dlc: name of the DAI or the DAI driver and optional component info to match
|
2016-04-22 12:25:33 +08:00
|
|
|
*
|
2017-05-09 06:57:50 +08:00
|
|
|
* This function will search all registered components and their DAIs to
|
2016-04-22 12:25:33 +08:00
|
|
|
* find the DAI of the same name. The component's of_node and name
|
|
|
|
* should also match if being specified.
|
|
|
|
*
|
|
|
|
* Return: pointer of DAI, or NULL if not found.
|
|
|
|
*/
|
2016-04-19 13:12:25 +08:00
|
|
|
struct snd_soc_dai *snd_soc_find_dai(
|
2014-08-19 21:51:27 +08:00
|
|
|
const struct snd_soc_dai_link_component *dlc)
|
2014-03-21 23:27:25 +08:00
|
|
|
{
|
2014-08-19 21:51:27 +08:00
|
|
|
struct snd_soc_component *component;
|
|
|
|
struct snd_soc_dai *dai;
|
2014-03-21 23:27:25 +08:00
|
|
|
|
2015-03-08 02:34:03 +08:00
|
|
|
lockdep_assert_held(&client_mutex);
|
|
|
|
|
2018-10-18 19:18:28 +08:00
|
|
|
/* Find CPU DAI from registered DAIs */
|
2023-07-10 09:19:53 +08:00
|
|
|
for_each_component(component)
|
|
|
|
if (snd_soc_is_matching_component(dlc, component))
|
|
|
|
for_each_component_dais(component, dai)
|
|
|
|
if (snd_soc_is_matching_dai(dlc, dai))
|
|
|
|
return dai;
|
2014-03-21 23:27:25 +08:00
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
2016-04-19 13:12:25 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_find_dai);
|
2014-03-21 23:27:25 +08:00
|
|
|
|
2020-08-27 07:55:39 +08:00
|
|
|
struct snd_soc_dai *snd_soc_find_dai_with_mutex(
|
|
|
|
const struct snd_soc_dai_link_component *dlc)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai *dai;
|
|
|
|
|
|
|
|
mutex_lock(&client_mutex);
|
|
|
|
dai = snd_soc_find_dai(dlc);
|
|
|
|
mutex_unlock(&client_mutex);
|
|
|
|
|
|
|
|
return dai;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_find_dai_with_mutex);
|
|
|
|
|
2019-11-05 14:45:50 +08:00
|
|
|
static int soc_dai_link_sanity_check(struct snd_soc_card *card,
|
|
|
|
struct snd_soc_dai_link *link)
|
2019-11-05 14:45:41 +08:00
|
|
|
{
|
|
|
|
int i;
|
2023-06-21 10:18:17 +08:00
|
|
|
struct snd_soc_dai_link_component *dlc;
|
2019-11-05 14:45:41 +08:00
|
|
|
|
2023-06-21 10:18:17 +08:00
|
|
|
/* Codec check */
|
|
|
|
for_each_link_codecs(link, i, dlc) {
|
2019-11-05 14:45:41 +08:00
|
|
|
/*
|
|
|
|
* Codec must be specified by 1 of name or OF node,
|
|
|
|
* not both or neither.
|
|
|
|
*/
|
2023-06-21 10:18:17 +08:00
|
|
|
if (snd_soc_dlc_component_is_invalid(dlc))
|
|
|
|
goto component_invalid;
|
|
|
|
|
|
|
|
if (snd_soc_dlc_component_is_empty(dlc))
|
|
|
|
goto component_empty;
|
2019-11-05 14:45:41 +08:00
|
|
|
|
|
|
|
/* Codec DAI name must be specified */
|
2023-06-21 10:18:17 +08:00
|
|
|
if (snd_soc_dlc_dai_is_empty(dlc))
|
|
|
|
goto dai_empty;
|
2019-11-05 14:45:41 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Defer card registration if codec component is not added to
|
|
|
|
* component list.
|
|
|
|
*/
|
2023-06-21 10:18:17 +08:00
|
|
|
if (!soc_find_component(dlc))
|
2023-08-25 03:38:37 +08:00
|
|
|
goto component_not_found;
|
2019-11-05 14:45:41 +08:00
|
|
|
}
|
|
|
|
|
2023-06-21 10:18:17 +08:00
|
|
|
/* Platform check */
|
|
|
|
for_each_link_platforms(link, i, dlc) {
|
2019-11-05 14:45:41 +08:00
|
|
|
/*
|
|
|
|
* Platform may be specified by either name or OF node, but it
|
|
|
|
* can be left unspecified, then no components will be inserted
|
|
|
|
* in the rtdcom list
|
|
|
|
*/
|
2023-06-21 10:18:17 +08:00
|
|
|
if (snd_soc_dlc_component_is_invalid(dlc))
|
|
|
|
goto component_invalid;
|
|
|
|
|
|
|
|
if (snd_soc_dlc_component_is_empty(dlc))
|
|
|
|
goto component_empty;
|
2019-11-05 14:45:41 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Defer card registration if platform component is not added to
|
|
|
|
* component list.
|
|
|
|
*/
|
2023-06-21 10:18:17 +08:00
|
|
|
if (!soc_find_component(dlc))
|
2023-08-25 03:38:37 +08:00
|
|
|
goto component_not_found;
|
2019-11-05 14:45:41 +08:00
|
|
|
}
|
|
|
|
|
2023-06-21 10:18:17 +08:00
|
|
|
/* CPU check */
|
|
|
|
for_each_link_cpus(link, i, dlc) {
|
2020-02-25 21:39:12 +08:00
|
|
|
/*
|
|
|
|
* CPU device may be specified by either name or OF node, but
|
|
|
|
* can be left unspecified, and will be matched based on DAI
|
|
|
|
* name alone..
|
|
|
|
*/
|
2023-06-21 10:18:17 +08:00
|
|
|
if (snd_soc_dlc_component_is_invalid(dlc))
|
|
|
|
goto component_invalid;
|
2019-11-05 14:45:41 +08:00
|
|
|
|
|
|
|
|
2023-06-21 10:18:17 +08:00
|
|
|
if (snd_soc_dlc_component_is_empty(dlc)) {
|
|
|
|
/*
|
|
|
|
* At least one of CPU DAI name or CPU device name/node must be specified
|
|
|
|
*/
|
|
|
|
if (snd_soc_dlc_dai_is_empty(dlc))
|
|
|
|
goto component_dai_empty;
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* Defer card registration if Component is not added
|
|
|
|
*/
|
|
|
|
if (!soc_find_component(dlc))
|
2023-08-25 03:38:37 +08:00
|
|
|
goto component_not_found;
|
2020-02-25 21:39:12 +08:00
|
|
|
}
|
2019-11-05 14:45:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2023-06-21 10:18:17 +08:00
|
|
|
|
|
|
|
component_invalid:
|
|
|
|
dev_err(card->dev, "ASoC: Both Component name/of_node are set for %s\n", link->name);
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
component_empty:
|
|
|
|
dev_err(card->dev, "ASoC: Neither Component name/of_node are set for %s\n", link->name);
|
|
|
|
return -EINVAL;
|
|
|
|
|
2023-08-25 03:38:37 +08:00
|
|
|
component_not_found:
|
|
|
|
dev_dbg(card->dev, "ASoC: Component %s not found for link %s\n", dlc->name, link->name);
|
2023-06-21 10:18:17 +08:00
|
|
|
return -EPROBE_DEFER;
|
|
|
|
|
|
|
|
dai_empty:
|
|
|
|
dev_err(card->dev, "ASoC: DAI name is not set for %s\n", link->name);
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
component_dai_empty:
|
|
|
|
dev_err(card->dev, "ASoC: Neither DAI/Component name/of_node are set for %s\n", link->name);
|
|
|
|
return -EINVAL;
|
2019-11-05 14:45:41 +08:00
|
|
|
}
|
|
|
|
|
2019-11-06 09:07:38 +08:00
|
|
|
/**
|
2019-12-10 08:34:23 +08:00
|
|
|
* snd_soc_remove_pcm_runtime - Remove a pcm_runtime from card
|
|
|
|
* @card: The ASoC card to which the pcm_runtime has
|
|
|
|
* @rtd: The pcm_runtime to remove
|
2019-11-06 09:07:38 +08:00
|
|
|
*
|
2019-12-10 08:34:23 +08:00
|
|
|
* This function removes a pcm_runtime from the ASoC card.
|
2019-11-06 09:07:38 +08:00
|
|
|
*/
|
2019-12-10 08:34:23 +08:00
|
|
|
void snd_soc_remove_pcm_runtime(struct snd_soc_card *card,
|
|
|
|
struct snd_soc_pcm_runtime *rtd)
|
ASoC: soc-core: add soc_unbind_dai_link()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
ALSA SoC has soc_bind_dai_link(), but its paired soc_unbind_dai_link()
is not implemented.
More confusable is that soc_remove_pcm_runtimes() which should be
soc_unbind_dai_link() is implemented without synchronised
to soc_bind_dai_link().
This patch cleanup this unbalance.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/877e4e3jni.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-05 14:46:25 +08:00
|
|
|
{
|
2019-11-06 09:07:38 +08:00
|
|
|
lockdep_assert_held(&client_mutex);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Notify the machine driver for extra destruction
|
|
|
|
*/
|
2020-05-28 09:50:46 +08:00
|
|
|
snd_soc_card_remove_dai_link(card, rtd->dai_link);
|
2019-11-06 09:07:38 +08:00
|
|
|
|
2019-12-10 08:34:23 +08:00
|
|
|
soc_free_pcm_runtime(rtd);
|
ASoC: soc-core: add soc_unbind_dai_link()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
ALSA SoC has soc_bind_dai_link(), but its paired soc_unbind_dai_link()
is not implemented.
More confusable is that soc_remove_pcm_runtimes() which should be
soc_unbind_dai_link() is implemented without synchronised
to soc_bind_dai_link().
This patch cleanup this unbalance.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/877e4e3jni.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-05 14:46:25 +08:00
|
|
|
}
|
2019-12-10 08:34:23 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_remove_pcm_runtime);
|
ASoC: soc-core: add soc_unbind_dai_link()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
ALSA SoC has soc_bind_dai_link(), but its paired soc_unbind_dai_link()
is not implemented.
More confusable is that soc_remove_pcm_runtimes() which should be
soc_unbind_dai_link() is implemented without synchronised
to soc_bind_dai_link().
This patch cleanup this unbalance.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/877e4e3jni.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-05 14:46:25 +08:00
|
|
|
|
2019-11-06 09:07:31 +08:00
|
|
|
/**
|
2019-12-10 08:34:19 +08:00
|
|
|
* snd_soc_add_pcm_runtime - Add a pcm_runtime dynamically via dai_link
|
|
|
|
* @card: The ASoC card to which the pcm_runtime is added
|
|
|
|
* @dai_link: The DAI link to find pcm_runtime
|
2019-11-06 09:07:31 +08:00
|
|
|
*
|
2019-12-10 08:34:19 +08:00
|
|
|
* This function adds a pcm_runtime ASoC card by using dai_link.
|
2019-11-06 09:07:31 +08:00
|
|
|
*
|
2019-12-10 08:34:19 +08:00
|
|
|
* Note: Topology can use this API to add pcm_runtime when probing the
|
2019-11-06 09:07:31 +08:00
|
|
|
* topology component. And machine drivers can still define static
|
|
|
|
* DAI links in dai_link array.
|
|
|
|
*/
|
2023-03-27 08:34:26 +08:00
|
|
|
static int snd_soc_add_pcm_runtime(struct snd_soc_card *card,
|
|
|
|
struct snd_soc_dai_link *dai_link)
|
2006-10-07 00:31:09 +08:00
|
|
|
{
|
2015-11-18 15:34:11 +08:00
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
2020-02-25 21:39:12 +08:00
|
|
|
struct snd_soc_dai_link_component *codec, *platform, *cpu;
|
2017-08-08 14:18:10 +08:00
|
|
|
struct snd_soc_component *component;
|
2019-11-05 14:45:50 +08:00
|
|
|
int i, ret;
|
2008-12-04 23:32:53 +08:00
|
|
|
|
2019-11-06 09:07:31 +08:00
|
|
|
lockdep_assert_held(&client_mutex);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Notify the machine driver for extra initialization
|
|
|
|
*/
|
2020-05-28 09:50:41 +08:00
|
|
|
ret = snd_soc_card_add_dai_link(card, dai_link);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2019-11-06 09:07:31 +08:00
|
|
|
|
2018-07-02 23:59:54 +08:00
|
|
|
if (dai_link->ignore)
|
|
|
|
return 0;
|
|
|
|
|
2015-11-24 00:03:52 +08:00
|
|
|
dev_dbg(card->dev, "ASoC: binding %s\n", dai_link->name);
|
2008-12-04 23:32:53 +08:00
|
|
|
|
2019-11-05 14:45:50 +08:00
|
|
|
ret = soc_dai_link_sanity_check(card, dai_link);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
2016-02-22 16:44:31 +08:00
|
|
|
rtd = soc_new_pcm_runtime(card, dai_link);
|
|
|
|
if (!rtd)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2020-02-25 21:39:12 +08:00
|
|
|
for_each_link_cpus(dai_link, i, cpu) {
|
2020-03-30 09:47:37 +08:00
|
|
|
asoc_rtd_to_cpu(rtd, i) = snd_soc_find_dai(cpu);
|
|
|
|
if (!asoc_rtd_to_cpu(rtd, i)) {
|
2020-02-25 21:39:12 +08:00
|
|
|
dev_info(card->dev, "ASoC: CPU DAI %s not registered\n",
|
|
|
|
cpu->dai_name);
|
|
|
|
goto _err_defer;
|
|
|
|
}
|
2020-03-30 09:47:37 +08:00
|
|
|
snd_soc_rtd_add_component(rtd, asoc_rtd_to_cpu(rtd, i)->component);
|
2010-03-18 04:15:21 +08:00
|
|
|
}
|
2020-02-25 21:39:12 +08:00
|
|
|
|
2014-07-09 05:19:34 +08:00
|
|
|
/* Find CODEC from registered CODECs */
|
2019-06-27 20:13:50 +08:00
|
|
|
for_each_link_codecs(dai_link, i, codec) {
|
2020-03-30 09:47:37 +08:00
|
|
|
asoc_rtd_to_codec(rtd, i) = snd_soc_find_dai(codec);
|
|
|
|
if (!asoc_rtd_to_codec(rtd, i)) {
|
2019-01-18 17:55:04 +08:00
|
|
|
dev_info(card->dev, "ASoC: CODEC DAI %s not registered\n",
|
2019-06-27 20:13:50 +08:00
|
|
|
codec->dai_name);
|
2015-11-18 15:34:11 +08:00
|
|
|
goto _err_defer;
|
2014-07-09 05:19:34 +08:00
|
|
|
}
|
2019-06-27 20:13:50 +08:00
|
|
|
|
2020-03-30 09:47:37 +08:00
|
|
|
snd_soc_rtd_add_component(rtd, asoc_rtd_to_codec(rtd, i)->component);
|
2014-03-21 23:27:25 +08:00
|
|
|
}
|
|
|
|
|
2019-05-13 15:06:44 +08:00
|
|
|
/* Find PLATFORM from registered PLATFORMs */
|
2019-06-27 20:13:50 +08:00
|
|
|
for_each_link_platforms(dai_link, i, platform) {
|
|
|
|
for_each_component(component) {
|
|
|
|
if (!snd_soc_is_matching_component(platform, component))
|
|
|
|
continue;
|
2017-08-08 14:18:10 +08:00
|
|
|
|
2024-03-05 14:56:06 +08:00
|
|
|
if (snd_soc_component_is_dummy(component) && component->num_dai)
|
|
|
|
continue;
|
|
|
|
|
2020-01-10 10:35:54 +08:00
|
|
|
snd_soc_rtd_add_component(rtd, component);
|
2019-06-27 20:13:50 +08:00
|
|
|
}
|
2017-08-08 14:18:10 +08:00
|
|
|
}
|
|
|
|
|
2012-03-15 05:18:39 +08:00
|
|
|
return 0;
|
2015-11-18 15:34:11 +08:00
|
|
|
|
|
|
|
_err_defer:
|
2019-12-10 08:34:23 +08:00
|
|
|
snd_soc_remove_pcm_runtime(card, rtd);
|
2018-10-18 19:18:28 +08:00
|
|
|
return -EPROBE_DEFER;
|
2010-03-18 04:15:21 +08:00
|
|
|
}
|
2023-03-27 08:34:26 +08:00
|
|
|
|
|
|
|
int snd_soc_add_pcm_runtimes(struct snd_soc_card *card,
|
|
|
|
struct snd_soc_dai_link *dai_link,
|
|
|
|
int num_dai_link)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < num_dai_link; i++) {
|
|
|
|
int ret = snd_soc_add_pcm_runtime(card, dai_link + i);
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_add_pcm_runtimes);
|
2010-03-18 04:15:21 +08:00
|
|
|
|
ASoC: soc-core: add snd_soc_runtime_get_dai_fmt()
ASoC is using dai_link which specify DAI format (= dai_link->dai_fmt),
and it is selected by "Sound Card" driver in corrent implementation.
In other words, Sound Card *needs* to setup it.
But, it should be possible to automatically selected from CPU and
Codec driver settings.
This patch adds new .auto_selectable_formats support
at snd_soc_dai_ops.
By this patch, dai_fmt can be automatically selected from each
driver if both CPU / Codec driver had it.
Automatically selectable *field* is depends on each drivers.
For example, some driver want to select format "automatically",
but want to select other fields "manually", because of complex limitation.
Or other example, in case of both CPU and Codec are possible to be
clock provider, but the quality was different.
In these case, user need/want to *manually* select each fields
from Sound Card driver.
This .auto_selectable_formats can set priority.
For example, no limitaion format can be HI priority,
supported but has picky limitation format can be next priority, etc.
It uses Sound Card specified fields preferentially, and try to select
non-specific fields from CPU and Codec driver automatically
if all drivers have .auto_selectable_formats.
In other words, we can select all dai_fmt via Sound Card driver
same as before.
Link: https://lore.kernel.org/r/871rb3hypy.wl-kuninori.morimoto.gx@renesas.com
Link: https://lore.kernel.org/r/871racbx0w.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87h7ionc8s.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2021-05-27 10:26:12 +08:00
|
|
|
static void snd_soc_runtime_get_dai_fmt(struct snd_soc_pcm_runtime *rtd)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai_link *dai_link = rtd->dai_link;
|
|
|
|
struct snd_soc_dai *dai, *not_used;
|
|
|
|
u64 pos, possible_fmt;
|
|
|
|
unsigned int mask = 0, dai_fmt = 0;
|
|
|
|
int i, j, priority, pri, until;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Get selectable format from each DAIs.
|
|
|
|
*
|
|
|
|
****************************
|
|
|
|
* NOTE
|
|
|
|
* Using .auto_selectable_formats is not mandatory,
|
|
|
|
* we can select format manually from Sound Card.
|
|
|
|
* When use it, driver should list well tested format only.
|
|
|
|
****************************
|
|
|
|
*
|
|
|
|
* ex)
|
|
|
|
* auto_selectable_formats (= SND_SOC_POSSIBLE_xxx)
|
|
|
|
* (A) (B) (C)
|
|
|
|
* DAI0_: { 0x000F, 0x00F0, 0x0F00 };
|
|
|
|
* DAI1 : { 0xF000, 0x0F00 };
|
|
|
|
* (X) (Y)
|
|
|
|
*
|
|
|
|
* "until" will be 3 in this case (MAX array size from DAI0 and DAI1)
|
|
|
|
* Here is dev_dbg() message and comments
|
|
|
|
*
|
|
|
|
* priority = 1
|
|
|
|
* DAI0: (pri, fmt) = (1, 000000000000000F) // 1st check (A) DAI1 is not selected
|
|
|
|
* DAI1: (pri, fmt) = (0, 0000000000000000) // Necessary Waste
|
|
|
|
* DAI0: (pri, fmt) = (1, 000000000000000F) // 2nd check (A)
|
|
|
|
* DAI1: (pri, fmt) = (1, 000000000000F000) // (X)
|
|
|
|
* priority = 2
|
|
|
|
* DAI0: (pri, fmt) = (2, 00000000000000FF) // 3rd check (A) + (B)
|
|
|
|
* DAI1: (pri, fmt) = (1, 000000000000F000) // (X)
|
|
|
|
* DAI0: (pri, fmt) = (2, 00000000000000FF) // 4th check (A) + (B)
|
|
|
|
* DAI1: (pri, fmt) = (2, 000000000000FF00) // (X) + (Y)
|
|
|
|
* priority = 3
|
|
|
|
* DAI0: (pri, fmt) = (3, 0000000000000FFF) // 5th check (A) + (B) + (C)
|
|
|
|
* DAI1: (pri, fmt) = (2, 000000000000FF00) // (X) + (Y)
|
|
|
|
* found auto selected format: 0000000000000F00
|
|
|
|
*/
|
|
|
|
until = snd_soc_dai_get_fmt_max_priority(rtd);
|
|
|
|
for (priority = 1; priority <= until; priority++) {
|
|
|
|
for_each_rtd_dais(rtd, j, not_used) {
|
|
|
|
|
|
|
|
possible_fmt = ULLONG_MAX;
|
|
|
|
for_each_rtd_dais(rtd, i, dai) {
|
|
|
|
u64 fmt = 0;
|
|
|
|
|
|
|
|
pri = (j >= i) ? priority : priority - 1;
|
|
|
|
fmt = snd_soc_dai_get_fmt(dai, pri);
|
|
|
|
possible_fmt &= fmt;
|
|
|
|
}
|
|
|
|
if (possible_fmt)
|
|
|
|
goto found;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Not Found */
|
|
|
|
return;
|
|
|
|
found:
|
|
|
|
/*
|
|
|
|
* convert POSSIBLE_DAIFMT to DAIFMT
|
|
|
|
*
|
|
|
|
* Some basic/default settings on each is defined as 0.
|
|
|
|
* see
|
|
|
|
* SND_SOC_DAIFMT_NB_NF
|
|
|
|
* SND_SOC_DAIFMT_GATED
|
|
|
|
*
|
|
|
|
* SND_SOC_DAIFMT_xxx_MASK can't notice it if Sound Card specify
|
|
|
|
* these value, and will be overwrite to auto selected value.
|
|
|
|
*
|
|
|
|
* To avoid such issue, loop from 63 to 0 here.
|
|
|
|
* Small number of SND_SOC_POSSIBLE_xxx will be Hi priority.
|
|
|
|
* Basic/Default settings of each part and aboves are defined
|
|
|
|
* as Hi priority (= small number) of SND_SOC_POSSIBLE_xxx.
|
|
|
|
*/
|
|
|
|
for (i = 63; i >= 0; i--) {
|
|
|
|
pos = 1ULL << i;
|
|
|
|
switch (possible_fmt & pos) {
|
|
|
|
/*
|
|
|
|
* for format
|
|
|
|
*/
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_I2S:
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_RIGHT_J:
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_LEFT_J:
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_DSP_A:
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_DSP_B:
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_AC97:
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_PDM:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_FORMAT_MASK) | i;
|
|
|
|
break;
|
|
|
|
/*
|
|
|
|
* for clock
|
|
|
|
*/
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_CONT:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_CONT;
|
|
|
|
break;
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_GATED:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_MASK) | SND_SOC_DAIFMT_GATED;
|
|
|
|
break;
|
|
|
|
/*
|
|
|
|
* for clock invert
|
|
|
|
*/
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_NB_NF:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_NF;
|
|
|
|
break;
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_NB_IF:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_NB_IF;
|
|
|
|
break;
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_IB_NF:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_NF;
|
|
|
|
break;
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_IB_IF:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_INV_MASK) | SND_SOC_DAIFMT_IB_IF;
|
|
|
|
break;
|
|
|
|
/*
|
|
|
|
* for clock provider / consumer
|
|
|
|
*/
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_CBP_CFP:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBP_CFP;
|
|
|
|
break;
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_CBC_CFP:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBC_CFP;
|
|
|
|
break;
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_CBP_CFC:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBP_CFC;
|
|
|
|
break;
|
|
|
|
case SND_SOC_POSSIBLE_DAIFMT_CBC_CFC:
|
|
|
|
dai_fmt = (dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) | SND_SOC_DAIFMT_CBC_CFC;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Some driver might have very complex limitation.
|
|
|
|
* In such case, user want to auto-select non-limitation part,
|
|
|
|
* and want to manually specify complex part.
|
|
|
|
*
|
|
|
|
* Or for example, if both CPU and Codec can be clock provider,
|
|
|
|
* but because of its quality, user want to specify it manually.
|
|
|
|
*
|
|
|
|
* Use manually specified settings if sound card did.
|
|
|
|
*/
|
|
|
|
if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK))
|
|
|
|
mask |= SND_SOC_DAIFMT_FORMAT_MASK;
|
|
|
|
if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_CLOCK_MASK))
|
|
|
|
mask |= SND_SOC_DAIFMT_CLOCK_MASK;
|
|
|
|
if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_INV_MASK))
|
|
|
|
mask |= SND_SOC_DAIFMT_INV_MASK;
|
2021-06-08 08:11:50 +08:00
|
|
|
if (!(dai_link->dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK))
|
|
|
|
mask |= SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
|
ASoC: soc-core: add snd_soc_runtime_get_dai_fmt()
ASoC is using dai_link which specify DAI format (= dai_link->dai_fmt),
and it is selected by "Sound Card" driver in corrent implementation.
In other words, Sound Card *needs* to setup it.
But, it should be possible to automatically selected from CPU and
Codec driver settings.
This patch adds new .auto_selectable_formats support
at snd_soc_dai_ops.
By this patch, dai_fmt can be automatically selected from each
driver if both CPU / Codec driver had it.
Automatically selectable *field* is depends on each drivers.
For example, some driver want to select format "automatically",
but want to select other fields "manually", because of complex limitation.
Or other example, in case of both CPU and Codec are possible to be
clock provider, but the quality was different.
In these case, user need/want to *manually* select each fields
from Sound Card driver.
This .auto_selectable_formats can set priority.
For example, no limitaion format can be HI priority,
supported but has picky limitation format can be next priority, etc.
It uses Sound Card specified fields preferentially, and try to select
non-specific fields from CPU and Codec driver automatically
if all drivers have .auto_selectable_formats.
In other words, we can select all dai_fmt via Sound Card driver
same as before.
Link: https://lore.kernel.org/r/871rb3hypy.wl-kuninori.morimoto.gx@renesas.com
Link: https://lore.kernel.org/r/871racbx0w.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87h7ionc8s.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2021-05-27 10:26:12 +08:00
|
|
|
|
|
|
|
dai_link->dai_fmt |= (dai_fmt & mask);
|
|
|
|
}
|
|
|
|
|
2021-05-27 10:25:36 +08:00
|
|
|
/**
|
|
|
|
* snd_soc_runtime_set_dai_fmt() - Change DAI link format for a ASoC runtime
|
|
|
|
* @rtd: The runtime for which the DAI link format should be changed
|
|
|
|
* @dai_fmt: The new DAI link format
|
|
|
|
*
|
|
|
|
* This function updates the DAI link format for all DAIs connected to the DAI
|
|
|
|
* link for the specified runtime.
|
|
|
|
*
|
|
|
|
* Note: For setups with a static format set the dai_fmt field in the
|
|
|
|
* corresponding snd_dai_link struct instead of using this function.
|
|
|
|
*
|
|
|
|
* Returns 0 on success, otherwise a negative error code.
|
|
|
|
*/
|
|
|
|
int snd_soc_runtime_set_dai_fmt(struct snd_soc_pcm_runtime *rtd,
|
|
|
|
unsigned int dai_fmt)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai *cpu_dai;
|
|
|
|
struct snd_soc_dai *codec_dai;
|
|
|
|
unsigned int i;
|
|
|
|
int ret;
|
|
|
|
|
2021-10-18 10:05:44 +08:00
|
|
|
if (!dai_fmt)
|
|
|
|
return 0;
|
|
|
|
|
2021-05-27 10:25:36 +08:00
|
|
|
for_each_rtd_codec_dais(rtd, i, codec_dai) {
|
|
|
|
ret = snd_soc_dai_set_fmt(codec_dai, dai_fmt);
|
|
|
|
if (ret != 0 && ret != -ENOTSUPP)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-05-19 23:42:50 +08:00
|
|
|
/* Flip the polarity for the "CPU" end of link */
|
|
|
|
dai_fmt = snd_soc_daifmt_clock_provider_flipped(dai_fmt);
|
2021-06-14 08:56:54 +08:00
|
|
|
|
2021-05-27 10:25:36 +08:00
|
|
|
for_each_rtd_cpu_dais(rtd, i, cpu_dai) {
|
2022-05-19 23:42:50 +08:00
|
|
|
ret = snd_soc_dai_set_fmt(cpu_dai, dai_fmt);
|
2021-05-27 10:25:36 +08:00
|
|
|
if (ret != 0 && ret != -ENOTSUPP)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_runtime_set_dai_fmt);
|
|
|
|
|
2019-12-10 08:34:44 +08:00
|
|
|
static int soc_init_pcm_runtime(struct snd_soc_card *card,
|
|
|
|
struct snd_soc_pcm_runtime *rtd)
|
2019-12-10 08:34:36 +08:00
|
|
|
{
|
|
|
|
struct snd_soc_dai_link *dai_link = rtd->dai_link;
|
2020-03-30 09:47:37 +08:00
|
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
2019-12-10 08:34:36 +08:00
|
|
|
struct snd_soc_component *component;
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
int ret, num, i;
|
2019-12-10 08:34:36 +08:00
|
|
|
|
|
|
|
/* do machine specific initialization */
|
2020-05-25 08:57:14 +08:00
|
|
|
ret = snd_soc_link_init(rtd);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2019-12-10 08:34:36 +08:00
|
|
|
|
ASoC: soc-core: add snd_soc_runtime_get_dai_fmt()
ASoC is using dai_link which specify DAI format (= dai_link->dai_fmt),
and it is selected by "Sound Card" driver in corrent implementation.
In other words, Sound Card *needs* to setup it.
But, it should be possible to automatically selected from CPU and
Codec driver settings.
This patch adds new .auto_selectable_formats support
at snd_soc_dai_ops.
By this patch, dai_fmt can be automatically selected from each
driver if both CPU / Codec driver had it.
Automatically selectable *field* is depends on each drivers.
For example, some driver want to select format "automatically",
but want to select other fields "manually", because of complex limitation.
Or other example, in case of both CPU and Codec are possible to be
clock provider, but the quality was different.
In these case, user need/want to *manually* select each fields
from Sound Card driver.
This .auto_selectable_formats can set priority.
For example, no limitaion format can be HI priority,
supported but has picky limitation format can be next priority, etc.
It uses Sound Card specified fields preferentially, and try to select
non-specific fields from CPU and Codec driver automatically
if all drivers have .auto_selectable_formats.
In other words, we can select all dai_fmt via Sound Card driver
same as before.
Link: https://lore.kernel.org/r/871rb3hypy.wl-kuninori.morimoto.gx@renesas.com
Link: https://lore.kernel.org/r/871racbx0w.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87h7ionc8s.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2021-05-27 10:26:12 +08:00
|
|
|
snd_soc_runtime_get_dai_fmt(rtd);
|
2021-10-18 10:05:44 +08:00
|
|
|
ret = snd_soc_runtime_set_dai_fmt(rtd, dai_link->dai_fmt);
|
|
|
|
if (ret)
|
2023-09-29 18:32:43 +08:00
|
|
|
goto err;
|
2019-12-10 08:34:36 +08:00
|
|
|
|
|
|
|
/* add DPCM sysfs entries */
|
|
|
|
soc_dpcm_debugfs_add(rtd);
|
|
|
|
|
|
|
|
num = rtd->num;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* most drivers will register their PCMs using DAI link ordering but
|
|
|
|
* topology based drivers can use the DAI link id field to set PCM
|
|
|
|
* device number and then use rtd + a base offset of the BEs.
|
|
|
|
*/
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
for_each_rtd_components(rtd, i, component) {
|
2019-12-10 08:34:36 +08:00
|
|
|
if (!component->driver->use_dai_pcm_id)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (rtd->dai_link->no_pcm)
|
|
|
|
num += component->driver->be_pcm_base;
|
|
|
|
else
|
|
|
|
num = rtd->dai_link->id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* create compress_device if possible */
|
|
|
|
ret = snd_soc_dai_compress_new(cpu_dai, rtd, num);
|
2021-03-15 08:58:37 +08:00
|
|
|
if (ret != -ENOTSUPP)
|
2023-09-29 18:32:43 +08:00
|
|
|
goto err;
|
2019-12-10 08:34:36 +08:00
|
|
|
|
|
|
|
/* create the pcm */
|
|
|
|
ret = soc_new_pcm(rtd, num);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(card->dev, "ASoC: can't create pcm %s :%d\n",
|
|
|
|
dai_link->stream_name, ret);
|
2023-09-29 18:32:43 +08:00
|
|
|
goto err;
|
2019-12-10 08:34:36 +08:00
|
|
|
}
|
2020-03-16 14:37:20 +08:00
|
|
|
|
2023-09-29 18:32:43 +08:00
|
|
|
ret = snd_soc_pcm_dai_new(rtd);
|
|
|
|
if (ret < 0)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
rtd->initialized = true;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
snd_soc_link_exit(rtd);
|
|
|
|
return ret;
|
2019-12-10 08:34:36 +08:00
|
|
|
}
|
|
|
|
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
static void soc_set_name_prefix(struct snd_soc_card *card,
|
|
|
|
struct snd_soc_component *component)
|
|
|
|
{
|
2019-12-10 08:34:52 +08:00
|
|
|
struct device_node *of_node = soc_component_to_node(component);
|
2019-12-11 12:31:39 +08:00
|
|
|
const char *str;
|
|
|
|
int ret, i;
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
|
2019-12-10 08:34:48 +08:00
|
|
|
for (i = 0; i < card->num_configs; i++) {
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
struct snd_soc_codec_conf *map = &card->codec_conf[i];
|
|
|
|
|
2020-11-02 23:10:08 +08:00
|
|
|
if (snd_soc_is_matching_component(&map->dlc, component) &&
|
|
|
|
map->name_prefix) {
|
2019-12-13 08:54:36 +08:00
|
|
|
component->name_prefix = map->name_prefix;
|
|
|
|
return;
|
|
|
|
}
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If there is no configuration table or no match in the table,
|
|
|
|
* check if a prefix is provided in the node
|
|
|
|
*/
|
2019-12-11 12:31:39 +08:00
|
|
|
ret = of_property_read_string(of_node, "sound-name-prefix", &str);
|
|
|
|
if (ret < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
component->name_prefix = str;
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
}
|
|
|
|
|
2019-11-06 09:07:46 +08:00
|
|
|
static void soc_remove_component(struct snd_soc_component *component,
|
|
|
|
int probed)
|
2019-01-21 08:32:55 +08:00
|
|
|
{
|
2019-11-06 09:07:46 +08:00
|
|
|
|
|
|
|
if (!component->card)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (probed)
|
|
|
|
snd_soc_component_remove(component);
|
|
|
|
|
2019-09-18 21:31:31 +08:00
|
|
|
list_del_init(&component->card_list);
|
2019-01-21 08:32:55 +08:00
|
|
|
snd_soc_dapm_free(snd_soc_component_get_dapm(component));
|
|
|
|
soc_cleanup_component_debugfs(component);
|
|
|
|
component->card = NULL;
|
2019-08-08 10:51:31 +08:00
|
|
|
snd_soc_component_module_put_when_remove(component);
|
2019-01-21 08:32:55 +08:00
|
|
|
}
|
|
|
|
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
static int soc_probe_component(struct snd_soc_card *card,
|
|
|
|
struct snd_soc_component *component)
|
|
|
|
{
|
|
|
|
struct snd_soc_dapm_context *dapm =
|
|
|
|
snd_soc_component_get_dapm(component);
|
|
|
|
struct snd_soc_dai *dai;
|
2019-11-06 09:07:46 +08:00
|
|
|
int probed = 0;
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
int ret;
|
|
|
|
|
2021-04-16 10:00:26 +08:00
|
|
|
if (snd_soc_component_is_dummy(component))
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (component->card) {
|
|
|
|
if (component->card != card) {
|
|
|
|
dev_err(component->dev,
|
2023-09-29 18:32:42 +08:00
|
|
|
"Trying to bind component \"%s\" to card \"%s\" but is already bound to card \"%s\"\n",
|
|
|
|
component->name, card->name, component->card->name);
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = snd_soc_component_module_get_when_probe(component);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
component->card = card;
|
|
|
|
soc_set_name_prefix(card, component);
|
|
|
|
|
|
|
|
soc_init_component_debugfs(component);
|
|
|
|
|
ASoC: soc-core: add snd_soc_dapm_init()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc-dapm has snd_soc_dapm_free() which cleanups debugfs, widgets, list.
But, there is no paired initialize function.
This patch adds snd_soc_dapm_init() and initilaizing dapm
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87pnkw7lbj.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:52 +08:00
|
|
|
snd_soc_dapm_init(dapm, card, component);
|
2019-08-23 08:58:47 +08:00
|
|
|
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
ret = snd_soc_dapm_new_controls(dapm,
|
|
|
|
component->driver->dapm_widgets,
|
|
|
|
component->driver->num_dapm_widgets);
|
|
|
|
|
|
|
|
if (ret != 0) {
|
|
|
|
dev_err(component->dev,
|
|
|
|
"Failed to create new controls %d\n", ret);
|
|
|
|
goto err_probe;
|
|
|
|
}
|
|
|
|
|
|
|
|
for_each_component_dais(component, dai) {
|
|
|
|
ret = snd_soc_dapm_new_dai_widgets(dapm, dai);
|
|
|
|
if (ret != 0) {
|
|
|
|
dev_err(component->dev,
|
|
|
|
"Failed to create DAI widgets %d\n", ret);
|
|
|
|
goto err_probe;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = snd_soc_component_probe(component);
|
2021-03-15 08:58:41 +08:00
|
|
|
if (ret < 0)
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
goto err_probe;
|
2021-03-15 08:58:41 +08:00
|
|
|
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
WARN(dapm->idle_bias_off &&
|
|
|
|
dapm->bias_level != SND_SOC_BIAS_OFF,
|
|
|
|
"codec %s can not start from non-off bias with idle_bias_off==1\n",
|
|
|
|
component->name);
|
2019-11-06 09:07:46 +08:00
|
|
|
probed = 1;
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
|
2020-06-04 16:07:54 +08:00
|
|
|
/*
|
|
|
|
* machine specific init
|
|
|
|
* see
|
|
|
|
* snd_soc_component_set_aux()
|
|
|
|
*/
|
|
|
|
ret = snd_soc_component_init(component);
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_probe;
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
|
|
|
|
ret = snd_soc_add_component_controls(component,
|
|
|
|
component->driver->controls,
|
|
|
|
component->driver->num_controls);
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_probe;
|
|
|
|
|
|
|
|
ret = snd_soc_dapm_add_routes(dapm,
|
|
|
|
component->driver->dapm_routes,
|
|
|
|
component->driver->num_dapm_routes);
|
2020-03-10 03:27:43 +08:00
|
|
|
if (ret < 0) {
|
|
|
|
if (card->disable_route_checks) {
|
|
|
|
dev_info(card->dev,
|
|
|
|
"%s: disable_route_checks set, ignoring errors on add_routes\n",
|
|
|
|
__func__);
|
|
|
|
} else {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"%s: snd_soc_dapm_add_routes failed: %d\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto err_probe;
|
|
|
|
}
|
|
|
|
}
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
|
|
|
|
/* see for_each_card_components */
|
|
|
|
list_add(&component->card_list, &card->component_dev_list);
|
|
|
|
|
|
|
|
err_probe:
|
|
|
|
if (ret < 0)
|
2019-11-06 09:07:46 +08:00
|
|
|
soc_remove_component(component, probed);
|
ASoC: soc-core: move soc_probe_component() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_comonent() has paired soc_remove_comonent(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_component() next to
soc_remove_component().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgps7lbt.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:42 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-09-04 08:14:57 +08:00
|
|
|
static void soc_remove_link_dais(struct snd_soc_card *card)
|
2014-03-21 23:27:26 +08:00
|
|
|
{
|
2019-09-04 08:14:57 +08:00
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
|
|
|
int order;
|
|
|
|
|
|
|
|
for_each_comp_order(order) {
|
|
|
|
for_each_card_rtds(card, rtd) {
|
2020-04-24 07:15:20 +08:00
|
|
|
/* remove all rtd connected DAIs in good order */
|
|
|
|
snd_soc_pcm_dai_remove(rtd, order);
|
2019-09-04 08:14:57 +08:00
|
|
|
}
|
|
|
|
}
|
2010-03-18 04:15:21 +08:00
|
|
|
}
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2019-09-04 08:15:23 +08:00
|
|
|
static int soc_probe_link_dais(struct snd_soc_card *card)
|
|
|
|
{
|
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
2020-04-24 07:15:15 +08:00
|
|
|
int order, ret;
|
2019-09-04 08:15:23 +08:00
|
|
|
|
|
|
|
for_each_comp_order(order) {
|
|
|
|
for_each_card_rtds(card, rtd) {
|
2020-04-24 07:15:15 +08:00
|
|
|
/* probe all rtd connected DAIs in good order */
|
|
|
|
ret = snd_soc_pcm_dai_probe(rtd, order);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2019-09-04 08:15:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-09-04 08:14:51 +08:00
|
|
|
static void soc_remove_link_components(struct snd_soc_card *card)
|
2012-06-09 02:34:23 +08:00
|
|
|
{
|
2014-08-19 21:51:21 +08:00
|
|
|
struct snd_soc_component *component;
|
2019-09-04 08:14:51 +08:00
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
int i, order;
|
2012-06-09 02:34:23 +08:00
|
|
|
|
2019-09-04 08:14:51 +08:00
|
|
|
for_each_comp_order(order) {
|
|
|
|
for_each_card_rtds(card, rtd) {
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
for_each_rtd_components(rtd, i, component) {
|
2019-09-04 08:14:51 +08:00
|
|
|
if (component->driver->remove_order != order)
|
|
|
|
continue;
|
2012-06-09 02:34:23 +08:00
|
|
|
|
2019-11-06 09:07:46 +08:00
|
|
|
soc_remove_component(component, 1);
|
2019-09-04 08:14:51 +08:00
|
|
|
}
|
|
|
|
}
|
2012-06-09 02:34:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-04 08:14:46 +08:00
|
|
|
static int soc_probe_link_components(struct snd_soc_card *card)
|
ASoC: soc-core: move soc_probe_link_components() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_link_components() has paired soc_remove_link_components(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_link_components() next to
soc_remove_link_components().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87o90g7lbd.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:58 +08:00
|
|
|
{
|
|
|
|
struct snd_soc_component *component;
|
2019-09-04 08:14:46 +08:00
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
int i, ret, order;
|
ASoC: soc-core: move soc_probe_link_components() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_link_components() has paired soc_remove_link_components(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_link_components() next to
soc_remove_link_components().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87o90g7lbd.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:58 +08:00
|
|
|
|
2019-09-04 08:14:46 +08:00
|
|
|
for_each_comp_order(order) {
|
|
|
|
for_each_card_rtds(card, rtd) {
|
ASoC: soc-core: remove snd_soc_rtdcom_list
Current ALSA SoC is using struct snd_soc_rtdcom_list to
connecting component to rtd by using list_head.
struct snd_soc_rtdcom_list {
struct snd_soc_component *component;
struct list_head list; /* rtd::component_list */
};
struct snd_soc_pcm_runtime {
...
struct list_head component_list; /* list of connected components */
...
};
The CPU/Codec/Platform component which will be connected to rtd (a)
is indicated via dai_link at snd_soc_add_pcm_runtime()
int snd_soc_add_pcm_runtime(...)
{
...
/* Find CPU from registered CPUs */
rtd->cpu_dai = snd_soc_find_dai(dai_link->cpus);
...
(a) snd_soc_rtdcom_add(rtd, rtd->cpu_dai->component);
...
/* Find CODEC from registered CODECs */
(b) for_each_link_codecs(dai_link, i, codec) {
rtd->codec_dais[i] = snd_soc_find_dai(codec);
...
(a) snd_soc_rtdcom_add(rtd, rtd->codec_dais[i]->component);
}
...
/* Find PLATFORM from registered PLATFORMs */
(b) for_each_link_platforms(dai_link, i, platform) {
for_each_component(component) {
...
(a) snd_soc_rtdcom_add(rtd, component);
}
}
}
It shows, it is possible to know how many components will be
connected to rtd by using
dai_link->num_cpus
dai_link->num_codecs
dai_link->num_platforms
If so, we can use component pointer array instead of list_head,
in such case, code can be more simple.
This patch removes struct snd_soc_rtdcom_list that is only
of temporary value, and convert to pointer array.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-By: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/87a76wt4wm.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2020-01-10 10:35:21 +08:00
|
|
|
for_each_rtd_components(rtd, i, component) {
|
2019-09-04 08:14:46 +08:00
|
|
|
if (component->driver->probe_order != order)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ret = soc_probe_component(card, component);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
}
|
ASoC: soc-core: move soc_probe_link_components() position
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_probe_link_components() has paired soc_remove_link_components(),
but, these are implemented at different place.
So it is difficult to confirm code.
This patch moves soc_probe_link_components() next to
soc_remove_link_components().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87o90g7lbd.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-09-04 08:15:40 +08:00
|
|
|
static void soc_unbind_aux_dev(struct snd_soc_card *card)
|
ASoC: soc-core: add soc_unbind_aux_dev()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc-core.c has soc_bind_aux_dev(), but, there is no its paired
soc_unbind_aux_dev().
This patch adds soc_unbind_aux_dev().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgpcor14.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-09-04 08:15:35 +08:00
|
|
|
{
|
2019-09-04 08:15:40 +08:00
|
|
|
struct snd_soc_component *component, *_component;
|
|
|
|
|
|
|
|
for_each_card_auxs_safe(card, component, _component) {
|
2020-06-04 16:07:54 +08:00
|
|
|
/* for snd_soc_component_init() */
|
|
|
|
snd_soc_component_set_aux(component, NULL);
|
2019-09-04 08:15:40 +08:00
|
|
|
list_del(&component->card_aux_list);
|
|
|
|
}
|
ASoC: soc-core: add soc_unbind_aux_dev()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc-core.c has soc_bind_aux_dev(), but, there is no its paired
soc_unbind_aux_dev().
This patch adds soc_unbind_aux_dev().
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87sgpcor14.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-09-04 08:15:35 +08:00
|
|
|
}
|
|
|
|
|
2019-09-04 08:15:28 +08:00
|
|
|
static int soc_bind_aux_dev(struct snd_soc_card *card)
|
2014-04-28 22:07:22 +08:00
|
|
|
{
|
2016-01-06 13:29:31 +08:00
|
|
|
struct snd_soc_component *component;
|
2019-09-04 08:15:28 +08:00
|
|
|
struct snd_soc_aux_dev *aux;
|
|
|
|
int i;
|
2019-08-08 13:52:33 +08:00
|
|
|
|
2019-09-04 08:15:28 +08:00
|
|
|
for_each_card_pre_auxs(card, i, aux) {
|
|
|
|
/* codecs, usually analog devices */
|
|
|
|
component = soc_find_component(&aux->dlc);
|
|
|
|
if (!component)
|
|
|
|
return -EPROBE_DEFER;
|
2016-11-30 14:22:55 +08:00
|
|
|
|
2020-06-04 16:07:54 +08:00
|
|
|
/* for snd_soc_component_init() */
|
|
|
|
snd_soc_component_set_aux(component, aux);
|
2019-09-04 08:15:28 +08:00
|
|
|
/* see for_each_card_auxs */
|
|
|
|
list_add(&component->card_aux_list, &card->aux_comp_list);
|
|
|
|
}
|
2014-07-02 04:13:47 +08:00
|
|
|
return 0;
|
2012-03-15 05:18:39 +08:00
|
|
|
}
|
|
|
|
|
2016-01-06 13:29:31 +08:00
|
|
|
static int soc_probe_aux_devices(struct snd_soc_card *card)
|
2010-11-25 23:47:38 +08:00
|
|
|
{
|
2019-11-06 09:08:06 +08:00
|
|
|
struct snd_soc_component *component;
|
2016-01-06 13:29:31 +08:00
|
|
|
int order;
|
2014-07-02 04:13:47 +08:00
|
|
|
int ret;
|
2014-04-28 22:07:22 +08:00
|
|
|
|
2018-09-18 09:30:41 +08:00
|
|
|
for_each_comp_order(order) {
|
2019-11-06 09:08:06 +08:00
|
|
|
for_each_card_auxs(card, component) {
|
|
|
|
if (component->driver->probe_order != order)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ret = soc_probe_component(card, component);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2014-07-02 04:13:48 +08:00
|
|
|
}
|
|
|
|
}
|
2010-11-25 23:47:38 +08:00
|
|
|
|
2016-01-06 13:29:31 +08:00
|
|
|
return 0;
|
2010-11-25 23:47:38 +08:00
|
|
|
}
|
|
|
|
|
2016-01-06 13:29:31 +08:00
|
|
|
static void soc_remove_aux_devices(struct snd_soc_card *card)
|
2010-11-25 23:47:38 +08:00
|
|
|
{
|
2016-01-06 13:29:31 +08:00
|
|
|
struct snd_soc_component *comp, *_comp;
|
|
|
|
int order;
|
2010-11-25 23:47:38 +08:00
|
|
|
|
2018-09-18 09:30:41 +08:00
|
|
|
for_each_comp_order(order) {
|
2019-08-08 13:54:44 +08:00
|
|
|
for_each_card_auxs_safe(card, comp, _comp) {
|
2019-09-04 08:15:40 +08:00
|
|
|
if (comp->driver->remove_order == order)
|
2019-11-06 09:07:46 +08:00
|
|
|
soc_remove_component(comp, 1);
|
2016-01-06 13:29:31 +08:00
|
|
|
}
|
2010-11-25 23:47:38 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-24 14:54:41 +08:00
|
|
|
#ifdef CONFIG_DMI
|
2019-12-11 12:32:05 +08:00
|
|
|
/*
|
|
|
|
* If a DMI filed contain strings in this blacklist (e.g.
|
|
|
|
* "Type2 - Board Manufacturer" or "Type1 - TBD by OEM"), it will be taken
|
|
|
|
* as invalid and dropped when setting the card long name from DMI info.
|
|
|
|
*/
|
|
|
|
static const char * const dmi_blacklist[] = {
|
|
|
|
"To be filled by OEM",
|
|
|
|
"TBD by OEM",
|
|
|
|
"Default String",
|
|
|
|
"Board Manufacturer",
|
|
|
|
"Board Vendor Name",
|
|
|
|
"Board Product Name",
|
|
|
|
NULL, /* terminator */
|
|
|
|
};
|
|
|
|
|
2018-10-18 19:18:28 +08:00
|
|
|
/*
|
|
|
|
* Trim special characters, and replace '-' with '_' since '-' is used to
|
2017-01-14 16:13:02 +08:00
|
|
|
* separate different DMI fields in the card long name. Only number and
|
|
|
|
* alphabet characters and a few separator characters are kept.
|
|
|
|
*/
|
|
|
|
static void cleanup_dmi_name(char *name)
|
|
|
|
{
|
|
|
|
int i, j = 0;
|
|
|
|
|
|
|
|
for (i = 0; name[i]; i++) {
|
|
|
|
if (isalnum(name[i]) || (name[i] == '.')
|
|
|
|
|| (name[i] == '_'))
|
|
|
|
name[j++] = name[i];
|
|
|
|
else if (name[i] == '-')
|
|
|
|
name[j++] = '_';
|
|
|
|
}
|
|
|
|
|
|
|
|
name[j] = '\0';
|
|
|
|
}
|
|
|
|
|
2018-10-18 19:18:28 +08:00
|
|
|
/*
|
|
|
|
* Check if a DMI field is valid, i.e. not containing any string
|
2017-06-28 15:01:39 +08:00
|
|
|
* in the black list.
|
|
|
|
*/
|
|
|
|
static int is_dmi_valid(const char *field)
|
|
|
|
{
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
while (dmi_blacklist[i]) {
|
|
|
|
if (strstr(field, dmi_blacklist[i]))
|
|
|
|
return 0;
|
|
|
|
i++;
|
2017-06-30 00:27:13 +08:00
|
|
|
}
|
2017-06-28 15:01:39 +08:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-11-21 01:44:34 +08:00
|
|
|
/*
|
|
|
|
* Append a string to card->dmi_longname with character cleanups.
|
|
|
|
*/
|
|
|
|
static void append_dmi_string(struct snd_soc_card *card, const char *str)
|
|
|
|
{
|
|
|
|
char *dst = card->dmi_longname;
|
|
|
|
size_t dst_len = sizeof(card->dmi_longname);
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
len = strlen(dst);
|
|
|
|
snprintf(dst + len, dst_len - len, "-%s", str);
|
|
|
|
|
|
|
|
len++; /* skip the separator "-" */
|
|
|
|
if (len < dst_len)
|
|
|
|
cleanup_dmi_name(dst + len);
|
|
|
|
}
|
|
|
|
|
2017-01-14 16:13:02 +08:00
|
|
|
/**
|
|
|
|
* snd_soc_set_dmi_name() - Register DMI names to card
|
|
|
|
* @card: The card to register DMI names
|
|
|
|
* @flavour: The flavour "differentiator" for the card amongst its peers.
|
|
|
|
*
|
|
|
|
* An Intel machine driver may be used by many different devices but are
|
|
|
|
* difficult for userspace to differentiate, since machine drivers ususally
|
|
|
|
* use their own name as the card short name and leave the card long name
|
|
|
|
* blank. To differentiate such devices and fix bugs due to lack of
|
|
|
|
* device-specific configurations, this function allows DMI info to be used
|
|
|
|
* as the sound card long name, in the format of
|
|
|
|
* "vendor-product-version-board"
|
|
|
|
* (Character '-' is used to separate different DMI fields here).
|
|
|
|
* This will help the user space to load the device-specific Use Case Manager
|
|
|
|
* (UCM) configurations for the card.
|
|
|
|
*
|
|
|
|
* Possible card long names may be:
|
|
|
|
* DellInc.-XPS139343-01-0310JH
|
|
|
|
* ASUSTeKCOMPUTERINC.-T100TA-1.0-T100TA
|
|
|
|
* Circuitco-MinnowboardMaxD0PLATFORM-D0-MinnowBoardMAX
|
|
|
|
*
|
|
|
|
* This function also supports flavoring the card longname to provide
|
|
|
|
* the extra differentiation, like "vendor-product-version-board-flavor".
|
|
|
|
*
|
|
|
|
* We only keep number and alphabet characters and a few separator characters
|
|
|
|
* in the card long name since UCM in the user space uses the card long names
|
|
|
|
* as card configuration directory names and AudoConf cannot support special
|
|
|
|
* charactors like SPACE.
|
|
|
|
*
|
|
|
|
* Returns 0 on success, otherwise a negative error code.
|
|
|
|
*/
|
|
|
|
int snd_soc_set_dmi_name(struct snd_soc_card *card, const char *flavour)
|
|
|
|
{
|
2021-07-29 09:15:33 +08:00
|
|
|
const char *vendor, *product, *board;
|
2017-01-14 16:13:02 +08:00
|
|
|
|
|
|
|
if (card->long_name)
|
|
|
|
return 0; /* long name already set by driver or from DMI */
|
|
|
|
|
2021-03-11 03:39:27 +08:00
|
|
|
if (!dmi_available)
|
2021-03-03 19:55:26 +08:00
|
|
|
return 0;
|
|
|
|
|
2019-11-21 01:44:34 +08:00
|
|
|
/* make up dmi long name as: vendor-product-version-board */
|
2017-01-14 16:13:02 +08:00
|
|
|
vendor = dmi_get_system_info(DMI_BOARD_VENDOR);
|
2017-06-28 15:01:39 +08:00
|
|
|
if (!vendor || !is_dmi_valid(vendor)) {
|
2017-01-14 16:13:02 +08:00
|
|
|
dev_warn(card->dev, "ASoC: no DMI vendor name!\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-11-21 01:44:34 +08:00
|
|
|
snprintf(card->dmi_longname, sizeof(card->dmi_longname), "%s", vendor);
|
2017-01-14 16:13:02 +08:00
|
|
|
cleanup_dmi_name(card->dmi_longname);
|
|
|
|
|
|
|
|
product = dmi_get_system_info(DMI_PRODUCT_NAME);
|
2017-06-28 15:01:39 +08:00
|
|
|
if (product && is_dmi_valid(product)) {
|
2021-07-29 09:15:33 +08:00
|
|
|
const char *product_version = dmi_get_system_info(DMI_PRODUCT_VERSION);
|
|
|
|
|
2019-11-21 01:44:34 +08:00
|
|
|
append_dmi_string(card, product);
|
2017-01-14 16:13:02 +08:00
|
|
|
|
2018-10-18 19:18:28 +08:00
|
|
|
/*
|
|
|
|
* some vendors like Lenovo may only put a self-explanatory
|
2017-01-14 16:13:02 +08:00
|
|
|
* name in the product version field
|
|
|
|
*/
|
2019-11-21 01:44:34 +08:00
|
|
|
if (product_version && is_dmi_valid(product_version))
|
|
|
|
append_dmi_string(card, product_version);
|
2017-01-14 16:13:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
board = dmi_get_system_info(DMI_BOARD_NAME);
|
2017-06-28 15:01:39 +08:00
|
|
|
if (board && is_dmi_valid(board)) {
|
2019-11-21 01:44:35 +08:00
|
|
|
if (!product || strcasecmp(board, product))
|
|
|
|
append_dmi_string(card, board);
|
2017-01-14 16:13:02 +08:00
|
|
|
} else if (!product) {
|
|
|
|
/* fall back to using legacy name */
|
|
|
|
dev_warn(card->dev, "ASoC: no DMI board/product name!\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add flavour to dmi long name */
|
2019-11-21 01:44:34 +08:00
|
|
|
if (flavour)
|
|
|
|
append_dmi_string(card, flavour);
|
2017-01-14 16:13:02 +08:00
|
|
|
|
|
|
|
/* set the card long name */
|
|
|
|
card->long_name = card->dmi_longname;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_set_dmi_name);
|
2017-04-24 14:54:41 +08:00
|
|
|
#endif /* CONFIG_DMI */
|
2017-01-14 16:13:02 +08:00
|
|
|
|
2018-07-02 23:59:54 +08:00
|
|
|
static void soc_check_tplg_fes(struct snd_soc_card *card)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *component;
|
|
|
|
const struct snd_soc_component_driver *comp_drv;
|
|
|
|
struct snd_soc_dai_link *dai_link;
|
|
|
|
int i;
|
|
|
|
|
2018-09-21 13:23:01 +08:00
|
|
|
for_each_component(component) {
|
2018-07-02 23:59:54 +08:00
|
|
|
|
2019-09-26 02:33:58 +08:00
|
|
|
/* does this component override BEs ? */
|
2018-07-02 23:59:54 +08:00
|
|
|
if (!component->driver->ignore_machine)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* for this machine ? */
|
2019-03-02 09:08:51 +08:00
|
|
|
if (!strcmp(component->driver->ignore_machine,
|
|
|
|
card->dev->driver->name))
|
|
|
|
goto match;
|
2018-07-02 23:59:54 +08:00
|
|
|
if (strcmp(component->driver->ignore_machine,
|
2019-03-02 09:08:51 +08:00
|
|
|
dev_name(card->dev)))
|
2018-07-02 23:59:54 +08:00
|
|
|
continue;
|
2019-03-02 09:08:51 +08:00
|
|
|
match:
|
2018-07-02 23:59:54 +08:00
|
|
|
/* machine matches, so override the rtd data */
|
2018-09-18 09:28:49 +08:00
|
|
|
for_each_card_prelinks(card, i, dai_link) {
|
2018-07-02 23:59:54 +08:00
|
|
|
|
|
|
|
/* ignore this FE */
|
|
|
|
if (dai_link->dynamic) {
|
|
|
|
dai_link->ignore = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-06-13 04:40:48 +08:00
|
|
|
dev_dbg(card->dev, "info: override BE DAI link %s\n",
|
|
|
|
card->dai_link[i].name);
|
2018-07-02 23:59:54 +08:00
|
|
|
|
|
|
|
/* override platform component */
|
2019-06-06 12:22:19 +08:00
|
|
|
if (!dai_link->platforms) {
|
2018-08-31 11:10:08 +08:00
|
|
|
dev_err(card->dev, "init platform error");
|
|
|
|
continue;
|
|
|
|
}
|
2021-04-14 18:12:12 +08:00
|
|
|
|
|
|
|
if (component->dev->of_node)
|
|
|
|
dai_link->platforms->of_node = component->dev->of_node;
|
|
|
|
else
|
|
|
|
dai_link->platforms->name = component->name;
|
2018-07-02 23:59:54 +08:00
|
|
|
|
|
|
|
/* convert non BE into BE */
|
2020-06-09 03:44:13 +08:00
|
|
|
if (!dai_link->no_pcm) {
|
|
|
|
dai_link->no_pcm = 1;
|
|
|
|
|
|
|
|
if (dai_link->dpcm_playback)
|
|
|
|
dev_warn(card->dev,
|
|
|
|
"invalid configuration, dailink %s has flags no_pcm=0 and dpcm_playback=1\n",
|
|
|
|
dai_link->name);
|
|
|
|
if (dai_link->dpcm_capture)
|
|
|
|
dev_warn(card->dev,
|
|
|
|
"invalid configuration, dailink %s has flags no_pcm=0 and dpcm_capture=1\n",
|
|
|
|
dai_link->name);
|
|
|
|
|
|
|
|
/* convert normal link into DPCM one */
|
|
|
|
if (!(dai_link->dpcm_playback ||
|
|
|
|
dai_link->dpcm_capture)) {
|
|
|
|
dai_link->dpcm_playback = !dai_link->capture_only;
|
|
|
|
dai_link->dpcm_capture = !dai_link->playback_only;
|
|
|
|
}
|
|
|
|
}
|
2018-07-02 23:59:54 +08:00
|
|
|
|
2020-05-25 08:57:36 +08:00
|
|
|
/*
|
|
|
|
* override any BE fixups
|
|
|
|
* see
|
|
|
|
* snd_soc_link_be_hw_params_fixup()
|
|
|
|
*/
|
2018-07-02 23:59:54 +08:00
|
|
|
dai_link->be_hw_params_fixup =
|
|
|
|
component->driver->be_hw_params_fixup;
|
|
|
|
|
2018-10-18 19:18:28 +08:00
|
|
|
/*
|
|
|
|
* most BE links don't set stream name, so set it to
|
2018-07-02 23:59:54 +08:00
|
|
|
* dai link name if it's NULL to help bind widgets.
|
|
|
|
*/
|
|
|
|
if (!dai_link->stream_name)
|
|
|
|
dai_link->stream_name = dai_link->name;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Inform userspace we are using alternate topology */
|
|
|
|
if (component->driver->topology_name_prefix) {
|
|
|
|
|
2018-10-18 19:18:28 +08:00
|
|
|
/* topology shortname created? */
|
2018-07-02 23:59:54 +08:00
|
|
|
if (!card->topology_shortname_created) {
|
|
|
|
comp_drv = component->driver;
|
|
|
|
|
|
|
|
snprintf(card->topology_shortname, 32, "%s-%s",
|
|
|
|
comp_drv->topology_name_prefix,
|
|
|
|
card->name);
|
|
|
|
card->topology_shortname_created = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* use topology shortname */
|
|
|
|
card->name = card->topology_shortname;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-29 22:37:54 +08:00
|
|
|
#define soc_setup_card_name(card, name, name1, name2) \
|
|
|
|
__soc_setup_card_name(card, name, sizeof(name), name1, name2)
|
|
|
|
static void __soc_setup_card_name(struct snd_soc_card *card,
|
|
|
|
char *name, int len,
|
|
|
|
const char *name1, const char *name2)
|
2019-10-02 13:22:49 +08:00
|
|
|
{
|
2022-09-29 22:37:54 +08:00
|
|
|
const char *src = name1 ? name1 : name2;
|
2019-10-02 13:22:49 +08:00
|
|
|
int i;
|
|
|
|
|
2022-09-29 22:37:54 +08:00
|
|
|
snprintf(name, len, "%s", src);
|
2019-10-02 13:22:49 +08:00
|
|
|
|
2022-09-29 22:37:54 +08:00
|
|
|
if (name != card->snd_card->driver)
|
2019-10-02 13:22:49 +08:00
|
|
|
return;
|
|
|
|
|
|
|
|
/*
|
2022-09-29 22:37:54 +08:00
|
|
|
* Name normalization (driver field)
|
2019-10-02 13:22:49 +08:00
|
|
|
*
|
|
|
|
* The driver name is somewhat special, as it's used as a key for
|
|
|
|
* searches in the user-space.
|
|
|
|
*
|
|
|
|
* ex)
|
|
|
|
* "abcd??efg" -> "abcd__efg"
|
|
|
|
*/
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
switch (name[i]) {
|
|
|
|
case '_':
|
|
|
|
case '-':
|
|
|
|
case '\0':
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (!isalnum(name[i]))
|
|
|
|
name[i] = '_';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-09-29 22:37:54 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The driver field should contain a valid string from the user view.
|
|
|
|
* The wrapping usually does not work so well here. Set a smaller string
|
|
|
|
* in the specific ASoC driver.
|
|
|
|
*/
|
|
|
|
if (strlen(src) > len - 1)
|
|
|
|
dev_err(card->dev, "ASoC: driver name too long '%s' -> '%s'\n", src, name);
|
2019-10-02 13:22:49 +08:00
|
|
|
}
|
|
|
|
|
2020-05-28 09:50:21 +08:00
|
|
|
static void soc_cleanup_card_resources(struct snd_soc_card *card)
|
2019-01-21 08:32:42 +08:00
|
|
|
{
|
2019-12-10 08:33:40 +08:00
|
|
|
struct snd_soc_pcm_runtime *rtd, *n;
|
ASoC: soc-core: remove soc_remove_dai_links()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_cleanup_card_resources() (a) which is paired function of
snd_soc_instantiate_card() (A) is calling soc_remove_dai_links() (*)
to remove card related resources, but it is breaking
add/remove balance (B)(b)(C)(c)(D)(d), in other words
these should be called from soc_cleanup_card_resources() (a)
from balance point of view.
More headacke is that it is using original removing method for
dai_link even though we already have snd_soc_remove_dai_link()
which is the function for it (d).
This patch removes snd_soc_remove_dai_links() and balance up code.
static void soc_remove_dai_links(...)
{
...
(b) soc_remove_link_dais(card);
(c) soc_remove_link_components(card);
for_each_card_links_safe(card, link, _link) {
...
/* it should use snd_soc_remove_dai_link() here */
(d) list_del(&link->list);
}
}
(a) static int soc_cleanup_card_resources(...)
{
...
/* remove and free each DAI */
(*) soc_remove_dai_links(card);
...
}
(A) static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
...
/* add predefined DAI links to the list */
for_each_card_prelinks(card, i, dai_link)
(B) snd_soc_add_dai_link(card, dai_link);
...
/* probe all components used by DAI links on this card */
(C) ret = soc_probe_link_components(card);
...
/* probe all DAI links on this card */
(D) ret = soc_probe_link_dais(card);
...
}
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/875zl7bu1r.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-10-02 13:22:40 +08:00
|
|
|
|
ASoC: soc-pcm: remove soc_pcm_private_free()
soc-topology adds extra dai_link by using snd_soc_add_dai_link(),
and removes it by snd_soc_romove_dai_link().
This snd_soc_add/remove_dai_link() and/or its related
functions are unbalanced before, and now, these are balance-uped.
But, it finds the random operation issue, and it is reported by
Pierre-Louis.
When card was released, topology will call snd_soc_remove_dai_link()
via (A).
static void soc_cleanup_card_resources(struct snd_soc_card *card)
{
struct snd_soc_dai_link *link, *_link;
/* This should be called before snd_card_free() */
(A) soc_remove_link_components(card);
/* free the ALSA card at first; this syncs with pending operations */
if (card->snd_card) {
(B) snd_card_free(card->snd_card);
card->snd_card = NULL;
}
/* remove and free each DAI */
(X) soc_remove_link_dais(card);
for_each_card_links_safe(card, link, _link)
(C) snd_soc_remove_dai_link(card, link);
...
}
At (A), topology calls snd_soc_remove_dai_link().
Then topology rtd, and its related all data are freed.
Next, (B) is called, and then, pcm->private_free = soc_pcm_private_free()
is called.
static void soc_pcm_private_free(struct snd_pcm *pcm)
{
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
/* need to sync the delayed work before releasing resources */
flush_delayed_work(&rtd->delayed_work);
snd_soc_pcm_component_free(rtd);
}
Here, it gets rtd via pcm->private_data.
But, topology related rtd are already freed at (A).
Normal sound card has no damage, becase it frees rtd at (C).
These are finalizing rtd related data.
Thus, these should be called when rtd was freed, not sound card
was freed. It is very natural and understandable.
In other words, pcm->private_free = soc_pcm_private_free()
is no longer needed.
Extra issue is that there is zero chance to call
soc_remove_dai() for topology related dai at (X).
Because (A) removes rtd connection from card too, and,
(X) is based on card connected rtd.
This means, (X) need to be called before (C) (= for normal sound)
and (A) (= for topology).
Now, I want to focus this patch which is the reason why
snd_card_free() = (B) is located there.
commit 4efda5f2130da033aeedc5b3205569893b910de2
("ASoC: Fix use-after-free at card unregistration")
Original snd_card_free() was called last of this function.
But moved to top to avoid use-after-free issue.
The issue was happen at soc_pcm_free() which was pcm->private_free,
today it is updated/renamed to soc_pcm_private_free().
In other words, (B) need to be called before (C) (= for normal sound)
and (A) (= for topology), because it needs (not yet freed) rtd.
But, (A) need to be called before (B),
because it needs card->snd_card pointer.
If we call flush_delayed_work() and snd_soc_pcm_component_free()
(= same as soc_pcm_private_free()) when rtd was freed (= (C), (A)),
there is no reason to call snd_card_free() at top of this function.
It can be called end of this function, again.
But, in such case, it will likely break unbind again, as Takashi-san
reported. When unbind is performed in a busy state, the code may
release still-in-use resources.
At least we need to call snd_card_disconnect_sync() at the first place.
The final code will be...
static void soc_cleanup_card_resources(struct snd_soc_card *card)
{
struct snd_soc_dai_link *link, *_link;
if (card->snd_card)
(Z) snd_card_disconnect_sync(card->snd_card);
(X) soc_remove_link_dais(card);
(A) soc_remove_link_components(card);
for_each_card_links_safe(card, link, _link)
(C) snd_soc_remove_dai_link(card, link);
...
if (card->snd_card) {
(B) snd_card_free(card->snd_card);
card->snd_card = NULL;
}
}
To avoid release still-in-use resources,
call snd_card_disconnect_sync() at (Z).
(X) is needed for both non-topology and topology.
topology removes rtd via (A), and
non topology removes rtd via (C).
snd_card_free() is no longer related to use-after-free issue.
Thus, locating (B) is no problem.
Fixes: df95a16d2a9626 ("ASoC: soc-core: fix RIP warning on card removal")
Fixes: bc7a9091e5b927 ("ASoC: soc-core: add soc_unbind_dai_link()")
Reported-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Tested-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/87o8xax88g.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-18 09:51:11 +08:00
|
|
|
if (card->snd_card)
|
|
|
|
snd_card_disconnect_sync(card->snd_card);
|
2019-01-21 08:32:42 +08:00
|
|
|
|
2019-11-13 09:16:29 +08:00
|
|
|
snd_soc_dapm_shutdown(card);
|
|
|
|
|
ASoC: core: Exit all links before removing their components
Flows leading to link->init() and link->exit() are not symmetric.
Currently the relevant part of card probe sequence goes as:
for_each_card_rtds(card, rtd)
for_each_rtd_components(rtd, i, component)
component->probe()
for_each_card_rtds(card, rtd)
for_each_rtd_dais(rtd, i, dai)
dai->probe()
for_each_card_rtds(card, rtd)
rtd->init()
On the other side, equivalent remove sequence goes as:
for_each_card_rtds(card, rtd)
for_each_rtd_dais(rtd, i, dai)
dai->remove()
for_each_card_rtds(card, rtd)
for_each_rtd_components(rtd, i, component)
component->remove()
for_each_card_rtds(card, rtd)
rtd->exit()
what can lead to errors as link->exit() may still operate on resources
owned by its components despite the probability of them being freed
during the component->remove().
This change modifies the remove sequence to:
for_each_card_rtds(card, rtd)
rtd->exit()
for_each_card_rtds(card, rtd)
for_each_rtd_dais(rtd, i, dai)
dai->remove()
for_each_card_rtds(card, rtd)
for_each_rtd_components(rtd, i, component)
component->remove()
so code found in link->exit() is safe to touch any component stuff as
component->remove() has not been called yet.
Signed-off-by: Cezary Rojewski <cezary.rojewski@intel.com>
Reviewed-by: Amadeusz Sławiński <amadeuszx.slawinski@linux.intel.com>
Link: https://lore.kernel.org/r/20221027085840.1562698-1-cezary.rojewski@intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2022-10-27 16:58:40 +08:00
|
|
|
/* release machine specific resources */
|
|
|
|
for_each_card_rtds(card, rtd)
|
2023-09-29 18:32:43 +08:00
|
|
|
if (rtd->initialized)
|
|
|
|
snd_soc_link_exit(rtd);
|
2019-01-21 08:32:42 +08:00
|
|
|
/* remove and free each DAI */
|
ASoC: soc-core: remove soc_remove_dai_links()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_cleanup_card_resources() (a) which is paired function of
snd_soc_instantiate_card() (A) is calling soc_remove_dai_links() (*)
to remove card related resources, but it is breaking
add/remove balance (B)(b)(C)(c)(D)(d), in other words
these should be called from soc_cleanup_card_resources() (a)
from balance point of view.
More headacke is that it is using original removing method for
dai_link even though we already have snd_soc_remove_dai_link()
which is the function for it (d).
This patch removes snd_soc_remove_dai_links() and balance up code.
static void soc_remove_dai_links(...)
{
...
(b) soc_remove_link_dais(card);
(c) soc_remove_link_components(card);
for_each_card_links_safe(card, link, _link) {
...
/* it should use snd_soc_remove_dai_link() here */
(d) list_del(&link->list);
}
}
(a) static int soc_cleanup_card_resources(...)
{
...
/* remove and free each DAI */
(*) soc_remove_dai_links(card);
...
}
(A) static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
...
/* add predefined DAI links to the list */
for_each_card_prelinks(card, i, dai_link)
(B) snd_soc_add_dai_link(card, dai_link);
...
/* probe all components used by DAI links on this card */
(C) ret = soc_probe_link_components(card);
...
/* probe all DAI links on this card */
(D) ret = soc_probe_link_dais(card);
...
}
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/875zl7bu1r.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-10-02 13:22:40 +08:00
|
|
|
soc_remove_link_dais(card);
|
ASoC: soc-pcm: remove soc_pcm_private_free()
soc-topology adds extra dai_link by using snd_soc_add_dai_link(),
and removes it by snd_soc_romove_dai_link().
This snd_soc_add/remove_dai_link() and/or its related
functions are unbalanced before, and now, these are balance-uped.
But, it finds the random operation issue, and it is reported by
Pierre-Louis.
When card was released, topology will call snd_soc_remove_dai_link()
via (A).
static void soc_cleanup_card_resources(struct snd_soc_card *card)
{
struct snd_soc_dai_link *link, *_link;
/* This should be called before snd_card_free() */
(A) soc_remove_link_components(card);
/* free the ALSA card at first; this syncs with pending operations */
if (card->snd_card) {
(B) snd_card_free(card->snd_card);
card->snd_card = NULL;
}
/* remove and free each DAI */
(X) soc_remove_link_dais(card);
for_each_card_links_safe(card, link, _link)
(C) snd_soc_remove_dai_link(card, link);
...
}
At (A), topology calls snd_soc_remove_dai_link().
Then topology rtd, and its related all data are freed.
Next, (B) is called, and then, pcm->private_free = soc_pcm_private_free()
is called.
static void soc_pcm_private_free(struct snd_pcm *pcm)
{
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
/* need to sync the delayed work before releasing resources */
flush_delayed_work(&rtd->delayed_work);
snd_soc_pcm_component_free(rtd);
}
Here, it gets rtd via pcm->private_data.
But, topology related rtd are already freed at (A).
Normal sound card has no damage, becase it frees rtd at (C).
These are finalizing rtd related data.
Thus, these should be called when rtd was freed, not sound card
was freed. It is very natural and understandable.
In other words, pcm->private_free = soc_pcm_private_free()
is no longer needed.
Extra issue is that there is zero chance to call
soc_remove_dai() for topology related dai at (X).
Because (A) removes rtd connection from card too, and,
(X) is based on card connected rtd.
This means, (X) need to be called before (C) (= for normal sound)
and (A) (= for topology).
Now, I want to focus this patch which is the reason why
snd_card_free() = (B) is located there.
commit 4efda5f2130da033aeedc5b3205569893b910de2
("ASoC: Fix use-after-free at card unregistration")
Original snd_card_free() was called last of this function.
But moved to top to avoid use-after-free issue.
The issue was happen at soc_pcm_free() which was pcm->private_free,
today it is updated/renamed to soc_pcm_private_free().
In other words, (B) need to be called before (C) (= for normal sound)
and (A) (= for topology), because it needs (not yet freed) rtd.
But, (A) need to be called before (B),
because it needs card->snd_card pointer.
If we call flush_delayed_work() and snd_soc_pcm_component_free()
(= same as soc_pcm_private_free()) when rtd was freed (= (C), (A)),
there is no reason to call snd_card_free() at top of this function.
It can be called end of this function, again.
But, in such case, it will likely break unbind again, as Takashi-san
reported. When unbind is performed in a busy state, the code may
release still-in-use resources.
At least we need to call snd_card_disconnect_sync() at the first place.
The final code will be...
static void soc_cleanup_card_resources(struct snd_soc_card *card)
{
struct snd_soc_dai_link *link, *_link;
if (card->snd_card)
(Z) snd_card_disconnect_sync(card->snd_card);
(X) soc_remove_link_dais(card);
(A) soc_remove_link_components(card);
for_each_card_links_safe(card, link, _link)
(C) snd_soc_remove_dai_link(card, link);
...
if (card->snd_card) {
(B) snd_card_free(card->snd_card);
card->snd_card = NULL;
}
}
To avoid release still-in-use resources,
call snd_card_disconnect_sync() at (Z).
(X) is needed for both non-topology and topology.
topology removes rtd via (A), and
non topology removes rtd via (C).
snd_card_free() is no longer related to use-after-free issue.
Thus, locating (B) is no problem.
Fixes: df95a16d2a9626 ("ASoC: soc-core: fix RIP warning on card removal")
Fixes: bc7a9091e5b927 ("ASoC: soc-core: add soc_unbind_dai_link()")
Reported-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Tested-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/87o8xax88g.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-18 09:51:11 +08:00
|
|
|
soc_remove_link_components(card);
|
ASoC: soc-core: remove soc_remove_dai_links()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_cleanup_card_resources() (a) which is paired function of
snd_soc_instantiate_card() (A) is calling soc_remove_dai_links() (*)
to remove card related resources, but it is breaking
add/remove balance (B)(b)(C)(c)(D)(d), in other words
these should be called from soc_cleanup_card_resources() (a)
from balance point of view.
More headacke is that it is using original removing method for
dai_link even though we already have snd_soc_remove_dai_link()
which is the function for it (d).
This patch removes snd_soc_remove_dai_links() and balance up code.
static void soc_remove_dai_links(...)
{
...
(b) soc_remove_link_dais(card);
(c) soc_remove_link_components(card);
for_each_card_links_safe(card, link, _link) {
...
/* it should use snd_soc_remove_dai_link() here */
(d) list_del(&link->list);
}
}
(a) static int soc_cleanup_card_resources(...)
{
...
/* remove and free each DAI */
(*) soc_remove_dai_links(card);
...
}
(A) static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
...
/* add predefined DAI links to the list */
for_each_card_prelinks(card, i, dai_link)
(B) snd_soc_add_dai_link(card, dai_link);
...
/* probe all components used by DAI links on this card */
(C) ret = soc_probe_link_components(card);
...
/* probe all DAI links on this card */
(D) ret = soc_probe_link_dais(card);
...
}
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/875zl7bu1r.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-10-02 13:22:40 +08:00
|
|
|
|
2019-12-10 08:33:40 +08:00
|
|
|
for_each_card_rtds_safe(card, rtd, n)
|
2019-12-10 08:34:23 +08:00
|
|
|
snd_soc_remove_pcm_runtime(card, rtd);
|
ASoC: soc-core: remove soc_remove_dai_links()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc_cleanup_card_resources() (a) which is paired function of
snd_soc_instantiate_card() (A) is calling soc_remove_dai_links() (*)
to remove card related resources, but it is breaking
add/remove balance (B)(b)(C)(c)(D)(d), in other words
these should be called from soc_cleanup_card_resources() (a)
from balance point of view.
More headacke is that it is using original removing method for
dai_link even though we already have snd_soc_remove_dai_link()
which is the function for it (d).
This patch removes snd_soc_remove_dai_links() and balance up code.
static void soc_remove_dai_links(...)
{
...
(b) soc_remove_link_dais(card);
(c) soc_remove_link_components(card);
for_each_card_links_safe(card, link, _link) {
...
/* it should use snd_soc_remove_dai_link() here */
(d) list_del(&link->list);
}
}
(a) static int soc_cleanup_card_resources(...)
{
...
/* remove and free each DAI */
(*) soc_remove_dai_links(card);
...
}
(A) static int snd_soc_instantiate_card(struct snd_soc_card *card)
{
...
/* add predefined DAI links to the list */
for_each_card_prelinks(card, i, dai_link)
(B) snd_soc_add_dai_link(card, dai_link);
...
/* probe all components used by DAI links on this card */
(C) ret = soc_probe_link_components(card);
...
/* probe all DAI links on this card */
(D) ret = soc_probe_link_dais(card);
...
}
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/875zl7bu1r.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-10-02 13:22:40 +08:00
|
|
|
|
2019-01-21 08:32:42 +08:00
|
|
|
/* remove auxiliary devices */
|
|
|
|
soc_remove_aux_devices(card);
|
2019-09-04 08:15:40 +08:00
|
|
|
soc_unbind_aux_dev(card);
|
2019-01-21 08:32:42 +08:00
|
|
|
|
|
|
|
snd_soc_dapm_free(&card->dapm);
|
|
|
|
soc_cleanup_card_debugfs(card);
|
|
|
|
|
|
|
|
/* remove the card */
|
2020-05-28 09:49:26 +08:00
|
|
|
snd_soc_card_remove(card);
|
ASoC: soc-pcm: remove soc_pcm_private_free()
soc-topology adds extra dai_link by using snd_soc_add_dai_link(),
and removes it by snd_soc_romove_dai_link().
This snd_soc_add/remove_dai_link() and/or its related
functions are unbalanced before, and now, these are balance-uped.
But, it finds the random operation issue, and it is reported by
Pierre-Louis.
When card was released, topology will call snd_soc_remove_dai_link()
via (A).
static void soc_cleanup_card_resources(struct snd_soc_card *card)
{
struct snd_soc_dai_link *link, *_link;
/* This should be called before snd_card_free() */
(A) soc_remove_link_components(card);
/* free the ALSA card at first; this syncs with pending operations */
if (card->snd_card) {
(B) snd_card_free(card->snd_card);
card->snd_card = NULL;
}
/* remove and free each DAI */
(X) soc_remove_link_dais(card);
for_each_card_links_safe(card, link, _link)
(C) snd_soc_remove_dai_link(card, link);
...
}
At (A), topology calls snd_soc_remove_dai_link().
Then topology rtd, and its related all data are freed.
Next, (B) is called, and then, pcm->private_free = soc_pcm_private_free()
is called.
static void soc_pcm_private_free(struct snd_pcm *pcm)
{
struct snd_soc_pcm_runtime *rtd = pcm->private_data;
/* need to sync the delayed work before releasing resources */
flush_delayed_work(&rtd->delayed_work);
snd_soc_pcm_component_free(rtd);
}
Here, it gets rtd via pcm->private_data.
But, topology related rtd are already freed at (A).
Normal sound card has no damage, becase it frees rtd at (C).
These are finalizing rtd related data.
Thus, these should be called when rtd was freed, not sound card
was freed. It is very natural and understandable.
In other words, pcm->private_free = soc_pcm_private_free()
is no longer needed.
Extra issue is that there is zero chance to call
soc_remove_dai() for topology related dai at (X).
Because (A) removes rtd connection from card too, and,
(X) is based on card connected rtd.
This means, (X) need to be called before (C) (= for normal sound)
and (A) (= for topology).
Now, I want to focus this patch which is the reason why
snd_card_free() = (B) is located there.
commit 4efda5f2130da033aeedc5b3205569893b910de2
("ASoC: Fix use-after-free at card unregistration")
Original snd_card_free() was called last of this function.
But moved to top to avoid use-after-free issue.
The issue was happen at soc_pcm_free() which was pcm->private_free,
today it is updated/renamed to soc_pcm_private_free().
In other words, (B) need to be called before (C) (= for normal sound)
and (A) (= for topology), because it needs (not yet freed) rtd.
But, (A) need to be called before (B),
because it needs card->snd_card pointer.
If we call flush_delayed_work() and snd_soc_pcm_component_free()
(= same as soc_pcm_private_free()) when rtd was freed (= (C), (A)),
there is no reason to call snd_card_free() at top of this function.
It can be called end of this function, again.
But, in such case, it will likely break unbind again, as Takashi-san
reported. When unbind is performed in a busy state, the code may
release still-in-use resources.
At least we need to call snd_card_disconnect_sync() at the first place.
The final code will be...
static void soc_cleanup_card_resources(struct snd_soc_card *card)
{
struct snd_soc_dai_link *link, *_link;
if (card->snd_card)
(Z) snd_card_disconnect_sync(card->snd_card);
(X) soc_remove_link_dais(card);
(A) soc_remove_link_components(card);
for_each_card_links_safe(card, link, _link)
(C) snd_soc_remove_dai_link(card, link);
...
if (card->snd_card) {
(B) snd_card_free(card->snd_card);
card->snd_card = NULL;
}
}
To avoid release still-in-use resources,
call snd_card_disconnect_sync() at (Z).
(X) is needed for both non-topology and topology.
topology removes rtd via (A), and
non topology removes rtd via (C).
snd_card_free() is no longer related to use-after-free issue.
Thus, locating (B) is no problem.
Fixes: df95a16d2a9626 ("ASoC: soc-core: fix RIP warning on card removal")
Fixes: bc7a9091e5b927 ("ASoC: soc-core: add soc_unbind_dai_link()")
Reported-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Tested-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Link: https://lore.kernel.org/r/87o8xax88g.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-18 09:51:11 +08:00
|
|
|
|
|
|
|
if (card->snd_card) {
|
|
|
|
snd_card_free(card->snd_card);
|
|
|
|
card->snd_card = NULL;
|
|
|
|
}
|
2019-01-21 08:32:42 +08:00
|
|
|
}
|
|
|
|
|
2019-11-13 09:16:34 +08:00
|
|
|
static void snd_soc_unbind_card(struct snd_soc_card *card, bool unregister)
|
|
|
|
{
|
2023-01-31 10:01:29 +08:00
|
|
|
if (snd_soc_card_is_instantiated(card)) {
|
2019-11-13 09:16:34 +08:00
|
|
|
card->instantiated = false;
|
|
|
|
snd_soc_flush_all_delayed_work(card);
|
|
|
|
|
2020-05-28 09:50:21 +08:00
|
|
|
soc_cleanup_card_resources(card);
|
2019-11-13 09:16:34 +08:00
|
|
|
if (!unregister)
|
|
|
|
list_add(&card->list, &unbind_card_list);
|
|
|
|
} else {
|
|
|
|
if (unregister)
|
|
|
|
list_del(&card->list);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-06 09:07:56 +08:00
|
|
|
static int snd_soc_bind_card(struct snd_soc_card *card)
|
2010-03-18 04:15:21 +08:00
|
|
|
{
|
2015-11-18 15:34:11 +08:00
|
|
|
struct snd_soc_pcm_runtime *rtd;
|
2020-01-10 10:36:13 +08:00
|
|
|
struct snd_soc_component *component;
|
2023-03-27 08:34:26 +08:00
|
|
|
int ret;
|
2009-11-04 06:13:13 +08:00
|
|
|
|
2015-03-08 02:34:03 +08:00
|
|
|
mutex_lock(&client_mutex);
|
2023-04-06 08:16:27 +08:00
|
|
|
snd_soc_card_mutex_lock_root(card);
|
2010-02-12 19:37:24 +08:00
|
|
|
|
ASoC: soc-core: add snd_soc_dapm_init()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and it will be difficult to
debug.
soc-dapm has snd_soc_dapm_free() which cleanups debugfs, widgets, list.
But, there is no paired initialize function.
This patch adds snd_soc_dapm_init() and initilaizing dapm
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Link: https://lore.kernel.org/r/87pnkw7lbj.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-08-23 08:58:52 +08:00
|
|
|
snd_soc_dapm_init(&card->dapm, card, NULL);
|
2019-01-21 08:32:42 +08:00
|
|
|
|
2018-07-02 23:59:54 +08:00
|
|
|
/* check whether any platform is ignore machine FE and using topology */
|
|
|
|
soc_check_tplg_fes(card);
|
|
|
|
|
2014-07-02 04:13:47 +08:00
|
|
|
/* bind aux_devs too */
|
2019-09-04 08:15:28 +08:00
|
|
|
ret = soc_bind_aux_dev(card);
|
|
|
|
if (ret < 0)
|
|
|
|
goto probe_end;
|
2008-12-04 23:32:53 +08:00
|
|
|
|
2015-12-02 14:11:22 +08:00
|
|
|
/* add predefined DAI links to the list */
|
2019-10-02 13:23:07 +08:00
|
|
|
card->num_rtd = 0;
|
2023-03-27 08:34:26 +08:00
|
|
|
ret = snd_soc_add_pcm_runtimes(card, card->dai_link, card->num_links);
|
|
|
|
if (ret < 0)
|
|
|
|
goto probe_end;
|
2015-12-02 14:11:22 +08:00
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
/* card bind complete so register a sound card */
|
2014-01-29 21:42:55 +08:00
|
|
|
ret = snd_card_new(card->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
|
2010-03-18 04:15:21 +08:00
|
|
|
card->owner, 0, &card->snd_card);
|
|
|
|
if (ret < 0) {
|
2013-05-05 04:21:38 +08:00
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: can't create sound card for card %s: %d\n",
|
|
|
|
card->name, ret);
|
2019-01-21 08:32:42 +08:00
|
|
|
goto probe_end;
|
2010-03-18 04:15:21 +08:00
|
|
|
}
|
|
|
|
|
2015-04-09 16:52:36 +08:00
|
|
|
soc_init_card_debugfs(card);
|
|
|
|
|
2019-08-07 09:31:24 +08:00
|
|
|
soc_resume_init(card);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2019-08-07 09:30:53 +08:00
|
|
|
ret = snd_soc_dapm_new_controls(&card->dapm, card->dapm_widgets,
|
|
|
|
card->num_dapm_widgets);
|
|
|
|
if (ret < 0)
|
|
|
|
goto probe_end;
|
2011-04-13 08:51:37 +08:00
|
|
|
|
2019-08-07 09:30:53 +08:00
|
|
|
ret = snd_soc_dapm_new_controls(&card->dapm, card->of_dapm_widgets,
|
|
|
|
card->num_of_dapm_widgets);
|
|
|
|
if (ret < 0)
|
|
|
|
goto probe_end;
|
2015-02-15 09:22:49 +08:00
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
/* initialise the sound card only once */
|
2020-05-28 09:49:11 +08:00
|
|
|
ret = snd_soc_card_probe(card);
|
|
|
|
if (ret < 0)
|
|
|
|
goto probe_end;
|
2009-11-04 06:13:13 +08:00
|
|
|
|
2012-06-09 02:34:23 +08:00
|
|
|
/* probe all components used by DAI links on this card */
|
2019-09-04 08:14:46 +08:00
|
|
|
ret = soc_probe_link_components(card);
|
|
|
|
if (ret < 0) {
|
2023-07-05 20:30:18 +08:00
|
|
|
if (ret != -EPROBE_DEFER) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: failed to instantiate card %d\n", ret);
|
|
|
|
}
|
2019-09-04 08:14:46 +08:00
|
|
|
goto probe_end;
|
2012-06-09 02:34:23 +08:00
|
|
|
}
|
|
|
|
|
2016-01-06 13:29:31 +08:00
|
|
|
/* probe auxiliary components */
|
|
|
|
ret = soc_probe_aux_devices(card);
|
2019-11-06 09:08:06 +08:00
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: failed to probe aux component %d\n", ret);
|
2019-01-21 08:32:42 +08:00
|
|
|
goto probe_end;
|
2019-11-06 09:08:06 +08:00
|
|
|
}
|
2016-01-06 13:29:31 +08:00
|
|
|
|
2012-06-09 02:34:23 +08:00
|
|
|
/* probe all DAI links on this card */
|
2019-09-04 08:15:17 +08:00
|
|
|
ret = soc_probe_link_dais(card);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: failed to instantiate card %d\n", ret);
|
|
|
|
goto probe_end;
|
2010-03-18 04:15:21 +08:00
|
|
|
}
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2019-12-10 08:34:40 +08:00
|
|
|
for_each_card_rtds(card, rtd) {
|
2019-12-10 08:34:44 +08:00
|
|
|
ret = soc_init_pcm_runtime(card, rtd);
|
2019-12-10 08:34:40 +08:00
|
|
|
if (ret < 0)
|
|
|
|
goto probe_end;
|
|
|
|
}
|
2019-09-04 08:15:12 +08:00
|
|
|
|
2012-02-17 11:37:51 +08:00
|
|
|
snd_soc_dapm_link_dai_widgets(card);
|
2014-01-08 18:40:19 +08:00
|
|
|
snd_soc_dapm_connect_dai_link_widgets(card);
|
2012-02-17 11:37:51 +08:00
|
|
|
|
2019-08-07 09:31:08 +08:00
|
|
|
ret = snd_soc_add_card_controls(card, card->controls,
|
|
|
|
card->num_controls);
|
|
|
|
if (ret < 0)
|
|
|
|
goto probe_end;
|
2011-04-07 18:18:44 +08:00
|
|
|
|
2019-08-07 09:31:03 +08:00
|
|
|
ret = snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes,
|
|
|
|
card->num_dapm_routes);
|
2020-03-10 03:27:43 +08:00
|
|
|
if (ret < 0) {
|
|
|
|
if (card->disable_route_checks) {
|
|
|
|
dev_info(card->dev,
|
|
|
|
"%s: disable_route_checks set, ignoring errors on add_routes\n",
|
|
|
|
__func__);
|
|
|
|
} else {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"%s: snd_soc_dapm_add_routes failed: %d\n",
|
|
|
|
__func__, ret);
|
|
|
|
goto probe_end;
|
|
|
|
}
|
|
|
|
}
|
2011-03-03 02:35:51 +08:00
|
|
|
|
2019-08-07 09:31:03 +08:00
|
|
|
ret = snd_soc_dapm_add_routes(&card->dapm, card->of_dapm_routes,
|
|
|
|
card->num_of_dapm_routes);
|
|
|
|
if (ret < 0)
|
|
|
|
goto probe_end;
|
2011-09-27 23:41:01 +08:00
|
|
|
|
2017-04-24 14:54:42 +08:00
|
|
|
/* try to set some sane longname if DMI is available */
|
|
|
|
snd_soc_set_dmi_name(card, NULL);
|
|
|
|
|
2022-09-29 22:37:54 +08:00
|
|
|
soc_setup_card_name(card, card->snd_card->shortname,
|
|
|
|
card->name, NULL);
|
|
|
|
soc_setup_card_name(card, card->snd_card->longname,
|
|
|
|
card->long_name, card->name);
|
|
|
|
soc_setup_card_name(card, card->snd_card->driver,
|
|
|
|
card->driver_name, card->name);
|
2010-03-18 04:15:21 +08:00
|
|
|
|
2019-11-20 01:49:32 +08:00
|
|
|
if (card->components) {
|
|
|
|
/* the current implementation of snd_component_add() accepts */
|
|
|
|
/* multiple components in the string separated by space, */
|
|
|
|
/* but the string collision (identical string) check might */
|
|
|
|
/* not work correctly */
|
|
|
|
ret = snd_component_add(card->snd_card, card->components);
|
|
|
|
if (ret < 0) {
|
|
|
|
dev_err(card->dev, "ASoC: %s snd_component_add() failed: %d\n",
|
|
|
|
card->name, ret);
|
|
|
|
goto probe_end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-28 09:49:20 +08:00
|
|
|
ret = snd_soc_card_late_probe(card);
|
|
|
|
if (ret < 0)
|
|
|
|
goto probe_end;
|
2011-03-03 02:36:34 +08:00
|
|
|
|
2013-08-27 21:51:01 +08:00
|
|
|
snd_soc_dapm_new_widgets(card);
|
2022-06-07 03:19:09 +08:00
|
|
|
snd_soc_card_fixup_controls(card);
|
2013-08-27 21:51:00 +08:00
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
ret = snd_card_register(card->snd_card);
|
|
|
|
if (ret < 0) {
|
2012-11-19 22:47:09 +08:00
|
|
|
dev_err(card->dev, "ASoC: failed to register soundcard %d\n",
|
|
|
|
ret);
|
2019-01-21 08:32:42 +08:00
|
|
|
goto probe_end;
|
2006-10-07 00:31:09 +08:00
|
|
|
}
|
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
card->instantiated = 1;
|
2018-11-14 17:06:13 +08:00
|
|
|
dapm_mark_endpoints_dirty(card);
|
2011-10-07 21:29:19 +08:00
|
|
|
snd_soc_dapm_sync(&card->dapm);
|
2010-03-18 04:15:21 +08:00
|
|
|
|
2019-11-06 09:07:56 +08:00
|
|
|
/* deactivate pins to sleep state */
|
2020-01-10 10:36:13 +08:00
|
|
|
for_each_card_components(card, component)
|
2020-05-15 08:46:51 +08:00
|
|
|
if (!snd_soc_component_active(component))
|
2020-01-10 10:36:13 +08:00
|
|
|
pinctrl_pm_select_sleep_state(component->dev);
|
2019-11-06 09:07:56 +08:00
|
|
|
|
2019-01-21 08:32:42 +08:00
|
|
|
probe_end:
|
|
|
|
if (ret < 0)
|
2020-05-28 09:50:21 +08:00
|
|
|
soc_cleanup_card_resources(card);
|
2010-03-18 04:15:21 +08:00
|
|
|
|
2023-04-06 08:16:27 +08:00
|
|
|
snd_soc_card_mutex_unlock(card);
|
2015-03-08 02:34:03 +08:00
|
|
|
mutex_unlock(&client_mutex);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2012-03-15 05:18:39 +08:00
|
|
|
return ret;
|
2008-12-04 23:32:53 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* probes a new socdev */
|
|
|
|
static int soc_probe(struct platform_device *pdev)
|
|
|
|
{
|
2010-03-18 04:15:21 +08:00
|
|
|
struct snd_soc_card *card = platform_get_drvdata(pdev);
|
2008-12-04 23:32:53 +08:00
|
|
|
|
2011-01-14 21:52:48 +08:00
|
|
|
/*
|
|
|
|
* no card, so machine driver should be registering card
|
|
|
|
* we should not be here in that case so ret error
|
|
|
|
*/
|
|
|
|
if (!card)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2012-03-02 21:07:41 +08:00
|
|
|
dev_warn(&pdev->dev,
|
2012-11-19 22:47:09 +08:00
|
|
|
"ASoC: machine %s should use snd_soc_register_card()\n",
|
2012-03-02 21:07:41 +08:00
|
|
|
card->name);
|
|
|
|
|
2008-12-04 23:32:53 +08:00
|
|
|
/* Bodge while we unpick instantiation */
|
|
|
|
card->dev = &pdev->dev;
|
2010-03-18 04:15:21 +08:00
|
|
|
|
2020-09-29 19:29:33 +08:00
|
|
|
return devm_snd_soc_register_card(&pdev->dev, card);
|
2006-10-07 00:31:09 +08:00
|
|
|
}
|
|
|
|
|
2011-01-26 22:59:27 +08:00
|
|
|
int snd_soc_poweroff(struct device *dev)
|
2009-06-22 20:16:51 +08:00
|
|
|
{
|
2011-01-26 22:59:27 +08:00
|
|
|
struct snd_soc_card *card = dev_get_drvdata(dev);
|
2020-01-10 10:36:13 +08:00
|
|
|
struct snd_soc_component *component;
|
2009-06-22 20:16:51 +08:00
|
|
|
|
2023-01-31 10:01:29 +08:00
|
|
|
if (!snd_soc_card_is_instantiated(card))
|
2009-07-01 02:05:15 +08:00
|
|
|
return 0;
|
2009-06-22 20:16:51 +08:00
|
|
|
|
2018-10-18 19:18:28 +08:00
|
|
|
/*
|
|
|
|
* Flush out pmdown_time work - we actually do want to run it
|
|
|
|
* now, we're shutting down so no imminent restart.
|
|
|
|
*/
|
2019-01-21 08:32:37 +08:00
|
|
|
snd_soc_flush_all_delayed_work(card);
|
2009-06-22 20:16:51 +08:00
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
snd_soc_dapm_shutdown(card);
|
2009-07-01 02:05:15 +08:00
|
|
|
|
ASoC: Add pinctrl PM to components of active DAIs
It's quite popular that more drivers are using pinctrl PM, for example:
(Documentation/devicetree/bindings/arm/primecell.txt). Just like what
runtime PM does, it would deactivate and activate pin group depending
on whether it's being used or not.
And this pinctrl PM might be also beneficial to cpu dai drivers because
they might have actual pinctrl so as to sleep their pins and wake them
up as needed.
To achieve this goal, this patch sets pins to the default state during
resume or startup; While during suspend and shutdown, it would set pins
to the sleep state.
As pinctrl PM would return zero if there is no such pinctrl sleep state
settings, this patch would not break current ASoC subsystem directly.
[ However, there is still an exception that the patch can not handle,
that is, when cpu dai driver does not have pinctrl property but another
device has it. (The AUDMUX <-> SSI on Freescale i.MX6 series for example.
SSI as a cpu dai doesn't contain pinctrl property while AUDMUX, an Audio
Multiplexer, has it). In this case, this kind of cpu dai driver needs to
find a way to obtain the pinctrl property as its own, by moving property
from AUDMUX to SSI, or creating a pins link/dependency between these two
devices, or using a more decent way after we figure it out. ]
Signed-off-by: Nicolin Chen <b42378@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2013-11-04 14:57:31 +08:00
|
|
|
/* deactivate pins to sleep state */
|
2020-01-10 10:36:13 +08:00
|
|
|
for_each_card_components(card, component)
|
|
|
|
pinctrl_pm_select_sleep_state(component->dev);
|
ASoC: Add pinctrl PM to components of active DAIs
It's quite popular that more drivers are using pinctrl PM, for example:
(Documentation/devicetree/bindings/arm/primecell.txt). Just like what
runtime PM does, it would deactivate and activate pin group depending
on whether it's being used or not.
And this pinctrl PM might be also beneficial to cpu dai drivers because
they might have actual pinctrl so as to sleep their pins and wake them
up as needed.
To achieve this goal, this patch sets pins to the default state during
resume or startup; While during suspend and shutdown, it would set pins
to the sleep state.
As pinctrl PM would return zero if there is no such pinctrl sleep state
settings, this patch would not break current ASoC subsystem directly.
[ However, there is still an exception that the patch can not handle,
that is, when cpu dai driver does not have pinctrl property but another
device has it. (The AUDMUX <-> SSI on Freescale i.MX6 series for example.
SSI as a cpu dai doesn't contain pinctrl property while AUDMUX, an Audio
Multiplexer, has it). In this case, this kind of cpu dai driver needs to
find a way to obtain the pinctrl property as its own, by moving property
from AUDMUX to SSI, or creating a pins link/dependency between these two
devices, or using a more decent way after we figure it out. ]
Signed-off-by: Nicolin Chen <b42378@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2013-11-04 14:57:31 +08:00
|
|
|
|
2009-07-01 02:05:15 +08:00
|
|
|
return 0;
|
2009-06-22 20:16:51 +08:00
|
|
|
}
|
2011-01-26 22:59:27 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_poweroff);
|
2009-06-22 20:16:51 +08:00
|
|
|
|
2011-01-26 22:59:27 +08:00
|
|
|
const struct dev_pm_ops snd_soc_pm_ops = {
|
2012-02-24 18:55:49 +08:00
|
|
|
.suspend = snd_soc_suspend,
|
|
|
|
.resume = snd_soc_resume,
|
|
|
|
.freeze = snd_soc_suspend,
|
|
|
|
.thaw = snd_soc_resume,
|
2011-01-26 22:59:27 +08:00
|
|
|
.poweroff = snd_soc_poweroff,
|
2012-02-24 18:55:49 +08:00
|
|
|
.restore = snd_soc_resume,
|
2009-07-01 02:05:15 +08:00
|
|
|
};
|
2011-04-06 09:35:30 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_pm_ops);
|
2009-07-01 02:05:15 +08:00
|
|
|
|
2006-10-07 00:31:09 +08:00
|
|
|
/* ASoC platform driver */
|
|
|
|
static struct platform_driver soc_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "soc-audio",
|
2011-01-26 22:59:27 +08:00
|
|
|
.pm = &snd_soc_pm_ops,
|
2006-10-07 00:31:09 +08:00
|
|
|
},
|
|
|
|
.probe = soc_probe,
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_soc_cnew - create new control
|
|
|
|
* @_template: control template
|
|
|
|
* @data: control private data
|
2009-01-01 20:18:17 +08:00
|
|
|
* @long_name: control long name
|
2011-03-09 01:23:24 +08:00
|
|
|
* @prefix: control name prefix
|
2006-10-07 00:31:09 +08:00
|
|
|
*
|
|
|
|
* Create a new mixer control from a template control.
|
|
|
|
*
|
|
|
|
* Returns 0 for success, else error.
|
|
|
|
*/
|
|
|
|
struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
|
2012-02-17 09:07:42 +08:00
|
|
|
void *data, const char *long_name,
|
2011-03-09 01:23:24 +08:00
|
|
|
const char *prefix)
|
2006-10-07 00:31:09 +08:00
|
|
|
{
|
|
|
|
struct snd_kcontrol_new template;
|
2011-03-09 01:23:24 +08:00
|
|
|
struct snd_kcontrol *kcontrol;
|
|
|
|
char *name = NULL;
|
2006-10-07 00:31:09 +08:00
|
|
|
|
|
|
|
memcpy(&template, _template, sizeof(template));
|
|
|
|
template.index = 0;
|
|
|
|
|
2011-03-09 01:23:24 +08:00
|
|
|
if (!long_name)
|
|
|
|
long_name = template.name;
|
|
|
|
|
|
|
|
if (prefix) {
|
2013-05-14 17:05:32 +08:00
|
|
|
name = kasprintf(GFP_KERNEL, "%s %s", prefix, long_name);
|
2011-03-09 01:23:24 +08:00
|
|
|
if (!name)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
template.name = name;
|
|
|
|
} else {
|
|
|
|
template.name = long_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
kcontrol = snd_ctl_new1(&template, data);
|
|
|
|
|
|
|
|
kfree(name);
|
|
|
|
|
|
|
|
return kcontrol;
|
2006-10-07 00:31:09 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_cnew);
|
|
|
|
|
2012-02-04 01:43:09 +08:00
|
|
|
static int snd_soc_add_controls(struct snd_card *card, struct device *dev,
|
|
|
|
const struct snd_kcontrol_new *controls, int num_controls,
|
|
|
|
const char *prefix, void *data)
|
|
|
|
{
|
2021-07-29 09:15:52 +08:00
|
|
|
int i;
|
2012-02-04 01:43:09 +08:00
|
|
|
|
|
|
|
for (i = 0; i < num_controls; i++) {
|
|
|
|
const struct snd_kcontrol_new *control = &controls[i];
|
2021-07-29 09:15:52 +08:00
|
|
|
int err = snd_ctl_add(card, snd_soc_cnew(control, data,
|
|
|
|
control->name, prefix));
|
2012-02-04 01:43:09 +08:00
|
|
|
if (err < 0) {
|
2012-11-19 22:47:09 +08:00
|
|
|
dev_err(dev, "ASoC: Failed to add %s: %d\n",
|
|
|
|
control->name, err);
|
2012-02-04 01:43:09 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-07-18 04:01:08 +08:00
|
|
|
/**
|
|
|
|
* snd_soc_add_component_controls - Add an array of controls to a component.
|
|
|
|
*
|
|
|
|
* @component: Component to add controls to
|
|
|
|
* @controls: Array of controls to add
|
|
|
|
* @num_controls: Number of elements in the array
|
|
|
|
*
|
|
|
|
* Return: 0 for success, else error.
|
|
|
|
*/
|
|
|
|
int snd_soc_add_component_controls(struct snd_soc_component *component,
|
|
|
|
const struct snd_kcontrol_new *controls, unsigned int num_controls)
|
|
|
|
{
|
|
|
|
struct snd_card *card = component->card->snd_card;
|
|
|
|
|
|
|
|
return snd_soc_add_controls(card, component->dev, controls,
|
|
|
|
num_controls, component->name_prefix, component);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_add_component_controls);
|
|
|
|
|
2012-02-04 01:43:09 +08:00
|
|
|
/**
|
|
|
|
* snd_soc_add_card_controls - add an array of controls to a SoC card.
|
|
|
|
* Convenience function to add a list of controls.
|
|
|
|
*
|
|
|
|
* @soc_card: SoC card to add controls to
|
|
|
|
* @controls: array of controls to add
|
|
|
|
* @num_controls: number of elements in the array
|
|
|
|
*
|
|
|
|
* Return 0 for success, else error.
|
|
|
|
*/
|
|
|
|
int snd_soc_add_card_controls(struct snd_soc_card *soc_card,
|
|
|
|
const struct snd_kcontrol_new *controls, int num_controls)
|
|
|
|
{
|
|
|
|
struct snd_card *card = soc_card->snd_card;
|
|
|
|
|
|
|
|
return snd_soc_add_controls(card, soc_card->dev, controls, num_controls,
|
|
|
|
NULL, soc_card);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_add_card_controls);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* snd_soc_add_dai_controls - add an array of controls to a DAI.
|
|
|
|
* Convienience function to add a list of controls.
|
|
|
|
*
|
|
|
|
* @dai: DAI to add controls to
|
|
|
|
* @controls: array of controls to add
|
|
|
|
* @num_controls: number of elements in the array
|
|
|
|
*
|
|
|
|
* Return 0 for success, else error.
|
|
|
|
*/
|
|
|
|
int snd_soc_add_dai_controls(struct snd_soc_dai *dai,
|
|
|
|
const struct snd_kcontrol_new *controls, int num_controls)
|
|
|
|
{
|
2014-11-04 18:30:58 +08:00
|
|
|
struct snd_card *card = dai->component->card->snd_card;
|
2012-02-04 01:43:09 +08:00
|
|
|
|
|
|
|
return snd_soc_add_controls(card, dai->dev, controls, num_controls,
|
|
|
|
NULL, dai);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_add_dai_controls);
|
|
|
|
|
2008-11-28 21:29:45 +08:00
|
|
|
/**
|
|
|
|
* snd_soc_register_card - Register a card with the ASoC core
|
|
|
|
*
|
2009-01-01 20:18:17 +08:00
|
|
|
* @card: Card to register
|
2008-11-28 21:29:45 +08:00
|
|
|
*
|
|
|
|
*/
|
2011-01-14 21:52:48 +08:00
|
|
|
int snd_soc_register_card(struct snd_soc_card *card)
|
2008-11-28 21:29:45 +08:00
|
|
|
{
|
|
|
|
if (!card->name || !card->dev)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2011-05-04 01:25:34 +08:00
|
|
|
dev_set_drvdata(card->dev, card);
|
|
|
|
|
2019-08-20 13:04:54 +08:00
|
|
|
INIT_LIST_HEAD(&card->widgets);
|
|
|
|
INIT_LIST_HEAD(&card->paths);
|
|
|
|
INIT_LIST_HEAD(&card->dapm_list);
|
|
|
|
INIT_LIST_HEAD(&card->aux_comp_list);
|
|
|
|
INIT_LIST_HEAD(&card->component_dev_list);
|
|
|
|
INIT_LIST_HEAD(&card->list);
|
2015-11-18 15:34:11 +08:00
|
|
|
INIT_LIST_HEAD(&card->rtd_list);
|
2011-10-04 04:06:40 +08:00
|
|
|
INIT_LIST_HEAD(&card->dapm_dirty);
|
2015-05-30 02:06:14 +08:00
|
|
|
INIT_LIST_HEAD(&card->dobj_list);
|
2019-08-20 13:04:54 +08:00
|
|
|
|
2008-11-28 21:29:45 +08:00
|
|
|
card->instantiated = 0;
|
2010-03-18 04:15:21 +08:00
|
|
|
mutex_init(&card->mutex);
|
2012-03-07 18:38:26 +08:00
|
|
|
mutex_init(&card->dapm_mutex);
|
2019-08-13 18:45:32 +08:00
|
|
|
mutex_init(&card->pcm_mutex);
|
2008-11-28 21:29:45 +08:00
|
|
|
|
2018-09-12 17:15:00 +08:00
|
|
|
return snd_soc_bind_card(card);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_register_card);
|
2014-07-09 05:19:34 +08:00
|
|
|
|
2008-11-28 21:29:45 +08:00
|
|
|
/**
|
|
|
|
* snd_soc_unregister_card - Unregister a card with the ASoC core
|
|
|
|
*
|
2009-01-01 20:18:17 +08:00
|
|
|
* @card: Card to unregister
|
2008-11-28 21:29:45 +08:00
|
|
|
*
|
|
|
|
*/
|
2022-06-21 22:58:34 +08:00
|
|
|
void snd_soc_unregister_card(struct snd_soc_card *card)
|
2008-11-28 21:29:45 +08:00
|
|
|
{
|
2019-06-19 09:07:19 +08:00
|
|
|
mutex_lock(&client_mutex);
|
2018-09-12 17:15:00 +08:00
|
|
|
snd_soc_unbind_card(card, true);
|
2019-06-19 09:07:19 +08:00
|
|
|
mutex_unlock(&client_mutex);
|
2018-09-12 17:15:00 +08:00
|
|
|
dev_dbg(card->dev, "ASoC: Unregistered card '%s'\n", card->name);
|
2008-11-28 21:29:45 +08:00
|
|
|
}
|
2011-01-14 21:52:48 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_unregister_card);
|
2008-11-28 21:29:45 +08:00
|
|
|
|
2010-03-18 04:15:21 +08:00
|
|
|
/*
|
|
|
|
* Simplify DAI link configuration by removing ".-1" from device names
|
|
|
|
* and sanitizing names.
|
|
|
|
*/
|
2010-12-06 23:49:20 +08:00
|
|
|
static char *fmt_single_name(struct device *dev, int *id)
|
2010-03-18 04:15:21 +08:00
|
|
|
{
|
2020-08-28 04:51:00 +08:00
|
|
|
const char *devname = dev_name(dev);
|
|
|
|
char *found, *name;
|
2021-04-17 03:11:41 +08:00
|
|
|
unsigned int id1, id2;
|
2010-03-18 04:15:21 +08:00
|
|
|
|
2020-08-28 04:51:00 +08:00
|
|
|
if (devname == NULL)
|
2010-03-18 04:15:21 +08:00
|
|
|
return NULL;
|
|
|
|
|
2020-08-28 04:51:00 +08:00
|
|
|
name = devm_kstrdup(dev, devname, GFP_KERNEL);
|
2021-05-24 10:49:41 +08:00
|
|
|
if (!name)
|
|
|
|
return NULL;
|
2010-03-18 04:15:21 +08:00
|
|
|
|
|
|
|
/* are we a "%s.%d" name (platform and SPI components) */
|
|
|
|
found = strstr(name, dev->driver->name);
|
|
|
|
if (found) {
|
|
|
|
/* get ID */
|
|
|
|
if (sscanf(&found[strlen(dev->driver->name)], ".%d", id) == 1) {
|
|
|
|
|
|
|
|
/* discard ID from name if ID == -1 */
|
|
|
|
if (*id == -1)
|
|
|
|
found[strlen(dev->driver->name)] = '\0';
|
|
|
|
}
|
|
|
|
|
2020-08-28 04:51:00 +08:00
|
|
|
/* I2C component devices are named "bus-addr" */
|
|
|
|
} else if (sscanf(name, "%x-%x", &id1, &id2) == 2) {
|
2010-03-18 04:15:21 +08:00
|
|
|
|
2020-08-28 04:51:00 +08:00
|
|
|
/* create unique ID number from I2C addr and bus */
|
|
|
|
*id = ((id1 & 0xffff) << 16) + id2;
|
2010-03-18 04:15:21 +08:00
|
|
|
|
2020-08-28 04:51:00 +08:00
|
|
|
devm_kfree(dev, name);
|
|
|
|
|
|
|
|
/* sanitize component name for DAI link creation */
|
|
|
|
name = devm_kasprintf(dev, GFP_KERNEL, "%s.%s", dev->driver->name, devname);
|
|
|
|
} else {
|
|
|
|
*id = 0;
|
2010-03-18 04:15:21 +08:00
|
|
|
}
|
|
|
|
|
2020-08-28 04:51:00 +08:00
|
|
|
return name;
|
2010-03-18 04:15:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Simplify DAI link naming for single devices with multiple DAIs by removing
|
|
|
|
* any ".-1" and using the DAI name (instead of device name).
|
|
|
|
*/
|
|
|
|
static inline char *fmt_multiple_name(struct device *dev,
|
|
|
|
struct snd_soc_dai_driver *dai_drv)
|
|
|
|
{
|
|
|
|
if (dai_drv->name == NULL) {
|
2013-05-05 04:21:38 +08:00
|
|
|
dev_err(dev,
|
|
|
|
"ASoC: error - multiple DAI %s registered with no name\n",
|
|
|
|
dev_name(dev));
|
2010-03-18 04:15:21 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-10-02 13:22:57 +08:00
|
|
|
return devm_kstrdup(dev, dai_drv->name, GFP_KERNEL);
|
2010-03-18 04:15:21 +08:00
|
|
|
}
|
|
|
|
|
2019-11-06 09:07:23 +08:00
|
|
|
void snd_soc_unregister_dai(struct snd_soc_dai *dai)
|
2019-11-05 14:47:04 +08:00
|
|
|
{
|
|
|
|
dev_dbg(dai->dev, "ASoC: Unregistered DAI '%s'\n", dai->name);
|
|
|
|
list_del(&dai->list);
|
|
|
|
}
|
2019-11-06 09:07:23 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_unregister_dai);
|
2019-11-05 14:47:04 +08:00
|
|
|
|
2019-11-06 09:07:17 +08:00
|
|
|
/**
|
|
|
|
* snd_soc_register_dai - Register a DAI dynamically & create its widgets
|
|
|
|
*
|
|
|
|
* @component: The component the DAIs are registered for
|
|
|
|
* @dai_drv: DAI driver to use for the DAI
|
2019-12-09 12:37:04 +08:00
|
|
|
* @legacy_dai_naming: if %true, use legacy single-name format;
|
|
|
|
* if %false, use multiple-name format;
|
2019-11-06 09:07:17 +08:00
|
|
|
*
|
|
|
|
* Topology can use this API to register DAIs when probing a component.
|
|
|
|
* These DAIs's widgets will be freed in the card cleanup and the DAIs
|
|
|
|
* will be freed in the component cleanup.
|
|
|
|
*/
|
|
|
|
struct snd_soc_dai *snd_soc_register_dai(struct snd_soc_component *component,
|
|
|
|
struct snd_soc_dai_driver *dai_drv,
|
|
|
|
bool legacy_dai_naming)
|
2015-12-31 16:40:20 +08:00
|
|
|
{
|
|
|
|
struct device *dev = component->dev;
|
|
|
|
struct snd_soc_dai *dai;
|
|
|
|
|
2019-11-06 09:07:17 +08:00
|
|
|
lockdep_assert_held(&client_mutex);
|
|
|
|
|
2019-10-02 13:22:57 +08:00
|
|
|
dai = devm_kzalloc(dev, sizeof(*dai), GFP_KERNEL);
|
2015-12-31 16:40:20 +08:00
|
|
|
if (dai == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Back in the old days when we still had component-less DAIs,
|
|
|
|
* instead of having a static name, component-less DAIs would
|
|
|
|
* inherit the name of the parent device so it is possible to
|
|
|
|
* register multiple instances of the DAI. We still need to keep
|
|
|
|
* the same naming style even though those DAIs are not
|
|
|
|
* component-less anymore.
|
|
|
|
*/
|
|
|
|
if (legacy_dai_naming &&
|
2018-10-18 19:18:28 +08:00
|
|
|
(dai_drv->id == 0 || dai_drv->name == NULL)) {
|
2015-12-31 16:40:20 +08:00
|
|
|
dai->name = fmt_single_name(dev, &dai->id);
|
|
|
|
} else {
|
|
|
|
dai->name = fmt_multiple_name(dev, dai_drv);
|
|
|
|
if (dai_drv->id)
|
|
|
|
dai->id = dai_drv->id;
|
|
|
|
else
|
|
|
|
dai->id = component->num_dai;
|
|
|
|
}
|
2019-10-02 13:22:57 +08:00
|
|
|
if (!dai->name)
|
2015-12-31 16:40:20 +08:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
dai->component = component;
|
|
|
|
dai->dev = dev;
|
|
|
|
dai->driver = dai_drv;
|
|
|
|
|
2018-09-21 13:23:17 +08:00
|
|
|
/* see for_each_component_dais */
|
2017-12-20 09:48:29 +08:00
|
|
|
list_add_tail(&dai->list, &component->dai_list);
|
2015-12-31 16:40:20 +08:00
|
|
|
component->num_dai++;
|
|
|
|
|
|
|
|
dev_dbg(dev, "ASoC: Registered DAI '%s'\n", dai->name);
|
|
|
|
return dai;
|
|
|
|
}
|
2022-03-11 23:35:29 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_register_dai);
|
2019-11-05 14:47:04 +08:00
|
|
|
|
2019-11-05 14:47:00 +08:00
|
|
|
/**
|
2020-10-24 00:33:35 +08:00
|
|
|
* snd_soc_unregister_dais - Unregister DAIs from the ASoC core
|
2019-11-05 14:47:00 +08:00
|
|
|
*
|
|
|
|
* @component: The component for which the DAIs should be unregistered
|
|
|
|
*/
|
|
|
|
static void snd_soc_unregister_dais(struct snd_soc_component *component)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai *dai, *_dai;
|
|
|
|
|
2019-11-05 14:47:04 +08:00
|
|
|
for_each_component_dais_safe(component, dai, _dai)
|
|
|
|
snd_soc_unregister_dai(dai);
|
2019-11-05 14:47:00 +08:00
|
|
|
}
|
|
|
|
|
2019-11-05 14:46:55 +08:00
|
|
|
/**
|
|
|
|
* snd_soc_register_dais - Register a DAI with the ASoC core
|
|
|
|
*
|
|
|
|
* @component: The component the DAIs are registered for
|
|
|
|
* @dai_drv: DAI driver to use for the DAIs
|
|
|
|
* @count: Number of DAIs
|
|
|
|
*/
|
|
|
|
static int snd_soc_register_dais(struct snd_soc_component *component,
|
|
|
|
struct snd_soc_dai_driver *dai_drv,
|
|
|
|
size_t count)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai *dai;
|
|
|
|
unsigned int i;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++) {
|
2019-11-05 14:47:18 +08:00
|
|
|
dai = snd_soc_register_dai(component, dai_drv + i, count == 1 &&
|
ASoC: core: Switch core to new DAI naming flag
Now all the drivers are updated to have the new legacy_dai_naming
flag, update the core code so it also uses the new flag. Paving
the way for the old non_legacy_dai_naming flag to be removed.
It should be noted this patch will affect the CODEC drivers that don't
specify the non_legacy_dai_naming flag. These drivers will update from
using legacy DAI naming to the new scheme after this patch, this is
being considered a fix as the intention was for all CODEC drivers to use
the new scheme and all existing CODEC drivers were updated to do so
before componentisation. This just corrects those devices that have
snuck in since componentisation. The corrected devices are as
follows:
adau1372, cros_ec_codec, cs35l41, cs35l45, cx2072x, hdac_hda,
jz4725/60/70, lpass-rx/tx/va/wsa-macro, max98504, max9877,
mt6351/58/59, mt6660, pcm3060, rk3328, rt1308/16, rt5514,
rt5677, rt700/11/15, rt9120, sdw-mockup, tlv320adc3xxx, tscs454,
wcd9335/4x/8x, wsa881x
Some of these devices are used in some in kernel machine drivers,
however it appears all the usages use the actual DAI driver name
(since snd_soc_find_dai checks both the DAI name and the DAI driver
name). So it is not believed this change will break any in tree
machine drivers.
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Link: https://lore.kernel.org/r/20220623125250.2355471-35-ckeepax@opensource.cirrus.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2022-06-23 20:51:48 +08:00
|
|
|
component->driver->legacy_dai_naming);
|
2019-11-05 14:46:55 +08:00
|
|
|
if (dai == NULL) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
snd_soc_unregister_dais(component);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-10-11 09:38:29 +08:00
|
|
|
#define ENDIANNESS_MAP(name) \
|
|
|
|
(SNDRV_PCM_FMTBIT_##name##LE | SNDRV_PCM_FMTBIT_##name##BE)
|
|
|
|
static u64 endianness_format_map[] = {
|
|
|
|
ENDIANNESS_MAP(S16_),
|
|
|
|
ENDIANNESS_MAP(U16_),
|
|
|
|
ENDIANNESS_MAP(S24_),
|
|
|
|
ENDIANNESS_MAP(U24_),
|
|
|
|
ENDIANNESS_MAP(S32_),
|
|
|
|
ENDIANNESS_MAP(U32_),
|
|
|
|
ENDIANNESS_MAP(S24_3),
|
|
|
|
ENDIANNESS_MAP(U24_3),
|
|
|
|
ENDIANNESS_MAP(S20_3),
|
|
|
|
ENDIANNESS_MAP(U20_3),
|
|
|
|
ENDIANNESS_MAP(S18_3),
|
|
|
|
ENDIANNESS_MAP(U18_3),
|
|
|
|
ENDIANNESS_MAP(FLOAT_),
|
|
|
|
ENDIANNESS_MAP(FLOAT64_),
|
|
|
|
ENDIANNESS_MAP(IEC958_SUBFRAME_),
|
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Fix up the DAI formats for endianness: codecs don't actually see
|
|
|
|
* the endianness of the data but we're using the CPU format
|
|
|
|
* definitions which do need to include endianness so we ensure that
|
|
|
|
* codec DAIs always have both big and little endian variants set.
|
|
|
|
*/
|
|
|
|
static void convert_endianness_formats(struct snd_soc_pcm_stream *stream)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(endianness_format_map); i++)
|
|
|
|
if (stream->formats & endianness_format_map[i])
|
|
|
|
stream->formats |= endianness_format_map[i];
|
|
|
|
}
|
|
|
|
|
2018-09-12 17:15:00 +08:00
|
|
|
static void snd_soc_try_rebind_card(void)
|
|
|
|
{
|
|
|
|
struct snd_soc_card *card, *c;
|
|
|
|
|
2019-08-07 09:31:19 +08:00
|
|
|
list_for_each_entry_safe(card, c, &unbind_card_list, list)
|
|
|
|
if (!snd_soc_bind_card(card))
|
|
|
|
list_del(&card->list);
|
2018-09-12 17:15:00 +08:00
|
|
|
}
|
|
|
|
|
ASoC: soc-core: add snd_soc_del_component_unlocked()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and is difficult to debug.
Now ALSA SoC has snd_soc_add_component(), but there is no paired
snd_soc_del_component(). Thus, snd_soc_unregister_component() is
calling cleanup function randomly. it is difficult to read.
This patch adds missing snd_soc_del_component_unlocked() and
balance up code.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/8736f23jn4.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-05 14:46:39 +08:00
|
|
|
static void snd_soc_del_component_unlocked(struct snd_soc_component *component)
|
|
|
|
{
|
2019-11-05 14:46:45 +08:00
|
|
|
struct snd_soc_card *card = component->card;
|
|
|
|
|
ASoC: soc-core: add snd_soc_del_component_unlocked()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and is difficult to debug.
Now ALSA SoC has snd_soc_add_component(), but there is no paired
snd_soc_del_component(). Thus, snd_soc_unregister_component() is
calling cleanup function randomly. it is difficult to read.
This patch adds missing snd_soc_del_component_unlocked() and
balance up code.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/8736f23jn4.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-05 14:46:39 +08:00
|
|
|
snd_soc_unregister_dais(component);
|
2019-11-05 14:46:45 +08:00
|
|
|
|
|
|
|
if (card)
|
|
|
|
snd_soc_unbind_card(card, false);
|
|
|
|
|
|
|
|
list_del(&component->list);
|
ASoC: soc-core: add snd_soc_del_component_unlocked()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and is difficult to debug.
Now ALSA SoC has snd_soc_add_component(), but there is no paired
snd_soc_del_component(). Thus, snd_soc_unregister_component() is
calling cleanup function randomly. it is difficult to read.
This patch adds missing snd_soc_del_component_unlocked() and
balance up code.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/8736f23jn4.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-05 14:46:39 +08:00
|
|
|
}
|
|
|
|
|
2020-07-31 22:41:44 +08:00
|
|
|
int snd_soc_component_initialize(struct snd_soc_component *component,
|
|
|
|
const struct snd_soc_component_driver *driver,
|
2020-07-31 22:41:45 +08:00
|
|
|
struct device *dev)
|
2020-07-31 22:41:44 +08:00
|
|
|
{
|
|
|
|
INIT_LIST_HEAD(&component->dai_list);
|
|
|
|
INIT_LIST_HEAD(&component->dobj_list);
|
|
|
|
INIT_LIST_HEAD(&component->card_list);
|
2021-10-09 14:58:40 +08:00
|
|
|
INIT_LIST_HEAD(&component->list);
|
2020-07-31 22:41:44 +08:00
|
|
|
mutex_init(&component->io_mutex);
|
|
|
|
|
2020-07-31 22:41:45 +08:00
|
|
|
component->name = fmt_single_name(dev, &component->id);
|
|
|
|
if (!component->name) {
|
|
|
|
dev_err(dev, "ASoC: Failed to allocate name\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2020-07-31 22:41:44 +08:00
|
|
|
component->dev = dev;
|
|
|
|
component->driver = driver;
|
|
|
|
|
2022-03-10 04:21:55 +08:00
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
|
|
if (!component->debugfs_prefix)
|
|
|
|
component->debugfs_prefix = driver->debugfs_prefix;
|
|
|
|
#endif
|
|
|
|
|
2020-07-31 22:41:44 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_component_initialize);
|
|
|
|
|
2020-07-31 22:41:46 +08:00
|
|
|
int snd_soc_add_component(struct snd_soc_component *component,
|
|
|
|
struct snd_soc_dai_driver *dai_drv,
|
|
|
|
int num_dai)
|
2013-09-05 10:39:03 +08:00
|
|
|
{
|
2014-06-17 00:13:03 +08:00
|
|
|
int ret;
|
2017-10-11 09:38:29 +08:00
|
|
|
int i;
|
2013-09-05 10:39:03 +08:00
|
|
|
|
2019-11-05 14:46:45 +08:00
|
|
|
mutex_lock(&client_mutex);
|
|
|
|
|
2020-07-31 22:41:46 +08:00
|
|
|
if (component->driver->endianness) {
|
2017-10-11 09:38:29 +08:00
|
|
|
for (i = 0; i < num_dai; i++) {
|
|
|
|
convert_endianness_formats(&dai_drv[i].playback);
|
|
|
|
convert_endianness_formats(&dai_drv[i].capture);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-08 11:23:01 +08:00
|
|
|
ret = snd_soc_register_dais(component, dai_drv, num_dai);
|
2014-06-17 00:13:03 +08:00
|
|
|
if (ret < 0) {
|
2020-07-31 22:41:46 +08:00
|
|
|
dev_err(component->dev, "ASoC: Failed to register DAIs: %d\n",
|
|
|
|
ret);
|
2014-06-17 00:13:03 +08:00
|
|
|
goto err_cleanup;
|
|
|
|
}
|
2013-09-05 10:39:03 +08:00
|
|
|
|
2019-11-05 14:46:45 +08:00
|
|
|
if (!component->driver->write && !component->driver->read) {
|
|
|
|
if (!component->regmap)
|
|
|
|
component->regmap = dev_get_regmap(component->dev,
|
|
|
|
NULL);
|
|
|
|
if (component->regmap)
|
|
|
|
snd_soc_component_setup_regmap(component);
|
|
|
|
}
|
2014-04-28 20:30:51 +08:00
|
|
|
|
2019-11-05 14:46:45 +08:00
|
|
|
/* see for_each_component */
|
|
|
|
list_add(&component->list, &component_list);
|
2014-04-28 20:30:51 +08:00
|
|
|
|
2014-06-17 00:13:03 +08:00
|
|
|
err_cleanup:
|
2019-11-05 14:46:45 +08:00
|
|
|
if (ret < 0)
|
|
|
|
snd_soc_del_component_unlocked(component);
|
2020-07-31 22:41:46 +08:00
|
|
|
|
2019-11-05 14:46:45 +08:00
|
|
|
mutex_unlock(&client_mutex);
|
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
snd_soc_try_rebind_card();
|
|
|
|
|
2014-06-17 00:13:03 +08:00
|
|
|
return ret;
|
2014-04-28 20:30:51 +08:00
|
|
|
}
|
2017-10-02 13:10:17 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_add_component);
|
|
|
|
|
|
|
|
int snd_soc_register_component(struct device *dev,
|
|
|
|
const struct snd_soc_component_driver *component_driver,
|
|
|
|
struct snd_soc_dai_driver *dai_drv,
|
|
|
|
int num_dai)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *component;
|
2020-07-31 22:41:46 +08:00
|
|
|
int ret;
|
2017-10-02 13:10:17 +08:00
|
|
|
|
2018-03-19 15:27:17 +08:00
|
|
|
component = devm_kzalloc(dev, sizeof(*component), GFP_KERNEL);
|
2017-10-02 13:10:33 +08:00
|
|
|
if (!component)
|
2017-10-02 13:10:17 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
|
2020-07-31 22:41:46 +08:00
|
|
|
ret = snd_soc_component_initialize(component, component_driver, dev);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return snd_soc_add_component(component, dai_drv, num_dai);
|
2017-10-02 13:10:17 +08:00
|
|
|
}
|
2014-06-17 00:13:03 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_register_component);
|
2014-04-28 20:30:51 +08:00
|
|
|
|
2020-07-07 15:42:37 +08:00
|
|
|
/**
|
|
|
|
* snd_soc_unregister_component_by_driver - Unregister component using a given driver
|
|
|
|
* from the ASoC core
|
|
|
|
*
|
|
|
|
* @dev: The device to unregister
|
|
|
|
* @component_driver: The component driver to unregister
|
|
|
|
*/
|
|
|
|
void snd_soc_unregister_component_by_driver(struct device *dev,
|
|
|
|
const struct snd_soc_component_driver *component_driver)
|
|
|
|
{
|
|
|
|
struct snd_soc_component *component;
|
|
|
|
|
|
|
|
if (!component_driver)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mutex_lock(&client_mutex);
|
|
|
|
component = snd_soc_lookup_component_nolocked(dev, component_driver->name);
|
|
|
|
if (!component)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
snd_soc_del_component_unlocked(component);
|
|
|
|
|
|
|
|
out:
|
|
|
|
mutex_unlock(&client_mutex);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_unregister_component_by_driver);
|
|
|
|
|
2013-09-05 10:39:03 +08:00
|
|
|
/**
|
2017-08-07 10:06:55 +08:00
|
|
|
* snd_soc_unregister_component - Unregister all related component
|
|
|
|
* from the ASoC core
|
2013-09-05 10:39:03 +08:00
|
|
|
*
|
2015-08-25 15:14:48 +08:00
|
|
|
* @dev: The device to unregister
|
2013-09-05 10:39:03 +08:00
|
|
|
*/
|
2019-11-05 14:46:51 +08:00
|
|
|
void snd_soc_unregister_component(struct device *dev)
|
2013-09-05 10:39:03 +08:00
|
|
|
{
|
2015-03-08 02:34:03 +08:00
|
|
|
mutex_lock(&client_mutex);
|
2019-11-05 14:46:51 +08:00
|
|
|
while (1) {
|
2021-07-29 09:15:47 +08:00
|
|
|
struct snd_soc_component *component = snd_soc_lookup_component_nolocked(dev, NULL);
|
|
|
|
|
2019-11-05 14:46:51 +08:00
|
|
|
if (!component)
|
|
|
|
break;
|
2017-08-07 10:06:40 +08:00
|
|
|
|
ASoC: soc-core: add snd_soc_del_component_unlocked()
It is easy to read code if it is cleanly using paired function/naming,
like start <-> stop, register <-> unregister, etc, etc.
But, current ALSA SoC code is very random, unbalance, not paired, etc.
It is easy to create bug at the such code, and is difficult to debug.
Now ALSA SoC has snd_soc_add_component(), but there is no paired
snd_soc_del_component(). Thus, snd_soc_unregister_component() is
calling cleanup function randomly. it is difficult to read.
This patch adds missing snd_soc_del_component_unlocked() and
balance up code.
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Link: https://lore.kernel.org/r/8736f23jn4.wl-kuninori.morimoto.gx@renesas.com
Signed-off-by: Mark Brown <broonie@kernel.org>
2019-11-05 14:46:39 +08:00
|
|
|
snd_soc_del_component_unlocked(component);
|
2013-09-05 10:39:03 +08:00
|
|
|
}
|
2015-03-08 02:34:03 +08:00
|
|
|
mutex_unlock(&client_mutex);
|
2013-09-05 10:39:03 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_unregister_component);
|
|
|
|
|
2011-12-13 06:55:34 +08:00
|
|
|
/* Retrieve a card's name from device tree */
|
2017-01-27 14:37:51 +08:00
|
|
|
int snd_soc_of_parse_card_name(struct snd_soc_card *card,
|
|
|
|
const char *propname)
|
2011-12-13 06:55:34 +08:00
|
|
|
{
|
2017-01-27 14:37:51 +08:00
|
|
|
struct device_node *np;
|
2011-12-13 06:55:34 +08:00
|
|
|
int ret;
|
|
|
|
|
2014-07-04 16:53:00 +08:00
|
|
|
if (!card->dev) {
|
|
|
|
pr_err("card->dev is not set before calling %s\n", __func__);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2017-01-27 14:37:51 +08:00
|
|
|
np = card->dev->of_node;
|
2014-07-04 16:53:00 +08:00
|
|
|
|
2011-12-13 06:55:34 +08:00
|
|
|
ret = of_property_read_string_index(np, propname, 0, &card->name);
|
|
|
|
/*
|
|
|
|
* EINVAL means the property does not exist. This is fine providing
|
|
|
|
* card->name was previously set, which is checked later in
|
|
|
|
* snd_soc_register_card.
|
|
|
|
*/
|
|
|
|
if (ret < 0 && ret != -EINVAL) {
|
|
|
|
dev_err(card->dev,
|
2012-11-19 22:47:09 +08:00
|
|
|
"ASoC: Property '%s' could not be read: %d\n",
|
2011-12-13 06:55:34 +08:00
|
|
|
propname, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2017-01-27 14:37:51 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_parse_card_name);
|
2011-12-13 06:55:34 +08:00
|
|
|
|
ASoC: add snd_soc_of_parse_audio_simple_widgets for DT
This patch adds snd_soc_of_parse_audio_simple_widgets() and supports
below style of widgets name on DT:
"template-wname", "user supplied wname"
For instance:
simple-audio-widgets =
"Microphone", "Microphone Jack",
"Line", "Line In Jack",
"Line", "Line Out Jack",
"Headphone", "Headphone Jack",
"Speaker", "Speaker External";
The "template-wname" currently includes: "Microphone", "Line", "Headphone"
and "Speaker".
Signed-off-by: Xiubo Li <Li.Xiubo@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2014-02-08 15:59:52 +08:00
|
|
|
static const struct snd_soc_dapm_widget simple_widgets[] = {
|
|
|
|
SND_SOC_DAPM_MIC("Microphone", NULL),
|
|
|
|
SND_SOC_DAPM_LINE("Line", NULL),
|
|
|
|
SND_SOC_DAPM_HP("Headphone", NULL),
|
|
|
|
SND_SOC_DAPM_SPK("Speaker", NULL),
|
|
|
|
};
|
|
|
|
|
2017-01-27 14:37:34 +08:00
|
|
|
int snd_soc_of_parse_audio_simple_widgets(struct snd_soc_card *card,
|
ASoC: add snd_soc_of_parse_audio_simple_widgets for DT
This patch adds snd_soc_of_parse_audio_simple_widgets() and supports
below style of widgets name on DT:
"template-wname", "user supplied wname"
For instance:
simple-audio-widgets =
"Microphone", "Microphone Jack",
"Line", "Line In Jack",
"Line", "Line Out Jack",
"Headphone", "Headphone Jack",
"Speaker", "Speaker External";
The "template-wname" currently includes: "Microphone", "Line", "Headphone"
and "Speaker".
Signed-off-by: Xiubo Li <Li.Xiubo@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2014-02-08 15:59:52 +08:00
|
|
|
const char *propname)
|
|
|
|
{
|
2017-01-27 14:37:34 +08:00
|
|
|
struct device_node *np = card->dev->of_node;
|
ASoC: add snd_soc_of_parse_audio_simple_widgets for DT
This patch adds snd_soc_of_parse_audio_simple_widgets() and supports
below style of widgets name on DT:
"template-wname", "user supplied wname"
For instance:
simple-audio-widgets =
"Microphone", "Microphone Jack",
"Line", "Line In Jack",
"Line", "Line Out Jack",
"Headphone", "Headphone Jack",
"Speaker", "Speaker External";
The "template-wname" currently includes: "Microphone", "Line", "Headphone"
and "Speaker".
Signed-off-by: Xiubo Li <Li.Xiubo@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2014-02-08 15:59:52 +08:00
|
|
|
struct snd_soc_dapm_widget *widgets;
|
|
|
|
const char *template, *wname;
|
2021-07-29 09:15:56 +08:00
|
|
|
int i, j, num_widgets;
|
ASoC: add snd_soc_of_parse_audio_simple_widgets for DT
This patch adds snd_soc_of_parse_audio_simple_widgets() and supports
below style of widgets name on DT:
"template-wname", "user supplied wname"
For instance:
simple-audio-widgets =
"Microphone", "Microphone Jack",
"Line", "Line In Jack",
"Line", "Line Out Jack",
"Headphone", "Headphone Jack",
"Speaker", "Speaker External";
The "template-wname" currently includes: "Microphone", "Line", "Headphone"
and "Speaker".
Signed-off-by: Xiubo Li <Li.Xiubo@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2014-02-08 15:59:52 +08:00
|
|
|
|
|
|
|
num_widgets = of_property_count_strings(np, propname);
|
|
|
|
if (num_widgets < 0) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: Property '%s' does not exist\n", propname);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
2022-04-22 00:25:05 +08:00
|
|
|
if (!num_widgets) {
|
|
|
|
dev_err(card->dev, "ASoC: Property '%s's length is zero\n",
|
|
|
|
propname);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
ASoC: add snd_soc_of_parse_audio_simple_widgets for DT
This patch adds snd_soc_of_parse_audio_simple_widgets() and supports
below style of widgets name on DT:
"template-wname", "user supplied wname"
For instance:
simple-audio-widgets =
"Microphone", "Microphone Jack",
"Line", "Line In Jack",
"Line", "Line Out Jack",
"Headphone", "Headphone Jack",
"Speaker", "Speaker External";
The "template-wname" currently includes: "Microphone", "Line", "Headphone"
and "Speaker".
Signed-off-by: Xiubo Li <Li.Xiubo@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2014-02-08 15:59:52 +08:00
|
|
|
if (num_widgets & 1) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: Property '%s' length is not even\n", propname);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
num_widgets /= 2;
|
|
|
|
|
|
|
|
widgets = devm_kcalloc(card->dev, num_widgets, sizeof(*widgets),
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!widgets) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: Could not allocate memory for widgets\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < num_widgets; i++) {
|
2021-07-29 09:15:56 +08:00
|
|
|
int ret = of_property_read_string_index(np, propname,
|
|
|
|
2 * i, &template);
|
ASoC: add snd_soc_of_parse_audio_simple_widgets for DT
This patch adds snd_soc_of_parse_audio_simple_widgets() and supports
below style of widgets name on DT:
"template-wname", "user supplied wname"
For instance:
simple-audio-widgets =
"Microphone", "Microphone Jack",
"Line", "Line In Jack",
"Line", "Line Out Jack",
"Headphone", "Headphone Jack",
"Speaker", "Speaker External";
The "template-wname" currently includes: "Microphone", "Line", "Headphone"
and "Speaker".
Signed-off-by: Xiubo Li <Li.Xiubo@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2014-02-08 15:59:52 +08:00
|
|
|
if (ret) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: Property '%s' index %d read error:%d\n",
|
|
|
|
propname, 2 * i, ret);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (j = 0; j < ARRAY_SIZE(simple_widgets); j++) {
|
|
|
|
if (!strncmp(template, simple_widgets[j].name,
|
|
|
|
strlen(simple_widgets[j].name))) {
|
|
|
|
widgets[i] = simple_widgets[j];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (j >= ARRAY_SIZE(simple_widgets)) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: DAPM widget '%s' is not supported\n",
|
|
|
|
template);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = of_property_read_string_index(np, propname,
|
|
|
|
(2 * i) + 1,
|
|
|
|
&wname);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: Property '%s' index %d read error:%d\n",
|
|
|
|
propname, (2 * i) + 1, ret);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
widgets[i].name = wname;
|
|
|
|
}
|
|
|
|
|
2015-02-15 09:22:49 +08:00
|
|
|
card->of_dapm_widgets = widgets;
|
|
|
|
card->num_of_dapm_widgets = num_widgets;
|
ASoC: add snd_soc_of_parse_audio_simple_widgets for DT
This patch adds snd_soc_of_parse_audio_simple_widgets() and supports
below style of widgets name on DT:
"template-wname", "user supplied wname"
For instance:
simple-audio-widgets =
"Microphone", "Microphone Jack",
"Line", "Line In Jack",
"Line", "Line Out Jack",
"Headphone", "Headphone Jack",
"Speaker", "Speaker External";
The "template-wname" currently includes: "Microphone", "Line", "Headphone"
and "Speaker".
Signed-off-by: Xiubo Li <Li.Xiubo@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2014-02-08 15:59:52 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2017-01-27 14:37:34 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_parse_audio_simple_widgets);
|
ASoC: add snd_soc_of_parse_audio_simple_widgets for DT
This patch adds snd_soc_of_parse_audio_simple_widgets() and supports
below style of widgets name on DT:
"template-wname", "user supplied wname"
For instance:
simple-audio-widgets =
"Microphone", "Microphone Jack",
"Line", "Line In Jack",
"Line", "Line Out Jack",
"Headphone", "Headphone Jack",
"Speaker", "Speaker External";
The "template-wname" currently includes: "Microphone", "Line", "Headphone"
and "Speaker".
Signed-off-by: Xiubo Li <Li.Xiubo@freescale.com>
Signed-off-by: Mark Brown <broonie@linaro.org>
2014-02-08 15:59:52 +08:00
|
|
|
|
2021-12-14 22:20:46 +08:00
|
|
|
int snd_soc_of_parse_pin_switches(struct snd_soc_card *card, const char *prop)
|
|
|
|
{
|
|
|
|
const unsigned int nb_controls_max = 16;
|
|
|
|
const char **strings, *control_name;
|
|
|
|
struct snd_kcontrol_new *controls;
|
|
|
|
struct device *dev = card->dev;
|
|
|
|
unsigned int i, nb_controls;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!of_property_read_bool(dev->of_node, prop))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
strings = devm_kcalloc(dev, nb_controls_max,
|
|
|
|
sizeof(*strings), GFP_KERNEL);
|
|
|
|
if (!strings)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
ret = of_property_read_string_array(dev->of_node, prop,
|
|
|
|
strings, nb_controls_max);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
nb_controls = (unsigned int)ret;
|
|
|
|
|
|
|
|
controls = devm_kcalloc(dev, nb_controls,
|
|
|
|
sizeof(*controls), GFP_KERNEL);
|
|
|
|
if (!controls)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
for (i = 0; i < nb_controls; i++) {
|
|
|
|
control_name = devm_kasprintf(dev, GFP_KERNEL,
|
|
|
|
"%s Switch", strings[i]);
|
|
|
|
if (!control_name)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
|
|
controls[i].name = control_name;
|
|
|
|
controls[i].info = snd_soc_dapm_info_pin_switch;
|
|
|
|
controls[i].get = snd_soc_dapm_get_pin_switch;
|
|
|
|
controls[i].put = snd_soc_dapm_put_pin_switch;
|
|
|
|
controls[i].private_value = (unsigned long)strings[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
card->controls = controls;
|
|
|
|
card->num_controls = nb_controls;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_parse_pin_switches);
|
|
|
|
|
2018-07-17 23:43:02 +08:00
|
|
|
int snd_soc_of_get_slot_mask(struct device_node *np,
|
|
|
|
const char *prop_name,
|
|
|
|
unsigned int *mask)
|
2015-09-10 02:27:43 +08:00
|
|
|
{
|
|
|
|
u32 val;
|
2015-09-17 18:13:38 +08:00
|
|
|
const __be32 *of_slot_mask = of_get_property(np, prop_name, &val);
|
2015-09-10 02:27:43 +08:00
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!of_slot_mask)
|
|
|
|
return 0;
|
|
|
|
val /= sizeof(u32);
|
|
|
|
for (i = 0; i < val; i++)
|
|
|
|
if (be32_to_cpup(&of_slot_mask[i]))
|
|
|
|
*mask |= (1 << i);
|
|
|
|
|
|
|
|
return val;
|
|
|
|
}
|
2018-07-17 23:43:02 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_get_slot_mask);
|
2015-09-10 02:27:43 +08:00
|
|
|
|
2014-02-14 09:34:35 +08:00
|
|
|
int snd_soc_of_parse_tdm_slot(struct device_node *np,
|
2015-09-10 02:27:43 +08:00
|
|
|
unsigned int *tx_mask,
|
|
|
|
unsigned int *rx_mask,
|
2014-02-14 09:34:35 +08:00
|
|
|
unsigned int *slots,
|
|
|
|
unsigned int *slot_width)
|
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
int ret;
|
|
|
|
|
2015-09-10 02:27:43 +08:00
|
|
|
if (tx_mask)
|
|
|
|
snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask", tx_mask);
|
|
|
|
if (rx_mask)
|
|
|
|
snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask", rx_mask);
|
|
|
|
|
2014-02-14 09:34:35 +08:00
|
|
|
if (of_property_read_bool(np, "dai-tdm-slot-num")) {
|
|
|
|
ret = of_property_read_u32(np, "dai-tdm-slot-num", &val);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (slots)
|
|
|
|
*slots = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (of_property_read_bool(np, "dai-tdm-slot-width")) {
|
|
|
|
ret = of_property_read_u32(np, "dai-tdm-slot-width", &val);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (slot_width)
|
|
|
|
*slot_width = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_parse_tdm_slot);
|
|
|
|
|
2023-07-10 09:20:11 +08:00
|
|
|
void snd_soc_dlc_use_cpu_as_platform(struct snd_soc_dai_link_component *platforms,
|
|
|
|
struct snd_soc_dai_link_component *cpus)
|
|
|
|
{
|
|
|
|
platforms->of_node = cpus->of_node;
|
|
|
|
platforms->dai_args = cpus->dai_args;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_dlc_use_cpu_as_platform);
|
|
|
|
|
2018-11-22 08:55:09 +08:00
|
|
|
void snd_soc_of_parse_node_prefix(struct device_node *np,
|
|
|
|
struct snd_soc_codec_conf *codec_conf,
|
|
|
|
struct device_node *of_node,
|
|
|
|
const char *propname)
|
2015-07-15 15:07:42 +08:00
|
|
|
{
|
|
|
|
const char *str;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = of_property_read_string(np, propname, &str);
|
|
|
|
if (ret < 0) {
|
|
|
|
/* no prefix is not error */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-12-13 08:54:36 +08:00
|
|
|
codec_conf->dlc.of_node = of_node;
|
2015-07-15 15:07:42 +08:00
|
|
|
codec_conf->name_prefix = str;
|
|
|
|
}
|
2018-11-22 08:55:09 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_parse_node_prefix);
|
2015-07-15 15:07:42 +08:00
|
|
|
|
2017-01-27 14:36:50 +08:00
|
|
|
int snd_soc_of_parse_audio_routing(struct snd_soc_card *card,
|
2011-12-13 06:55:35 +08:00
|
|
|
const char *propname)
|
|
|
|
{
|
2017-01-27 14:36:50 +08:00
|
|
|
struct device_node *np = card->dev->of_node;
|
2014-12-18 19:46:38 +08:00
|
|
|
int num_routes;
|
2011-12-13 06:55:35 +08:00
|
|
|
struct snd_soc_dapm_route *routes;
|
2021-07-29 09:16:01 +08:00
|
|
|
int i;
|
2011-12-13 06:55:35 +08:00
|
|
|
|
|
|
|
num_routes = of_property_count_strings(np, propname);
|
2012-04-24 15:24:43 +08:00
|
|
|
if (num_routes < 0 || num_routes & 1) {
|
2013-05-05 04:21:38 +08:00
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: Property '%s' does not exist or its length is not even\n",
|
|
|
|
propname);
|
2011-12-13 06:55:35 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
num_routes /= 2;
|
|
|
|
|
treewide: devm_kzalloc() -> devm_kcalloc()
The devm_kzalloc() function has a 2-factor argument form, devm_kcalloc().
This patch replaces cases of:
devm_kzalloc(handle, a * b, gfp)
with:
devm_kcalloc(handle, a * b, gfp)
as well as handling cases of:
devm_kzalloc(handle, a * b * c, gfp)
with:
devm_kzalloc(handle, array3_size(a, b, c), gfp)
as it's slightly less ugly than:
devm_kcalloc(handle, array_size(a, b), c, gfp)
This does, however, attempt to ignore constant size factors like:
devm_kzalloc(handle, 4 * 1024, gfp)
though any constants defined via macros get caught up in the conversion.
Any factors with a sizeof() of "unsigned char", "char", and "u8" were
dropped, since they're redundant.
Some manual whitespace fixes were needed in this patch, as Coccinelle
really liked to write "=devm_kcalloc..." instead of "= devm_kcalloc...".
The Coccinelle script used for this was:
// Fix redundant parens around sizeof().
@@
expression HANDLE;
type TYPE;
expression THING, E;
@@
(
devm_kzalloc(HANDLE,
- (sizeof(TYPE)) * E
+ sizeof(TYPE) * E
, ...)
|
devm_kzalloc(HANDLE,
- (sizeof(THING)) * E
+ sizeof(THING) * E
, ...)
)
// Drop single-byte sizes and redundant parens.
@@
expression HANDLE;
expression COUNT;
typedef u8;
typedef __u8;
@@
(
devm_kzalloc(HANDLE,
- sizeof(u8) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(__u8) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(char) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(unsigned char) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(u8) * COUNT
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(__u8) * COUNT
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(char) * COUNT
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(unsigned char) * COUNT
+ COUNT
, ...)
)
// 2-factor product with sizeof(type/expression) and identifier or constant.
@@
expression HANDLE;
type TYPE;
expression THING;
identifier COUNT_ID;
constant COUNT_CONST;
@@
(
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * (COUNT_ID)
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * COUNT_ID
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * (COUNT_CONST)
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * COUNT_CONST
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * (COUNT_ID)
+ COUNT_ID, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * COUNT_ID
+ COUNT_ID, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * (COUNT_CONST)
+ COUNT_CONST, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * COUNT_CONST
+ COUNT_CONST, sizeof(THING)
, ...)
)
// 2-factor product, only identifiers.
@@
expression HANDLE;
identifier SIZE, COUNT;
@@
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- SIZE * COUNT
+ COUNT, SIZE
, ...)
// 3-factor product with 1 sizeof(type) or sizeof(expression), with
// redundant parens removed.
@@
expression HANDLE;
expression THING;
identifier STRIDE, COUNT;
type TYPE;
@@
(
devm_kzalloc(HANDLE,
- sizeof(TYPE) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
)
// 3-factor product with 2 sizeof(variable), with redundant parens removed.
@@
expression HANDLE;
expression THING1, THING2;
identifier COUNT;
type TYPE1, TYPE2;
@@
(
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(TYPE2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
)
// 3-factor product, only identifiers, with redundant parens removed.
@@
expression HANDLE;
identifier STRIDE, SIZE, COUNT;
@@
(
devm_kzalloc(HANDLE,
- (COUNT) * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- (COUNT) * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- (COUNT) * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- (COUNT) * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
)
// Any remaining multi-factor products, first at least 3-factor products,
// when they're not all constants...
@@
expression HANDLE;
expression E1, E2, E3;
constant C1, C2, C3;
@@
(
devm_kzalloc(HANDLE, C1 * C2 * C3, ...)
|
devm_kzalloc(HANDLE,
- (E1) * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
|
devm_kzalloc(HANDLE,
- (E1) * (E2) * E3
+ array3_size(E1, E2, E3)
, ...)
|
devm_kzalloc(HANDLE,
- (E1) * (E2) * (E3)
+ array3_size(E1, E2, E3)
, ...)
|
devm_kzalloc(HANDLE,
- E1 * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
)
// And then all remaining 2 factors products when they're not all constants,
// keeping sizeof() as the second factor argument.
@@
expression HANDLE;
expression THING, E1, E2;
type TYPE;
constant C1, C2, C3;
@@
(
devm_kzalloc(HANDLE, sizeof(THING) * C2, ...)
|
devm_kzalloc(HANDLE, sizeof(TYPE) * C2, ...)
|
devm_kzalloc(HANDLE, C1 * C2 * C3, ...)
|
devm_kzalloc(HANDLE, C1 * C2, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * (E2)
+ E2, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * E2
+ E2, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * (E2)
+ E2, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * E2
+ E2, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- (E1) * E2
+ E1, E2
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- (E1) * (E2)
+ E1, E2
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- E1 * E2
+ E1, E2
, ...)
)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-06-13 05:07:58 +08:00
|
|
|
routes = devm_kcalloc(card->dev, num_routes, sizeof(*routes),
|
2011-12-13 06:55:35 +08:00
|
|
|
GFP_KERNEL);
|
|
|
|
if (!routes) {
|
|
|
|
dev_err(card->dev,
|
2012-11-19 22:47:09 +08:00
|
|
|
"ASoC: Could not allocate DAPM route table\n");
|
2021-06-17 18:37:29 +08:00
|
|
|
return -ENOMEM;
|
2011-12-13 06:55:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < num_routes; i++) {
|
2021-07-29 09:16:01 +08:00
|
|
|
int ret = of_property_read_string_index(np, propname,
|
|
|
|
2 * i, &routes[i].sink);
|
2011-12-13 06:55:35 +08:00
|
|
|
if (ret) {
|
2012-12-10 15:19:52 +08:00
|
|
|
dev_err(card->dev,
|
|
|
|
"ASoC: Property '%s' index %d could not be read: %d\n",
|
|
|
|
propname, 2 * i, ret);
|
2011-12-13 06:55:35 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
ret = of_property_read_string_index(np, propname,
|
2014-12-18 19:46:38 +08:00
|
|
|
(2 * i) + 1, &routes[i].source);
|
2011-12-13 06:55:35 +08:00
|
|
|
if (ret) {
|
|
|
|
dev_err(card->dev,
|
2012-12-10 15:19:52 +08:00
|
|
|
"ASoC: Property '%s' index %d could not be read: %d\n",
|
|
|
|
propname, (2 * i) + 1, ret);
|
2011-12-13 06:55:35 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-15 09:22:49 +08:00
|
|
|
card->num_of_dapm_routes = num_routes;
|
|
|
|
card->of_dapm_routes = routes;
|
2011-12-13 06:55:35 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2017-01-27 14:36:50 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_parse_audio_routing);
|
2011-12-13 06:55:35 +08:00
|
|
|
|
2020-08-01 18:02:55 +08:00
|
|
|
int snd_soc_of_parse_aux_devs(struct snd_soc_card *card, const char *propname)
|
|
|
|
{
|
|
|
|
struct device_node *node = card->dev->of_node;
|
|
|
|
struct snd_soc_aux_dev *aux;
|
|
|
|
int num, i;
|
|
|
|
|
|
|
|
num = of_count_phandle_with_args(node, propname, NULL);
|
|
|
|
if (num == -ENOENT) {
|
|
|
|
return 0;
|
|
|
|
} else if (num < 0) {
|
|
|
|
dev_err(card->dev, "ASOC: Property '%s' could not be read: %d\n",
|
|
|
|
propname, num);
|
|
|
|
return num;
|
|
|
|
}
|
|
|
|
|
|
|
|
aux = devm_kcalloc(card->dev, num, sizeof(*aux), GFP_KERNEL);
|
|
|
|
if (!aux)
|
|
|
|
return -ENOMEM;
|
|
|
|
card->aux_dev = aux;
|
|
|
|
card->num_aux_devs = num;
|
|
|
|
|
|
|
|
for_each_card_pre_auxs(card, i, aux) {
|
|
|
|
aux->dlc.of_node = of_parse_phandle(node, propname, i);
|
|
|
|
if (!aux->dlc.of_node)
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_parse_aux_devs);
|
|
|
|
|
2022-05-13 17:05:30 +08:00
|
|
|
unsigned int snd_soc_daifmt_clock_provider_flipped(unsigned int dai_fmt)
|
2021-06-14 08:56:54 +08:00
|
|
|
{
|
|
|
|
unsigned int inv_dai_fmt = dai_fmt & ~SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
|
|
|
|
|
|
|
|
switch (dai_fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_CBP_CFP:
|
|
|
|
inv_dai_fmt |= SND_SOC_DAIFMT_CBC_CFC;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_CBP_CFC:
|
|
|
|
inv_dai_fmt |= SND_SOC_DAIFMT_CBC_CFP;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_CBC_CFP:
|
|
|
|
inv_dai_fmt |= SND_SOC_DAIFMT_CBP_CFC;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_CBC_CFC:
|
|
|
|
inv_dai_fmt |= SND_SOC_DAIFMT_CBP_CFP;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return inv_dai_fmt;
|
|
|
|
}
|
2022-05-13 17:05:30 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_daifmt_clock_provider_flipped);
|
2021-06-14 08:56:54 +08:00
|
|
|
|
2021-06-14 08:56:46 +08:00
|
|
|
unsigned int snd_soc_daifmt_clock_provider_from_bitmap(unsigned int bit_frame)
|
|
|
|
{
|
2021-06-14 08:57:08 +08:00
|
|
|
/*
|
|
|
|
* bit_frame is return value from
|
|
|
|
* snd_soc_daifmt_parse_clock_provider_raw()
|
|
|
|
*/
|
|
|
|
|
2021-06-14 08:56:46 +08:00
|
|
|
/* Codec base */
|
|
|
|
switch (bit_frame) {
|
|
|
|
case 0x11:
|
|
|
|
return SND_SOC_DAIFMT_CBP_CFP;
|
|
|
|
case 0x10:
|
|
|
|
return SND_SOC_DAIFMT_CBP_CFC;
|
|
|
|
case 0x01:
|
|
|
|
return SND_SOC_DAIFMT_CBC_CFP;
|
|
|
|
default:
|
|
|
|
return SND_SOC_DAIFMT_CBC_CFC;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_daifmt_clock_provider_from_bitmap);
|
|
|
|
|
2021-06-14 08:57:08 +08:00
|
|
|
unsigned int snd_soc_daifmt_parse_format(struct device_node *np,
|
|
|
|
const char *prefix)
|
2013-01-15 10:36:04 +08:00
|
|
|
{
|
2021-07-29 09:15:42 +08:00
|
|
|
int ret;
|
2013-01-15 10:36:04 +08:00
|
|
|
char prop[128];
|
|
|
|
unsigned int format = 0;
|
|
|
|
int bit, frame;
|
|
|
|
const char *str;
|
|
|
|
struct {
|
|
|
|
char *name;
|
|
|
|
unsigned int val;
|
|
|
|
} of_fmt_table[] = {
|
|
|
|
{ "i2s", SND_SOC_DAIFMT_I2S },
|
|
|
|
{ "right_j", SND_SOC_DAIFMT_RIGHT_J },
|
|
|
|
{ "left_j", SND_SOC_DAIFMT_LEFT_J },
|
|
|
|
{ "dsp_a", SND_SOC_DAIFMT_DSP_A },
|
|
|
|
{ "dsp_b", SND_SOC_DAIFMT_DSP_B },
|
|
|
|
{ "ac97", SND_SOC_DAIFMT_AC97 },
|
|
|
|
{ "pdm", SND_SOC_DAIFMT_PDM},
|
|
|
|
{ "msb", SND_SOC_DAIFMT_MSB },
|
|
|
|
{ "lsb", SND_SOC_DAIFMT_LSB },
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!prefix)
|
|
|
|
prefix = "";
|
|
|
|
|
|
|
|
/*
|
2017-04-20 09:33:24 +08:00
|
|
|
* check "dai-format = xxx"
|
|
|
|
* or "[prefix]format = xxx"
|
2013-01-15 10:36:04 +08:00
|
|
|
* SND_SOC_DAIFMT_FORMAT_MASK area
|
|
|
|
*/
|
2017-04-20 09:33:24 +08:00
|
|
|
ret = of_property_read_string(np, "dai-format", &str);
|
|
|
|
if (ret < 0) {
|
|
|
|
snprintf(prop, sizeof(prop), "%sformat", prefix);
|
|
|
|
ret = of_property_read_string(np, prop, &str);
|
|
|
|
}
|
2013-01-15 10:36:04 +08:00
|
|
|
if (ret == 0) {
|
2021-07-29 09:15:42 +08:00
|
|
|
int i;
|
|
|
|
|
2013-01-15 10:36:04 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(of_fmt_table); i++) {
|
|
|
|
if (strcmp(str, of_fmt_table[i].name) == 0) {
|
|
|
|
format |= of_fmt_table[i].val;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2013-01-30 13:03:36 +08:00
|
|
|
* check "[prefix]continuous-clock"
|
2013-01-15 10:36:04 +08:00
|
|
|
* SND_SOC_DAIFMT_CLOCK_MASK area
|
|
|
|
*/
|
2013-01-30 13:03:36 +08:00
|
|
|
snprintf(prop, sizeof(prop), "%scontinuous-clock", prefix);
|
2016-08-05 16:56:51 +08:00
|
|
|
if (of_property_read_bool(np, prop))
|
2013-01-30 13:03:36 +08:00
|
|
|
format |= SND_SOC_DAIFMT_CONT;
|
|
|
|
else
|
|
|
|
format |= SND_SOC_DAIFMT_GATED;
|
2013-01-15 10:36:04 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* check "[prefix]bitclock-inversion"
|
|
|
|
* check "[prefix]frame-inversion"
|
|
|
|
* SND_SOC_DAIFMT_INV_MASK area
|
|
|
|
*/
|
|
|
|
snprintf(prop, sizeof(prop), "%sbitclock-inversion", prefix);
|
|
|
|
bit = !!of_get_property(np, prop, NULL);
|
|
|
|
|
|
|
|
snprintf(prop, sizeof(prop), "%sframe-inversion", prefix);
|
|
|
|
frame = !!of_get_property(np, prop, NULL);
|
|
|
|
|
|
|
|
switch ((bit << 4) + frame) {
|
|
|
|
case 0x11:
|
|
|
|
format |= SND_SOC_DAIFMT_IB_IF;
|
|
|
|
break;
|
|
|
|
case 0x10:
|
|
|
|
format |= SND_SOC_DAIFMT_IB_NF;
|
|
|
|
break;
|
|
|
|
case 0x01:
|
|
|
|
format |= SND_SOC_DAIFMT_NB_IF;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* SND_SOC_DAIFMT_NB_NF is default */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-06-14 08:57:08 +08:00
|
|
|
return format;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_daifmt_parse_format);
|
|
|
|
|
|
|
|
unsigned int snd_soc_daifmt_parse_clock_provider_raw(struct device_node *np,
|
|
|
|
const char *prefix,
|
|
|
|
struct device_node **bitclkmaster,
|
|
|
|
struct device_node **framemaster)
|
|
|
|
{
|
|
|
|
char prop[128];
|
|
|
|
unsigned int bit, frame;
|
|
|
|
|
|
|
|
if (!prefix)
|
|
|
|
prefix = "";
|
|
|
|
|
2013-01-15 10:36:04 +08:00
|
|
|
/*
|
|
|
|
* check "[prefix]bitclock-master"
|
|
|
|
* check "[prefix]frame-master"
|
|
|
|
*/
|
|
|
|
snprintf(prop, sizeof(prop), "%sbitclock-master", prefix);
|
|
|
|
bit = !!of_get_property(np, prop, NULL);
|
2014-03-24 18:15:24 +08:00
|
|
|
if (bit && bitclkmaster)
|
|
|
|
*bitclkmaster = of_parse_phandle(np, prop, 0);
|
2013-01-15 10:36:04 +08:00
|
|
|
|
|
|
|
snprintf(prop, sizeof(prop), "%sframe-master", prefix);
|
|
|
|
frame = !!of_get_property(np, prop, NULL);
|
2014-03-24 18:15:24 +08:00
|
|
|
if (frame && framemaster)
|
|
|
|
*framemaster = of_parse_phandle(np, prop, 0);
|
2013-01-15 10:36:04 +08:00
|
|
|
|
2021-06-14 08:57:08 +08:00
|
|
|
/*
|
|
|
|
* return bitmap.
|
|
|
|
* It will be parameter of
|
|
|
|
* snd_soc_daifmt_clock_provider_from_bitmap()
|
|
|
|
*/
|
|
|
|
return (bit << 4) + frame;
|
2013-01-15 10:36:04 +08:00
|
|
|
}
|
2021-06-14 08:57:08 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_daifmt_parse_clock_provider_raw);
|
2013-01-15 10:36:04 +08:00
|
|
|
|
2023-06-01 08:42:49 +08:00
|
|
|
int snd_soc_get_stream_cpu(struct snd_soc_dai_link *dai_link, int stream)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* [Normal]
|
|
|
|
*
|
|
|
|
* Playback
|
|
|
|
* CPU : SNDRV_PCM_STREAM_PLAYBACK
|
|
|
|
* Codec: SNDRV_PCM_STREAM_PLAYBACK
|
|
|
|
*
|
|
|
|
* Capture
|
|
|
|
* CPU : SNDRV_PCM_STREAM_CAPTURE
|
|
|
|
* Codec: SNDRV_PCM_STREAM_CAPTURE
|
|
|
|
*/
|
|
|
|
if (!dai_link->c2c_params)
|
|
|
|
return stream;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* [Codec2Codec]
|
|
|
|
*
|
|
|
|
* Playback
|
|
|
|
* CPU : SNDRV_PCM_STREAM_CAPTURE
|
|
|
|
* Codec: SNDRV_PCM_STREAM_PLAYBACK
|
|
|
|
*
|
|
|
|
* Capture
|
|
|
|
* CPU : SNDRV_PCM_STREAM_PLAYBACK
|
|
|
|
* Codec: SNDRV_PCM_STREAM_CAPTURE
|
|
|
|
*/
|
|
|
|
if (stream == SNDRV_PCM_STREAM_CAPTURE)
|
|
|
|
return SNDRV_PCM_STREAM_PLAYBACK;
|
|
|
|
|
|
|
|
return SNDRV_PCM_STREAM_CAPTURE;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_get_stream_cpu);
|
|
|
|
|
2017-05-18 09:39:25 +08:00
|
|
|
int snd_soc_get_dai_id(struct device_node *ep)
|
|
|
|
{
|
2019-05-13 15:07:20 +08:00
|
|
|
struct snd_soc_component *component;
|
2023-06-21 10:18:10 +08:00
|
|
|
struct snd_soc_dai_link_component dlc = {
|
|
|
|
.of_node = of_graph_get_port_parent(ep),
|
|
|
|
};
|
2017-05-18 09:39:25 +08:00
|
|
|
int ret;
|
|
|
|
|
2023-06-21 10:18:10 +08:00
|
|
|
|
2017-05-18 09:39:25 +08:00
|
|
|
/*
|
|
|
|
* For example HDMI case, HDMI has video/sound port,
|
|
|
|
* but ALSA SoC needs sound port number only.
|
|
|
|
* Thus counting HDMI DT port/endpoint doesn't work.
|
|
|
|
* Then, it should have .of_xlate_dai_id
|
|
|
|
*/
|
|
|
|
ret = -ENOTSUPP;
|
|
|
|
mutex_lock(&client_mutex);
|
2019-06-20 08:49:27 +08:00
|
|
|
component = soc_find_component(&dlc);
|
2019-07-26 12:51:26 +08:00
|
|
|
if (component)
|
|
|
|
ret = snd_soc_component_of_xlate_dai_id(component, ep);
|
2017-05-18 09:39:25 +08:00
|
|
|
mutex_unlock(&client_mutex);
|
|
|
|
|
2019-06-20 08:49:27 +08:00
|
|
|
of_node_put(dlc.of_node);
|
2017-07-28 16:23:15 +08:00
|
|
|
|
2017-05-18 09:39:25 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_get_dai_id);
|
|
|
|
|
2023-06-20 10:14:06 +08:00
|
|
|
int snd_soc_get_dlc(const struct of_phandle_args *args, struct snd_soc_dai_link_component *dlc)
|
2013-09-11 08:39:56 +08:00
|
|
|
{
|
|
|
|
struct snd_soc_component *pos;
|
2014-11-25 20:16:12 +08:00
|
|
|
int ret = -EPROBE_DEFER;
|
2013-09-11 08:39:56 +08:00
|
|
|
|
|
|
|
mutex_lock(&client_mutex);
|
2018-09-21 13:23:01 +08:00
|
|
|
for_each_component(pos) {
|
2021-07-29 09:15:38 +08:00
|
|
|
struct device_node *component_of_node = soc_component_to_node(pos);
|
2015-05-27 02:59:05 +08:00
|
|
|
|
2022-02-10 19:19:12 +08:00
|
|
|
if (component_of_node != args->np || !pos->num_dai)
|
2013-09-11 08:39:56 +08:00
|
|
|
continue;
|
|
|
|
|
2023-06-20 10:14:06 +08:00
|
|
|
ret = snd_soc_component_of_xlate_dai_name(pos, args, &dlc->dai_name);
|
2019-07-26 12:51:31 +08:00
|
|
|
if (ret == -ENOTSUPP) {
|
2017-12-20 09:48:29 +08:00
|
|
|
struct snd_soc_dai *dai;
|
2013-10-17 13:05:26 +08:00
|
|
|
int id = -1;
|
|
|
|
|
2014-11-25 20:16:12 +08:00
|
|
|
switch (args->args_count) {
|
2013-10-17 13:05:26 +08:00
|
|
|
case 0:
|
|
|
|
id = 0; /* same as dai_drv[0] */
|
|
|
|
break;
|
|
|
|
case 1:
|
2014-11-25 20:16:12 +08:00
|
|
|
id = args->args[0];
|
2013-10-17 13:05:26 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* not supported */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (id < 0 || id >= pos->num_dai) {
|
|
|
|
ret = -EINVAL;
|
2014-04-21 19:14:46 +08:00
|
|
|
continue;
|
2013-10-17 13:05:26 +08:00
|
|
|
}
|
2013-12-20 14:39:51 +08:00
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
2017-12-20 09:48:29 +08:00
|
|
|
/* find target DAI */
|
2018-09-21 13:23:17 +08:00
|
|
|
for_each_component_dais(pos, dai) {
|
2017-12-20 09:48:29 +08:00
|
|
|
if (id == 0)
|
|
|
|
break;
|
|
|
|
id--;
|
|
|
|
}
|
|
|
|
|
2023-07-10 09:19:53 +08:00
|
|
|
dlc->dai_name = snd_soc_dai_name_get(dai);
|
2020-02-13 23:51:51 +08:00
|
|
|
} else if (ret) {
|
|
|
|
/*
|
|
|
|
* if another error than ENOTSUPP is returned go on and
|
|
|
|
* check if another component is provided with the same
|
|
|
|
* node. This may happen if a device provides several
|
|
|
|
* components
|
|
|
|
*/
|
|
|
|
continue;
|
2013-09-11 08:39:56 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2023-06-30 07:52:20 +08:00
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
dlc->of_node = args->np;
|
|
|
|
|
2013-09-11 08:39:56 +08:00
|
|
|
mutex_unlock(&client_mutex);
|
2014-11-25 20:16:12 +08:00
|
|
|
return ret;
|
|
|
|
}
|
2023-06-20 10:14:06 +08:00
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_get_dlc);
|
2014-11-25 20:16:12 +08:00
|
|
|
|
2023-06-20 10:14:06 +08:00
|
|
|
int snd_soc_of_get_dlc(struct device_node *of_node,
|
|
|
|
struct of_phandle_args *args,
|
|
|
|
struct snd_soc_dai_link_component *dlc,
|
|
|
|
int index)
|
2014-11-25 20:16:12 +08:00
|
|
|
{
|
2023-06-20 10:14:06 +08:00
|
|
|
struct of_phandle_args __args;
|
2014-11-25 20:16:12 +08:00
|
|
|
int ret;
|
|
|
|
|
2023-06-20 10:14:06 +08:00
|
|
|
if (!args)
|
|
|
|
args = &__args;
|
|
|
|
|
2014-11-25 20:16:12 +08:00
|
|
|
ret = of_parse_phandle_with_args(of_node, "sound-dai",
|
2023-06-20 10:14:06 +08:00
|
|
|
"#sound-dai-cells", index, args);
|
2014-11-25 20:16:12 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2023-06-20 10:14:06 +08:00
|
|
|
return snd_soc_get_dlc(args, dlc);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_get_dlc);
|
|
|
|
|
|
|
|
int snd_soc_get_dai_name(const struct of_phandle_args *args,
|
|
|
|
const char **dai_name)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai_link_component dlc;
|
|
|
|
int ret = snd_soc_get_dlc(args, &dlc);
|
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
*dai_name = dlc.dai_name;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_get_dai_name);
|
2013-09-11 08:39:56 +08:00
|
|
|
|
2023-06-20 10:14:06 +08:00
|
|
|
int snd_soc_of_get_dai_name(struct device_node *of_node,
|
2023-06-20 10:14:11 +08:00
|
|
|
const char **dai_name, int index)
|
2023-06-20 10:14:06 +08:00
|
|
|
{
|
|
|
|
struct snd_soc_dai_link_component dlc;
|
2023-06-20 10:14:11 +08:00
|
|
|
int ret = snd_soc_of_get_dlc(of_node, NULL, &dlc, index);
|
2023-06-20 10:14:06 +08:00
|
|
|
|
|
|
|
if (ret == 0)
|
|
|
|
*dai_name = dlc.dai_name;
|
2013-09-11 08:39:56 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_name);
|
|
|
|
|
2023-07-10 09:20:06 +08:00
|
|
|
struct snd_soc_dai *snd_soc_get_dai_via_args(struct of_phandle_args *dai_args)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai *dai;
|
|
|
|
struct snd_soc_component *component;
|
|
|
|
|
|
|
|
mutex_lock(&client_mutex);
|
|
|
|
for_each_component(component) {
|
|
|
|
for_each_component_dais(component, dai)
|
|
|
|
if (snd_soc_is_match_dai_args(dai->driver->dai_args, dai_args))
|
|
|
|
goto found;
|
|
|
|
}
|
|
|
|
dai = NULL;
|
|
|
|
found:
|
|
|
|
mutex_unlock(&client_mutex);
|
|
|
|
return dai;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_get_dai_via_args);
|
|
|
|
|
2022-06-22 13:54:13 +08:00
|
|
|
static void __snd_soc_of_put_component(struct snd_soc_dai_link_component *component)
|
|
|
|
{
|
|
|
|
if (component->of_node) {
|
|
|
|
of_node_put(component->of_node);
|
|
|
|
component->of_node = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int __snd_soc_of_get_dai_link_component_alloc(
|
|
|
|
struct device *dev, struct device_node *of_node,
|
|
|
|
struct snd_soc_dai_link_component **ret_component,
|
|
|
|
int *ret_num)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai_link_component *component;
|
|
|
|
int num;
|
|
|
|
|
|
|
|
/* Count the number of CPUs/CODECs */
|
|
|
|
num = of_count_phandle_with_args(of_node, "sound-dai", "#sound-dai-cells");
|
|
|
|
if (num <= 0) {
|
|
|
|
if (num == -ENOENT)
|
|
|
|
dev_err(dev, "No 'sound-dai' property\n");
|
|
|
|
else
|
|
|
|
dev_err(dev, "Bad phandle in 'sound-dai'\n");
|
|
|
|
return num;
|
|
|
|
}
|
|
|
|
component = devm_kcalloc(dev, num, sizeof(*component), GFP_KERNEL);
|
|
|
|
if (!component)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
*ret_component = component;
|
|
|
|
*ret_num = num;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-03-10 01:48:54 +08:00
|
|
|
/*
|
|
|
|
* snd_soc_of_put_dai_link_codecs - Dereference device nodes in the codecs array
|
|
|
|
* @dai_link: DAI link
|
|
|
|
*
|
|
|
|
* Dereference device nodes acquired by snd_soc_of_get_dai_link_codecs().
|
|
|
|
*/
|
|
|
|
void snd_soc_of_put_dai_link_codecs(struct snd_soc_dai_link *dai_link)
|
|
|
|
{
|
2018-09-03 10:12:40 +08:00
|
|
|
struct snd_soc_dai_link_component *component;
|
2018-03-10 01:48:54 +08:00
|
|
|
int index;
|
|
|
|
|
2022-06-22 13:54:13 +08:00
|
|
|
for_each_link_codecs(dai_link, index, component)
|
|
|
|
__snd_soc_of_put_component(component);
|
2018-03-10 01:48:54 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_put_dai_link_codecs);
|
|
|
|
|
2014-11-25 20:16:12 +08:00
|
|
|
/*
|
|
|
|
* snd_soc_of_get_dai_link_codecs - Parse a list of CODECs in the devicetree
|
|
|
|
* @dev: Card device
|
|
|
|
* @of_node: Device node
|
|
|
|
* @dai_link: DAI link
|
|
|
|
*
|
|
|
|
* Builds an array of CODEC DAI components from the DAI link property
|
|
|
|
* 'sound-dai'.
|
|
|
|
* The array is set in the DAI link and the number of DAIs is set accordingly.
|
2018-03-10 01:48:54 +08:00
|
|
|
* The device nodes in the array (of_node) must be dereferenced by calling
|
|
|
|
* snd_soc_of_put_dai_link_codecs() on @dai_link.
|
2014-11-25 20:16:12 +08:00
|
|
|
*
|
|
|
|
* Returns 0 for success
|
|
|
|
*/
|
|
|
|
int snd_soc_of_get_dai_link_codecs(struct device *dev,
|
|
|
|
struct device_node *of_node,
|
|
|
|
struct snd_soc_dai_link *dai_link)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai_link_component *component;
|
2022-06-22 13:54:13 +08:00
|
|
|
int index, ret;
|
|
|
|
|
|
|
|
ret = __snd_soc_of_get_dai_link_component_alloc(dev, of_node,
|
|
|
|
&dai_link->codecs, &dai_link->num_codecs);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2014-11-25 20:16:12 +08:00
|
|
|
|
|
|
|
/* Parse the list */
|
2018-09-03 10:12:40 +08:00
|
|
|
for_each_link_codecs(dai_link, index, component) {
|
2023-06-20 10:14:46 +08:00
|
|
|
ret = snd_soc_of_get_dlc(of_node, NULL, component, index);
|
2014-11-25 20:16:12 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
2018-03-10 01:48:54 +08:00
|
|
|
snd_soc_of_put_dai_link_codecs(dai_link);
|
2014-11-25 20:16:12 +08:00
|
|
|
dai_link->codecs = NULL;
|
|
|
|
dai_link->num_codecs = 0;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_link_codecs);
|
|
|
|
|
2022-03-31 08:04:48 +08:00
|
|
|
/*
|
|
|
|
* snd_soc_of_put_dai_link_cpus - Dereference device nodes in the codecs array
|
|
|
|
* @dai_link: DAI link
|
|
|
|
*
|
|
|
|
* Dereference device nodes acquired by snd_soc_of_get_dai_link_cpus().
|
|
|
|
*/
|
|
|
|
void snd_soc_of_put_dai_link_cpus(struct snd_soc_dai_link *dai_link)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai_link_component *component;
|
|
|
|
int index;
|
|
|
|
|
2022-06-22 13:54:13 +08:00
|
|
|
for_each_link_cpus(dai_link, index, component)
|
|
|
|
__snd_soc_of_put_component(component);
|
2022-03-31 08:04:48 +08:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_put_dai_link_cpus);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* snd_soc_of_get_dai_link_cpus - Parse a list of CPU DAIs in the devicetree
|
|
|
|
* @dev: Card device
|
|
|
|
* @of_node: Device node
|
|
|
|
* @dai_link: DAI link
|
|
|
|
*
|
|
|
|
* Is analogous to snd_soc_of_get_dai_link_codecs but parses a list of CPU DAIs
|
|
|
|
* instead.
|
|
|
|
*
|
|
|
|
* Returns 0 for success
|
|
|
|
*/
|
|
|
|
int snd_soc_of_get_dai_link_cpus(struct device *dev,
|
|
|
|
struct device_node *of_node,
|
|
|
|
struct snd_soc_dai_link *dai_link)
|
|
|
|
{
|
|
|
|
struct snd_soc_dai_link_component *component;
|
2022-06-22 13:54:13 +08:00
|
|
|
int index, ret;
|
2022-03-31 08:04:48 +08:00
|
|
|
|
2022-06-22 13:54:06 +08:00
|
|
|
/* Count the number of CPUs */
|
2022-06-22 13:54:13 +08:00
|
|
|
ret = __snd_soc_of_get_dai_link_component_alloc(dev, of_node,
|
|
|
|
&dai_link->cpus, &dai_link->num_cpus);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
2022-03-31 08:04:48 +08:00
|
|
|
|
|
|
|
/* Parse the list */
|
|
|
|
for_each_link_cpus(dai_link, index, component) {
|
2023-06-20 10:14:46 +08:00
|
|
|
ret = snd_soc_of_get_dlc(of_node, NULL, component, index);
|
2022-03-31 08:04:48 +08:00
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
err:
|
2022-06-22 13:54:06 +08:00
|
|
|
snd_soc_of_put_dai_link_cpus(dai_link);
|
2022-03-31 08:04:48 +08:00
|
|
|
dai_link->cpus = NULL;
|
|
|
|
dai_link->num_cpus = 0;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(snd_soc_of_get_dai_link_cpus);
|
|
|
|
|
2008-12-10 14:47:22 +08:00
|
|
|
static int __init snd_soc_init(void)
|
2006-10-07 00:31:09 +08:00
|
|
|
{
|
2022-10-28 11:16:03 +08:00
|
|
|
int ret;
|
|
|
|
|
2015-04-09 16:52:38 +08:00
|
|
|
snd_soc_debugfs_init();
|
2022-10-28 11:16:03 +08:00
|
|
|
ret = snd_soc_util_init();
|
|
|
|
if (ret)
|
|
|
|
goto err_util_init;
|
2011-04-28 17:57:54 +08:00
|
|
|
|
2022-10-28 11:16:03 +08:00
|
|
|
ret = platform_driver_register(&soc_driver);
|
|
|
|
if (ret)
|
|
|
|
goto err_register;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_register:
|
|
|
|
snd_soc_util_exit();
|
|
|
|
err_util_init:
|
|
|
|
snd_soc_debugfs_exit();
|
|
|
|
return ret;
|
2006-10-07 00:31:09 +08:00
|
|
|
}
|
2010-10-13 00:41:03 +08:00
|
|
|
module_init(snd_soc_init);
|
2006-10-07 00:31:09 +08:00
|
|
|
|
2008-12-01 06:11:24 +08:00
|
|
|
static void __exit snd_soc_exit(void)
|
2006-10-07 00:31:09 +08:00
|
|
|
{
|
2011-04-28 17:57:54 +08:00
|
|
|
snd_soc_util_exit();
|
2015-04-09 16:52:38 +08:00
|
|
|
snd_soc_debugfs_exit();
|
2011-04-28 17:57:54 +08:00
|
|
|
|
2008-05-19 18:32:25 +08:00
|
|
|
platform_driver_unregister(&soc_driver);
|
2006-10-07 00:31:09 +08:00
|
|
|
}
|
|
|
|
module_exit(snd_soc_exit);
|
|
|
|
|
|
|
|
/* Module information */
|
2008-10-12 20:17:36 +08:00
|
|
|
MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk");
|
2006-10-07 00:31:09 +08:00
|
|
|
MODULE_DESCRIPTION("ALSA SoC Core");
|
|
|
|
MODULE_LICENSE("GPL");
|
2008-04-14 19:33:36 +08:00
|
|
|
MODULE_ALIAS("platform:soc-audio");
|